Создание меню и подменю

Пришло время создать первый интерфейсный элемент – меню. Я напомню, что для его создания используется три класса:

  • MenuBar – для создания панели меню;
  • Menu – для создания вкладки меню;
  • MenuItem – для создания отдельного пункта.

Давайте для начала создадим вот такое простое меню. На уровне языка Python это выглядит так:

class MyFrame(wx.Frame ):
    def __init__(self, parent, title):
        super().__init__(parent, -1, title)
 
        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
 
        item = wx.MenuItem(fileMenu, wx.ID_EXIT, "Выход", "Выход из приложения")
        fileMenu.Append(item)
 
        menubar.Append(fileMenu, "&File")
        self.SetMenuBar(menubar)

Смотрите, мы сначала создаем экземпляр класса MenuBar и экземпляр класса Menu. Далее, указываем, что создаем пункт во вкладке fileMenu со строкой «Выход». Параметр ID_EXIT – это id нашего пункта. Оно выбрано как стандартное и, по идее, должно добавлять соответствующую иконку и комбинацию клавиш Ctrl+Q (но это не всегда срабатывает). В конце идет описание этого пункта, которое можно позже вывести, например, в статусную строку. Далее, мы добавляем созданный пункт во вкладку fileMenu с помощью метода Append и на панели menubar размещаем эту вкладку, также вызывая метод Append класса MenuBar. В конце размещаем панель меню в нашем окне.

Запустим программу и видим ожидаемый функционал. И смотрите, если сейчас нажать комбинацию клавиш

Alt+F

то наша вкладка «File» будет автоматически выбрана. Так получилось благодаря амперсанду перед символом F. Если мы его поставим, например, перед буквой «i», то вкладка откроется при нажатии

Alt+i

И так далее. Вот в этом смысл этого амперсанда.

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

item = wx.MenuItem(fileMenu, wx.ID_EXIT, "Выход", "Выход из приложения")
fileMenu.Append(item)

можно объединить в одну:

item = fileMenu.Append(wx.ID_EXIT, "Выход", "Выход из приложения")

Результат будет абсолютно таким же. В дальнейшем мы будем создавать пункты именно так, если не понадобится первый способ.

Теперь свяжем данный пункт с обработчиком. Например, пусть при его выборе вызывается метод:

    def onQuit(self, event):
        self.Close()

Обратите внимание, у этого метода следует прописывать обязательный параметр event, который будет ссылаться на объект события. Через него можно получать дополнительную информацию о том, что произошло.

Само связывание делается с помощью специального метода Bind, который наследуется почти всеми классами wxPython. Его синтаксис следующий:

Bind(event, handler, source=None)

  • event – тип события, связанный с определенным интерфейсным объектом;
  • handler – ссылка на функцию-обработчик;
  • source – источник, генерирующий событие.

В нашем случае этот метод можно записать так:

self.Bind(wx.EVT_MENU, self.onQuit, item)

Мы здесь указали тип события – EVT_MENU, далее, ссылка на метод onQuit, и, наконец, источник – наш пункт меню item. Все, теперь при запуске программы сработает метод onQuit и окно будет закрыто.

Добавим теперь нашему пункту дополнительный функционал. Во-первых, мы хотим, чтобы при комбинации клавиш

Ctrl+Q

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

item = fileMenu.Append(wx.ID_EXIT, "Выход\tCtrl+Q", "Выход из приложения")

Далее, добавим нашему пункту изображение иконки. Вначале определим константу

APP_EXIT = 1

которая будет связана с ним. И создадим пункт, используя класс MenuItem:

item = wx.MenuItem(fileMenu, APP_EXIT, "Выход\tCtrl+Q", "Выход из приложения")

В данном случае это необходимо, т.к. перед его добавление во вкладку мы присвоим ему иконку:

item.SetBitmap(wx.Bitmap('exit16.png'))
fileMenu.Append(item)

Запускаем программу и видим такой эффект:

Разумеется, картинка exit16.png должна располагаться в том же каталоге, что и запускаемый файл с программой на питоне. Размеры картинки должны соответствовать нашему пункту. Если ее взять больше:

item.SetBitmap(wx.Bitmap('exit32.png'))

то это может выглядеть не так эстетично.

Кстати, используя нашу собственную константу APP_EXIT в качестве id пункта, в дальнейшем, мы можем связывать через нее обработчик в методе Bind:

self.Bind(wx.EVT_MENU, self.onQuit, id=APP_EXIT)

Такой подход более предпочтителен, чем использование ссылок, т.к. их легко изменить или многократно использовать для разных пунктов. А вот id остается неизменным.

Подменю и сепараторы

Давайте расширим нашу вкладку, добавим на нее еще несколько стандартных пунктов, например, такие:

fileMenu.Append(wx.ID_NEW, '&Новый\tCtrl+N')
fileMenu.Append(wx.ID_OPEN, '&Открыть\tCtrl+O')
fileMenu.Append(wx.ID_SAVE, '&Сохранить\tCtrl+S')

И смотрите, нам бы здесь хотелось визуально отделить эти пункты от последнего. Как раз это можно сделать с помощью специального пункта, который называется separator. Он добавляется специальным методом:

fileMenu.AppendSeparator()

Теперь наша вкладка выглядит привычнее.

Следующим шагом добавим подменю. Создадим еще одну вкладку:

expMenu = wx.Menu()

пропишем туда вот такие пункты:

expMenu.Append(wx.ID_ANY, "Экспорт изображения")
expMenu.Append(wx.ID_ANY, "Экспорт видео")
expMenu.Append(wx.ID_ANY, "Экспорт данных")

и добавим на вкладку fileMenu:

fileMenu.AppendSubMenu(expMenu, "&Экспорт")

Запускаем программу и видим такой результат:

Типы пунктов меню

Модуль wxPython поддерживает четыре типа пуктов:

  • ITEM_NORMAL – обычный текст;
  • ITEM_SEPARATOR – разделитель (сепаратор);
  • ITEM_CHECK – пункт с флажком;
  • ITEM_RADIO – пункт с возможностью перебора.

С первыми двумя мы уже познакомились. Для демонстрации двух других создадим еще одну вкладку:

viewMenu = wx.Menu()

Добавим туда следующие строчки:

viewMenu.Append(wx.ID_ANY, 'Статустная строка', kind=wx.ITEM_CHECK)
viewMenu.Append(wx.ID_ANY, 'Тип RGB', 'Тип RGB', kind=wx.ITEM_RADIO)
viewMenu.Append(wx.ID_ANY, 'Тип sRGB', 'Тип sRGB', kind=wx.ITEM_RADIO)

Смотрите, здесь последним именованным параметром идет kind, определяющий тип пункта меню. У первого стоит ITEM_CHECK, а у двух других - ITEM_RADIO.

Добавим эту вкладку на панель меню:

menubar.Append(viewMenu, "&Вид")

Запустим программу и увидим вот такой эффект:

Смотрите, первую строчку можно отмечать флажком, а последние две только перебирать: либо RGB, либо sRGB.

Давайте теперь посмотрим как все это можно отследить в обработчиках этих событий. Пропишем в начале вот такие константы:

VIEW_STATUS = 2
VIEW_RGB = 3
VIEW_SRGB = 4

Укажем их при создании строчек меню:

self.vStatus = viewMenu.Append(VIEW_STATUS, 'Статустная строка', kind=wx.ITEM_CHECK)
self.vRgb = viewMenu.Append(VIEW_RGB, 'Тип RGB', 'Тип RGB', kind=wx.ITEM_RADIO)
self.vSrgb = viewMenu.Append(VIEW_SRGB, 'Тип sRGB', 'Тип sRGB', kind=wx.ITEM_RADIO)

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

self.Bind(wx.EVT_MENU, self.onStatus, id=VIEW_STATUS)
self.Bind(wx.EVT_MENU, self.onImageType, id=VIEW_RGB)
self.Bind(wx.EVT_MENU, self.onImageType, id=VIEW_SRGB)

Объявим два метода:

    def onStatus(self, event):
        if self.vStatus.IsChecked():
           print("Показать статусную строку")
        else:
            print("Скрыть статусную строку")
 
    def onImageType(self, event):
        if self.vRgb.IsChecked():
            print("Режим RGB")
        elif self.vSrgb.IsChecked():
            print("Режим sRGB")

Смотрите, мы здесь используем метод IsChecked(), чтобы определить: выбран ли данный пункт. И в соответствии с этим выводим в консоль сообщения. Запустим программу и убедимся, что все работает корректно.

Видео по теме