Kategória: Python

Zmenené: 8. august 2011

Ukončovanie GTK aplikácií

Počas učenia sa programovania GTK aplikácie v Pythone som narazil na zaujímavý problém, ktorým je ošetrenie ukončenia aplikácie. Oficiálne návody a tutoriál síce popisujú signály delete-event a destroy, ale len ich základné použitie…

Popis problému

Oficiálny tutoriál popisuje použite spomínaných udalostí, avšak veľmi rýchlo prídete na to, že život nie je taký jednoduchý. Ak sa pokúsite aplikovať postup na jednoduchú aplikáciu, ktorá bude mať len jedno tlačidlo na zatvorenie programu:

class MainQuit(gtk.Window):

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

        self.connect('delete-event', self.on_delete_event)                 #pozn. 1
        self.connect('destroy';, self.on_quit)                             #pozn. 2

        self.set_title('Quit Monitor')
        self.set_position(gtk.WIN_POS_CENTER)
        self.set_default_size(250,40)

        button = gtk.Button('', gtk.STOCK_QUIT)
        button.connect('clicked', self.on_click)                           #pozn. 3

        self.add(button)
        self.show_all()

    def on_click(self, widget, event=None):
        print "on_click"
        self.destroy()

    def on_delete_event(self, widget, event, data=None):
        print "on_delete_event"
        return False

    def on_quit(self, widget, data=None):
        print "on_quit"
        gtk.main_quit()

if __name__ == "__main__":
    main = MainQuit()
    gtk.main()

Možno ste aj sami pochopili, že tento program toho veľa neurobí, zobrazí okno s jedným tlačidlom, ale hlavne, bude do príkazového riadku vypisovať, ktoré udalosti nastali. najprv ale popíšem, kam je ktorá obsluha udalosti pripojená. V programe sú definované tri udalosti:

  1. on_delete_event – obsluha udalosti, ktorá predchádza zatvoreniu okna,
  2. on_quit – obsluha udalosti zatvorenia okna,
  3. on_click – obsluha udalosti kliknutia na tlačidlo.

Dokumentácia odporúča vykonávať v obsluhe udalosti delete-event vykonávať kontrolu, či aplikácia (okno) môže byť ukončená, napríklad, či sú uložené zmeny dokumentu. Má to však jeden problém. Očakával som, že túto udalosť vyvolá aj ukončenie aplikácie pomocou gtk.main_quit(), k čomu však nedôjde. Môžete si to overiť spustením programu v konzole, kde bude vypisovať jednotlivé udalosti, ktoré nastanú.

Zatvorenie krížikom

Teda zatvorenie, o ktoré sa postrá správca okien (window manager). V tomto prípade je najprv vykonaná obsluha udalosti on_delete_event(). V závislosti na jej návratovej hodnote (ak je False), nasleduje obsluha udalosti on_quit().

Zatvorenie tlačidlom

Ak aplikáciu zatvoríte tlačidlom Zatvoriť, je vykonaná obsluha udalosti on_click(), nasledovaná obsluhou udalosti on_quit().

Testovanie ukončenia

Je dobrým nápadom vykonať pred ukončením aplikácie nejaký kód, napríklad spomínané overenie, či sú uložené zmeny, či overiť, že používateľ neuzatvára aplikáciu omylom a dať mu možnosť ukončenie zrušiť.

Z vyššie uvedeného vyplýva, že vloženie testovacej časti kódu, ktorá testuje možnosť ukončenia, prípadne vykonáva nejaké iné operácie do obsluhy on_delete_event() je nevhodné, pretože pri zatvorení iným spôsobom nebude vykonané. Môžete však jednoducho docieliť, aby túto metódu volala i obsluha udalosti tlačidla, napríklad takto:

def on_click(self, widget, event=None):
    print "on_click"
    if not self.on_delete_event(None, None):
        self.destroy()

Týmto spôsobom možno testovať návratovú hodnotu metódy a ak vrátila False, zruší okno aplikácie, čím zároveň ukončí aplikáciu, keďže je to jediné okno GTK programu.

Iný spôsob, ktorému ja dávam prednosť, je vytvorenie samostatnej metódy, ktorá vykoná všetky potrebné testovacie operácie. Zvyknem ju volať can_quit() a vracia True, ak možno program zatvoriť. Túto metódu potom volám z oboch obslúh, teda aj z on_click() aj z on_delete_event():

def can_quit(self):
    print "can_quit"
    return True

def on_click(self, widget, event=None):
    print "on_click"
    if self.can_close():
        self.destroy()

def on_delete_event(self, widget, event, data=None):
    print "on_delete_event"
    if self.can_close():
        return False
    return True

Ak si chcete overiť, že pri vrátení False k ukončeniu nedôjde, zmeňte návratovú hodnotu funkcie can_quit() na False.

Varovanie

Potom nepôjde zatvoriť aplikáciu z jej grafického prostredia a bude treba použiť kill alebo xkill.

Spracovanie signálov

A keď som už došiel k príkazu kill, tak ma napadá neduh mnohých programov, ktoré pri ukončovaní spolu s reláciou (session manager) napríklad neuložia zmeny v nastavení, či nevykonajú operácie, ktoré robia pri ukončení z GUI, ako je odstránenie zámkov a podobne.

Štandardným signálom, ktorý žiada o ukončenie aplikácie je SIGTERM. Okrem toho pripadá do úvahy ešte signál SIGINT. Oba tieto signály program v Pythone spracuje a ukončí sa, avšak nevykoná ani jednu z našich obslúh ukončovania. V prípade signálu SIGINT ho Python prevedie na výnimku KeyboardInterrupt:

Traceback (most recent call last):
  File "/tmp/gtkquit1.py", line 49, in <module>
    gtk.main()
KeyboardInterrupt

Môžete samozrejme ošetriť túto výnimku, ale ukážem v poslednej časti článku ako tieto signály aplikáciou zachytiť a aplikáciu korektne ukončiť, pomôžem si štandardnou knižnicou signal a začnem pripravením obsluhy udalosti, ktoré pridám ako metódy do triedy MainQuit:

import signal

...

    def on_signal(self, signum, frame):
        print 'on_signal', signum
        if main.can_quit():
            self.destroy()
        else:
            pass

Ostáva ešte pripojiť obsluhu udalosti so signálom:

if __name__ == "__main__":
   main = MainQuit()
   signal.signal(signal.SIGTERM, main.on_signal)
   signal.signal(signal.SIGINT, main.on_signal)
   gtk.main()

Kvôli zjednodušeniu som oba signály pripojil k jednej obsluhe, v praxi bude asi vhodnejšie použiť pre každý signál samostatnú obsluhu.

Záver

Na záver len zopakujem, že tu uvedený kód nie je zamýšľaný ako plnohodnotná aplikácia, ale len ako študijný materiál, ktorý pri spustení (z príkazového riadku) postupne vypisuje spracovávané udalosti.