Написание приложений на 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)