12 марта 2010 в 19:10

Введение в pygtk/gtkbuilder: пишем калькулятор

Разберем создание интерфейса на pygtk на примере самого примитивного калькулятора. Много картинок, немного кода.
gtkbuilder наиболее прогрессивный формат описания gtk интерфейса в xml,
если вы ранее использовали libglade, вы можете сконфертировать .glade файл в новый формат командой libglade-convert

Всё это может работать под windows, вам нужно установить pygtk и все нужные библиотеки; в дальнейшем, вы сможете упаковывать всё с помощью py2exe.
Запускаем glade-3

Создаем окно, помещаем в него таблицу

В верхнюю левую ячейку вставляем Entry, в packing выставляем right
attachment-4, чтобы растянуть её на 4 колонки
В general выставляем Editable: No


Для window1 в signals вводим в key-press-event «key_press»;
в destroy — «quit»(чтобы при закрытии окна, мы выходили из программы)
key_press и quit это название методов в нашем коде, которые будут
вызываться при совершении определенных действий, в данном случае — нажатию кнопки и закрытии окна.

Добавляем кнопочки

Остальные мы сгенерируем в коде.
Для всех кнопок, кроме "=" и сброса выставляем signals/clicked «put_text»
Для "=" — «calculate», для сброса — «reset»

Выставляем common/accelerators для кнопочек, чтобы clicked у нас вызывался
при нажатии соотв. клавиш, для "=" это будет "=" и Return, для сброса не
выставляем ничего(мы хотим Escape и сделаем это в коде)

Сохраняем в файл demo.ui
Пишем код

Если возникают вопросы — запускаем «devhelp» и читаем документацию


Обратите внимание на EntryDescriptor — это питоновский дескриптор, который
позволяет нам гораздо удобнее работать со значениями в gtk.Entry

Остальное должно быть понятно из комментариев

Примечание: калькулятор работает с целыми числами и целочисленным делением,
т.е. 5/2 = 2:) Это статья про создание интерфейса, всё остальное я сделал
максимально просто, чтобы не отвлекало.

#!/usr/bin/env python
# coding: utf-8
# vim: ai ts=4 sts=4 et sw=4

import gtk
import os

class EntryDescriptor(object):
    def __init__(self, object_name):
        self.object_name = object_name
    def __get__(self, obj, cls):
        return obj.get_object(self.object_name).get_text()
    def __set__(self, obj, value):
        obj.get_object(self.object_name).set_text(value)


class Calculator(gtk.Builder):
    def __init__(self):
        super(Calculator, self).__init__()
        # загружаем demo.ui
        self.add_from_file(os.path.join(os.path.dirname(__file__),'demo.ui'))
        # подключаем обработчики сигналов, описанные в demo.ui к объекту self
        self.connect_signals(self)
        # создаем группу горячих клавишь, для клавиш с цифрами и подключаем её к окну
        agroup = gtk.AccelGroup()
        self.window1.add_accel_group(agroup)
        # создаем кнопочки, вешаем на них горячии клавиши и помещаем их в table1
        for i in xrange(10):
            btn = gtk.Button(str(i))
            btn.connect('clicked', self.put_text)
            btn.add_accelerator('clicked', agroup, ord(str(i)), 0, 0)
            y, x = i / 3, i % 3
            self.table1.attach(btn, x, x+1, y+1, y+2)
        # показываем окно и все виджеты на нем
        self.window1.show_all()

    def __getattr__(self, attr):
        # удобней писать self.window1, чем self.get_object('window1')
        obj = self.get_object(attr)
        if not obj:
            raise AttributeError('object %r has no attribute %r' % (self,attr))
        setattr(self, attr, obj)
        return obj

    # питоновский дескриптор, который позволяет нам гораздо удобнее работать со значениями в gtk.Entry
    expr = EntryDescriptor('entry1')
    
    def calculate(self, widget=None):
        if not self.expr: return
        try:
            self.expr = str(eval(self.expr))
        except Exception, e:
            # мы можем заделить на ноль и т.п.
            print e

    def reset(self, widget):
        self.expr = ''

    def quit(self, widget):
        gtk.main_quit()

    def put_text(self, widget):
        text = widget.get_label()
        expr = self.expr
        if not expr[-1:].isdigit() and not text.isdigit():
            # чтобы нельзя было написать 8/*2 и т.п.
            return
        self.expr += text

    def key_press(self, widget, event):
        # получаем название кнопки из её кода
        key = gtk.gdk.keyval_name(event.keyval)
        if key == 'BackSpace':
            if self.expr:
                self.expr = self.expr[:-1]
        elif key == 'Escape':
            self.reset(None)

if __name__ == '__main__':
    calculator = Calculator()
    gtk.main()


demo.py
demo.ui

Запускаем python demo.py:


Если тема интересна, в следующей статье я думаю описать создание браузера на базе pygtk и pywebkitgtk, для которого можно писать расширения на python в т.ч. в интерактивной консоли(ipython); интеграцией python и javascript.
+40
2008
74
pawnhearts 34,0

Комментарии (18)

+2
spanasik, #
Статья отличная, как раз для блога PyGTK :-)
+1
XolodIT, #
Сказать, что интересно — ничего сказать. Пишите еще, с нетерпение… Мне это очень актуально.
Спасибо.
0
Thomas, #
Усложненный аналог Hello World. В хорошем смысле. Особенно полезно когда учишь новый язык сделать что-то простое, а заодно в процессе разобраться с кодом.
0
Zubchick, #
свойства лучше так оформлять. Через декораторы.
0
pawnhearts, #
где там свойства?
0
Zubchick, #
class Calculator(gtk.Builder):?
0
pawnhearts, #
хм, это класс-наследник gtk.Builder. причем тут свойства?
0
Zubchick, #
Кстати, попутно вопрос, что вы считаете удобней для построения интерфеса кьют или гтк всетаки?
0
andy128k, #
Интерфейс строить плюс/минус одинаково удобно и там и там.
0
pawnhearts, #
да, на самом деле очень похоже. раньше ещё вопрос лицензий стоял. если нужна поддержка macos, то однозначно лучше qt/pyside
0
andy128k, #
GTK+ и под Windows не очень гладко выглядит.
0
InneR, #
Плюс! Пишите, я как раз подумываю написать плагинчик под pygtk. Ваша статья пришлась крайне кстати, спасибо!
0
vostryakov, #
Жду статью про создание браузера и интеграцию с javascript :)
+1
topchiyev, #
Ага. Я тоже обожаю присать калькулятор на новом языке.
Отличная разминка.

Тут калькуляторы для Mac и iPhone.
0
taliban, #
вот это я понимаю, статья так статья, где там мои плюсики… главное не останавливаться на сделанном, вы обещали новую статью, мы помним ;)
0
Proklado4ka_A11ways, #
[offtopic]А можно увидеть обоину? :) [/offtopic]
0
NucleoFag, #
Спасибо! Наглядно=)
0
bitfroster, #
Спасибо, было бы хорошо прочитать продолжение :)

Только зарегистрированные пользователи могут оставлять комментарии.
Войдите, пожалуйста.