воскресенье, 3 июня 2012 г.

wxPython in Action. Глава 10. Создаём и используем меню в wxPython.

В этой главе Вы узнаете:
  1. Как создавать меню
  2. Как работать с элементами меню
  3. Как добавлять подменю, всплывающие меню и собственные виды меню
  4. Как создавать удобные меню
Трудно представить себе приложение без знакомой полоски сверху со словами "Файл", "Редактировать" и "Справка". Меню настолько привычная часть интерфейса, что многие не уделяют достаточного внимания его созданию, что тоже плохо. Ведь настолько легко и быстро предоставить пользователю доступ к возможностям вашего приложения без меню практически не возможно!
В wxPython есть три исходных класса, обеспечивающих создание меню. Класс wx.MenuBar управляет самой панелью меню, wx.Menu - каждым конкретным меню в этой панели (очевидно, что экземпляр wx.MenuBar может содержать несколько экземпляров wx.Menu). Класс wx.MenuItem отвечает за каждый пункт в wx.Menu.
Во второй главе мы уже вкратце познакомились с меню, в листинге 5.5 показали простой механизм их создания, а в 7 главе поговорили о специальных возможностях меню. Теперь настало время поговорить о них более подробно.
10.1 Создаём меню
Для начала займемся полосой меню. Для того, чтобы её использовать, нам необходимо сделать следующее:
  • Создать полосу меню (menu bar)
  • Добавить полосу меню к фрейму
  • Создать отдельные меню
  • Добавить эти меню к полосе меню или к родительскому меню
  • Создать отдельные пункты меню
  • Добавить эти пункты к соответствующим меню
  • Создать привязку (binding) для событий каждого пункта меню
На самом деле порядок выполнения этих действий достаточно произволен пока вы создаёте меню до его отображения в методе инициализации формы. Вы можете изменять меню и после его отображения, но в таком случае то, что увидит пользователь зависит от порядка ваших действий. Например, не будет иметь значение прикрепляете ли Вы полосу меню к фрейму сразу после создания или сначала создаёте пункты и подменю. Но всё же, для облегчения сопровождения кода и большей его читабельности, мы рекомендуем размещать в коде логически связанные компоненты поблизости вместе. Более подробно эта тема освещалась в главе 5, посвящённой рефакторингу кода. В следующем разделе мы начнём наш разговор с основных действий при создании меню.
10.1.1 Как создать полосу меню и прикрепить её к фрейму.
Для создания полосы меню необходимо использовать конструктор класса wx.MenuBar, не принимающий аргументов:
wx.MenuBar()
После того, как полоса меню создана, можно прикрепить её к wx.Frame или его подклассу используя его метод SetMenuBar(). Обычно это происходит в методе __init__() или OnInit() фрейма:

menubar = wx.MenuBar()

self.SetMenuBar

Вам не обязательно создавать временную переменную для полосы меню, но её наличие облегчит Вам позже добавление отдельных меню. Если Вы не создаёте для неё отдельной переменной, то можно получить ссылку на полосу меню с помощью метода wx.Frame.GetMenuBar().
10.1.2 Как создать меню и прикрепить его к полосе меню?
Полоса меню wxPython состоит из отдельных меню, каждое из которых необходимо создавать по отдельности. Вот конструктор класса wx.Menu:
wx.Menu(title="", style=0)
У нас есть только одни доступный стиль для экземпляров wx.Menu. При использовании GTK стиль wx.MENU_TEAROFF даёт возможность отделить меню от полосы меню и использовать его отдельно. На других платформах этот флаг будет просто проигнорирован. Если платформа позволяет, то конструктору меню при создании можно передать заголовок (title), который будет отображаться над всеми элементами меню, добавленными позже. На рисунке 10.1 показано окно с тремя меню, код этого окна приведён в листинге 10.1
рис 10.1

import wx
class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Simple Menu Example")
        p = wx.Panel(self)
        # создаём панель меню
        menuBar = wx.MenuBar()
        # создаём меню
        menu = wx.Menu()
        # добавляем меню к панели меню
        menuBar.Append(menu, "Left Menu")
        menu2 = wx.Menu()
        menuBar.Append(menu2, "Middle Menu")
        menu3 = wx.Menu()
        menuBar.Append(menu3, "Right Menu")
        self.SetMenuBar(menuBar)
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()
В API меню wxPython большинство манипуляций с объектом производятся через класс его контейнера. Позже в этой главе мы обсудим специфичные для wx.Menu методы и Вы увидите, что почти все они предназначены для работы с пунктами меню из самого меню. В завершение этого раздела, раз уж мы говорим о работе с объектом wx.Menu, мы перечислим методы wx.Menubar предназначенные для управления объектами меню:
ФункцияОписание
Append(menu, title)
Добавляет menu в конец полосы меню (то есть в после последнего правого элемента). Параметр title используется для отображения нового меню. При удачном выполнении возвращает True, иначе - False.
Insert(pos, menu, title)
Вставляет переданное menu в заданную в pos позицию (после данного вызова выражение GetMenu(pos)==menu будет истинно). Как и при вставке в список, все позиции всех последующих меню сдвигаются вправо. Отсчёт позиций начинается с 0, так что pos=0 эквивалентно помещению меню в крайнюю левую позицию полосы меню. Если в качестве pos передать результат вызова GetMenuCount(), то результат будет такой же как при вызове метода Append(). Параметр title используется для отображения нового меню. При удачном выполнении возвращает True, иначе - False.
Remove(pos)
Удаляет меню из позиции pos, сдвигая оставшиеся меню влево. Возвращает то меню, которое было удалено.
Replace(pos, menu, title)
Заменяет меню на позиции pos меню переданным в аргументе menu а для отображения использует параметр title. Не затрагивает остальные меню в полосе. Возвращает то меню, которое раньше находилось на этом месте.
Класс wx.MenuBar содержит ещё и методы, позволяющие манипулировать меню таким образом:
МетодОписание
EnableTop(pos, enable)Делает меню на позиции pos доступным или нет, в зависимости от значения параметра enable - True или False.
GetMenu(pos)Возвращает объект меню, находящийся в заданной позиции.
GetMenuCount()Возвращает количество меню в полосе меню.
FindMenu(title)
Возвращает числовой индекс меню в полосе меню при заданном title. Если такого меню нет, метод возвращает wx.NOT_FOUND. Метод игнорирует декораторы акселераторов (то есть значки перед буквой для быстрого вызова) при их наличии.
GetLabelTop(pos)
SetLabelTop(pos, label)
Метод установки и получения метки меню в данной позиции
10.1.3 Как добавить элементы в выпадающее меню?
Есть несколько методов для добавления пунктов меню в меню. Самый простой - использовать метод Apend() экземпляра wx.Menu:
Append(id, string, helpStr="", kind=wx.ITEM_NORMAL)
Параметр id - это ID wxPython'а. string - стока, которая будет отображаться в меню. helpStr - стока, которая будет отображаться в статусной строке фрейма при подсветке этого пункта меню. Аргумент kind позволяет установить тип элемента меню в переключатель, позже мы поговорим об этом подробнее. Метод Append помещает данный элемент в конец выбранного меню.
Если Вы хотите добавить в меню разделитель - самый простой способ сделать это - вызвать метод wx.Menu.AppendSeparator(), не принимающий аргументов, который помещает разделитель в конец меню.
В листинге 10.2 приведен пример использования метода Append() для построения меню с двумя разделителями.
Листинг 10.2

import wx
class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "Menu Example with StatusBar")
        p = wx.Panel(self)
        self.CreateStatusBar()
        menu = wx.Menu()
        simple = menu.Append(-1, "Simple menu item",
            "This is some help text")
        menu.AppendSeparator()
        exit = menu.Append(-1, "Exit",
            "Selecting this item will exit the program")
        self.Bind(wx.EVT_MENU, self.OnSimple, simple)
        self.Bind(wx.EVT_MENU, self.OnExit, exit)
        menuBar = wx.MenuBar()
        menuBar.Append(menu, "Menu")
        self.SetMenuBar(menuBar)
    def OnSimple(self, event):
        wx.MessageBox("You selected the simple menu item")
    def OnExit(self, event):
        self.Close()

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()

На рисунке 10.2 показан получившийся фрейм:
рис 10.2
Кроме метода Append есть ещё два других набора методов для добавления элементов меню. Для того, чтобы поместить элемент меню в начало меню используйте один из этих двух методов:
  • Prepend(id, string, helpStr="", kind=wx.ITEM_NORMAL
  • PrependSeparator()
Эти два метода принимают те же аргументы, что и их аналоги Append и AppendSeparator. Для того же, чтобы поместить элемент в любую другую позицию у Вас есть
  • Insert(pos, id, string, helpStr="", kind=wx.ITEM_NORMAL)
  • InsertSeparator(pos)
Аргумент pos, как понятно, содержит номер позиции, куда будет помещён указанный элемент, так что если pos=0, элемент будет помещён в начало списка, а если он равен длине списка - то в его конец. Все элементы списка, находящиеся после указанной позиции, сдвигаются вниз. Все эти методы неявно создают экземпляры класса wx.MenuItem, однако Вы можете создать их и явно при помощи конструктора данного класса для того, чтобы установить дополнительные свойства, кроме названия, этого экземпляра. Вы можете задать, например, свой цвет или шрифт. Конструктор данного класса выглядит таким образом:
wx.MenuItem(parentMenu=None, id=ID_ANY, text="", helpString="", kind=wx.ITEM_NORMAL, subMenu=None)
Аргумент parentMenu, если передан, должен быть экземпляром класса wx.Menu. Новый элемент меню, при создании, автоматически не добавляется к родительскому меню для отображения - Вы должны сделать это вручную. В этом меню отличается от других виджетов и контейнеров wxPython. id определяет идентификатор для нового элемента, и с ним можно проделать тот же трюк, передав значение -1 для автоматической генерации значения, как и с окнами. Аргумент text определяет текст, отображаемый на элементе, а helpString - текст, отображаемый в строке статуса. kind определяет тип элемента меню, для стандартного элемента он равен wx.ITEM_NORMAL, о других значениях мы поговорим позже. Если subMenu не пустой аргумент, то элемент на самом деле является субменю. Однако мы не рекомендуем использовать такой механизм - лучше воспользоваться методом, описанным в разделе 10.3
В отличие от большинства виджетов, созданный элемент не добавляется к родительскому меню. Для того, чтобы сделать это необходимо использовать один из трёх методов экземпляра wx.Menu:
  • AppendItem(aMenuItem)
  • InsertItem(pos, aMenuItem)
  • PrependItem(aMenuItem)

Все они ведут себя так, как и ожидается из их названия.
Для того, чтобы удалить элемент из меню, нужно использовать метод Remove(id) или RemoveItem(item), в зависимости от того, что у Вас есть - id или сам элемент. Все нижележащие меню сдвигаются, соответственно, вверх. Метод Remove() возвращает удалённый элемент меню, что позволяет Вам использовать его позже. В отличие от полосы меню, сами меню не имеют метода для непосредственной замены элементов, для этого вам сперва надо удалить элемент, а после - вставить в нужную позицию.
Класс wx.Menu так же содержит два метода для получения информации о своих компонентах. GetMenuItemCount() возвращает количество элементов в меню, а GetMenuItems() возвращает список элементов меню в порядке их расположения в меню. Этот список является копией актуальной последовательности элементов, то есть изменения в нём не повлияют на само меню. Поэтому Вы не можете использовать этот список в качестве замены методов добавления и удаления элементов.
Вы можете добавлять и удалять элементы меню в процессе выполнения программы пока меню активно. Листинг 10.3 демонстрирует примеры кода, добавляющего элементы меню в процессе выполнения программы. Метод OnAddItem(), вызываемый при нажатии на кнопку, добавляет новый элемент в конец меню.

import wx
class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "Add Menu Items")
        p = wx.Panel(self)
        self.txt = wx.TextCtrl(p, -1, "new item")
        btn = wx.Button(p, -1, "Add Menu Item")
        # связываем метод с событием кнопки
        self.Bind(wx.EVT_BUTTON, self.OnAddItem, btn)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.txt, 0, wx.ALL, 20)
        sizer.Add(btn, 0, wx.TOP|wx.RIGHT, 20)
        p.SetSizer(sizer)

        self.menu = menu = wx.Menu()
        simple = menu.Append(-1, "Simple menu item")
        menu.AppendSeparator()
        exit = menu.Append(-1, "Exit")
        self.Bind(wx.EVT_MENU, self.OnSimple, simple)
        self.Bind(wx.EVT_MENU, self.OnExit, exit)

        menuBar = wx.MenuBar()
        menuBar.Append(menu, "Menu")
        self.SetMenuBar(menuBar)

    def OnSimple(self, event):
        wx.MessageBox("You selected the simple menu item")

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

    def OnAddItem(self, event):
        # добавляем элемент меню
        item = self.menu.Append(-1, self.txt.GetValue())
        # связываем его с методом
        self.Bind(wx.EVT_MENU, self.OnNewItemSelected, item)

    def OnNewItemSelected(self, event):
        wx.MessageBox("You selected a new item")

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()

В этом примере onAddItem() читает значение, введённое в текстовое поле и использует Append() для добавления нового элемента в меню. Кроме того, он связывает событие выбора элемента меню, так что у него появляется некоторая функциональность. В следующем разделе мы обсудим события элементов меню.
10.1.4 Как отвечать на события меню?
Теперь посмотрим на два примера, которые покажут нам, как реагировать на событие выбора меню. Как и многие другие виджеты, которые мы рассматривали в 8 главе, события элементов меню являются экземплярами специального типа wx.CommandEvent. В нашем случае это будет wx.EVT_MENU.
События элементов меню отличаются от других командных событий двумя деталями. Во-первых, функция Bind(), которая связывает событие меню с определенной функцией, относится не к элементу меню, меню или полосе меню, а к фрейму, содержащему полосу меню. Во-вторых, так как фрейм обычно содержит несколько элементов меню с одним и тем же событием wx.EVT_MENU, методу Bind() нужно передать третий параметр, который и является этим элементом меню. Это позволяет фрейму отличать события от разных элементов меню (c этим же мы встречались при обработке нажатий на кнопки).
Таким образом, типичный вызов Bind() для привязки события меню выглядит так:
self.Bind(wx.EVT_MENU, self.OnExit, exit_menu_item)
где self - это фрейм, self.OnExit - метод обработчик события, а exit_menu_item - элемент меню.
Хотя идея связывания элементов меню с обработчиками через фрейм может казаться немного странной, тем не менее для этого есть своя причина. Связывание на уровне фрейма позволит Вам прозрачно привязать кнопку с панели инструментов к тому же обработчику, что и элемент меню. Если кнопка панели инструментов имеет тот же id, что и элемент меню, то будет достаточно одного вызова Bind() c передачей ему этого id. Это становится возможным благодаря тому, что и события меню и события панели инструментов проходят через фрейм. Если бы события элементов меню перехватывались в полосе меню, этот обработчик никогда бы не узнал о событиях с панели инструментов.
Иногда бывает так, что у Вас есть несколько элементов меню, которые должны быть связаны с одним и тем же обработчиком. Например, набор переключателей, которые фактически делают одно и то же, могут быть связаны с одним обработчиком. Если элементы меню имеют последовательные id, то вместо того, чтобы связывать их по отдельности, Вы можете использовать тип события wx.EVT_MENU_RANGE:
self.Bind(wx.EVT_MENU_RANGE, function, id1=menu1, id2=menu2)
В этом случае все элементы меню с идентификаторами между menu1 и menu2 (включительно) будут связаны с переданной функцией.
И хотя обычно Вас интересуют только командные события меню, тем не менее у меню есть и другие события, которые можно использовать. В wxPython класс wx.MenuEvent управляет событиями отображения и выделения меню. Таблица 10.3 описывает четыре типа событий класса wx.MenuEvent
Таблица 10.3 Типы событий wx.MenuEvent
Тип событияОписание
EVT_MENU_CLOSEВозникает когда меню закрывается
EVT_MENU_HIGHLIGHT
Возникает при подсветке (наведении на) элемента меню. Связано с конкретным id. По умолчанию отображает подсказку в статусной панели фрейма
EVT_MENU_HIGHLIGHT_ALL
Возникает когда меню подсвечивается на при этом не привязано к конкретному id, другими словами может быть только один обработчик этого события для всей полосы меню. Его следует использовать если Вы хотите чтобы происходило какое-то действие при подсвечивании любого элемента меню.
EVT_MENU_OPENВозникает когда меню открывается

После того, как мы обсудили основы создания меню, можно перейти к описанию того, как работать с элементами меню.
10.2 Работа с элементами меню
Хотя меню и полосы меню очевидно определяют структуру меню, тем не менее большую часть своего времени и усилий Вы потратите на работу с элементами меню. В следующих разделах мы поговорим о том, как найти элемент меню, разрешить или запретить взаимодействие с ним, создать меню переключателей и добавлять акселераторы (горячие клавиши).
10.2.1 Как найти определённый элемент меню в меню?
Есть несколько способов найти нужное меню или элемент меню имея его метку или идентификатор. Чаще всего Вы будете использовать эти методы в обработчиках событий, особенно если Вы захотите изменить элемент меню или отобразить его метку (текст на элементе) в другом месте. Листинг 10.4 расширяет динамическое меню из предыдущего примера используя FindItemById() для получения подходящего элемента меню для дальнейшего отображения.

import wx

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "Find Item Example")
        p = wx.Panel(self)
        self.txt = wx.TextCtrl(p, -1, "new item")
        btn = wx.Button(p, -1, "Add Menu Item")
        self.Bind(wx.EVT_BUTTON, self.OnAddItem, btn)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.txt, 0, wx.ALL, 20)
        sizer.Add(btn, 0, wx.TOP|wx.RIGHT, 20)
        p.SetSizer(sizer)

        self.menu = menu = wx.Menu()
        simple = menu.Append(-1, "Simple menu item")
        menu.AppendSeparator()
        exit = menu.Append(-1, "Exit")
        self.Bind(wx.EVT_MENU, self.OnSimple, simple)
        self.Bind(wx.EVT_MENU, self.OnExit, exit)

        menuBar = wx.MenuBar()
        menuBar.Append(menu, "Menu")
        self.SetMenuBar(menuBar)

    def OnSimple(self, event):
        wx.MessageBox("You selected the simple menu item")

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

    def OnAddItem(self, event):
        item = self.menu.Append(-1, self.txt.GetValue())
        self.Bind(wx.EVT_MENU, self.OnNewItemSelected, item)

    def OnNewItemSelected(self, event):
        # получаем элемент меню
        item = self.GetMenuBar().FindItemById(event.GetId())
        text = item.GetText()
        wx.MessageBox("You selected the '%s' item" % text)

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()
В этом примере метод FindItemById() используется для получения ссылки на элемент меню, чтобы отобразить его метку.
И wx.MenuBar и wx.Menu имеют по сути одни и те же методы для поиска информации о конкретных элементах меню. Главная разница в том, что метод wx.MenuBar будет искать элемент в пределах полосы меню, а wx.Menu будет искать только в пределах этого меню. В большинстве случаев предпочтительнее использовать wx.MenuBar, хотя бы потому, что ссылку на неё легко получить с помощью метода wx.Frame.GetMenuBar().
Для поиска меню верхнего уровня можно использовать метод wx.MenuBar.FindMenu(title). Он возвращает либо индекс соответствующего меню либо константу wx.NOT_FOUND. Для получения ссылки на найденное меню используйте GetMenu():
def FindMenuInMenuBar(menuBar, title):
    pos = menuBar.FindMenu(title)
    if pos == wx.NOT_FOUND:
        return None
    return menuBar.GetMenu(pos)

Параметр title метода FindMenu является текстом меню с или без декоратора акселератора, о которых мы будем говорить ниже. Например FindMenu("File") найдет меню даже если его название задано как &File. Это касается и всех методов класса wx.Menu, которые производят поиск на основании строки названия. В таблице 10.4 перечислены методы wx.MenuBar, которые могут использоваться для поиска и манипуляции определённым элементом меню:
Таблица 10.4 Методы взаимодействия wx.MenuBar
МетодОписание
FindMenuItem(menuString, itemString)
Ищет элемент меню с названием itemString в меню с названием menuString. Либо возвращает найденный элемент меню либо wx.NOT_FOUND
FindItemById(id
Возвращает элемент меню, ассоциированный с переданным идентификатором. Если такого элемента нет - возвращает None.
GetHelpString(id)
SetHelpString(id, helpString)
Методы для получения и установки строки помощи для элемента меню с переданным идентификатором. Если такого элемента меню нет, метод установки вернёт "", а метод установки просто ничего не сделает.
GetLabel(id)
SetLabel(id,label)
Методы получения и установки для отображаемого названия элемента меню с переданным идентификатором. Обрабатывает отсутствующие элементы так же как и методы для строки помощи. Эти методы следует использовать только после того, как полоса меню была соединена с фреймом.

Таблица 10.5 содержит API wx.Menu для элементов меню. Эти методы ведут себя так же как и методы wx.MenuBar кроме того, что элемент меню должен присутствовать в данном меню.
Таблица 10.4 Методы взаимодействия wx.Menu
МетодОписание
FindItem(itemString)Возвращает элемент меню с названием либо wx.NOT_FOUND
FindItemById(id)Как и у полосы меню
FindItemByPosition(pos)Возвращает элемент меню, находящийся в данной позиции
GetHelpString(id)
SetHelpString(id, helpString)
Как и у полосы меню
GetLabel(id)
SetLabel(id,label)
Как и у полосы меню

После того, как вы получили элемент меню, Вы можете захотеть сделать что-то полезное, например активировать или дезактивировать его. В следующем разделе мы поговорим об этом.
10.2.2 Как я могу активировать или деактивировать элемент меню?
Так же как и другие виджеты меню и элементы меню могут быть активированы или деактивированы. Деактивированное меню или элемент меню отображается серым, а не черным цветом; оно не продуцирует события при подсветке или выборе и оно "невидимо" для системы событий.
Листинг 10.5 показывает пример простого кода, который изменяет состояния элементов меню используя методы полосы меню IsEnabled() и Enable() при обработке события кнопки:
Листинг 10.5
import wx

ID_SIMPLE = wx.NewId()

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "Enable/Disable Menu Example")
        p = wx.Panel(self)
        self.btn = wx.Button(p, -1, "Disable Item", (20,20))
        self.Bind(wx.EVT_BUTTON, self.OnToggleItem, self.btn)

        menu = wx.Menu()
        menu.Append(ID_SIMPLE, "Simple menu item")
        self.Bind(wx.EVT_MENU, self.OnSimple, id=ID_SIMPLE)

        menu.AppendSeparator()
        menu.Append(wx.ID_EXIT, "Exit")
        self.Bind(wx.EVT_MENU, self.OnExit, id=wx.ID_EXIT)

        menuBar = wx.MenuBar()
        menuBar.Append(menu, "Menu")
        self.SetMenuBar(menuBar)

    def OnSimple(self, event):
        wx.MessageBox("You selected the simple menu item")
    def OnExit(self, event):
        self.Close()
    def OnToggleItem(self, event):
        menubar = self.GetMenuBar()
        enabled = menubar.IsEnabled(ID_SIMPLE)
        menubar.Enable(ID_SIMPLE, not enabled)
        self.btn.SetLabel(
            (enabled and "Enable" or "Disable") + " Item")

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()
Чтобы посмотреть или изменить статус элемента меню из полосы меню, меню или самого этого элемента меню используйте wx.MenuBar.IsEnabled(id), wx.Menu.IsEnabled(id) или wx.MenuItem.IsEnabled(). Методы полосы меню и меню принимают в качестве аргумента идентификатор меню. Оба метода возвращают True если элемент активирован и False в противном случае или если элемент не существует. Единственная разница между ними в том, что метод wx.Menu ищет лишь в данном меню, тогда как метод wx.MenuBar ищет среди всех меню. Метод wx.MenuItem не принимает аргументов и возвращает значение для того элемента меню, для которого он вызывается.
Для изменения состояния меню используйте wx.MenuBar.Enable(id, enable), wx.Menu.Enable(id, enable) или wx.MenuItem.Enable(enable). Параметр enable принимает логическое значение True для активации элемента и False для его деактивации. Область действия методов Enable такая же как и для IsEnabled. Кроме того Вы можете деактивировать меню целиком с помощью метода wx.MenuBar.EnableTop(pos, enable). Аргумент pos определяет номер позиции меню в полосе меню.
10.2.3 Как прикрепить к элементу меню акселератор (горячую клавишу)
В wxPython Вы можете настроить управление с помощью клавиатуры и клавиатурные сокращения (акселераторы) для элементов меню. На рисунке 10.3 изображено простое меню с добавленными к нему акселераторами. Обратите внимание, что в названии элементов меню есть подчёркнутые буквы и что рядом с элементом с названием Accelerated есть надпись Ctrl+A
Рисунок 10.3 Элементы меню с горячими клавишами
Практика показывает, что горячие клавиши не всегда увеличивают производительность труда. И всё же они являются стандартными элементами интерфейса и пользователи всегда предполагают их наличие. Кроме того, горячие клавиши полезны для людей с ограниченными возможностями. Листинг 10.6 содержит код для добавления горячих клавиш к элементам меню.
Листинг 10.6 Добавление горячих клавиш к элементам меню
import wx

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "Accelerator Example")
        p = wx.Panel(self)
        menu = wx.Menu()
        # создаём мнемонику
        simple = menu.Append(-1, "Simple &menu item")
        # создаём акселератор
        accel = menu.Append(-1, "&Accelerated\tCtrl-A")

        menu.AppendSeparator()
        exit = menu.Append(-1, "E&xit")

        self.Bind(wx.EVT_MENU, self.OnSimple, simple)
        self.Bind(wx.EVT_MENU, self.OnAccelerated, accel)
        self.Bind(wx.EVT_MENU, self.OnExit, exit)

        menuBar = wx.MenuBar()
        menuBar.Append(menu, "&Menu")
        self.SetMenuBar(menuBar)

        # используем таблицу акселераторов
        acceltbl = wx.AcceleratorTable( [
                (wx.ACCEL_CTRL, ord('Q'), exit.GetId())
            ])
        self.SetAcceleratorTable(acceltbl)

    def OnSimple(self, event):
        wx.MessageBox("You selected the simple menu item")

    def OnAccelerated(self, event):
        wx.MessageBox("You selected the accelerated menu item")

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

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()
У нас есть два типа горячих клавиш в wxPython: мнемоники и акселераторы. Сейчас мы с ними разберёмся.
Использование мнемоник
Мнемоники - это символ, который используется для доступа к элементу меню и выделяется при отображении подчёркиванием. Мнемоники могут быть созданы при определении отображаемого текста для меню или элемента меню если вы поместите знак амперсанда перед нужным символом в метке элемента, например &File, &Edit, Ma&cros. Если амперсанд должен отображаться в метке меню Вы должны его продублировать, например, Font && Spacing. Мнемоники предоставляют альтернативный способ навигации по дереву меню. Для перехода в режим выбора мнемоники необходимо специальное действие; в MS Windows это нажатие на клавишу alt. После того, как режим выбора мнемоники активирован, нажатие на дополнительную клавишу открывает меню высшего уровня соответствующего мнемонике. Выбор следующей мнемоники откроет соответствующее подменю и так до тех пор, пока Вы не выберете элемент меню, вслед за чем активируется событие выбора меню, как если бы оно было выбрано при помощи мышки. Мнемоники должны быть уникальны в пределах меню, но вполне могут повторяться в пределах полосы меню. Обычно мнемоникой делают первую букву отображаемого текста (метки). Если у Вас есть несколько элементов, начинающихся на одну и ту же букву каких-то общепринятых правил для выбора мнемоники не существует (обычно это вторая или последняя буква, кому как больше нравится). Главное не забывать, что важнее продуманные названия меню чем продуманные мнемоники.
Использование акселераторов
Акселератор в wxPython собственно и есть горячая клавиша, то есть комбинация клавиш, которую можно нажать в любой момент для активации элемента меню. Акселератор можно создать двумя способами. Самый простой путь - добавить комбинацию клавиш в отображаемый текст меню или элемента меню при его добавлении к родительскому виджету. Для этого добавьте в конец метки вашего меню знак табуляции ('\t') и собственно данную комбинацию. Первой частью комбинации клавиш должна быть Alt, Ctrl или Shift, затем + или - и, наконец, сама клавиша акселератора. Например New\tctrl+n, Save As\tctrl-shift-s. Регистр клавиш значения не имеет.
Сама клавиша акселератора может быть цифрой, буквой или функциональной клавишей, обозначаемой от F1 до F12, а так же одним из слов, приведённых в таблице 10.6
АкселераторКлавиша
delDelete
deleteDelete
downDown arrow
endEnd
enterEnter
escEscape
escapeEscape
homeHome
insInsert
insertInsert
leftLeft arrow
pgdnPage down
pgupPage up
returnEnter
rightRight arrow
spaceSpace bar
tabTab
upUp arrow
Методы wxPython не обращают внимания ни на акселераторы ни на мнемоники при поиске меню или элемента меню по имени. Другими словами, вызов menubar.FindMenuItem("File","Save As") найдёт нужный нам элемент меню, даже если его имя было задано как Save &As\tctrl-shift-s.
Кроме того, акселераторы могут быть созданы напрямую при помощи таблицы акселераторов, которая представляет из себя экземпляр класса wx.AcceleratorTable. Она содержит список объектов wx.AcceleratorEntry. Конструктор wx.AcceleratorTable принимает список акселераторов; кроме того, он может быть вызван вообще без аргументов. В листинге 10.6 мы используем тот факт, что wxPython вызывает конструктор wx.AcceleratorEntry со списком аргументов (wx.ACCEL_CTRL, ord('Q'), exit.GetId()). Конструктор wx.AcceleretorEntry выглядит следующим образом:
wx.AcceleratorEntry(flags, keyCode, cmd)
Параметр flags это битовая маска c одной или несколькими из следующих констант: wx.ACCEL_ALT, wx.ACCEL_CTRL, wx.ACCEL_NORMAL, wx.ACCEL_SHIFT. Он определяет, какая из клавиш-модификаторов должна быть нажата для вызова акселератора. Аргумент keyCode содержит саму буквенно-цифровую клавишу, определяющую акселератор. Это может быть либо ASCII код соответствующего символа, либо специальный символ из документации wxWidgets из раздела Keycodes. Аргумент cmd является идентификатором элемента меню, который должен создать командное событие при вызове акселератора. Как можно видеть из листинга 10.6, такое определение акселератора никак не будет отображено в получившейся форме, так что информацию о его наличии на форму придётся добавлять вручную.
10.2.4 Как создать меню с переключателями или чекбоксами?
Элементы меню используются не только для предоставления возможности выбора пользователю, они так же могут отображать состояние приложения. Наиболее популярный способ отображения состояния при помощи меню - использовать меню с переключателями чекбоксами (хотя Вы можете просто изменять текст на элементе меню или активировать/деактивировать его). На рисунке 10.4 отображён пример использования обоих вариантов.
Рисунок 10.4
Листинг 10.7


import wx

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "Toggle Items Example")
        p = wx.Panel(self)
        menuBar = wx.MenuBar()
        menu = wx.Menu()
        exit = menu.Append(-1, "Exit")
        self.Bind(wx.EVT_MENU, self.OnExit, exit)
        menuBar.Append(menu, "Menu")

        menu = wx.Menu()
        menu.AppendCheckItem(-1,"Check Item 1")
        menu.AppendCheckItem(-1,"Check Item 2")
        menu.AppendCheckItem(-1,"Check Item 3")
        menu.AppendSeparator()
        menu.AppendRadioItem(-1,"Radio Item 1")
        menu.AppendRadioItem(-1,"Radio Item 2")
        menu.AppendRadioItem(-1, "Radio Item 3")
        menuBar.Append(menu, "Toggle Items")

        self.SetMenuBar(menuBar)
    def OnExit(self, event):
        self.Close()

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()

Как Вы можете увидеть в листинге, чекбоксы добавляются при помощи метода AppendCheckItem(id, item, helpString=""), который похож на Append(). Аргументами являются идентификатор wxPython, отображаемое имя и строка помощи для отображения в строке статуса. Кроме того, Вы можете использовать PrependCheckItem(id, item, helpString="") и InsertCheckItem(pos, id, item, helpString=""), которые ведут себя так, как Вы от них и ожидаете.
Для добавления переключателей используйте AppendRadioItem(id, item, helpString=""), PrependRadioItem(id, item, helpString="") и InsertRadioItem(pos, id, item, helpString=""). Набор последовательных переключателей образуют группу и из этой группы только один переключатель может быть выбран. Для того, чтобы создать новую группу надо добавить любой другой элемент меню или сепаратор. По умолчанию выбранным является первый переключатель в группе.
Кроме того, различные элементы меню можно создать и при помощи обычного метода Append(), если передать в качестве значения параметра kind wx.ITEM_CHECK, wx.ITEM_NORMAL, wx.ITEM_RADIO или wx.ITEM_SEPARATOR. Это полезно, если вы создаёте меню автоматически на основании каких-либо данных, так как Вы можете создавать любые варианты элементов меню (только учтите, что для создания сепаратора необходимо в качестве id передать wx.ID_SEPARATOR).
Более того, Вы можете создать элемент меню при помощи конструктора wx.MenuItem c использованием данных констант, а потом передать полученный элемент одному из этих методов: AppendItem(), PrependItem() или InsertItem().
Для того, чтобы определить тип элемента меню используйте IsCheckable(), который возвращает True, если это чекбокс или переключатель; или используйте IsChecked(), который возвращает True, если элемент может изменять состояние и на данный момент выбран. Вы можете задать состояние переключаемого элемента при помощи метода Check(check), который принимает логический аргумент, отражающий новое состояние элемента. Если же элемент является переключателем, то автоматически поменяются состояния других элементов группы.
Кроме того, Вы можете получить состояние элемента меню из меню или из строки меню используя метод IsChecked(id), которому надо передать идентификатор элемента, который Вы хотите проверить. Кончено, тут есть ожидаемые ограничения - метод полосы меню работает только если она уже прикреплена к фрейму, а метод меню работает для элементов, которые находятся в этом меню. Для установки состояния можно использовать их методы Check(id, check) c очевидными аргументами.
10.3 Наводим блеск на ваши меню
В следующих разделах мы рассмотрим с помощью чего Вы можете сделать ваши меню более удобными и более содержательными. Сперва мы поговорим о вложенных подменю, затем о выплывающих меню. В заключение же мы обсудим, как изменить внешний вид ваших меню.
10.3.1 Как создать подменю?
Если ваше приложение сложнее, чем те, которые мы рассмотрели, Вам могут пригодиться вложенные меню (подменю). Обычно они используются для группировки набора опций, объединённых логически, особенно в тех случаях, когда таких опций так много, что поместить их в меню верхнего уровня не представляется возможным. На рисунке 10.5 показан пример такого приложения в wxPython. А в листинге 10.5 приведён его код.
Рисунок 10.5 подменю в wxPython
Листинг 10.5
import wx

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "Sub-menu Example")
        p = wx.Panel(self)
        menu = wx.Menu()
        
        submenu = wx.Menu()
        submenu.Append(-1, "Sub-item 1")
        submenu.Append(-1, "Sub-item 2")
        # добавляем подменю
        menu.AppendMenu(-1, "Sub-menu", submenu)
        
        menu.AppendSeparator()
        exit = menu.Append(-1, "Exit")
        self.Bind(wx.EVT_MENU, self.OnExit, exit)

        menuBar = wx.MenuBar()
        menuBar.Append(menu, "Menu")
        self.SetMenuBar(menuBar)

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

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()

Вы можете заметить из нашего примера, что подменю создаётся так же как и меню верхнего уровня. Сперва Вы создаёте экземпляр класса wx.Menu, затем добавляете в него элементы меню, как обычно. Разница лишь в том, что вместо того, чтобы прикреплять его к полосе меню, Вы добавляете его к обычному меню при помощи метода AppendMenu(id, text, submenu, helpStr). Аргументы идентичны аргументам метода Append(). Ну и кроме того, есть методы PrependMenu(id, text,submenu, helpStr) и InsertMenu(pos, text, submenu, helpStr) c уже понятным действием.
Помните, что порядок действий для создания подменю более важен, чем для обычных элементов меню. Рекомендуется сперва добавить элементы меню и лишь затем прикреплять его к родительскому меню. Это позволит wxPython корректно зарегистрировать горячие клавиши. Вы можете создавать подменю произвольной глубины, добавляя новое подменю к уже существующему, но порядок создания должен оставаться описанным выше.
10.3.2 Как создавать всплывающие меню?
Меню совсем не обязаны выпадать из полосы меню, находящейся в вершине вашего фрейма. Они вполне могут появляться в любом месте в пределах фрейма. По большей части такие меню используются для выбора действий в зависимости от контекста и относятся к тому объекту, на котором вызывается меню. На рисунке 10.6 показан пример такого приложения. Такие меню создаются так же как и обычные меню, но они не присоединяются к полосе меню. Код этого приложения показан в листинге 10.9
Рисунок 10.6
Листинг 10.9
import wx

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "Popup Menu Example")
        self.panel = p = wx.Panel(self)
        menu = wx.Menu()
        exit = menu.Append(-1, "Exit")
        self.Bind(wx.EVT_MENU, self.OnExit, exit)

        menuBar = wx.MenuBar()
        menuBar.Append(menu, "Menu")
        self.SetMenuBar(menuBar)

        wx.StaticText(p, -1,
            "Right-click on the panel to show a popup menu",
            (25,25))

        # создаём меню
        self.popupmenu = wx.Menu()
        # наполняем его
        for text in "one two three four five".split():
            item = self.popupmenu.Append(-1, text)
            self.Bind(wx.EVT_MENU, self.OnPopupItemSelected, item)
        # связываем всплывающее меню с событием для его вызова
        p.Bind(wx.EVT_CONTEXT_MENU, self.OnShowPopup)

        # показываем всплывающее меню
        def OnShowPopup(self, event):
            pos = event.GetPosition()
            pos = self.panel.ScreenToClient(pos)
            self.panel.PopupMenu(self.popupmenu, pos)

        def OnPopupItemSelected(self, event):
            item = self.popupmenu.FindItemById(event.GetId())
            text = item.GetText()
            wx.MessageBox("You selected item '%s'" % text)

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

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()
Всплывающие меню создаются так же как и любые другие меню (обратите внимание на использование цикла для быстрого создания элементов меню). Но вместо того, чтобы прикреплять его к полосе меню мы сохраняем его в self.popupmenu. После этого мы связываем метод OnShowPopup() c событием wx.EVT_CONTEXT_MENU, которое вызывается тогда, когда в данной ОС должно вызываться контекстное меню. В Windows и GTK это нажатие правой кнопкой мыши, в Mac OS это нажатие на control.
В результате, когда пользователь вызывает контекстное меню срабатывает метод-обработчик OnShowPopup(). Во-первых, он определяет координаты для отображения меню. Координаты, передаваемые в экземпляре wx.ContextMenuEvent отражают абсолютные значения для экрана, так что нам надо перевести их в относительные значения для нашей панели. Для этого мы используем метод ScreenToClient().
После того, отображается контекстное меню с помощью метода PopupMenu(menu, pos) или PopupMenuXY(menu, x, y). Данная функция возвращает значение только после того, как будет выбран элемент меню или меню будет закрыто нажатием кнопки escape или кликом вне пределов меню. Если был выбран какой-либо элемент меню, его обработчик вызывается стандартным образом (то есть, Вы должны связать обработчик этого элемента с событием EVT_MENU), и лишь после завершения обработчика управление перейдёт к следующему оператору после вызова PopupMenu(). Этот метод возвращает не интересующее нас логическое значение, так что единственный способ обработать выбор элемента меню - использовать стандартный механизм обработки событий.
Всплывающее меню может иметь заголовок, который отображается вверху меню, когда оно появляется на экране. Заголовок управляется при помощи свойств wx.Menu.SetTitle(title) и wx.Menu.GetTitle().
10.3.3 Как создавать забавные меню
Если обычные элементы меню выглядят не очень интересно, то Вы можете добавить к ним свой рисунок для отображения рядом с этим элементом меню (или использовать какой-то значок). Кроме того, в Windows Вы можете изменить шрифт или цвет элемента меню. На рисунке 10.7 приведён пример такого меню:
Рисунок 10.7
Листинг 10.10 содержит код для получения такого меню. Для определения платформы, на которой запускается ваша программа можно проверить наличие wxMSW в кортеже wx.PlatformInfo.
Листинг 10.10
import wx


class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1,
            "Fancier Menu Example")
        p = wx.Panel(self)
        menu = wx.Menu()


        bmp = wx.Bitmap("open.png", wx.BITMAP_TYPE_PNG)
        item = wx.MenuItem(menu, -1, "Has Open Bitmap")
        # добавляем собственную картинку
        item.SetBitmap(bmp)
        menu.AppendItem(item)


        if 'wxMSW' in wx.PlatformInfo:
            font = wx.SystemSettings.GetFont(
                wx.SYS_DEFAULT_GUI_FONT)
            font.SetWeight(wx.BOLD)
            item = wx.MenuItem(menu, -1, "Has Bold Font")
            # изменяем шрифт
            item.SetFont(font)
            menu.AppendItem(item)


            item = wx.MenuItem(menu, -1, "Has Red Text")
            # изменяем цвет
            item.SetTextColour("red")
            menu.AppendItem(item)


        menu.AppendSeparator()
        exit = menu.Append(-1, "Exit")
        self.Bind(wx.EVT_MENU, self.OnExit, exit)
        menuBar = wx.MenuBar()
        menuBar.Append(menu, "Menu")
        self.SetMenuBar(menuBar)

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

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show()
    app.MainLoop()
Изменение цвета или стиля элемента меню достигается манипуляциями со свойствами этого элемента. Единственным кроссплатформенным свойством является картинка, которая управляется методом GetBitmap(), возвращающим элемент типа wx.Bitmap. Для этого свойства есть два установщика. Первый, работающий на всех платформах, это SetBitmap(bmp). Он устанавливает картинку, отображаемую рядом с элементом меню. Если Вы работаете на Windows и хотите установить картинку для переключаемых меню, Вы можете использовать SetBitmap(checked, unchecked=wx.NullBitmap), который устанавливает одну картинку для отображения рядом с отмеченным элементом и другую для отображения рядом с не отмеченными.
В Windows есть ещё три свойства, которые Вы можете изменять для управления внешним видом элементов меню; они перечислены в таблице 10.7. Мы рекомендуем использовать их осторожно и только в тех случаях, когда они действительно облегчают использование вашего приложения.
МетодыОписание
GetBackgroundColour()
SetBackgroundColour(colour)
Тип свойства - wx.Colour. Установщику так же можно передать строку с именем цвета wxPython. Управляет фоновым цветом элемента меню.
GetFont()
SetFont(font)
Управляет шрифтом элемента. Тип - wx.Font
GetTextColour()
SetTextColour(colour)
Управляет цветом шрифта элемента. Тип как и у методов для цвета фона
Теперь, когда мы поговорили о функциональных аспектах использования меню, мы поговорим о том, как правильно делать меню и как облегчить пользователю работу с ними.
10.4 Как создавать "правильные" меню
Для сложных приложений полоса меню - главная точка взаимодействия пользователя с вашим приложением. Правильно составленное меню может значительно облегчить работу с вашей программой; неверная же компоновка может сделать её использование невозможным. Принимая во внимание сказанное выше, приступим к обсуждению советов по созданию меню.
10.4.1 Поддержка одинаковой длины меню
Если Вы посмотрите на те приложения, которые Вы постоянно используете, то заметите, что меню длиной 10-15 элементов можно охватить взглядом, тогда как более длинные меню просто замыливают взгляд пользователя. Если ваше меню превышает эти пределы - настоятельно рекомендуется его разделить на несколько частей. В идеале все меню должны обладать одинаковым количеством элементов, хотя надо понимать, что это не всегда возможно и не всегда желательно.
10.4.2 Логичная группировка элементов
Никогда не стоит группировать более пяти элементов без разделителя, если у Вас нет на то особой причины (например история изменений или список плагинов). Группы, с количеством элементов больше пяти, людям очень тяжело воспринимать. Если уж Вы хотите сделать длинную группу, то элементы в ней должны иметь особо сильную логическую связь между собой и сам пользователь должен ожидать в этой группе большое число элементов.
Твёрдо придерживайтесь стандартов при упорядочивании меню
Вы всегда должны придерживаться стандарта при определении порядка расположения меню. Самое левое меню - FILE, содержащее такие элементы, как new, open, save, print и exit, обычно именно в таком порядке. Если Вам нужно добавить сюда ещё какие-то возможности - лучше всего поместить их между print и exit. Такое расположение Вы встретите почти в каждом приложении. Следующим меню идёт EDIT, содержащий такие пункты как undeo, cut, copy, paaste и обычно find, в зависимости от вашего приложения. Меню HELP всегда является самым правым меню, а рядом с ним обычно находится меню WINODWS. Между этими крайними точками Вы можете располагать те меню, которые сочтёте нужным.
Предоставьте простой доступ для часто используемых элементов
Пользователю всегда проще использовать те элементы меню, которые расположены выше, чем те, что расположены ниже, поэтому наиболее часто используемые элементы должны находиться вверху меню. Единственное исключение в том, что эксперимент показывает, что второй сверху элемент доступнее первого.
Используйте понятные названия для меню
Помните, что ширина меню в полосе меню пропорциональна длине имени меню, а ширина меню определяется по элементу меню с самым длинным именем. Постарайтесь не давать меню верхнего уровня имена короче четырёх букв. Мы рекомендуем, за исключением общепринятых обозначений, давать настолько длинные имена, чтобы суть их была однозначна ясна. Не бойтесь длинных имен для меню, но и не забывайте, что читать 30-40 символом может быть утомительно.
Помните про многоточия, когда элемент ведёт к диалоговому окну
Все элементы меню, которые вызывают диалоговые окна, должны заканчиваться на многоточие (...)
Придерживайтесь стандартных горячих клавиш
Для горячих клавиш используйте общепринятые стандарты соответствия комбинаций клавиш действиям, приведённые в таблице 10.8
Для возврата действия нет общепринятой комбинации; Вы можете встретить как ctrl+y так и alt+z, или другие наборы клавиш. Если Вы предоставляете большое количество дополнительных горячих клавиш, то рекомендуется дать пользователю возможность их переназначать. Горячие клавиши особенно полезны в приложениях, гд пользователю приходится много печатать, например в текстовых редакторах, и они могут быть бесполезны там, где пользователь много работает мышкой.
Таблица 10.8
Комбинация клавишФункция
Crtl+aВыделить всё
Crtl+cКопировать
Crtl+fИскать
Crtl+gИскать далее
Crtl+nНовое
Crtl+oОткрыть
Crtl+pПечатать
Crtl+qВыйти
Crtl+sСохранить
Crtl+vВставить
Crtl+wЗакрыть
Crtl+xВырезать
Crtl+zОтменить

Показывайте текущий статус переключателей
Когда Вы создаёте элемент меню, переключающий что-то из одного состояния в другое, обратите внимание на несколько вещей. Во первых, помните, что не выбранный элемент меню выглядит так же как и обычный элемент меню. Если элемент меню называется "fancy mode on", то пользователь может и не понять, что выбор этого элемента изменяет этот режим. Вместо этого можно отображать на элементе меню не то состояние, в котором он находится сейчас, а то, которое включится после его выбора. При этом неплохо подчеркнуть, что этот элемент меню изменяет состояние, например, написать на нём "Turn fancy mode off". Но при таком решении нет ничего, что указывало бы на текущее состояние программы. Для этого имеет смысл поставить свою картинку, которая бы символизировала о том, выбран элемент или нет (на тех платформах, где это возможно). Если у Вас такой возможности нет - можно написать toggle fancy mode или switch fancy mode (now on).
Используйте вложенность с осторожностью
Вложенные меню могут быть неудобны для работы, так как для их использования пользователю сперва надо пройти вдоль стрелки а потом, изменив направление на 90 градусов, выбрать нужный элемент. Определённо не следует делать более одного уровня вложенности. Если Вам надо сделать меню для выбора чего-то древоподобного, например, иерархии директорий, используйте лучше диалоговое окно. Кроме того, если среди ваших пользователей будут люди с ограниченными возможностями - то вложенные меню так же не лучший выбор.
Избегайте менять цвет и шрифт
Можете ли Вы вспомнить приложение с разноцветным меню? И мы тоже (кроме тех меню, где надо выбрать цвет). И Вам не советуем.
10.5 Итог

  • Меню - самый часто используемый механизм вызова команд пользователем в графическом интерфейсе. В wxPython для их создания используется три основных класса: wx.MenuBar, представляющий полосу меню и содержащий меню, создаваемые при помощи wx.Menu. Меню, в свою очередь, состоят из элементов меню, создаваемых при помощи wx.MenuItem. Для того, чтобы получить полосу меню, её сперва нужно создать, а затем прикрепить к фрейму. Точно так же сперва создаются сами меню, а затем к ним прикрепляются элементы меню. После этого меню добавляются в полосу меню. Элементы в меню могут добавляться в любое место, кроме того, сепаратор так же является элементом меню. Объекты элементов меню огут быть созданы как явно, так и неявно, путём добавления элемента в меню.
  • Выбор меню вызывает командное событие типа wx.EVT_MENU.  Эти события связываются через содержащий их фрейм, а не через элементы меню, меню или полосы меню. Это позволяет панели инструментов вызывать те же обработчики, что и для соответствующего элемента меню. Если у Вас есть много элементов меню с последовательными идентификаторами и у них у всех должен быть один и тот же обработчик, то их можно связать одним вызовом используя тип события wx.EVT_MENU_RANGE.
  • Элементы меню могут быть найдены по их ID или по их тексту либо из содержащего их меню либо из полосы меню. Оттуда же можно их активировать или деактивировать.
  • Меню может быть прикреплено не к полосе меню а к другому меню, создавая вложенную структуру. У класса wx.Menu есть специальный метод для прикрепления субменю таким же образом как и элемента меню.
  • Меню могут быть связаны с клавишами двумя способами. Мнемоники позволляют премещаться по меню при нажатой клавише ALT. В этом режиме нажатие клавиши на клавиатуре открывает соответствующее меню или вызывает элемент меню. Для создания мнемоник необходимо поместить амперсанд перед нужным символом в названии меню или элемента меню при вызове конструктора класса. Акселераторы же создаются связыванием комбинации клавиш и элемента меню, что тоже можно сделать при создании элемента меню. Декораторы клавиатурных сокращений (например, амперсанд) игнорируются при поиске меню по имени.
  • Элемент меню может быть переключаемым, например чекбоксом, который может быть выбран или не выбран, или переключателем (radio button), когда выбран может быть только один элемент из группы. Состояние элемента (выбран он или нет) может быть получено или изменено из меню или полосы меню.
  • Можно создать меню, всплывающее при клике на виджете wxPython. Это делается перехватом события wx.EVT_CONTECT_MENU и вызовом метода PopupMenu() для его отображения. События элементов всплывающего меню обрабатываются стандартным образом.
  • Вы можете использовать собственную картинку для элемента меню, а под Windows Вы так же можете менять цвет и шрифт отображения элемента меню. Однако нет никаких причин делать это.

2 комментария:

  1. Очень доступно и наглядно!
    Было бы интересно узнать о печати в wxpython. Например как распечатать grid.

    ОтветитьУдалить
    Ответы
    1. мне было бы интересно узнать, как в Python можно печатать кирилицу... Для печати используются сторонние библиотеки, но они её не поддерживали (по крайней мере полгода назад, когда я искал)
      как вариант можно создать pdf и распечатать его, но для pdf придётся таблицу рисовать вручную

      PS беглый поиск по PyPI нашёл вот такой проект http://newcenturycomputers.net/projects/mswinprint.html, но его работоспособность ещё надо проверить. Может, завтра этим и займусь

      Удалить