furas.pl
# prywatne notatki - Python, Linux, Machine Learning, etc.

PyGTK - minimalny program

Minimalny program

Oto jak mógłby wyglądać minimalny program w Pythona z użyciem GTK.

Niby działa i wyświetla okno ale mimo wszystko będzie wymagał kilku dodatków o czym poniżej.

python gtk minimalny przykład
from gi.repository import Gtk

win = Gtk.Window()
win.show_all()

Gtk.main()

Opis kodu:

  • importujemy bibliotekę GTK (w pewien specyficzny sposób).
  • win = Gtk.Window() - tworzymy w pamięci obiekt Window - ale okno jeszcze się nie wyświetli na ekranie.
  • win.show_all() - informujemy okno, że chcemy aby się pojawiło na ekranie - ale okno wciąż jeszcze się nie wyświetli - choć bez tego to ono nie pojawi się nigdy.
  • Gtk.main() - uruchamiamy główną pętlę obsługi zdarzeń (często zwaną też "main loop" lub "event loop"), która w kółko bez końca odbiera od systemu informacje o zdarzeniach (takich jak wciśnięcie klawisza na klawiaturze, ruszenie myszką, wciśnięcie przycisku w myszce, itp.) i przekazuje do elementów okna aby one mogły sprawdzić czy zdarzenie wydarzyło się w ich obszarze. Ono też odbiera zdarzenia od widgetów i wykonuje funkcje do nich przypisane. Jedna z wywoływanych funkcji rysujące widgety na ekranie. To dopiero sprawia, że okno pojawia się na ekranie.

Sprawa, która często wymaga wyjaśnienia:

  • wszystkie instrukcje przed main() to tylko deklaracja jak ma wyglądać okno i co ma się dziać w programie jak już zostanie uruchomione main(). Wszystkie te instrukcje wykonują się zanim okno zostanie wyświetlone więc nie ma tam jeszcze dostępu do tego co użytkownik wprowadzi w oknie (np. wpisze tekst, zaznaczy opcję) więc nie ma co próbować odczytać tych danych.
  • wszystkie instrukcje po main() zostaną wykonane dopiero po zamknięciu programu - tak więc nie ma tam już dostępu do zawartości okna. Jeśli chcemy jakiś danych z okna po main() (czyli po zamknięciu okna) to należy te dane przypisać do zmiennej, która nie jest wewnątrz klasy Window lub MyWindow w dalszych przykładach.

Całe działanie programu odbywa się podczas wykonywania się funkcji Gtk.main().

Minimalny program z poprawkami

Taki minimalny program działa i wyświetla okno ale nie jest do końca przydatny.

Po pierwsze: w systemie może być zainstalowanych wiele wersji GTK i program może wymagać określenia z jakiej wersji chcemy korzystać - o czym może poinformować takim oto komunikatem

PyGIWarning: Gtk was imported without specifying a version first.
Use gi.require_version('Gtk', '3.0') before import
to ensure that the right version gets loaded.
from gi.repository import Gtk

Po drugie: klikając w przycisk zamykający okno [X] okazuje się, że okno znika ale program nie kończy działania. Domyślnie przycisk [X] nie informuje main(), że ma zakończyć działanie i tym samym zakończyć program. Chyba we wszystkich znanych mi frameworkach przycisk ten wymaga przypisania funkcji, która poinformuje główną pętlę obsługi zdarzeń, że ma zakończyć działanie

Podczas wciśnięcia [X] tworzone jest zdarzenie destroy i w oknie można zdefiniwac co ma ono zrobić gdy takie zdarzenie się pojawi.

W minimalnej wersji można przypisać wywołanie funkcji Gtk.main_quit, która poinformuje main() o zakończeniu programu.

Tak więc minimalny program w Pythona z użyciem GTK 3.0 to

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

win = Gtk.Window()
win.connect("destroy", Gtk.main_quit)
win.show_all()

Gtk.main()

Uwagi:

  • w connect() podajemy samą nazwę funkcji bez () i argumentów - co często jest nazywane callback. Program w odpowienim momencie wywoła tą funkcję - czyli wywoła z () i odpowiednimi argumentami. Dla różnych zdarzeń mogą być przekazywane różne argumenty. Przeważnie przekazywany jest to widget, który spowodował zdrarzenie oraz event zawierający informacje o zdarzeniu (np. przy zarzeniach związanych z myszą będzie to jej położenie i jakie klawisze zostały wciśnięte).
  • w bardziej rozbudowanych programach (np. gdy to będzie edytor tekst) do obsługi zdarzenia destroy przypisuje się funkcję, która sprawdza czy wszystkie dane są zapisane i która pyta o potwierdzenie zakończenia programu. Oczywiście taką funkcję trzeba samemu napisać.
  • win.connect() może być użyte po win.show_all() ale zwyczajowo daje się to przed. Pewnie dlatego, że wszystkie inne elementy (Widgety) będzie trzeba dodawać przed show_all()

Minimalny program z jednym widgetem

Minimalny progam działa - uruchamia się bez komunikatów o błędach, wyświetla się okno, zamykając okno program kończy działanie - ale przydałaby się jeszcze jakaś minimalny zawartość w oknie. Jakiś napis (Label) lub przycisk (Button)

python gtk minimalny przykład
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

win = Gtk.Window()
win.connect("destroy", Gtk.main_quit)

label = Gtk.Label(label="Hello World!")
win.add(label)

win.show_all()

Gtk.main()

Tworzymy obiekt Label z napisem i następnie dodajemy go do istniejącego okna.

python gtk minimalny przykład
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

win = Gtk.Window()
win.connect("destroy", Gtk.main_quit)

button = Gtk.Button(label="Zamknij")
button.connect("clicked", Gtk.main_quit)
win.add(button)

win.show_all()

Gtk.main()

Tworzymy obiekt Button z napisem i następnie dodajemy go do istniejącego okna. Przed lub po dodaniu przycisku do okna przypisujemy funkcję Gtk.main_quit, która zostanie wykonana po kliknięciu przycisku (gdy przycisk stworzy zdarzenie clicked)

Oczywiści można przypisać własną funkcję do przycisku.

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

def moja_funkcja(widget):
    print("Wciśnieto widget:", widget)
    print("Label widget:", widget.get_label())
    Gtk.main_quit()

win = Gtk.Window()
win.connect("destroy", Gtk.main_quit)

button = Gtk.Button(label="Zamknij")
button.connect("clicked", moja_funkcja)
win.add(button)

win.show_all()

Gtk.main()

Tu także podajemy tylko nazwę funkcji bez użycia () i argumentów (tak zwany callback).

Gtk oczekuje, że przypisana funkcja pobiera jeden argument - widget, który został wciśnięty - więc nasza funkcja musi przyjmować jeden argument. Wewnątrz funkcji mamy dostęp do wciśniętego widget co pozwala zmieniać jego właściwości lub przypisanie jedenej funkcji do wielu przycisków i rozpoznawać, który przycisk został wciśnięty.

Wciśnieto widget: <Gtk.Button object at 0x7f6dbaf94af8 (GtkButton at 0x55cf115f4180)>
Label widget: Zamknij

Jeśli przycisk ma zamykać program to wewnątrz funkcji możemy użyć Gtk.main_quit() - tym razem już z ().

GTK pozwala też przypisywać wiele funkcji do jednego zdarzenia i będą one wykonywane w kolejności przypisania. Ale to już wykracza poza temat minimalnego programu.

button = Gtk.Button(label="Zamknij")
button.connect("clicked", moja_funkcja)
button.connect("clicked", Gtk.main_quit)
win.add(button)

Minimalny program z dziedziczeniem klasy

Powyższy przykład można też przedstawić w postaci własnej klasy, która dziedziczy z Gtk.Window. Taka postać może być bardziej przydatna przy większej ilości kodu.

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk


class MyWindow(Gtk.Window):

    def __init__(self):
        super().__init__()

        button = Gtk.Button(label="Zamknij")
        button.connect("clicked", self.moja_funkcja)
        self.add(button)

    def moja_funkcja(self, widget):
        print("Wciśnieto widget:", widget)
        print("Label widget:", widget.get_label())
        Gtk.main_quit()


win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()

Gtk.main()

W sumie wewnątrz __init__ można dać nawet win.connect("destroy", Gtk.main_quit) i win.show_all() ale wtedy takiego okna nie daje się wykorzystać jako podokienko, które pokazywane jest dopiero po wciśnięciu przycisku w głównym oknie. Dodatkowo zamkięcie takiego podokienka zamykałoby cały program zamiast zamykać tylko to podokno.

Jeśli jednak tworzymy tylko jedno okno lub jest to główne okno to wszystko można schować w __init__.

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk


class MyWindow(Gtk.Window):

    def __init__(self):
        super().__init__()

        button = Gtk.Button(label="Zamknij")
        button.connect("clicked", self.moja_funkcja)
        self.add(button)

        self.connect("destroy", Gtk.main_quit)
        self.show_all()

    def moja_funkcja(self, widget):
        print("Wciśnieto widget:", widget)
        print("Label widget:", widget.get_label())
        Gtk.main_quit()


win = MyWindow()

Gtk.main()

Minimalny program z wieloma oknami

W poniższym przykładzie przycisk tworzy kolejne okno, które używa self.close() aby zamykąć tylko siebie a nie cały program.

Program może otworzyć wiele podokien.

python gtk minimalny przykład
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk


class MySubWindow(Gtk.Window):

    def __init__(self):
        super().__init__()

        button = Gtk.Button(label="Zamknij")
        button.connect("clicked", self.moja_funkcja)
        self.add(button)

    def moja_funkcja(self, widget):
        print("Wciśnieto widget:", widget)
        print("Label widget:", widget.get_label())

        self.close()


class MyWindow(Gtk.Window):

    def __init__(self):
        super().__init__()

        button = Gtk.Button(label="Otwórz Podokno")
        button.connect("clicked", self.moja_funkcja)
        self.add(button)

    def moja_funkcja(self, widget):
        print("Wciśnieto widget:", widget)
        print("Label widget:", widget.get_label())

        subokno =  MySubWindow()
        subokno.show_all()


win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()

Gtk.main()

Minimalny program z wieloma widgetami

Za pomocą win.add() można dodać tylko jeden widget. Próba dodania drugiego powoduje, że w oknie pojawi się tylko pierwszy widgetem a program wyświetli komunikat

Gtk-WARNING **: 18:20:41.487: Attempting to add a widget with type GtkLabel to a GtkWindow,
but as a GtkBin subclass a GtkWindow can only contain one widget at a time;
it already contains a widget of type GtkButton

W oknie trzeba najpierw dodać jedne z kontenerów układu (Layout Conteners) czyli widget, który pozwala dodawać wiele widgetów i który będzie zarządzać ułożeniem tych widgetów w oknie.

Niektóre z kontenerów układu to Gtk.Box, Gtk.Grid, Gtk.Notebook.

  • Gtk.Box pozwala ustawić elementy w jednym wierszu lub w jednej kolumnie.
  • Gtk.Grid pozwala ustawiać w siatce podając wiersz i kolumnę (jak w tabeli).
  • Gtk.Notebook tworzy karty tak jak w przeglądarce WWW.

Oczywiście wewnątrz każdego kontenera można wstawiać inne kontenery aby budować bardziej skomplikowane układy.

Przykładowo wewnątrz kolumny Gtk.Box zawierającej wiele Labels z informacjami można umieścić wiersz Gtk.Box z przyciskami "OK" i "Anuluj".

Poniższy przykład używa Box aby umieścić Label i Button w jednym wierszu

python gtk minimalny przykład

a po dodaniu orientation=Gtk.Orientation.VERTICAL w jednej kolumnie.

python gtk minimalny przykład
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class MyWindow(Gtk.Window):

    def __init__(self):
        super().__init__()

        print(Gtk.Orientation.VERTICAL)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.add(box)

        label = Gtk.Label(label="Hello World!")
        box.add(label)

        button = Gtk.Button(label="Zamknij")
        button.connect("clicked", self.moja_funkcja)
        box.add(button)

    def moja_funkcja(self, widget):
        print("Wciśnieto widget:", widget)
        print("Label widget:", widget.get_label())
        Gtk.main_quit()


win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()

Gtk.main()

Więcej: Layout Containers

Uwagi:

  • GTK wszystkie komunikaty wyświetla w oknie terminalu, w którym został program uruchomiony - nie są komunikaty w wyskakujących okienkach - więc jeśli uruchamia się program przez kliknięcie w ikonę to tych komunikatów może się nie zobaczyć.

  • użycie Gtk.Label("Hello World") lub Gtk.Button("Quit") bez label= też zadziała ale pojawi się ostrzeżenie, że taka forma jest niewskazana (deprecated) i należy używać label=

    PyGTKDeprecationWarning: Using positional arguments with the GObject constructor has been deprecated.
    Please specify keyword(s) for "label" or use a class specific constructor.
    See: https://wiki.gnome.org/PyGObject/InitializerDeprecations
    label = Gtk.Label("Hello World")
    
  • funkcja przypisana do Button oczywiście może przyjmować więcej argumentów (zwłaszcza jeśli wykorzystujemy ją gdzieś indziej) ale pozostałe argumenty muszą mieć przypisaną wartość domyślną. Może to być choćby None. Może to być przydatne gdy tą samą funkcję chcemy przypisać do zdarzenia clicked, które wywołuje funkcję tylko z widget i do zdarzenia key-pressed-event, które wywołuje funkcję z widget i event

    def moja_funkcja(widget, event=None):
        print("widget:", widget)
        if event:
            print("event:", event)
            print("event.keyval:", event.keyval)
    
Książki: python-dla-kazdego-podstawy-programowania python-wprowadzenie python-leksykon-kieszonkowy python-receptury python-programuj-szybko-i-wydajnie python-projekty-do-wykorzystania black-hat-python-jezyk-python-dla-hackerow-i-pentesterow efektywny-python-59-sposobow-na-lepszy-kod tdd-w-praktyce-niezawodny-kod-w-jezyku-python aplikacje-internetowe-z-django-najlepsze-receptury