ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 第十章 创建和使用wxPython菜单 1. [创建和使用wxPython菜单](#A.2BUhte.2BlSMT391KA-wxPython.2Bg9xTVQ-) 1. [创建菜单](#A.2BUhte.2BoPcU1U-) 1. [如何创建一个菜单栏并把它附加到一个框架?](#A.2BWYJPVVIbXvpOAE4qg9xTVWgPXnZiiluDlkRSoFIwTgBOKmhGZ7b.2FHw-) 2. [如何创建一个菜单并把它附加到菜单栏?](#A.2BWYJPVVIbXvpOAE4qg9xTVV52Yopbg5ZEUqBSMIPcU1VoD.2F8f-) 3. [如何给下拉菜单填加项目?](#A.2BWYJPVX7ZTgtiyYPcU1VYa1KgmHl27v8f-) 4. [如何响应一个菜单事件?](#A.2BWYJPVVTNXpROAE4qg9xTVU6LTvb.2FHw-) 2. [使用菜单项工作](#A.2BT391KIPcU1WYeV3lT1w-) 1. [如何在一个菜单中找到一个特定的菜单项?](#A.2BWYJPVVcoTgBOKoPcU1VOLWJ.2BUjBOAE4qcnlbmnaEg9xTVZh5.2Fx8-) 2. [如何使一个菜单项有效或无效?](#A.2BWYJPVU9.2FTgBOKoPcU1WYeWcJZUhiFmXgZUj.2FHw-) 3. [如何将一个菜单项与一个快捷键关联起来?](#A.2BWYJPVVwGTgBOKoPcU1WYeU4OTgBOKl.2FrY3eVLlFzgFSNd2dl.2Fx8-) 4. [如何创建一个复选或单选开关菜单项?](#A.2BWYJPVVIbXvpOAE4qWQ2QCWIWU1WQCV8AUXOD3FNVmHn.2FHw-) 3. [进一步构建菜单](#A.2Bj9tOAGtlZ4Re.2BoPcU1U-) 1. [如何创建一个子菜单?](#A.2BWYJPVVIbXvpOAE4qW1CD3FNV.2Fx8-) 2. [如何创建弹出式菜单?](#A.2BWYJPVVIbXvpfOVH6Xw.2BD3FNV.2Fx8-) 3. [如何创建自己个性的菜单?](#A.2BWYJPVVIbXvqB6l3xTipgJ3aEg9xTVf8f-) 4. [菜单设计的适用性准则](#A.2Bg9xTVYu.2Bi6F2hJACdShgJ1HGUhk-) 1. [使菜单有均衡的长度](#A.2BT3.2BD3FNVZwlXR4hhdoSVf16m-) 2. [创建合理的项目组](#A.2BUhte.2BlQIdAZ2hJh5du5.2BxA-) 5. [本章小结](#A.2BZyx64FwPftM-) **本章内容包括**: 创建菜单 使用菜单项工作 添加子菜单、弹出菜单和自定义菜单 菜单的设计准则 难以想象一个应用程序的顶部没有我们常见的以`File`和`Edit`开头,以`Help`结尾的栏目。这太糟糕了。菜单是那些隐藏在背后并不太重视绘制的标准界面工具的一个公共的部分,由于菜单使得用户能够快速而容易地访问所有的功能,所以它实实在在是一个革命。 在`wxPython`中有三个主要的类,它们管理菜单的功能。类`wx.MenuBar`管理菜单栏自身,而`wx.Menu`管理一个单独的下拉或弹出菜单。当然,一个`wx.MenuBar`实例可以包含多个`wx.Menu`实例。类`wx.MenuItem`代表一个`wx.Menu`中的一个特定项目。 在第二章中我们对菜单作了一个简要的介绍,在例5.5中我们提供了一个容易创建菜单项的机制,在第7章中我对特殊的菜单效果作了一些介绍。在这一章,我们将对`wxPython`菜单的创建和使用提供更多的细节。 ## 创建菜单 首先,我们将讨论菜单栏。要使用一个菜单栏,就要执行下面这些行动: ·创建菜单栏 ·把菜单栏附加给框架 ·创建单个的菜单 ·把菜单附加给菜单栏或一个父菜单 ·创建单个的菜单项 ·把这些菜单项附加给适当的菜单 ·为每个菜单项创建一个事件绑定 上面行动的执行顺序可以灵活点,只要你在使用之前创建所有项目,并且所有行动在框架的初始化方法中完成就可以了。在这个过程中你可以以后来处理菜单,但是在框架可见后,你的执行顺序将影响用户所见到的东西。例如,如果你在框架创建后将菜单栏附加给框架,或等到直到所有其它的过程完成了。考虑到可读性和维护的目的,我们推荐你将相关的部分分整理在一起。对于如何组织菜单的创建的建议,请看第5章的重构。在接下来的部分,我们将涉及基本的菜单任务。 ### 如何创建一个菜单栏并把它附加到一个框架? 要创建一个菜单栏,使用`wx.MenuBar`构造函数,它没有参数: ``` wx.MenuBar() ``` 一旦菜单栏被创建了,使用`SetMenuBar()`方法将它附加给一个`wx.Frame`(或其子类)。通常这些都在框架的`__init__`或`OnInit()`方法中实施: ``` menubar = wx.MenuBar() self.SetMenuBar ``` 你不必为菜单栏维护一个临时变量,但是这样做将使得添加菜单到菜单栏多少更简单点。要掌握程序中的其它地方的菜单栏,使用`wx.Frame.GetMenuBar()`。 ### 如何创建一个菜单并把它附加到菜单栏? `wxPython`菜单栏由一个个的菜单组成,其中的每个菜单都需要分别被创建。下面显示了`wx.Menu`的构造函数: ``` wx.Menu(title="", style=0) ``` 对于`wx.Menu`只有一个有效的样式。在`GTK`下,样式`wx.MENU_TEAROFF`使得菜单栏上的菜单能够被分开并作为独立的选择器。在其它平台下,这个样式没有作用。如果平台支持,那么菜单被创建时可以有一个标题。图10.1显示了一个带有三个菜单的空白窗口。例10.1显示了被添到一个菜单栏上的一系列菜单,这些菜单没有被添加菜单项。 **图10.1** ![](https://box.kancloud.cn/2016-08-21_57b9964400c67.gif) **例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() ``` 在`wxPython`的菜单`API`中,一个对象的大部分处理是由它的容器类来管理的。在本章的后面,我们将讨论`wx.Menu`的特定的方法,因为这些方法的大多数涉及到菜单中的菜单项的处理。在这一节的剩余部分,由于我们正在谈论处理`wx.Menu`对象,所以我们将列出`wx.MenuBar`的那些涉及到菜单的方法。表10.1显示了`wx.MenuBar`中的四个方法,它们处理菜单栏的内容。 **表10.1** **在菜单栏处理菜单的`wx.MenuBar`的方法** | | | | --- | --- | | `Append(menu`, `title)` | 将`menu`参数添加到菜单栏的尾部(靠右显示)。`title`参数被用来显示新的菜单。如果成功返回`True`,否则返回`False`。 | | `Insert(pos`, `menu`, `title)` | 将给定的`menu`插入到指定的位置`pos`(调用这个函数之后,`GetMenu(pos)` == `menu`成立)。就像在列表中插入一样,所有后面的菜单将被右移。菜单的索引以0开始,所以`pos`为0等同于将菜单放置于菜单栏的左端。使用`GetMenuCount()`作为`pos`等同于使用`Append`。`title`被用于显示名字。函数如果成功则返回`True`。 | | `Remove(pos)` | 删除位于`pos`的菜单,之后的其它菜单左移。函数返回被删除的菜单。 | | `Replace(pos`, `menu`, `title)` | 使用给定的`menu`,和`title`替换位置`pos`处的菜单。菜单栏上的其它菜单不受影响。函数返回替换前的菜单。 | `wx.MenuBar`包含一些其它的方法。它们用另外的方式处理菜单栏中的菜单,如表10.2所示。 **表10.2** **`wx.MenuBar`的菜单属性方法** | | | | --- | --- | | `EnableTop(pos`, `enable)` | 设置位置`pos`处的菜单的可用/不可用状态。如果`enable`是`True`,那么该菜单是可用的,如果是`False`,那么它不可用。 | | `GetMenu(pos)` | 返回给定位置处的菜单对象。 | | `GetMenuCount()` | 返回菜单栏中的菜单的数量。 | | `FindMenu(title)` | 返回菜单栏有给定`title`的菜单的整数索引。如果没有这样的菜单,那么函数返回常量`wx.NOT_FOUND`。该方法将忽略快捷键,如果有的话。 | | `GetLabelTop(pos)`,`SetLabelTop(pos`, `label)` | 得到或设置给定位置的菜单的标签。 | ### 如何给下拉菜单填加项目? 这里有一对机制用于将新的菜单项添加给一个下拉菜单。较容易的是使用`wx.Menu`的`Append()`方法: ``` Append(id, string, helpStr="", kind=wx.ITEM_NORMAL) ``` 参数`id`是一个`wxPython` `ID`。参数`string`是将被显示在菜单上的字符串。当某个菜单高亮时,如果设置了参数`helpStr`,那么该参数将被显示在框架的状态栏中。`kind`参数使你能够设置菜单项的类型,通过该参数可以将菜单项设置为开关类型。在这一章的后面,我们将讨论管理开关项的更好的方法。`Append`方法把新的项放到菜单的尾部。 例10.2 显示了一个使用`Append()`方法来建立一个有两个项链和一个分隔符的菜单。 **例10.2 添加项目到一个菜单的示例代码** ``` import wx class MyFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "Simple Menu Example") 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") self.Bind(wx.EVT_MENU, self.OnSimple, simple) self.Bind(wx.EVT_MENU, self.OnExit, exit) menuBar = wx.MenuBar() menuBar.Append(menu, "Simple 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** ![](https://box.kancloud.cn/2016-08-21_57b9964414ce2.gif) 连同`Append()`一起,这里还有两个另外的用于菜单项插入的方法。要把一个菜单项放在菜单的开头,使用下面两个方法之一: ·`Prepend(id`, `string`, `helpStr`="", `kind`=`wx.ITEM_NORMAL)` ·`PrependSeparator()` 要把新的项放入菜单中的任一位置,使用这下面的`insert`方法之一: ·`Insert(pos`, `id`, `string`, `helpStr`="", `kind`=`wx.ITEM_NORMAL)` ·`InsertSeparator(pos)` 参数`pos`是菜单项要插入的位置的索引,所以如果索引为0,则新的项被放置在开头,如果索引值为菜单的尺寸,那么新的项被放置在尾部。所以在插入点后的菜单项将被向下移动。 所有的这些插入方法都隐含地创建一个`wx.MenuItem`类的实例。你也可以使用该类的构造函数显式地创建该类的一个实例,以便设置该菜单项的除了标签以外的其它的属性。比如你可以设置自定义的字体或颜色。`wx.MenuItem`的构造函数如下: `wx.MenuItem(parentMenu`=`None`, `id`=`ID_ANY`, `text`="", * `helpString`="", `kind`=`wx.ITEM_NORMAL`, `subMenu`=`None)` 参数`parentMenu`必须是一个`wx.Menu`实例(如果设置了的话)。当构造时,这个新的菜单项不是自动被添加到父菜单上显示的。你必须自己来实现。这个行为与`wxPython`窗口部件和它们的容器的通常的行为不同。参数`id`是新项的标识符。参数`text`是新项显示在菜单中的字符串,参数`helpString`是当该菜单项高亮时显示在状态栏中的字符串。`kind`是菜单项的类型,`wx.ITEM_NORMAL`代表纯菜单项;我们以后会看到开关菜单项有不同的类型值。如果参数`subMenu`的值不是`null`,那么这个新的菜单项实际上就是一个子菜单。我们不推荐你使用这个机制来创建子菜单;替而代之,可以使用在10.3节中说明的机制来装扮你的菜单。 不像大多数窗口部件,创建菜单项并不将它添加到指定的父菜单。要将新的菜单项添加到一个菜单中,使用下面的`wx.Menu`方法之一: ·`AppendItem(aMenuItem)` ·`InsertItem(pos`, `aMenuItem)` ·`PrependItem(aMenuItem)` 要从菜单中删除一个菜单项,使用方法`Remove(id)`,它要求一个`wxPython` `ID`,或`RemoveItem(item)`,它要求一个菜单项作为参数。删除一个菜单项后,后面的菜单项将上移。`Remove()`方法将返回所删除的实际的菜单项。这使你能够存储该菜单项以备后用。与菜单栏不同,菜单没有直接替换菜单项的方法。替换必须通过先删除再插入来实现。 `wx.Menu`类也有两个用来获取它的菜单项的信息的`get`*方法。`GetMenuItemCount()`返回菜单中项目的数量,`GetMenuItems()`返回菜单中项目的一个列表,项目在列表中的顺序与菜单中的位置相一致。这个列表是菜单中实际列表的一个拷贝,意味着改变所返回的列表,不会改变菜单本身。 对于有效的菜单,你可以在运行时继续添加或删除菜单项。例10.3显示了在运行时添加菜单的示例代码。当按钮被按下时,调用`OnAddItem()`的方法来插入一个新的项到菜单的尾部。 **例10.3** **运行时添加菜单项** ``` 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()`来添加一个新的项到菜单中。另外,它绑定了一个菜单事件,以便这个新的菜单项有相应的功能。在下一节,我们将讨论菜单事件。 ### 如何响应一个菜单事件? 在最后这一小节,我们展示两个响应菜单选择的例子代码。像我们在第8章见过的许多窗口部件一样,选择一个菜单项将触发一个特定类型的`wx.CommandEvent`的一个实例。在此处,该类型是`wx.EVT_MENU`。 菜单项事件在两个方面与系统中其它的命令事件不同。首先,用于关联菜单项事件与特定回调函数的`Bind()`函数是在包含菜单栏的框架上的。第二,由于框架通常有多个与`wx.EVT_MENU`触发相对应的菜单项,所以`Bind()`方法需要第三个参数,它就是菜单项本身。这使得框架能区分不同的菜单项事件。 一个典型的绑定一个菜单事件的调用如下所示: ``` self.Bind(wx.EVT_MENU, self.OnExit, exit_menu_item) ``` `self`是框架,`self.OnExit`是处理方法,`exit_menu_item`是菜单项自身。 尽管通过框架绑定菜单事件的主意似乎有一点古怪,但是它是有原因的。通过框架绑定事件使你能够透明地绑定一个工具栏按钮到与菜单项相同的处理器。如果该工具栏按钮有与一个菜单项相同的`wxPython` `ID`的话,那么这个单个的对`wx.EVT_MENU`的`Bind()`调用将同时绑定该菜单选择和该工具栏按钮敲击。这是可行的,因为菜单项事件与工具事件都经由该框架得到。如果菜单项事件在菜单栏中被处理,那么菜单栏将不会看到工具栏事件。 有时,你会有多个菜单项需要被绑定到同一个处理器。例如,一套单选按钮开关菜单(它们本质上作相同的事情)可能被绑定给同一处理器。如果菜单项有连续的标识符号的话,为了避免分别的一一绑定,可以使用`wx.EVT_MENU_RANGE`事件类型: ``` self.Bind(wx.EVT_MENU_RANGE, function, id=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` | 当一个菜单被打开时触发。 | 现在我们已经讨论了创建菜单的基础知识,我们将开始说明如何使用菜单项来工作。 ## 使用菜单项工作 尽管菜单和菜单栏对于一个菜单系统很明显是至关重要的,但是你的大部分时间和工作将化在处理菜单项上。在下面的几节中,我们将谈论通常的菜单项函数,如查找一个项目,使一个菜单项有效或无效,创建开关菜单项和分配快捷键。 ### 如何在一个菜单中找到一个特定的菜单项? 在`wxPython`中有许多方法用于查找一个特定的菜单或给定了标签或标识符的菜单项。你经常在事件处理器中使用这些方法,尤其是当你想修改一个菜单项或在另一个位置显示它的标签文本时。例10.1对先前的动态菜单例子作了补充,它通过使用`FindItemById()`得到菜单项以显示。 **例10.4** **发现一个特定的菜单项** ``` 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()`方法来访问。 要从一个菜单栏查找一个顶级的菜单,使用菜单栏方法`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) ``` `FindMenu`方法的`title`参数匹配菜单的标题(不管菜单的标题有无修饰标签字符)。例如,即使菜单的标题是 ,`FindMenu(`"`File`")仍将匹配该菜单项。在菜单类中的所有基于标签字符串发现一个菜单项的方法都有这个功能。 表10.4指出了`wx.MenuBar`的这些方法,它们能够被用于查找或处理一个特定的菜单项。 **表10.4 `wx.MenuBar`的菜单项处理方法** | | | | --- | --- | | `FindMenuItem(menuString`,`itemString)` | 在一个名为`menuString`的菜单中查找名为`itemString`的菜单项。返回找到的菜单项或`wx.NOT_FOUND`。 | | `FindItemById(id)` | 返回与给定的`wxPython`标识符相关联的菜单项。如果没有,返回`None`。 | | `GetHelpString(id)`,`SetHelpString(id`,`helpString)` | 用于给定`id`的菜单项的帮助字符串的获取或设置。如果没有这样的菜单项,那么`get`*方法返回"",`set`*方法不起作用。 | | `GetLabel(id)`,`SetLabel(id`, `label)` | 用于给定`id`的菜单项的标签的获取或设置。如果没有这样的菜单项,那么`get`*方法返回"",`set`*方法不起作用。这些方法只能用在菜单栏已附加到一个框架后。 | 表10.5 显示了用于`wx.Menu`的菜单项处理方法。它们分别与菜单栏中的方法相类似,除了所返回的菜单项必须在所调用的菜单实例中。 在菜单项返回后,你可能想做些有用的事情,如使该菜单项有效或无效。在下一节,我们将讨论使用菜单项有效或无效。 **表10.5 `wx.Menu`的菜单项方法** | | | | --- | --- | | `FindItem(itemString)` | 返回与给定的`itemString`相关的菜单项或`wx.NOT_FOUND`。 | | `FindItemById(id)` | 返回与给定的`wxPython`标识符相关联的菜单项。如果没有,返回`None`。 | | `FindItemByPosition(pos)` | 返回菜单中给定位置的菜单项 | | `GetHelpString(id)`,`SetHelpString(id`,`helpString)` | 与菜单栏的对应方法相同。 | | `GetLabel(id)`,`SetLabel(id`, `label)` | 与菜单栏的对应方法相同。 | ### 如何使一个菜单项有效或无效? 类似于其它的窗口部件,菜单和菜单项也可以有有效或无效状态。一个无效的菜单或菜单项通常显示为灰色文本,而非黑色。无效的菜单或菜单项不触发高亮或选择事件,它对于系统来说是不可见的。 例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" o "Disable") + " Item") if __name__ == "__main__": app = wx.PySimpleApp() frame = MyFrame() frame.Show() app.MainLoop() ``` 要查看或改变菜单项自身或菜单栏上的,或特定菜单上的一个菜单项的有效状态,调用 `wx.MenuItem.IsEnabled()`、`wx.MenuBar.IsEnabled(id)`或`wx.Menu.IsEnabled(id)`方法。菜单栏和菜单方法都要求一个菜单项的`wxPython`标识符。如果该菜单项存在且有效,那么这两个方法都返回`True`,如果该菜单项不存在或无效,那么这两个方法都返回`False`。唯一的区别是`wx.Menu`的方法只在特定的菜单中搜索,而菜单栏方法搜索整个菜单栏。`wx.MenuItem`方法不要求参数,它返回特定菜单项的状态。 要改变有效状态,使用`wx.MenuBar.Enable(id`, `enable)`, `wx.Menu.Enable(id`,`enable)`, 或 `wx.MenuItem.Enable(enable)`。`enable`参数是布尔值。如果为`True`,相关菜单项有效,如果为`False`,相关菜单项无效。`Enable()`方法的作用域和`IsEnabled()`方法相同。你也可以使用`wx.MenuBarEnableTop(pos`,`enable)`方法来让整个顶级菜单有效或无效。在这里,`pos`参数是菜单栏中菜单的整数位置,`enable`参数是个布尔值。 ### 如何将一个菜单项与一个快捷键关联起来? 图10.3显示了一个示例菜单。注意该菜单的菜单名中有一个带下划线的字符,其中的标签为`Accelerated`的菜单项的快捷键是`Ctrl`-A。 **图10.3** ![](https://box.kancloud.cn/2016-08-21_57b9964428a50.gif) 研究表明快捷键并不总是能够节省时间。但是它们是标准的界面元素,并且你的用户也希望它们的存在。 例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 item") # Creating a mnemonic accel = menu.Append(-1, " \tCtrl-A") # Creating an accelerator menu.AppendSeparator() exit = menu.Append(-1, "E ") 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, " ") self.SetMenuBar(menuBar) acceltbl = wx.AcceleratorTable( [ #Using an accelerator table (wx.ACCEL_CTRL, od('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`中有两种快捷键;`mnemonics`(助记符)和`accelerator`(加速器)。下面,我们将讨论这两种的区别。 **使用助记符快捷方式** 助记符是一个用来访问菜单项的单个字符,以一个带有下划线的字母表示。助记符可以通过为菜单或菜单项指定显示的文本来创建,并在你想用来作为助记符的字符前面放置一个&符号,例如 , 或`Ma` 。如果你希望在你的菜单文本中有一个&符号,那么你必须输入两个&&符号,例如&&。 助记符是作为在菜单树中选择的一个备用的方法。它仅在被用户显式地调用时被激活;在微软`Windows`下,通过按下`alt`键来激活它。一旦助记符被激活,下一步按下顶级菜单的助记符来打开顶级菜单。这样一步打开菜单,直到一个菜单项被选择,此时一个菜单事件被触发。助记符在菜单中必须是独一无二的,但在整个菜单栏中可以不是独一无二。通常菜单文本的第一个字符被用作助记符。如果你有多个菜单项有相同的开头字母,那么就没有特定的准则来决定那个字符用作助记符(最常用的选择是第二个和最后一个,这要看哪个更合理)。菜单文本清晰的含义比有一个好的助记符更重要。 **使用加速器快捷方式** 在`wxPython`中加速器是一个更加典型的键盘快捷方式,它意味能够随时调用的按键组合,这些按键组合直接触发菜单项。加速器可以用两种方法创建。最简单的方法是,在菜单或菜单项的显示文本中包括加速器按键组合(当菜单或菜单项被添加到其父中时)。实现的方法是,在你的菜单项的文本后放置一个\t。在\t之后定义组合键。组合键的第一部分是一个或多个`Alt`, `Ctrl`, 或`Shift`,由一个+或一个-分隔,随后是实际的加速器按键。例如:`New`\`tctrl`-n, `SaveAs`\`tctrl`-`shift`-s。即使在第一部分你只有一个专用的键,你仍可使用+或-来将该部分与实际的按键分隔。这不区分按键组合的大小写。 实际的键可以是任何数字、字母或功能键(如`F1`~`F12`),还有表10.6所列出的专用词。 `wxPython`的方法在通过名字查找一个菜单或菜单项时忽略助记符和加速器。换句话说,对`menubar.FindMenuItem(`"`File`", "`SaveAs`")的调用将仍匹配`Save` `as`菜单项,即使菜单项的显示名是以`Save` \`tctrl`-`shift`-s形式输入的。 加速器也可能使用加速器表被直接创建,加速器表是类`wx.AccleratorTable`的一个实例。一个加速器表由`wx.AccelratorEntry`对象的一个列表组成。`wx.AcceleratorTable`的构造函数要求一个加速器项的列表,或不带参数。在例10.6中,我们利用了`wxPython`将隐式使用参数(`wx.ACCEL_CTRL`, `od(`'Q'),`exit.GetId())`调用`wx.AcceleratorEntry`构造函数的事实。`wx.AcceleratorEntry`的构造函数如下: `wx.AcceleratorEntry(flags`, `keyCode`, `cmd)` `flags`参数是一个使用了一个或多个下列常量的位掩码:`wx.ACCEL_ALT`, `wx.ACCEL_CTRL`, `wxACCEL_NORMAL` , 或`wx.ACCEL_SHIFT`。该参数表明哪个控制键需要被按下来触发该加速器。`keyCode`参数代表按下来触发加速器的常规键,它是对应于一个字符的`ASCII`数字,或在`wxWidgets`文本中的`Keycodes`下的一个专用字符。`cmd`参数是菜单项的`wxPython`标识符,该菜单项当加速器被调用时触发其命令事件。正如你从例10.6所能看到的,使用这种方法声明一个加速器,不会在这个带菜单项显示名的菜单上列出组合键。你仍需要单独实现它。 **表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` ### 如何创建一个复选或单选开关菜单项? 菜单项不仅用于从选择表单中得到用户的输入,它们也被用于显示应用程序的状态。经由菜单项来显示状 态的最常用的机制是开关菜单项的使用,开关菜单项仿效一个复选框或单选按钮(你只能够通过改变该菜 单项的文本或使用有效或无效状态来反映应用程序的状态)。图10.4显示了复选和单选菜单项的例子。 **图10.4** ![](https://box.kancloud.cn/2016-08-21_57b996443c913.gif) 顾名思义,一个复选开关菜单项在它每次被选择时,它在开和关状态间转换。在一个组中,一次只允许一 个单选菜单项处于开的状态。当同一组中的另一个菜单项被选择时,先前的菜单项改变为关状态。例10.7 显示了如何创建复选和单选菜单项。 **例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()`来创建。`Append()`的`kind`参数要求下列常量值之一:`wx.ITEM_CHECK`, `wx.ITEM_NORMAL`, `wx.ITEM_RADIO`或`wx.ITEM_SEPARATOR`,其中的每个值创建一个适当类型的菜单项。这是有用的,如果你正在使用某种数据驱动过程自动创建这些菜单项的话。所有类型的菜单项都可以使用这同种方法来创建,尽管指定`kind`为`wx.ITEM_SEPARATOR`来生成一个分隔符必须给`id`参数传递`wx.ID_SEPARATOR`。 当你使用`wx.MenuItem`构造函数时你也可以创建一个开关菜单项(给参数`kind`一个相应的常量值)。所得 的菜单项可以使用`AppendItem()`, `PrependItem()`, `InsertItem()`之一的方法被添加到一个菜单。 要确定一个菜单项的开关状态,使用`IsCheckable()`,如果该项是一个复选或单选项,函数返回`True`,使 用`IsChecked()`,如果该项是可开关的且处于选中状态,那么返回`True`。你也可以使用`Check(check)`方法 来设置一个菜单项的开关状态,`check`是一个布尔参数。使用`Check(check)`方法设置时,被设置的菜单项 是单选的,那么将影响同一组别的项。 你也可以使用`IsChecked(id)`从菜单或菜单栏得到一个菜单项的开关状态,它要求相应菜单项的`id`。你也 可以使用`Check(id`, `check)`来设置菜单栏或菜单中的菜单项,参数`check`是布尔值。 ## 进一步构建菜单 在接下来的几节,我们将通过使你的菜单更杂化来让它更有用。首先我们将讨论嵌套的子菜单,然后是在你的程序中加入弹出菜单。最后是构建喜爱样式的菜单项。 ### 如何创建一个子菜单? 如果你的应用程序变得太复杂,你可以在顶级菜单中创建子菜单,这使你能够在一个项级菜单中嵌套菜单项且装入更多的项目。对于将一系列的属于同一逻辑的选项组成一组,子菜单是十分有用的,尤其是要将太多的选项地放入顶级菜单的时候。图10.5显示了一个使用子菜单的示例。 **图10.5** ![](https://box.kancloud.cn/2016-08-21_57b99644522d5.gif) 例10.8显示了产生图10.5的代码。 **例10.8** **建造一个嵌套的子菜单** ``` 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() ``` 你从例10.8会注意到,子菜单的创建方法与顶级菜单的相同。你创建类`wx.Menu`的一个实例,然后以相同的方法给子菜单增加菜单项。不同的是子菜单没有被添加到顶级菜单栏,而是使用`AppendMenu(id`, * `text`, `submenu`, `helpStr)`把它添加给另一个菜单。该函数的参数类似于`Append()`的。参数`id`是要添加到的菜单的`wxPython`标识符。参数`text`是子菜单显示在父菜单中的字符串。参数`submenu`是子菜单自身,`helpStr`是显示在状态栏中的文本。另外还有子菜单的插入方法:`PrependMenu(id`,`text`, `submenu`, `helpStr)`和`InsertMenu(pos`, `text`, `submenu`, `helpStr)`。这些方法的行为与这章前面我们所讨论的菜单项的插入方法的行为类似。 记住,子菜单创建的步骤的顺序相对于单纯的菜单项来说是较为重要的,我们推荐你先将项目添加给子菜单,然后将子菜单附加给父菜单。这使得`wxPython`能够正确地为菜单注册快捷键。你可以嵌套子菜单到任意深度,这通过给已有的子菜单添加子菜单来实现,而非将子菜单添加到顶级菜单,在添加新的子菜单之前,你仍需要创建创建它。 ### 如何创建弹出式菜单? 菜单不仅能够从框架顶部的菜单栏向下拉,而且它们也可以在框架的任一处弹出。多数情况下,一个弹出菜单用于根据上下文和用户所敲击位置的对象来提供相应的行为。图10.6显示了一个弹出式菜单的例子。 **图10.6** ![](https://box.kancloud.cn/2016-08-21_57b9964499f76.gif) 弹出菜单的创建是非常类似于标准菜单的,但是它们不附加到菜单栏。例10.9显示了一个弹出菜单的示例代码。 **例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() ``` 弹出菜单像任一其它菜单一样被创建(注意`for`循环对于快速创建菜单项的用法)。它没有被添加到菜单栏,它被存储在实例变量`self.popupmenu`中。然后,框架将方法`OnShowPopup()`绑定到事件`wx.EVT_CONTEXT_MENU`。该事件被操作系统的触发弹出菜单的标准机制所触发。在微软`Windows`和`GTK`下,这个机制是鼠标右键敲击,在`Mac` `OS`下,它是一个`control`敲击。 当用户在框架上执行一个弹出触发敲击的时候,处理器`OnShowPopup()`被调用。该方法所做的第一件事是确定显示菜单的位置。传递给该方法的事件的位置(在`wx.EVT_CONTEXT_MENU`的实例中)是以屏幕的绝对坐标存储的,所以我们需要将位置坐标转换为相对于包含弹出菜单的面板的坐标,我们使用方法`ScreenToClient()`。 此后,使用方法`PopupMenu(menu`, `pos)`调用弹出菜单,你也可以使用相关的方法`PopupMenuXY(menu`, x, `y)`。`PopupMenu`函数不返回,直到一个菜单项被选择或通过按下`Esc`或在该弹出菜单之外敲击使该弹出菜单消失。如果一个菜单项被选择,那么它的事件被正常处理(这意味它必须有一个方法与事件`EVT_MENU`绑定),并且在`PopupMenu`方法返回前该事件也被完成。`PopupMenu`的返回值是布尔值,没什么意思。 弹出菜单可以有一个标题,当弹出菜单被激活时它显示在弹出菜单的顶部。这个标题使用属性`wx.Menu.SetTitle(title)`和`wx.Menu.GetTitle()`来处理。 ### 如何创建自己个性的菜单? 如果普通的菜单项引不起你足够的兴趣,你可以添加自定义的位图到菜单项的旁边(或用作自定义的检查符号)。在微软`Windows`下,你也可以调整菜单项的字体和颜色。图10.7显示了一个个性菜单的例子。 **图10.7** ![](https://box.kancloud.cn/2016-08-21_57b99644abb2c.gif) 例10.10显示了产生这种菜单的代码。要确定程序是否运行在`Windows`下,你可以检查‘`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 True o '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() ``` 处理控制显示属性的主要的内容是给一个菜单项添加颜色或样式。适合除了`Windows`外(包括`Windows)`的平台的唯一的属性是`bitmap`,由`GetBitmap()`管理,该函数返回一个`wx.Bitmap`类型的项。这儿有两个`set`*方法。第一个是`SetBitmap(bmp)`,它工作于所有的平台上。它总是在菜单项的旁边设置一个显示的位图。如果你是在微软`Windows`上,并且你想为一个开关菜单设置一个自定义的位图,你可以使用`SetBitmaps(checked`, `unchecked`=`wx.NullBitmap)`,它使得当该项被选中时显示一个位图,该项未选中时显示另一个位图。如果该菜单项不是一个开关菜单项,那么`checked`参数是没有用的。 在微软`Windws`下,有三个另外的属性,你可以用来改变菜单项的外观,如表10.7所示。我们建议你谨慎地使用它们,并且仅在它们能够明显地增强用户的体验的情况下。 **表10.7 菜单项的外观属性** | | | | --- | --- | | `GetBackgroundColour()` | 属性类型是`wx.Colour`,该`set`*方法的参数也可以是一个`wxPython`颜色的名称字符串。管理项目的背景色。 | | `SetBackgroundColour(colour)` | | `GetFont()` | 项目的显示字体。类型是`wx.Font`。 | | `SetFont(font)` | | `GetTextColour()` | 管理显示在项目中的文本的颜色。类型和背景色的相同。 | | `SetTextColour(colour)` | 目前我们已经讨论了使用菜单功能方面的内容,接下来我们将对如何更好的使用菜单以及如何使你的应用程序对用户来说更容易使用的问题作一个纲要性的说明,以结束本章。 ## 菜单设计的适用性准则 对于大多数复杂的应用程序,菜单栏是用户访问应用程序功能的主要入口。正确的设计菜单对你的程序的易用性有很大的帮助。本着这一想法,我们提供了一些关于菜单设计的适用性准则。 ### 使菜单有均衡的长度 建议菜单所包含的项目的最大数量在10到15之间。超过这个最大长度的菜单将会看不全。你应该学习创建长度基本一致的菜单,记住,这有时是不可能或不必要的。 ### 创建合理的项目组 你不应该创建一个没有分隔符的多于五个项目的组,除非你有非常合理的理由这样做——如一个历史列表,或一个插件列表。多于五个项目的组,用户处理起来非常困难。要有一个更大的组,项目需要被强有力地联系在一起并且要有用户期望长于五个项目的列表的原因。 **菜单的顺序要遵循标准** 对于菜单的顺序,你应该遵循公认的标准。最左边的菜单应该是`FILE`(文件),并且它包含`new`(新建),`open`(打开),`save`(保存),`print`(打印)和`quit`(退出)功能,所包含的功能的顺也是这样,另外的一些功能通常添加在打印和退出之间。几乎每个应用程序都要使用到这些功能。下一个菜单是`EDIT`(编辑),它包含`undo`(撤消),`cut`(剪切),`copy`(拷贝),`paste`(粘贴)和常用的`find`(查找),这些依赖于你的程序的应用范围。`HELP`(帮助)菜单总是在最右边,并且`windows`(窗口)菜单经常是挨着它的。中间的其它菜单通常由你自己来决定。 **对通常使用的项目提供方便的访问** 用户总是会更先访问到菜单中更上面的项目。这就说明了更常用的选项应放在顶部。有一个例外就是多数研究显示,第二项先于第一项。 **使用有含义的菜单名称** 记住,位于菜单栏上的菜单的宽度是与它的名称成正比的,并且当菜单打开时它的宽度与它所包含的项目的最长的名字成正比。尽量避免使顶级菜单的名字少于四个字母。除了通常的名称外,我们建议只要有可能,名字再长点,但意义要清楚。不要害怕给一个菜单项较长的文本,尽管30~40个字符可能难读。 **当一个项目会调用一个对话框时,记住带有省略号** 任何会导致一个对话框被显示的菜单项,都应该有一个以省略号(...)结尾的标签。 **使用标准的快捷键** 对快捷键,使用通常功能的公认的标准,如表10.8所示。 **表10.8 快捷键功能** | | | | --- | --- | | `Ctrl`-a | 全选 | | `Ctrl`-c | 拷贝 | | `Ctrl`-f | 查找 | | `Ctrl`-g | 查找下一个 | | `Ctrl`-n | 新建 | | `Ctrl`-o | 打开 | | `Ctrl`-p | 打印 | | `Ctrl`-q | 退出 | | `Ctrl`-s | 保存 | | `Ctrl`-v | 粘贴 | | `Ctrl`-w | 关闭 | | `Ctrl`-x | 剪切 | | `Ctrl`-z | 撤消 | 这里没有列出`Redo`(重做)的公认的快捷键,你有时会看到用于它的`Ctrl`-y,`Alt`-z或其它的组合。如果你给通常功能提供了更多的快捷键,那么建议你给用户提供一个方案来改变它们。快捷键在用户做大量的输入工作时是很有用的,例如一个文本编辑器。但是对于大部分用鼠标完成的工作,它们的作用就很少了。 **反映出开关状态** 当创建一个开关菜单时,有两个事情需要注意。第一,记住,一个未选中的复选菜单项看起来与一个通常的菜单项相同。如果该菜单项的文本如`fancy` `mode` `on`的话,那么用户就有可能不知道选择这个菜单项会改变样式。另一个要注意的是,菜单项文本要反映出当前不是被激活的状态,而非激活状态。比如菜单文本说明如果选择它会执行什么动作。例如,如果`fancy`样式是打开的,那么文本使用`Turn` `fancy` `mode` `off`。菜单中没有语句表明`fancy`样式实际是什么样的,这可以引起混淆。要避免这个问题,对于一个未被选择的菜单,使用一个自定义的位图以视觉的方式说明这个菜单是一个开关菜单,是一个好的主意(平台允许的话)。如果你的平台不支的话,使用像 `toggle` `fancy` `mode`或`switch` `fancy` `mode` (`now` `on)`这样的文本意思会更清楚。 **慎重地使用嵌套** 嵌套层次的菜单对于浏览来说不方便。 **避免使用字体和颜色** 你记得有哪一个应用程序在它的菜单项中使用了字体和颜色的。我们也不这要使用(但是用于选择字体或颜色的菜单是例外)。很明显,这种使用非常少见。 ## 本章小结 ·在图形用户界面中,菜单是最常用来让用户触发命令的机制。在`wxPython`中,创建菜单使用三个主要的类:`wx.MenuBar`,它表示菜单栏并包含菜单,菜单使用`wx.Menu`。菜单由菜单项组成,菜单项使用`wx.MenuItem`。对于菜单部分的创建,首先创建菜单栏并将它附加到框架。然后分别创建个个菜单,并添加菜单项。再将菜单添加到菜单栏。菜单项可以被添加到菜单的任意位置。一个菜单项也可以是一个菜单分隔符,而非普通的菜单项。当菜单项被添加到其父菜单时,菜单项对象可以被显式地或隐含地创建。 ·选择一个菜单将触发一个`wx.EVT_MENU`类型的命令事件。菜单事件经由框架绑定,而非菜单项,菜单或菜单栏。这让工具栏按钮可以触发与一个菜单项相同的`wx.EVT_MENU`事件。如果你有多个有着连续标识符的菜单项,它们有相同的处理器的话,那么它们可以使用`wx.EVT_MENU_RANGE`事件类型被绑定在一起调用。 ·菜单项能够从包含它们的菜单或菜单栏使用`ID`或标签来查找。也可以被设置成有效或无效。 ·一个菜单可以被附加给另一个菜单,而非菜单栏,从而形成一个嵌套的子菜单。`wx.Menu`有特定的方法使你能够添加子菜单,同样也能够添加一个菜单项。 ·菜单可以用两种方法与按键关联。助记符和加速器。 ·一个菜单项可以有一个开关状态。它可以是一个复选菜单项,也可以是一个单选菜单项。菜单项的选择状态可以经由包含它的菜单或菜单来查询或改变。 ·可以创建弹出式菜单。这通过捕获`wx.EVT_CONTEXT_MENU`类型事件,并使用`PopupMenu()`方法来显示弹出。在弹出中的菜单项事件被正常地处理。 ·你可以为一个菜单项创建一个自定义的位图,并且在`Windows`操作系统下,你可以改变一个菜单项的颜色和字体。