PyGTK: Динамическое создание виджетов
В интернете уже есть много информации про компоновку виджетов, но во всех отсутствует довольно важная деталь для новичка в программировании интерфейсов PyGTK — описание технологии динамического создания виджетов, их размещение на форме и доступ к их методам. Под динамическим здесь я подразумеваю такой программный код, который мог бы в одном или более циклов положить на форму бесконечно возможное количество кнопок, полей ввода, картинок и т.д., и связать между собой обработчиками сигналов.
Поняв эту несложную технологию, довольно просто будет написать предельно короткий код для однотипных связей между многочисленными виджетами.
Для демонстрационного примера я создал форму с визуальным представлением иконок, подписей к кнопкам и оригинальным названием классов gtk.STOCK_…
Код
Итак, листинг gtkstock.py с комментариями внутри:
#!/usr/bin/python # -*- coding: utf-8 -*- import pygtk pygtk.require('2.0') import gtk class ShowStock: def __init__(self): self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_position(gtk.WIN_POS_CENTER) self.window.set_title("Представление gtk.STOCK") self.window.set_border_width(3) self.window.resize(300,50) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", lambda w: gtk.main_quit()) self.button = gtk.Button("Показать список") self.button.connect("clicked", self.show_stock) self.list_stock = ['gtk.STOCK_ABOUT', 'gtk.STOCK_ADD', 'gtk.STOCK_APPLY', 'gtk.STOCK_BOLD', 'gtk.STOCK_CANCEL', 'gtk.STOCK_CDROM', 'gtk.STOCK_CLEAR', 'gtk.STOCK_CLOSE', 'gtk.STOCK_COLOR_PICKER', 'gtk.STOCK_CONVERT', 'gtk.STOCK_CONNECT', 'gtk.STOCK_COPY', 'gtk.STOCK_CUT', 'gtk.STOCK_DELETE', 'gtk.STOCK_DIALOG_AUTHENTICATION', 'gtk.STOCK_DIALOG_ERROR', 'gtk.STOCK_DIALOG_INFO', 'gtk.STOCK_DIALOG_QUESTION', 'gtk.STOCK_DIALOG_WARNING', 'gtk.STOCK_DIRECTORY', 'gtk.STOCK_DISCONNECT', 'gtk.STOCK_DND', 'gtk.STOCK_DND_MULTIPLE', 'gtk.STOCK_EDIT', 'gtk.STOCK_EXECUTE', 'gtk.STOCK_FILE', 'gtk.STOCK_FIND', 'gtk.STOCK_FIND_AND_REPLACE', 'gtk.STOCK_FLOPPY', 'gtk.STOCK_FULLSCREEN', 'gtk.STOCK_GOTO_BOTTOM', 'gtk.STOCK_GOTO_FIRST', 'gtk.STOCK_GOTO_LAST', 'gtk.STOCK_GOTO_TOP', 'gtk.STOCK_GO_BACK', 'gtk.STOCK_GO_DOWN', 'gtk.STOCK_GO_FORWARD', 'gtk.STOCK_GO_UP', 'gtk.STOCK_HARDDISK', 'gtk.STOCK_HELP', 'gtk.STOCK_HOME', 'gtk.STOCK_INDENT', 'gtk.STOCK_INDEX', 'gtk.STOCK_INFO', 'gtk.STOCK_ITALIC', 'gtk.STOCK_JUMP_TO', 'gtk.STOCK_JUSTIFY_CENTER', 'gtk.STOCK_JUSTIFY_FILL', 'gtk.STOCK_JUSTIFY_LEFT', 'gtk.STOCK_JUSTIFY_RIGHT', 'gtk.STOCK_LEAVE_FULLSCREEN', 'gtk.STOCK_MEDIA_FORWARD', 'gtk.STOCK_MEDIA_NEXT', 'gtk.STOCK_MEDIA_PAUSE', #'gtk.STOCK_MEDIA_PLAYRTL', 'gtk.STOCK_MEDIA_PREVIOUS', 'gtk.STOCK_MEDIA_RECORD', 'gtk.STOCK_MEDIA_REWIND', 'gtk.STOCK_MEDIA_STOP', 'gtk.STOCK_MISSING_IMAGE', 'gtk.STOCK_NETWORK', 'gtk.STOCK_NEW', 'gtk.STOCK_NO', 'gtk.STOCK_OK', 'gtk.STOCK_OPEN', 'gtk.STOCK_PASTE', 'gtk.STOCK_PREFERENCES', 'gtk.STOCK_PRINT', 'gtk.STOCK_PRINT_PREVIEW', 'gtk.STOCK_PROPERTIES', 'gtk.STOCK_QUIT', 'gtk.STOCK_REDO', 'gtk.STOCK_REFRESH', 'gtk.STOCK_REMOVE', 'gtk.STOCK_REVERT_TO_SAVED', 'gtk.STOCK_SAVE', 'gtk.STOCK_SAVE_AS', 'gtk.STOCK_SELECT_COLOR', 'gtk.STOCK_SELECT_FONT', 'gtk.STOCK_SORT_ASCENDING', 'gtk.STOCK_SORT_DESCENDING', 'gtk.STOCK_SPELL_CHECK', 'gtk.STOCK_STOP', 'gtk.STOCK_STRIKETHROUGH', 'gtk.STOCK_UNDELETE', 'gtk.STOCK_UNDERLINE', 'gtk.STOCK_UNDO', 'gtk.STOCK_UNINDENT', 'gtk.STOCK_YES', 'gtk.STOCK_ZOOM_100', 'gtk.STOCK_ZOOM_FIT', 'gtk.STOCK_ZOOM_IN', 'gtk.STOCK_ZOOM_OUT'] # Основа - окно с автоматически появляющейся прокруткой self.scrolledwindow = gtk.ScrolledWindow() self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) # Столбец для виджетов на всю ширину окна self.vbox = gtk.VBox() self.scrolledwindow.add_with_viewport(self.vbox) # Стартовая кнопка self.vbox.pack_start(self.button) # Таблица с программно добавляемыми виджетами self.table = gtk.Table(1,1) self.vbox.pack_start(self.table) self.window.add(self.scrolledwindow) self.window.show_all() # Словарик размеров картинок для функции change_scale self.dictimages = {} def show_stock(self, widget): """Программное создание виджетов, сигналов, размещения""" for x in range(0, len(self.list_stock)): if not self.__dict__.has_key("label" + str(x)): # Строки превращаются в ссылки на классы модуля "gtk" STOCK = eval(self.list_stock[x]) # Стандартный размер картинки scale = 4 # Ссылка на словарь атрибутов, для краткости написания. dictattr = self.__dict__ # Создание атрибутов текущего экземпляра и присваивание им классов, # как если бы вручную написали: self.button100500 = gtk.Button() dictattr["image" + str(x)] = gtk.Image() dictattr["image" + str(x)].set_from_stock(STOCK, scale) dictattr["button" + str(x)] = gtk.Button(STOCK, STOCK) #~ # сигнал с данными (Вариант №1): каждый хранит строку, #~ # в self.dictimages ключами тоже являются строки #~ dictattr["button" + str(x)].connect('clicked', #~ self.change_scale, ("image" + str(x), STOCK, scale)) # сигнал с данными (Вариант №2, предпочтительнее): хранит ссылку # на значение в словаре атрибутов, в self.dictimages ключи - ссылки dictattr["button" + str(x)].connect('clicked', self.change_scale, (dictattr["image" + str(x)], STOCK, scale)) dictattr["label" + str(x)] = gtk.Label("==>> " + self.list_stock[x]) dictattr["label" + str(x)].set_alignment(0,0.5) # Размещение виджетов в таблице self.table.attach(dictattr["image" + str(x)], 0,1,x,x+1) self.table.attach(dictattr["button" + str(x)], 1,2,x,x+1) self.table.attach(dictattr["label" + str(x)], 2,3,x,x+1) self.window.resize(600,600) self.window.show_all() self.button.hide() def change_scale(self, widget, data): """Увеличение размера картинки""" scale = 0 if not self.dictimages.has_key(data[0]): scale = data[2] + 1 else: scale = self.dictimages[data[0]] if scale == 6: scale = data[2] else: scale += 1 self.dictimages[data[0]] = scale #~ # Для варианта №1 в show_stock() #~ self.__dict__[data[0]].set_from_stock(data[1], scale) # Для варианта №2 в show_stock() data[0].set_from_stock(data[1], scale) def delete_event(self, widget, event, data=None): print "delete event occurred" return False #~ def destroy(self, widget, data=None): #~ print "destroy signal occurred" #~ gtk.main_quit() def main(self): gtk.main() if __name__ == "__main__": showstock = ShowStock() showstock.main()
Итоговое окно
Дополнительные пояснения
Построением виджетов занимается функция show_stock. В ней есть закомментированный участок 1 варианта кода, который, на мой взгляд, более явный для программиста, но требующий больше выделения памяти для создаваемых сигналов. Для того, чтобы использовать его, нужно закомментировать 2 вариант, а также сделать соответствующие изменения в функции change_scale.
Закомментированная функция destroy, заменена lambda-выражением прямо при декларировании сигнала. Но оставлена на тот случай, когда при закрытии главного окна потребуется что-либо сохранить (для этого в неё нужно будет добавить свой сохраняющий код).