Události jsou nedílnou součástí každé GUI aplikace. Všechny GUI aplikace jsou jimi vlastně řízeny. Události jsou většinou vygenerované uživatelem. Jakmile zavoláme metodu MainLoop(), bude naše aplikace v nekonečné smyčce odchytávat události a zpracovávat je. Tato smyčka skončí společně s koncem programu.
První příklad
Než začneme se samotným příkladem, měli bychom si vysvětlit několik pojmů. Jak už bylo řečeno, program ve smyčce MainLoop() zachytává všechny události a předává je do rozvodny. Tam se zkontroluje, zda je pro danou událost registrován nějaký ovladač. Pokud je, zavolá se funkce/metoda svázaná s ovladačem.
V následujícím příkladě si popíšeme velmi jednoduchou věc - ukážeme si, jaká událost se vygeneruje, pohneme-li oknem.
Vždy když pohneme oknem, vygeneruje se událost typu wx.MoveEvent
. Ovladač pro ni je wx.EVT_MOVE
.
import wx class MoveEvent(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 180)) wx.StaticText(self, -1, 'x:', (10,10)) wx.StaticText(self, -1, 'y:', (10,30)) self.st1 = wx.StaticText(self, -1, '', (30, 10)) self.st2 = wx.StaticText(self, -1, '', (30, 30)) self.Bind(wx.EVT_MOVE, self.OnMove) self.Centre() self.Show(True) def OnMove(self, event): x, y = event.GetPosition() self.st1.SetLabel(str(x)) self.st2.SetLabel(str(y)) app = wx.App() MoveEvent(None, -1, 'move event') app.MainLoop()
V tomto příkladu vypisujeme do wx.StaticText
aktuální pozici okna (ta se automaticky přepíše, pokud oknem pohneme).
self.Bind(wx.EVT_MOVE, self.OnMove)
Pomocí předcházejícího kusu kódu jsme svázali ovladač wx.EVT_MOVE
s metodou self.OnMove
.
def OnMove(self, event): x, y = event.GetPosition()
Jistě jste si všimli, že metoda OnMove()
přijímá kromě parametru self
ještě parametr event
. V tomto případě je to instance třídy wx.MoveEvent
. Pomocí této třídy můžete získat nějaké informace o události. Například aktuální pozici okna. Tu zjistíme zavoláním metody GetPosition()
.
Registrování událostí
Registrování událostí je ve wxPythonu poměrně jednoduchá záležitost. Skládá se ze dvou kroků:
- Vytvořit metodu, která se zavolá, pokud se vygeneruje daná událost.
- Registrovat ovladač (
wx.EVT_SIZE
,wx.EVT_BUTTOM
,wx.EVT_CLOSE
apod.) a svázat ho s metodou vytvořenou v předešlém kroku.
button = Button(self, label = 'I am button', id = -1) button.Bind(wx.EVT_BUTTON, self.OnClick)
Vetování událostí
Občas potřebujeme provádění události zastavit (vetovat). K tomu slouží metoda Veto()
.
import wx class Veto(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 200)) self.Bind(wx.EVT_CLOSE, self.OnClose) self.Centre() self.Show(True) def OnClose(self, event): dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) ret = dial.ShowModal() if ret == wx.ID_YES: self.Destroy() else: event.Veto() app = wx.App() Veto(None, -1, 'Veto') app.MainLoop()
V tomto příkladě odchytáváme událost wx.CloseEvent
. Tato událost je zavolána, když se pokoušíme zavřít okno. V mnoha aplikacích chceme zabránit uživateli, aby okno jen tak zavřel, protože například nestihl uložit rozdělanou práci. Musíme tedy registrovat ovladač wx.EVT_CLOSE
.
dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) ret = dial.ShowModal()
V průběhu metody wx.OnClose
vytvoříme dialog, kde se ptáme, zda uživatel chce aplikaci opravdu vypnout.
if ret == wx.ID_YES: self.Destroy() else: event.Veto()
V závislosti na proměnné ret
buď zničíme okno, nebo zastavíme událost. Všimněte si, že okno ničíme pomocí Destroy()
. Kdybychom použili metodu Close()
, skončili bychom v nekonečné smyčce.
Šíření událostí
Existují dva druhy událostí: šiřitelné a nešiřitelné. Ty šiřitelné putují od dceřiných komponent až k hlavnímu rodičovskému oknu. Nešiřitelné putovat přestanou, jakmile jsou jednou zachyceny. Pokud chceme, aby se událost šířila dál, musíme zavolat metodu Skip()
.
import wx class MyPanel(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) self.Bind(wx.EVT_BUTTON, self.OnClicked) def OnClicked(self, event): print 'event reached panel class' event.Skip() class MyButton(wx.Button): def __init__(self, parent, id, label, pos): wx.Button.__init__(self, parent, id, label, pos) self.Bind(wx.EVT_BUTTON, self.OnClicked) def OnClicked(self, event): print 'event reached button class' event.Skip() class Propagate(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 150)) panel = MyPanel(self, -1) MyButton(panel, -1, 'Ok', (15, 15)) self.Bind(wx.EVT_BUTTON, self.OnClicked) self.Centre() self.Show(True) def OnClicked(self, event): print 'event reached frame class' event.Skip() app = wx.App() Propagate(None, -1, 'Propagate') app.MainLoop()
V tomto příkladě máme jedno tlačítko v panelu, který je umístěn v okně wx.Frame
. Vytvořili jsme ovladače pro všechny tři komponenty.
event reached button class event reached panel class event reached frame class
Tento výstup dostaneme, pokud klikneme na tlačítko. Událost putuje od tlačítka přes panel až k hlavnímu oknu.
Zkuste vynechat metodu Skip()
a uvidíte, co se stane.
Identifikátory komponent
Identifikátory komponent jsou unikátní čísla (nelze použít nějaké číslo dvakrát), pomocí nichž poznáme, jaké komponentě máme předat danou událost. Existují tři způsoby, jak vytvořit identifikátor:
- Nechat Python jej vytvořit.
- Použít některý z předdefinovaných identifikátorů.
- Přiřadit komponentě identifikátor dle vlastního výběru.
wx.Button(parent, -1) wx.Button(parent, wx.ID_ANY)
Pokud jako identifikátor poskytneme -1
nebo wx.ID_ANY
, necháváme jeho výběr na wxPythonu. Takto automaticky vytvořený identifikátor je vždy záporný, zatímco námi určené identifikátory musí být vždy kladné. Automatické indentifikátory používáme hlavně tehdy, když víme, že s danou komponentou již nebudeme pracovat (například wx.StaticText
, který se po celou dobu aplikace nezmění). Pokud ale budeme chtít, můžeme získat hodnotu automatického identifikátoru pomocí metody GetId()
.
import wx class AuIds(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(170, 100)) panel = wx.Panel(self, -1) exit = wx.Button(panel, -1, 'Exit', (10, 10)) self.Bind(wx.EVT_BUTTON, self.OnExit, id=exit.GetId()) self.Centre() self.Show(True) def OnExit(self, event): self.Close() app = wx.App() AuIds(None, -1, '') app.MainLoop()
V předchozím příkladě jsme neurčili hodnotu identifikátoru → wxPython ji vybral za nás.
self.Bind(wx.EVT_BUTTON, self.OnExit, id=exit.GetId())
Hodnotu automaticky vytvořeného identifikátoru získáme metodou GetId()
.
Je doporučeno používat předdefinované indentifikátory co nejvíce. Na některých platformách to totiž zlepší vzhled aplikace.
import wx class Identifiers(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(200, 150)) panel = wx.Panel(self, -1) grid = wx.GridSizer(3, 2) grid.AddMany([(wx.Button(panel, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9), (wx.Button(panel, wx.ID_DELETE), 0, wx.TOP, 9), (wx.Button(panel, wx.ID_SAVE), 0, wx.LEFT, 9), (wx.Button(panel, wx.ID_EXIT)), (wx.Button(panel, wx.ID_STOP), 0, wx.LEFT, 9), (wx.Button(panel, wx.ID_NEW))]) self.Bind(wx.EVT_BUTTON, self.OnQuit, id=wx.ID_EXIT) panel.SetSizer(grid) self.Centre() self.Show(True) def OnQuit(self, event): self.Close() app = wx.App() Identifiers(None, -1, '') app.MainLoop()
V předchozí ukázce jsme použili předdefinované identifikátory na tlačítka. Na linuxu budou mít tato tlačítka automaticky malé ikony.
wx.Focus event
Pomocí focusu dává aplikace vědět uživateli, která komponenta je zrovna vybraná. Stisky kláves a další události jsou odeslány komponentě, která má focus. K jeho manipulaci existují dva ovladače: wx.EVT_SET_FOCUS
je zavolán, když daná komponenta získá focus. Druhý ovladač je wx.EVT_KILL_FOCUS
. Ten je zavolán vždy, když komponenta ztratí focus. Focus se mění kliknutím myši na jinou komponentu nebo pomocí klávesové zkratky (obvykle Tab/Shift+Tab).
import wx class MyWindow(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, -1) self.color = '#b3b3b3' self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetPen(wx.Pen(self.color)) x, y = self.GetSize() dc.DrawRectangle(0, 0, x, y) def OnSize(self, event): self.Refresh() def OnSetFocus(self, event): self.color = '#0099f7' self.Refresh() def OnKillFocus(self, event): self.color = '#b3b3b3' self.Refresh() class FocusEvent(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(350, 250)) grid = wx.GridSizer(2, 2, 10, 10) grid.AddMany([(MyWindow(self), 1, wx.EXPAND|wx.TOP|wx.LEFT,9), (MyWindow(self), 1, wx.EXPAND|wx.TOP|wx.RIGHT, 9), (MyWindow(self), 1, wx.EXPAND|wx.BOTTOM|wx.LEFT, 9), (MyWindow(self), 1, wx.EXPAND|wx.BOTTOM|wx.RIGHT, 9)]) self.SetSizer(grid) self.Centre() self.Show(True) app = wx.App() FocusEvent(None, -1, 'focus event') app.MainLoop()
V předchozím příkladě jsme měli čtyři panely. Panel s focusem byl zvýrazněn.
wx.SizeEvent
Událost wx.SizeEvent
je vygenerována vždy, když se změní velikost okna. V následujícím příkladu zobrazujeme aktuální velikost okna v titlebaru.
import wx class SizeEvent(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title) self.Bind(wx.EVT_SIZE, self.OnSize) self.Centre() self.Show(True) def OnSize(self, event): self.SetTitle(str(event.GetSize())) app = wx.App() SizeEvent(None, 1, 'sizeevent.py') app.MainLoop()
Nastavení titulku okna provádíme metodou SetTitle()
.
self.SetTitle(str(event.GetSize()))
Aktuální velikost okna získáme zavoláním metody GetSize()
.
wx.PaintEvent
Kdykoliv se okno překresluje, je vyvolána událost wx.PaintEvent
. Okno se překresluje celkem často. Například když změníte jeho velikost, nebo ho maximalizujete.
import wx class PaintEvent(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title) self.count = 0 self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): self.count = self.count + 1 print self.count event.Skip() app = wx.App() PaintEvent(None, -1, 'paintevent.py') app.MainLoop()
wx.KeyEvent
Pokud uživatel zmáčkne nějakou klávesu, vyvolá se událost wx.KeyEvent
. Tato událost je poslána komponentě, která má v té době focus. Pro zpracování wx.KeyEvent
existují tři různé ovladače:
- wx.EVT_KEY_DOWN
- wx.EVT_KEY_UP
- wx.EVT_CHAR
Je celkem obvyklé, že pokud uživatel zmáčkne klávesu Escape, aplikace se zavře. A to se také pokusíme naprogramovat v následujícím kódu:
import wx class KeyEvent(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title) panel = wx.Panel(self, -1) panel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) panel.SetFocus() self.Centre() self.Show(True) def OnKeyDown(self, event): keycode = event.GetKeyCode() if keycode == wx.WXK_ESCAPE: ret = wx.MessageBox('Are you sure to quit?', 'Question', wx.YES_NO | wx.NO_DEFAULT, self) if ret == wx.YES: self.Close() event.Skip() app = wx.App() KeyEvent(None, -1, 'keyevent.py') app.MainLoop()
Analýza kódu:
keycode = event.GetKeyCode()
Získali jsme kód stisknuté klávesy.
if keycode == wx.WXK_ESCAPE:
Zde kontrolujeme, jaká klávesa byla zmáčknuta. Kód klávesy Escape je wx.WXK_ESCAPE
.