Примеры событий, назначение id виджетам

wx.EVT_MOTION

Продолжаем тему событий и давайте реализуем окно, в котором будем отображать текущие координаты курсора мыши. Для этого в конструкторе класса MyFrame на событие EVT_MOTION повесим обработчик onMove

class MyFrame(wx.Frame ):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, size=(600,300))
 
        self.x = wx.StaticText(self, label='x:', pos=(10, 10))
        self.y = wx.StaticText(self, label='y:', pos=(10, 30))
 
        self.Bind(wx.EVT_MOTION, self.OnMove)

Также в нем объявлены два виджета в виде текстовых элементов, в которые мы и будем выводить информацию о позиции. Сам обработчик будет следующим:

    def OnMove(self, event):
        x, y = event.GetPosition()
        self.x.SetLabel('x: ' + str(x))
        self.y.SetLabel('y: ' + str(y))

Запустим программу и видим, что перемещая мышь, меняются значения ее координат в текстовых виджетах.

wx.EVT_PAINT

Это событие возникает при перерисовки окна или его отдельного элемента. В конструкторе класса MyFrame назначим обработчик этому событию:

self.Bind(wx.EVT_PAINT, self.OnPaint)

и ниже пропишем сам метод:

    def OnPaint(self, event):
        print("событие EVT_PAINT")
        dc = wx.PaintDC(self)
        dc.DrawLine(0,0, 100, 100)

Смотрите, когда элементы окна нуждаются в перерисовке, то возникает это событие и вызывается обработчик onPaint. В нем мы рисуем линию, которая гарантированно будет отображаться как бы мы ни перемещали наше окно. Например, именно так осуществляют прорисовку элементов пользовательского интерфейса, иначе бы он мог пропадать «стираться» в нашем окне.

wx.EVT_SET_FOCUS, wx.EVT_KILL_FOCUS

Следующие два события срабатывают в момент получения элементом фокуса и в момент его потери. Например, создадим в нашем окне два текстовых поля ввода:

        self.t1 = wx.TextCtrl(self)
        self.t2 = wx.TextCtrl(self)

Разместим их в сайзере BoxSizer с вертикальным расположением:

        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(t1, flag=wx.EXPAND | wx.ALL, border=10)
        vbox.Add(t2, flag=wx.EXPAND | wx.ALL, border=10)
        self.SetSizer(vbox)

И, далее, с каждым виджетом свяжем два обработчика:

        t1.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        t1.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
 
        t2.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        t2.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

Сами обработчики будут выглядеть так:

    def OnSetFocus(self, event):
        event.GetEventObject().SetBackgroundColour("#FFFFE5")
        event.Skip()
 
    def OnKillFocus(self, event):
        event.GetEventObject().SetBackgroundColour("#fff")
        event.Skip()

Смотрите, так как мы незнаем из какого виджета они были вызваны, используется метод GetEventObject, который возвращает связанный с событием объект, то есть, источник события. В нашем случае это будет первое или второе поле ввода. Затем, мы устанавливаем у объекта фон (светло-желтый) если элемент в фокусе и белый, если элемент не имеет фокуса. Последними строчками вызывается метод Skip, который распространяет соответствующее событие дальше, чтобы сам виджет мог его корректно обработать. Запустим программу и увидим вот такой результат:

Поле ввода с мигающим курсором подсвечивается светло-желтым, т.к. он в фокусе. Если выбрать другое поле, то прежнее приобретет белый фон, а новый будет светло-желтым.

wx.EVT_KEY_DOWN, wx.EVT_KEY_UP

Данные события срабатывают при нажатии и отпускании клавиши на клавиатуре. Давайте сначала реализуем вот такой простой пример:

class MyFrame(wx.Frame ):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, size=(600,300))
 
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
 
    def OnKeyDown(self, e):
        print("Нажали кнопку")
 
    def OnKeyUp(self, e):
        print("Отпустили кнопку")

Запустим и видим, что при нажатии на кнопку возникают эти два события: сначала EVT_KEY_DOWN, затем, EVT_KEY_UP.

Далее, сделаем следующее. При нажатии на кнопку ESC будет появляться сообщение с вопросом: хочет ли пользователь выйти из программы. Реализовать это можно следующим образом:

    def OnKeyDown(self, e):
        key = e.GetKeyCode()
        if key == wx.WXK_ESCAPE:
            ret = wx.MessageBox('Вы дейстительно хотите выйти из программы?', 'Вопрос', 
                                wx.YES_NO | wx.NO_DEFAULT, self)
 
            if ret == wx.YES:
                self.Close()

Порядок назначения id элементам интерфейса

Во второй части занятия поговорим о порядке назначения id виджетам интерфейса. Мы часто в программах писали так:

        b1 = wx.Button(self, wx.ID_ANY, label="Кнопка 1")
        b2 = wx.Button(self, wx.ID_ANY, label="Кнопка 2")
 
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(b1, flag=wx.EXPAND | wx.ALL, border=10)
        vbox.Add(b2, flag=wx.EXPAND | wx.ALL, border=10)
 
        self.SetSizer(vbox)

Вот эта константа wx.ID_ANY в действительности равна -1 и говорит wxPython, чтобы он автоматически назначил id данному элементу. То есть, вместо wx.ID_ANY, конечно, можно просто записать -1, но лучше делать это через константу и если в будущих версиях wxPython она будет изменена, то не придется переписывать всю программу.

Так вот, все автоматические id генерируются уникальными и отрицательными. И мы в этом можем убедиться, если вызовем метод GetId для этих кнопок:

print(b1.GetId(), b2.GetId(), sep="\n")

Благодаря этому, можно задавать свои собственные id в области положительных чисел, не опасаясь, что произойдет дублирование с уже существующими (автоматическими) значениями. То есть, можно сделать, например, так:

BUTTON1 = 1
BUTTON2 = 2

И, затем, использовать их при объявлении кнопок:

        b1 = wx.Button(self, BUTTON1, label="Кнопка 1")
        b2 = wx.Button(self, BUTTON2, label="Кнопка 2")

Теперь, при запуске программы увидим значения этих констант в консоли. Но и это не лучший способ создания собственных id. Обычно, мы полагаем, что они должны быть уникальными и чтобы случайно не сделать дубли, лучше пользоваться специальным методом wxPython:

wx.NewIdRef()

который возвращает объект WindowIDRef с уникальным сгенерированным id. Этот метод можно применять так. В глобальной области создаем две ссылки с уникальными id:

BUTTON1 = wx.NewIdRef()
BUTTON2 = wx.NewIdRef()

и, затем, назначаем их нашим кнопкам:

b1 = wx.Button(self, BUTTON1.GetId(), label="Кнопка 1")
b2 = wx.Button(self, BUTTON2.GetId(), label="Кнопка 2")

Обратите внимание, здесь мы вызываем метод GetId, чтобы из объекта WindowIDRef получить число (id) и назначить их виджетам. Преимущество использования BUTTON1 вместо wx.ID_ANY в том, что мы можем разным виджетам через BUTTON1 присвоить одни и те же id. Например, если пункт меню дублируется кнопкой на панели инструментов и вызывается один и тот же обработчик, то им назначается одно и то же значение id.

Стандартные id

Модуль wxPython содержит несколько стандартных (предопределенных) id, например,

ID_SAVE, ID_OPEN, ID_NEW

и много других. Назначая стандартные id виджетам wxPython добавляем им соответствующие иконки и горячие клавиши:

b1 = wx.Button(self, wx.ID_SAVE, label="Кнопка 1")

Правда, это работает преимущественно под ОС Linux. Под ОС Windows никакого эффекта они не дают.

Видео по теме