Написание приложений на Python с использованием фреймворка GTK
Написание десктопных приложений подразумевает разветвлённую многопоточную систему управления логикой. Причина проста: эвентлуп GTK требует минимальное число операций в функциях по отрисовке интерфейса по причине его подвисания в противном случае. Поэтому подход простой:
1. Написание функций по отрисовке интерфейса
Для разработки требуется установленная в системе библиотека PyGObject. Её предлагается использовать следующим образом:
import gi gi.require_version("Gdk", "3.0") gi.require_version("Gtk", "3.0") from gi.repository import Gdk, GLib, Gio, Gtk, GObject
Для быстрого прототипирования предлагается отрисовывать формы в Glade и подключать через декоратор:
@Gtk.Template.from_file("main_window.glade") class MainWindow(Gtk.ApplicationWindow): __gtype_name__ = "main_window" # подключение к необходимым объектам в прототипе rootcont = Gtk.Template.Child("rootcont") def __init__(self, **kwargs): super().__init__(**kwargs) ...
Стилевое оформление прописывается в каскадных таблицах:
provider = Gtk.CssProvider() provider.load_from_path("./style.css") screen = Gdk.Screen.get_default() Gtk.StyleContext.add_provider_for_screen( screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION )
Использование системы сигналов и слотов на примере с кнопками:
self.get_info_btn.connect("clicked", self.show_info_screen)
Документация по PyGObject: https://lazka.github.io/pgi-docs/Gtk-3.0/index.html
2. Добавление логики в приложение
def threaded_logic(): # some logic t = Thread(target=threaded_logic) t.daemon = True t.start()
Коммуникацию предлагается обеспечивать через сигналы и очереди:
class MySignals(GObject.GObject): __gsignals__ = { 'get': (GObject.SignalFlags.RUN_FIRST, None, ()), 'pass': (GObject.SignalFlags.RUN_FIRST, None, ()), 'default_screen': (GObject.SignalFlags.RUN_FIRST, None, ()), 'show_error': (GObject.SignalFlags.RUN_FIRST, None, (str,)), 'show_critical': (GObject.SignalFlags.RUN_FIRST, None, (str,)) } signals = MySignals() some_objects = queue.Queue()
Из потоков напрямую запрещается производить вызов отрисовки интерфейса, равно как и вызывать сигналы (в противном случае - segfault). Предлагается использовать безопасный способ:
Gdk.threads_add_idle( GLib.PRIORITY_HIGH_IDLE, signals.emit, 'default_screen' ) Gdk.threads_add_idle( GLib.PRIORITY_HIGH_IDLE, lambda: self.curr_sequencer.prev() )
3. Последовательность запуска
Сначала запускаются необходимые фоновые потоки, затем - эвентлуп GTK:
class Application(Gtk.Application): def __init__(self, *args, **kwargs): super().__init__( *args, application_id="org.comp.app", flags=Gio.ApplicationFlags.FLAGS_NONE, **kwargs, ) def do_activate(self): self.win = MainWindow(application=self) self.win.present() self.win.show_all() if __name__ == "__main__": Application().run(sys.argv)