ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 第十三章 建造列表控件并管理列表项 本章内容: * 创建不同样式的列表控件 * 处理列表中的项目 * 响应列表中用户的选择 * 编辑标签和对列表排序 * 创建大的列表控件 在`wxPython`中,有两个控件,你可以用来显示基于列表的信息。较简单的是列表框,它是可滚动的单列列表,类似于使用`HTML`的 `select `标记所得到的。列表框已经在第8章中讨论过了,本章不再作进一步的讨论。 本章讨论较为复杂的列表:列表控件,它是一个完整特性的列表窗口部件。这个列表控件可以每行显示多列信息,并可基于任一行进行排序,还能以不同的样式显示。对于列表控件的每个部分的细节显示,你有很大的灵活性。 ## 建造一个列表控件 列表控件能够以下面四种不同模式建造: * 图标(`icon)` * 小图标(`small icon)` * 列表(`list)` * 报告(`report)` 如果你用过微软`Windows`的资源管理器(`Explorer)`或`Mac`的`Finder`,那么这些方式你应该熟悉。我们将通过说明如何建立四种不同模式的列表作为开始来介绍列表控件。 ### 什么是图标模式? 列表控件看起来类似于微软的`Windows`资源管理器的一个文件树系统的显示面板。它以四种模式之一的一种显示一个信息的列表。默认模式是图标模式,显示在列表中的每个元素都是一个其下带有文本的一个图标。图13.1显示了一个图标模式的列表。 例13.1是产生图13.1的代码。注意这个例子使用了同目录下的一些.`png`文件。 **例13.1** **创建一个图标模式的列表** ``` #-*- encoding:UTF-8 -*- import wx import sys, glob class DemoFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "wx.ListCtrl in wx.LC_ICON mode", size=(600,400)) # load some images into an image list il = wx.ImageList(32,32, True)#创建图像列表 for name in glob.glob("icon??.png"): bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) il_max = il.Add(bmp) # create the list control #创建列表窗口部件 self.list = wx.ListCtrl(self, -1, style=wx.LC_ICON | wx.LC_AUTOARRANGE) # assign the image list to it self.list.AssignImageList(il, wx.IMAGE_LIST_NORMAL) # create some items for the list #为列表创建一些项目 for x in range(25): img = x % (il_max+1) self.list.InsertImageStringItem(x, "This is item %02d" % x, img) app = wx.PySimpleApp() frame = DemoFrame() frame.Show() app.MainLoop() ``` **图13.1** ![](https://box.kancloud.cn/2016-08-21_57b99646416ff.gif) 在例13.1中,`DemoFrame`创建了一个“`image list`(图像列表)”来包含对要显示的图像的引用,然后它建造并扩充了这个列表控件。我们将在本章稍后的部分讨论“`image list`(图像列表)”。 ### 什么是小图标模式? 小图标模式类似标准的图标模式,但是图标更小点。图13.2以小图标模式显示了相同的列表。 当你想在窗口部件中放入更多的显示项目时,小图标模式是最有用的,尤其是当图标不够精细时。 **图13.2** ![](https://box.kancloud.cn/2016-08-21_57b996465532d.gif) 产生图13.2的示例代码如下: ``` import wx import sys, glob class DemoFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "wx.ListCtrl in wx.LC_SMALL_ICON mode", size=(600,400)) # load some images into an image list il = wx.ImageList(16,16, True) for name in glob.glob("smicon??.png"): bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) il_max = il.Add(bmp) # create the list control self.list = wx.ListCtrl(self, -1, style=wx.LC_SMALL_ICON | wx.LC_AUTOARRANGE ) # assign the image list to it self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) # create some items for the list for x in range(25): img = x % (il_max+1) self.list.InsertImageStringItem(x, "This is item %02d" % x, img) app = wx.PySimpleApp() frame = DemoFrame() frame.Show() app.MainLoop() ``` ### 什么是列表模式? 在列表模式中,列表以多列的形式显示,一列到达底部后自动从下一列的上部继续,如图13.3所示。 列模式在相同元素的情况下,几乎与小图标模式所能容纳的项目数相同。对这两个模式的选择,主要是根据你的数据是按列组织好呢还是按行组织好。 **图13.3** ![](https://box.kancloud.cn/2016-08-21_57b996467478b.gif) 产生图13.3的示例代码如下: ``` import wx import sys, glob class DemoFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "wx.ListCtrl in wx.LC_LIST mode", size=(600,400)) # load some images into an image list il = wx.ImageList(16,16, True) for name in glob.glob("smicon??.png"): bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) il_max = il.Add(bmp) # create the list control self.list = wx.ListCtrl(self, -1, style=wx.LC_LIST) # assign the image list to it self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) # create some items for the list for x in range(25): img = x % (il_max+1) self.list.InsertImageStringItem(x, "This is item %02d" % x, img) app = wx.PySimpleApp() frame = DemoFrame() frame.Show() app.MainLoop() ``` 在报告模式中,列表显示为真正的多列格式,每行可以有任一数量的列,如图13.4所示。 ### 图13.4 ![](https://box.kancloud.cn/2016-08-21_57b9964687ef2.gif) 报告模式与图标模式不尽相同。例13.2显示了图13.4的代码。 **例13.2** **创建报告模式的一个列表** ``` #!/usr/bin/python #-*- encoding:UTF-8 -*- import wx import sys, glob, random import data class DemoFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "wx.ListCtrl in wx.LC_REPORT mode", size=(600,400)) il = wx.ImageList(16,16, True) for name in glob.glob("smicon??.png"): bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) il_max = il.Add(bmp) self.list = wx.ListCtrl(self, -1, style=wx.LC_REPORT)#创建列表 self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) # Add some columns for col, text in enumerate(data.columns):#增加列 self.list.InsertColumn(col, text) # add the rows for item in data.rows:#增加行 index = self.list.InsertStringItem(sys.maxint, item[0]) for col, text in enumerate(item[1:]): self.list.SetStringItem(index, col+1, text) # give each item a random image img = random.randint(0, il_max) self.list.SetItemImage(index, img, img) # set the width of the columns in various ways self.list.SetColumnWidth(0, 120)#设置列的宽度 self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE) self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE) self.list.SetColumnWidth(3, wx.LIST_AUTOSIZE_USEHEADER) app = wx.PySimpleApp() frame = DemoFrame() frame.Show() app.MainLoop() ``` 注意:如果代码中有中文或中文注释,那么请在代码开头加上#-*- `encoding:UTF`-8 -*- 在接下来的部分,我们将讨论如何将值插入适当的位置。报告控件是最适合用于那些包含一两个附加的数据列的简单列表,它的显示逻辑没有打算做得很复杂。如果你的列表控件复杂的话,或包含更多的数据的话,那么建议你使用`grid`控件,说明见第14章。 ### 如何创建一个列表控件? 一个`wxPython`列表控件是类`wx.ListCtrl`的一个实例。它的构造函数与其它的窗口部件的构造函数相似: `wx.ListCtrl(parent, id, pos=wx.DefaultPosition, ` * `size=wx.DefaultSize, style=wx.LC_ICON, ` `validator=wx.DefaultValidator, name=`"`listCtrl`") 这些参数我们在其它的窗口部件的构造函数中见过。参数`parent`是容器部件,`id`是`wxPython`标识符,使用-1表明自动创建标识符。具体的布局由参数`pos`和`size`来管理。`style`控制模式和其它的显示方案——贯穿本章,我们都将看到这些值。参数`validator`用于验证特定的输入,我们在第9章讨论过。参数`name`我们很少使用。 样式(`style`)标记是一个位掩码,它管理列表控件的一些不同的特定。样式标记的第一组值用于设置列表的显示模式。默认模式是`wx.LC_ICON`。表13.1显示了列表控件的模式值。 **表13.1** **列表控件模式值** | | | | --- | --- | | `wx.LC_ICON` | 图标模式,使用大图标 | | `wx.LC_LIST` | 列表模式 | | `wx.LC_REPORT` | 报告模式 | | `wx.LC_SMALL_ICON` | 图标模式,使用小图标 | 在图标或小图标列表中,有三个样式标记用来控件图标相对于列表对齐的。默认值是`wx.LC_ALIGN_TOP`,它按列表的顶部对齐。要左对齐的话,使用`wx.LC_ALIGN_LEFT`。样式`LC_AUTOARRANGE`使得当图标排列到达窗口右或底边时自动换行或换列。 表13.2显示了作用于报告列表显示的样式。 **表13.2** **报告列表的显示样式** | | | | --- | --- | | `wx.LC_HRULES` | 在列表的行与行间显示网格线(水平分隔线) | | `wx.LC_NO_HEADER` | 不显示列标题 | | `wx.LC_VRULES` | 显示列与列之间的网格线(竖直分隔线) | 样式标记可以通过位运算符来组合。使用`wx.LC_REPORT`|`wx.LC_HRULES`|`wx.LC_VRULES`组合可以得到一个非常像网格的一个列表。默认情况下,所有的列表控件都允许多选。要使得一次只能选列表中的一个项目,可以使用标记`wx.LC_SINGLE_SEL`。 与我们见过的其它的窗口部件不同,列表控件增加了一对用于在运行时改变已存在的列表控件的样式标记的方法。`SetSingleStyle(style, add=True)`方法使你能够增加或去掉一个样式标记,这依赖于参数`add`的值。`listCtrl.SetSingleStyle(LC_HRULES,True)`将增加水平分隔线,而`listCtrl.SetSingleStyle(LC_HRULES,False)`将去掉水平分隔线。`listCtrl`代表具体的列表控件。`SetWindowStyleFlag(style)`能够重置整个窗口的样式,如`SetWindowStyleFlag(LC_REPORT `| `LC_NO_HEADER)`。这些方法对于在运行时修改列表控件的样式就有用处的。 ## 处理列表中的项目 一旦列表控件被创建,你就能够开始将信息添加到该列表中。在`wxPython`中,对于纯文本信息和对与列表中的每个项目相关的图像的处理是不同的。在接下来的几节里,我们将如何添加图像和文本到你的列表控件中。 ### 什么是一个图像列表以及如何将图像添加给它? 在我们讨论信息是如何被添加到列表控件之前,我们需要对列表如何控制图像说两句。任何使用在一个列表控件中的图像,首先必须被添加到一个图像列表,图像列表是一个图像索引数组,使用列表控件存储。当一个图像与列表中的一个特定项目相关联时,图像列表中的该图像的索引被用来引用该图像,而非使用图像本身。该机制确保每个图像只被装载一次。这是为了在一个图标被列表中的几个项目重复使用时节约内存。它也允许相同图像的多个版本之间的相对直接的连接,这些版本被用来表示不同的模式。关于创建`wxPython`图像和位图的更多的信息,请看第12章。 **创建一个图像列表** 图像列表是`wx.ImageList`的一个实例,构造函数如下: `wx.ImageList(width, height, mask=True, initialCount=1) ` 参数`width`和`height`指定了添加到列表中的图像的像素尺寸。比指定大小大的图像是不允许的。参数`mask`是一个布尔值。如果为`True`,假如图像有遮罩,则使用遮罩绘制图像。参数`initialCount`设置列表的初始的内在尺寸。如果你知道列表会很大,那么指定初始量可以获得更多的内存分配以便稍后使用。 **添加及移去图像** 你可以使用方法`Add(bitmap, mask=wx.NullBitmap)`来将一个图像添加到列表,参数`bitmap`和`mask`都是`wx.Bitmap`的实例。`mask`参数是一个单色位图,它代表该图像的透明部分,如果指定了`mask`参数的话。如果位图已经有一个与之相关的遮罩,那么该遮罩被默认使用。如果位图没有一个遮罩,并且你不使用单色透明映射,但设置了该位图的一个特定颜色作为这个透明色的话,那么你可以使用`AddWithColourMask(bitmap`,`colour)`方法,其中参数`colour`是用作遮罩的`wxPython`颜色(或它的颜色名)。如果你有一个`wx.Icon`对象要添加到图像列表,可以使用方法`AddIcon(icon)`。所有这些添加方法都返回这个新加的图像在列表中的索引值,你可以保留索引值以便日后使用该图像。 下面的代码片断显示了一个创建图像列表的例子(类似于例13.1中的)。 ``` il = wx.ImageList(32, 32, True) for name in glob.glob("icon??.png"): bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) il_max = il.Add(bmp) ``` 然后这个图像列表必须被赋给一个列表控件,使用下面的方法: `self.list.AssignImageList(il, wx.IMAGE_LIST_NORMAL) ` 要从图像列表删除一个图像,可以使用`Remove(index)`方法,其中的`index`是图像在图像列表中的整数索引值。这个方法会修删除点之后的图像在图像列表中的索引值,如果在你的程序中有对特定的索引存在依赖关系的话,这可能会导致一些问题。要删除整个图像列表,使用`RemoveAll()`。你可以使用方法`Replace(index,  bitmap,  mask=wx.NullBitmap)`修改特定索引相关的位图,其中`index`是列表中要修改处的索引,`bitmap`和`mask`与`Add()`方法中的一样。如果要修改的项目是一个图标,可以使用方法`ReplaceIcon(index, icon)`。这里没有处理颜色遮罩的替换方法。 **使用图像列表** 通过使用方法`GetImageCount()`,你能够得到图像列表的长度,使用`GetSize()`方法,你可以得到其中个个图像的尺寸,它返回一个(`width, height)`元组。 在列表控件上下文中没有直接相关的图像的时候,你也可以根据图像列表绘制一个图像到设备上下文中。关于设备上下文的更多信息,请看第6章和第12章。这个方法是`Draw`,如下所示: ``` Draw(index, dc, x, y, flags=wx.IMAGELIST_DRAW_NORMAL, solidBackground=False) ``` 在这个调用中,参数`index`是要绘制的项目在图像列表中的索引,参数`dc`是要绘制到的一个`wx.DC`设备上下文。`flags`控制图像被如何绘制,`flags`的可取值有`wx.IMAGELIST_DRAW_NORMAL,  wx.IMAGELIST_DRAW_TRANSPARENT,  wx.IMAGELISTDRAW_SelectED, `和 `wx.IMAGELIST_DRAW_FOCUSED`。如果`solidBackground`为`True`,那么该绘制方法使用一个更快的算法工作。 一旦你有了一个图像列表,你就需要将它附给列表控件。这个以通过后面的任一个方法来实现:`AssignImage(imageList, which)`或`SetImage(imageList, which)`。`imageList`参数是一个图像列表,参数`which`是标记值:`wx.IMAGE_LIST_NORMAL `或 `wx.IMAGE_LIST_SMALL`。这两个方法的唯一的不同之处是C++对图像列表的处理方面。对于`AssignImage()`,图像列表变成了列表控件的一部分,并随列表控件的销毁而销毁。对于`SetImage()`,图像列表有自己的生命周期,当列表控件被销毁时不被自动处理,只是当其`Python`对象退出作用域时,才被处理。 可以赋给列表控件两个图像列表。普通的图像列表(使用了`wx.IMAGE_LIST_NORMAL`)被用于标准的图标模式。小图像列表(使用了`wx.IMAGE_LIST_SMALL`)被用于报告和小图标模式。在大多数情况下,你只需要一个图像列表,但是如果你希望列表以多模式显示(这样用户可以从普通模式切换到小图标模式),那么你应该两个都提供。如果你这样做了,那么记住,列表控件中的选项将只会经由图像列表中的索引知道相关的图像。如果文档图标在普通尺寸的图像列表中有两个索引,那么也必须在小图像列表中有两个索引。 关于列表控件还有一个相关的`get`*方法:`GetImageList(which)`,它返回与`which`标记参数相关的图像列表。 ### 如何对一个列表添加或删除项目? 在你能显示一个列表之前,你需要给它增加文本信息。在一个图标列表中,你可以增加新的项目如图标、字符串或两个都添加。在一个报告视图中,你也可以在设置了初始图标和/或字符串后,为一行中的不同的列设置信息。用于处理列表控件项目的方法的`API`及其命名习惯与迄今为止我们所见过的其它一些控件的是有区别的,因此,尽管你已经理解了菜单或列表框是如何工作的,但是你仍将需要读这一节。 对于一个图标列表,增加文本信息到列表控件是一个单步的处理过程,但是对于一个报告列表就需要多步才行。通常对于每个列表,第一步是在行中增加第一个项目。对于报告列表,你必须分别地增加列和列中的信息,而非最左边的一个。 **增加一个新行** 要增加一个新行,使用`InsertItem()`这类的一种方法。具体所用的方法依赖于你所插入的项目的类型。如果你仅仅插入一个字符串到列表中,使用`InsertStringItem(index, label)`,其中的`index`是要插入并显示新项目的行的索引。如果你只插入一个图像,那么使用`InsertImageItem(index, imageIndex)`。在这种情况下,这`index`是要插入图像的行的索引,`imageIndex`是附加到该列表控件的图像列表中的图像的索引。要插入一个图像项目,图像列表必须已经被创建并赋值。如果你使用的图像索引超出了图像列表的边界,那么你将得到一个空图像。如果你想增加一个既有图像又有字符串标签的项目,使用`InsertImageStringItem(index, label, imageIndex)`。这个方法综合了前面两个方法的参数,参数的意义不变。 在内部,列表控件使用类`wx.ListItem`的实例来管理有关它的项目的信息。我还要说的是,最后一种插入项目到列表控件中方法是`InsertItem(index, item)`,其中的`item`是`wx.ListItem`的一个实例。对于`wx.ListItem`,这里我们不将做很详细的说明,这是因为你几乎不会用到它并且该类也不很复杂——它几乎都是由`get`*和`set`*方法组成的。一个列表项的几乎所有属性都可通过列表控件的方法来访问。 **增加列** 要增加报告模式的列表控件的列,先要创建列,然后设置每行/列对的单独的数据单元格。使用`InsertColumn()`方法创建列,它的语法如下: ``` InsertColumn(col, heading, format=wx.LIST_FORMAT_LEFT, width=-1) ``` 在这个方法中,参数`col`是列表中的新列的索引,你必须提供这个值。参数`heading`是列标题。参数`format`控件列中文本的对齐方式,取值有:`wx.LIST_FORMAT_CENTRE`、`wx.LIST_FORMAT_LEFT`、和 `wx.LIST_FORMAT_RIGHT`。 参数`width`是列的初始显示宽度(像素单位)——用户可以通过拖动列的头部的边来改变它的宽度。要使用一个`wx.ListItem`对象来设置列的话,也有一个名为`InsertColumnInfo(info)`的方法,它要求一个列表项作为参数。 **设置多列列表中的值** 你可能已经注意到使用前面说明的行的方法来插入项目,对于一个多列的报告列表来说只能设置最初的那列。要在另外的列中设置字符串,可以使用方法`SetStringItem()`。 ``` SetStringItem(index, col, label, imageId=-1) ``` 参数`index`和`col`是你要设置的单元格的行和列的索引。你可以设定`col`为0来设置第一列,但是参数`index`必须对应列表控件中已有的行——换句话说,这个方法只能对已有的行使用。参数`label`是显示在单元格中文本,参数`imageId`是图像列表中的索引(如果你想在单元格中显示一个图像的话可以设置这个参数)。 `SetStringItem()`是`SetItem(info)`方法的一种特殊情况,`SetItem(info)`方法要求一个`wx.ListItem`实例。要使用这个方法,在将`wx.ListItem`实例增加到一个列表之前,要先设置它行,列和其它的参数。你也可以使用`GetItem(index,col=0)`方法来得到单元格处的`wx.ListItem`实例,默认情况下,该方法返回一行的第一列,你可以通过设置参数`col`来选择其它列的一项。 **项目属性** 有许多的`get`*和`set`*方法使你能够指定部分项目。通常这些方法工作在一行的第一列上。要得工作在其它的列上,你需要使用`GetItem()`来得到项目,并使用项目类的`get`*和`set`*方法。你可以使用`SetItemImage(item, image, selImage)`来为一个项目设置图像,其中的`item`参数是该项目在列表中的索引,`image`和`selImage`都是图像列表中的索引,分别代表通常显示的图像和被选中时显示的图像。你可以通过使用`GetItemText(item)`和`SetItemText(item,text)`方法来得到或设置一个项目的文本。 你可以使用`GetItemState(item,stateMask)`和`SetItemState(item, state, stateMask)`来得到或设置单独一个项目的状态。`state`和`stateMask`的取值见表13.3。参数`state`(及`GetItemState`的返回值)是项目的实际状态,`stateMask`是当前关注的所有可能值的一个掩码。 你可以使用`GetColumn(col)`来得到一个指定的列,它返回索引`col`处的列的`wx.ListItem`实例。 **表13.3** **状态掩码参数** 状态及说明如下: | | | | --- | --- | | `wx.LIST_STATE_CUT` | 被剪切状态。这个状态只在微软`Windows`下有效。 | | `wx.LIST_STATE_DONTCARE` | 无关状态。这个状态只在微软`Windows`下有效。 | | `wx.LIST_STATE_DropHILITED` | 拖放状态。项目显示为高亮,这个状态只在微软`Windows`下有效。 | | `wx.LIST_STATE_FOCUSED` | 获得光标焦点状态。 | | `wx.LIST_STATE_SelectED` | 被选中状态。 | 你也可以用`SetColumn(col, item)`方法对一个已添加的列进行设置。你也可以在程序中用`GetColumnWidth(col)`方法方法得到一个列的宽度,该方法返回列表的宽度(像素单位)——显然这只对报告模式的列表有用。你可以使用`SetColumnWidth(col,width)`来设置列的宽度。这个`width`可以是一个整数值或特殊值,这些特殊值有:`wx.LIST_AUTOSIZE`,它将列的宽度设置为最长项目的宽度,或`wx.LIST_AUTOSIZE_USEHEADER`,它将宽度设置为列的首部文本(列标题)的宽度。在非`Windows`操作系统下,`wx.LIST_AUTOSIZE_USEHEADER`可能只自动地将列宽度设置到80像素。 如果你对已有的索引不清楚了,你可以查询列表中项目的数量。方法有`GetColumnCount()`,它返回列表中所定义的列的数量,`GetItemCount()`返回行的数量。如果你的列表是列表模式,那么方法`GetCountPerPage()`返回每列中项目的数量。 要从列表中删除项目,使用`DeleteItem(item)`方法,参数`item`是项目在列表中的索引。如果你想一次删除所有的项目,可以使用`DeleteAllItems()`或`ClearAll()`。你可以使用`DeleteColumn(col)`删除一列,`col`是列的索引。 ## 响应用户 通常,一个列表控件在当用户选择了列表中的一个项目后都要做一些事情。在接下来的部分, 我们将展示一个列表控件都能响应哪些事件,并提供一个使用列表控件事件的例子。 ### 如何响应用户在列表中的选择? 像别的控件一样,列表控件也触发事件以响应用户的动作。你可以像我们在第三章那样使用`Bind()`方法为这些 事件设置处理器。所有这些事件处理器都接受一个`wx.ListEvent`实例,`wx.ListEvent`是`wx.CommandEvent`的子类。`wx.ListEvent`有少量专用的`get`*方法。某些属性只对特定的事件类型有效,这些特定的事件类型在本章的另外部分说明。适用于所有事件类型的属性见表13.4。 **表13.4** **`wx.ListEvent`的属性** | | | | --- | --- | | `GetData()` | 与该事件的列表项相关的用户数据项 | | `GetKeyCode()` | 在一个按键事件中,所按下的键的键码 | | `GetIndex()` | 得到列表中与事件相关的项目的索引 | | `GetItem()` | 得到与事件相关的实际的`wx.ListItem` | | `GetImage()` | 得到与事件相关单元格中的图像 | | `GetMask()` | 得到与事件相关单元格中的位掩码 | | `GetPoint()` | 产生事件的实际的鼠标位置 | | `GetText()` | 得到与事件相关的单元格中的文本 | 这儿有几个关于`wx.ListEvent`的不同的事件类型,每个都可以有一个不同的处理器。某些关联性更强的事件将在后面的部分讨论。表13.5列出了选择列表中的项目时的所有事件类型。 **表13.5** **与选择一个列表控件中的项目相关的事件类型** | | | | --- | --- | | `EVT_LIST_BEGIN_DRAG` | 当用户使用鼠标左按键开始一个拖动操作时,触发该事件 | | `EVT_LIST_BEGIN_RDRAG` | 当用户使用鼠标右按键开始一个拖动操作时,触发该事件 | | `EVT_LIST_Delete_ALL_ITEMS` | 调用列表的 `DeleteAll()`将触发该事件 | | `EVT_LIST_Delete_ITEM` | 调用列表的 `Delete()`将触发该事件 | | `EVT_LIST_Insert_ITEM` | 当一个项目被插入到列表中时,触发该事件 | | `EVT_LIST_ITEM_ACTIVATED` | 用户通过在已选择的项目上按下回车或双击来激活一个项目时 | | `EVT_LIST_ITEM_DESelectED` | 当项目被取消选择时触发该事件 | | `EVT_LIST_ITEM_FOCUSED` | 当项目的焦点变化时触发该事件 | | `EVT_LIST_ITEM_MIDDLE_CLICK` | 当在列表上敲击了鼠标的中间按钮时触发该事件 | | `EVT_LIST_ITEM_RIGHT_CLICK` | 当在列表上敲击了鼠标的右按钮时触发该事件 | | `EVT_LIST_ITEM_SelectED` | 当通过敲击鼠标左按钮来选择一个项目时,触发该事件 | | `EVT_LIST_ITEM_KEY_DOWN` | 在列表控件已经获得了焦点时,一个按键被按下将触发该事件 | 下节中例13.3将提供一个关于上述事件中的一些事件的应用例子。 ### 如何响应用户在一个列的首部中的选择? 除了用户在列表体中触发的事件以外,还有在报告列表控件的列首中所触发的事件。列事件创建的`wx.ListEvent`对象有另一个方法:`GetColumn()`,该方法返回产生事件的列的索引。如果事件是一个列边框的拖动事件,那么这个索引是所拖动的边框的左边位置。如果事件是一个敲击所触发的,且敲击不在列内,那么该方法返回-1。表13.6包含了列事件类型的列表。 **表13.6** **列表控件列事件类型** | | | | --- | --- | | `EVT_LIST_COL_BEGIN_DRAG` | 当用户开始拖动一个列的边框时,触发该事件 | | `EVT_LIST_COL_CLICK` | 列表首部内的一个敲击将触发该事件 | | `EVT_LIST_COL_RIGHT_CLICK` | 列表首部内的一个右击将触发该事件 | | `EVT_LiST_COL_END_DRAG` | 当用户完成对一个列表边框的拖动时,触发该事件 | 例13.3显示了一些列表事件的处理,并也提供了方法的一些演示。 **例13.3** **一些不同列表事件和属性的一个例子** ``` import wx import sys, glob, random import data class DemoFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "Other wx.ListCtrl Stuff", size=(700,500)) self.list = None self.editable = False self.MakeMenu() self.MakeListCtrl() def MakeListCtrl(self, otherflags=0): # if we already have a listctrl then get rid of it if self.list: self.list.Destroy() if self.editable: otherflags |= wx.LC_EDIT_LABELS # load some images into an image list il = wx.ImageList(16,16, True) for name in glob.glob("smicon??.png"): bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) il_max = il.Add(bmp) # create the list control self.list = wx.ListCtrl(self, -1, style=wx.LC_REPORT|otherflags) # assign the image list to it self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) # Add some columns for col, text in enumerate(data.columns): self.list.InsertColumn(col, text) # add the rows for row, item in enumerate(data.rows): index = self.list.InsertStringItem(sys.maxint, item[0]) for col, text in enumerate(item[1:]): self.list.SetStringItem(index, col+1, text) # give each item a random image img = random.randint(0, il_max) self.list.SetItemImage(index, img, img) # set the data value for each item to be its position in # the data list self.list.SetItemData(index, row) # set the width of the columns in various ways self.list.SetColumnWidth(0, 120) self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE) self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE) self.list.SetColumnWidth(3, wx.LIST_AUTOSIZE_USEHEADER) # bind some interesting events self.Bind(wx.EVT_LIST_ITEM_SelectED, self.OnItemSelected, self.list) self.Bind(wx.EVT_LIST_ITEM_DESelectED, self.OnItemDeselected, self.list) self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, self.list) # in case we are recreating the list tickle the frame a bit so # it will redo the layout self.SendSizeEvent() def MakeMenu(self): mbar = wx.MenuBar() menu = wx.Menu() item = menu.Append(-1, "E \tAlt-X") self.Bind(wx.EVT_MENU, self.OnExit, item) mbar.Append(menu, " ") menu = wx.Menu() item = menu.Append(-1, "Sort ascending") self.Bind(wx.EVT_MENU, self.OnSortAscending, item) item = menu.Append(-1, "Sort descending") self.Bind(wx.EVT_MENU, self.OnSortDescending, item) item = menu.Append(-1, "Sort by submitter") self.Bind(wx.EVT_MENU, self.OnSortBySubmitter, item) menu.AppendSeparator() item = menu.Append(-1, "Show selected") self.Bind(wx.EVT_MENU, self.OnShowSelected, item) item = menu.Append(-1, "Select all") self.Bind(wx.EVT_MENU, self.OnSelectAll, item) item = menu.Append(-1, "Select none") self.Bind(wx.EVT_MENU, self.OnSelectNone, item) menu.AppendSeparator() item = menu.Append(-1, "Set item text colour") self.Bind(wx.EVT_MENU, self.OnSetTextColour, item) item = menu.Append(-1, "Set item background colour") self.Bind(wx.EVT_MENU, self.OnSetBGColour, item) menu.AppendSeparator() item = menu.Append(-1, "Enable item editing", kind=wx.ITEM_CHECK) self.Bind(wx.EVT_MENU, self.OnEnableEditing, item) item = menu.Append(-1, "Edit current item") self.Bind(wx.EVT_MENU, self.OnEditItem, item) mbar.Append(menu, " ") self.SetMenuBar(mbar) def OnExit(self, evt): self.Close() def OnItemSelected(self, evt): item = evt.GetItem() print "Item selected:", item.GetText() def OnItemDeselected(self, evt): item = evt.GetItem() print "Item deselected:", item.GetText() def OnItemActivated(self, evt): item = evt.GetItem() print "Item activated:", item.GetText() def OnSortAscending(self, evt): # recreate the listctrl with a sort style self.MakeListCtrl(wx.LC_SORT_ASCENDING) def OnSortDescending(self, evt): # recreate the listctrl with a sort style self.MakeListCtrl(wx.LC_SORT_DESCENDING) def OnSortBySubmitter(self, evt): def compare_func(row1, row2): # compare the values in the 4th col of the data val1 = data.rows[row1][3] val2 = data.rows[row2][3] if val1 val2: return -1 if val1 val2: return 1 return 0 self.list.SortItems(compare_func) def OnShowSelected(self, evt): print "These items are selected:" index = self.list.GetFirstSelected() if index == -1: print "\tNone" return while index != -1: item = self.list.GetItem(index) print "\t%s" % item.GetText() index = self.list.GetNextSelected(index) def OnSelectAll(self, evt): for index in range(self.list.GetItemCount()): self.list.Select(index, True) def OnSelectNone(self, evt): index = self.list.GetFirstSelected() while index != -1: self.list.Select(index, False) index = self.list.GetNextSelected(index) def OnSetTextColour(self, evt): dlg = wx.ColourDialog(self) if dlg.ShowModal() == wx.ID_OK: colour = dlg.GetColourData().GetColour() index = self.list.GetFirstSelected() while index != -1: self.list.SetItemTextColour(index, colour) index = self.list.GetNextSelected(index) dlg.Destroy() def OnSetBGColour(self, evt): dlg = wx.ColourDialog(self) if dlg.ShowModal() == wx.ID_OK: colour = dlg.GetColourData().GetColour() index = self.list.GetFirstSelected() while index != -1: self.list.SetItemBackgroundColour(index, colour) index = self.list.GetNextSelected(index) dlg.Destroy() def OnEnableEditing(self, evt): self.editable = evt.IsChecked() self.MakeListCtrl() def OnEditItem(self, evt): index = self.list.GetFirstSelected() if index != -1: self.list.EditLabel(index) class DemoApp(wx.App): def OnInit(self): frame = DemoFrame() self.SetTopWindow(frame) print "Program output appears here..." frame.Show() return True app = DemoApp(redirect=True) app.MainLoop() ``` 一旦你输入上面的代码并执行它,你将看到列表控件特性的演示,包括像项目的排序,这我们将在下一节讨论。 在这一节,我们将讨论对列表控件中的项目进行编辑、排序和想找。 ### 如何编辑标签? 除了报告列表外,编辑一个列表中的项目是简单的,在报告列表中,用户只能编辑一行的第一个一。而对于其它的列表,则没有问题;每个项目的标准的标签都是可编辑的。 要使一个列表是可编辑的,则当列表被创建时要在构造函数中包含样?奖昙莧。 `list = wx.ListCtrl(self, `-1, `style=wx.LC_REPORT `| `wx.LC_EDIT_LABELS)` 如果这个编辑标记被设置了,那么用户就能够通过在一个已选择的列表项上敲击来开始一个编辑会话。编辑完后按下`Enter`键结束编辑会话,新的文本就变成了文本标签。在列表控件中的鼠标敲击也可结束编辑会话(一次只能有一个编辑会话)。按下`Esc`键则取消编辑会话,这样的话,新输入的文本就没有用了。 下面的两个事件类型是由编辑会话触发的。 * `EVT_LIST_BEGIN_LABEL_EDIT ` * `EVT_LIST_END_LABEL_EDIT` 记住,如果你想事件在被你的自定义的事件处理器处理后继续被处理,那么你需要在你的事件处理器中包括`Skip()`调用。当用户开始一个编辑会话时,一个`EVT_LIST_BEGIN_LABEL_EDIT`类型的列表事件被触发,当会话结束时(通过使用`Enter`或`Esc`),`EVT_LIST_END_LABEL_EDIT`类型的列表事件被触发。你可以否决(`veto)`编辑事件的开始,这样编辑会话就不会开始了。否决编辑事件的结束将阻止列表文本的改变。 `wx.ListEvent`类有两个属性,这两个属性只在当处理一个`EVT_LIST_END_LABEL_EDIT`事件时才会用到。如果编辑结束并确认后,`GetLabel()`返回列表项目标签的新文本,如果编辑被`Esc`键取消了,那么`GetLabel()`返回一个空字符串。这意味着你不能使用`GetLabel()`来区别“取消”和“用户故意输入的空字符串标签”。如果必须的话,可以使用`IsEditCancelled()`,它在因取消而导致的编辑结束时返回`True`,否则返回`False`。 如果你想通过其它的用户事件来启动一个编辑会话的话,你可以在程序中使用列表控件的`EditLabel(item)`方法来触发一个编辑。其中的`item`参数是是要被编辑的列表项的索引。该方法触发`EVT_LIST_BEGIN_LABEL_EDIT`事件。 如果你愿意直接处理用于列表项编辑控件,你可以使用列表控件的方法`GetEditControl()`来得到该编辑控件。该方法返回用于当前编辑的文本控件。如果当前没有编辑,该方法返回`None`。目前该方法只工作于`Windows`操作系统下。 **`boa`-`constructor`简介** `boa`-`constructor`是一个跨平台的`Python`集成开发环境和`wxPython`图形用户界面构建器。它提供了可视化方式的框架(窗口)的创建和处理、对象检视器(`object inspector)`、编辑器、继承的等级、`html`文档字符串、高级的调试器和集成化的帮助系统。俨然一个用于`Python`的`Delphi`。 对的`Zope`支持:对象的创建和编辑。剪切,复制,粘贴,导入和导出。检视器(`inspector)`和`Python`脚本调试中的创建和编辑。 `boa`-`constructor`是用`Python`和`wxPython`库写成的。使用它之前,你必须安装了`wxPython 2.4.0.7`或`wxPython`更高的版本以及`Python 2.1`或`Python`的更高的版本。建议在使用`boa`-`constructor`之前,先入门`wxPython`。 `boa`-`constructor`项目位于`SourceForge`中。 下载`boa`-`constructor  ` ### 如何对列表排序? 在`wxPython`中有三个有用的方法可以对列表进行排序,在这一节,我们将按照从易到难的顺序来讨论。 **在创建的时候告诉列表去排序** 对一个列表控件排序的最容易的方法,是在构造函数中告诉该列表控件对项目进行排序。你可以通过使用样式标记`wx.LC_SORT_ASCENDING`或`wx.LC_SORT_DESCENDING`来实现。这两个标记导致了列表在初始显示的时候被排序,并且在`Windows`上,当新的项目被添加时,依然遵循所样式标记来排序。对于每个列表项的数据的排序,是基于其字符串文本的,只是简单的对字符串进行比较。如果列表是报告模式的,则排序是基于每行的最左边的列的字符串的。 **基于数据而非所显示的文本来排序** 有时,你想根据其它方面而非列表标签的字符串来对列表排序。在`wxPython`中,你可以做到这一点,但这是较为复杂的。首先,你需要为列表中的每个项目设置项目数据,这通过使用`SetItemData(item, data)`方法。参数`item`是项目在未排序的列表中的索引,参数`data`必须是一个整形或长整形的值(由于C++的数据类型的限制),这就有点限制了该机制的作用。如果要获取某行的项目数据,可以使用方法`GetItemData(item)`。 一旦你设置了项目数据,你就可以使用方法`SortItems(func)`来排序项目。参数`func`是一个可调用的`Python`对象(函数),它需要两个整数。`func`函数对两个列表项目的数据进行比较——你不能得到行自身的引用。如果第一项比第二项大的话,函数将返回一个正整数,如果第一项比第二项小的话,返回一个负值,如果相等则返回0。尽管实现这个函数的最显而易见的方法是只对这两个项目做一个数字的比较就可以了,但是这并不唯一的排序方法。比如,数据项可能是外部字典或列表中的一个关键字,与该关键字相应的是一个更复杂的数据项,这种情况下,你可以通过比较与该关键字相应的数据项来排序。 **使用`mixin`类进行列排序** 关于对一个列表控件进行排序的常见的情况是,让用户能够通过在报告模式的列表的任一列上进行敲击来根据该列进行排序。你可以使用`SortItems()`机制来实现,但是它在保持到列的跟踪方面有点复杂。幸运的是,一个名为`ColumnSorterMixin`的`wxPython`的`mixin`类可以为你处理这些信息,它位于`wx.lib.mixins.listctrl`模块中。图13.5显示了使用该`mixin`类对列进行的排序。 **图13.5** ![](https://box.kancloud.cn/2016-08-21_57b99646a6246.gif) 声明这个`mixin`就和`Python`中声明任何其它的多重继承一样,如下所示: ``` import wx.lib.mixins.listctrl as listmix class ListCtrlPanel(wx.Panel, listmix.ColumnSorterMixin): def __init__(self, parent, log): wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS) self.list = TestListCtrl(self, tID) self.itemDataMap = musicdata listmix.ColumnSorterMixin.__init__(self, 3) ``` 例13.4是图13.5的例子代码 **例13.4 使用`mixin`对一个报告列表进行排序** ``` #!/usr/bin/python #-*- encoding:UTF-8 -*- import wx import wx.lib.mixins.listctrl import sys, glob, random import data class DemoFrame(wx.Frame, wx.lib.mixins.listctrl.ColumnSorterMixin):#多重继承 def __init__(self): wx.Frame.__init__(self, None, -1, "wx.ListCtrl with ColumnSorterMixin", size=(600,400)) # load some images into an image list il = wx.ImageList(16,16, True) for name in glob.glob("smicon??.png"): bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) il_max = il.Add(bmp) # add some arrows for the column sorter # 添加箭头到图像列表 self.up = il.AddWithColourMask( wx.Bitmap("sm_up.bmp", wx.BITMAP_TYPE_BMP), "blue") self.dn = il.AddWithColourMask( wx.Bitmap("sm_down.bmp", wx.BITMAP_TYPE_BMP), "blue") # create the list control self.list = wx.ListCtrl(self, -1, style=wx.LC_REPORT) # assign the image list to it self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) # Add some columns for col, text in enumerate(data.columns): self.list.InsertColumn(col, text) # add the rows # 创建数据映射 self.itemDataMap = {} for item in data.rows: index = self.list.InsertStringItem(sys.maxint, item[0]) for col, text in enumerate(item[1:]): self.list.SetStringItem(index, col+1, text) # give each item a data value, and map it back to the # item values, for the column sorter self.list.SetItemData(index, index)# 关联数据和映射 self.itemDataMap[index] = item # give each item a random image img = random.randint(0, il_max) self.list.SetItemImage(index, img, img) # set the width of the columns in various ways self.list.SetColumnWidth(0, 120) self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE) self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE) self.list.SetColumnWidth(3, wx.LIST_AUTOSIZE_USEHEADER) # initialize the column sorter wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self, len(data.columns)) def GetListCtrl(self): return self.list def GetSortImages(self): return (self.dn, self.up) app = wx.PySimpleApp() frame = DemoFrame() frame.Show() app.MainLoop() ``` 为了使用该`mixin`工作,你需要执行下面的东西: 1、扩展自`ColumnSorterMixin`的类(这里是`DemoFrame`)必须有一个名为`GetListCtrl()`的方法,它返回实际要被排序的列表控件。该方法被这个`mixin`用来得到控件的一个索引。 2、在扩展自`ColumnSorterMixin`的类(这里是`DemoFrame`)的`__init__()`方法中,在你调用`ColumnSorterMixin`的`__init__()`方法之前,你必须创建`GetListCtrl()`所要引用的列表控件。该`mixin`的`__init__()`方法要求一个代表列表控?械牧泻诺恼怠? 3、你必须使用`SetItemData()`为列表中的每行设置一个唯一的数据值。 4、扩展自`ColumnSorterMixin`的类(这里是`DemoFrame`)必须有一个名为`itemDataMap`的属性。该属性必须是一个字典。字典中的关键性的东西是由`SetItemData()`设置的数据值。这些值是你想用来对每列进行排序的值的一个元组。(典型情况下,这些值将是每列中的文本)。按句话说,`itemDataMap`本质上是将控件中的数据复制成另一种易于排序的形式。 在`ColumnSorterMixin`的通常用法中,你要么创建`itemDataMap`用来添加项目到你的列表控件,要么你首先创建`itemDataMap`,并用它来建造列表控件本身。 尽管配置可能有点复杂,但`ColumnSorterMixin`对于列的排序是一个不错的选择。 ### 进一步了解列表控件 有时候,在你的程序中的某处你需要确定列表中的哪个项目被选择了,或者你需要通过编程来改变当前的选择以响应用户事件,或其它发生在你的程序中的一些事情。 有几个与查找列表中的一个项目的索引相关的方法,它们提供了项目的一些信息,如表13.7所示。 表13.8显示出了由`HitTest()`方法返回的可能的标记。实际应用中,可能返回不止一个标记。 **表13.7** **查找列表中的项目的方法** | | | | --- | --- | | `FindItem(start, str, partial=False)` | 查找第一个与`str`匹配的项目。如果`start`为-1,那么搜索从头开始,否则搜索从`start`的指定的索引处开始。如果`partial`为`True`,那么这个匹配是匹配以`str`头的字符串,而非完全匹配。返回值是所匹配的字符串的索引。 | | `FindItemAtPos(start`,`point, direction)` | 查找与最接近位置点`point`的项目,`point`是一个`wx.Point`,它是相对于列表控件左上角的位置。参数`direction`是查找进行的方向。可能的取值有 `wx.LIST_FIND_DOWN, wx.LIST_FIND_LEFT, wx.LIST_FIND_RIGHT, `和`wx.LIST_FIND_UP`。 | | `FindItemData(start,data)` | 查找项目数据(使用`SetItemData()`设置的)与参数`data`匹配的项目。参数`start`同`FindItem()`。 | | `HitTest(point)` | 返回一个(`index, flags)`形式的`Python`元组。其中,`index`是项目在列表控件中的索引,如果没有所匹配的项目,那么`index`为-1。`flags`包含了关于位置点和项目的进一步的信息。`flags`是一个位掩码,其取值说明在表13.8中。 | **表13.8** **关于`HitTest()`方法返回值中的标记** | | | | --- | --- | | `wx.LIST_HITTEST_ABOVE` | 位置点在列表的客户区域的上面。 | | `wx.LIST_HITTEST_BELOW` | 位置点在列表的客户区域的下面。 | | `wx.LIST_HITTEST_NOWhere` | 位置点在列表的客户区域中,但不属于任何项目的部分。通常这是因为它是在列表的结尾处。 | | `wx.LIST_HITTEST_ONITEM` | 位置点在项目的矩形区域中,(`index, flags)`中的`index`是该项目的索引。 | | `wx.LIST_HITTEST_ONITEMICON` | 位置点在项目的图标区域中,(`index, flags)`中的`index`是该项目的索引。 | | `wx.LIST_HITTEST_ONITEMLABEL` | 位置点在项目的标签区域中,(`index, flags)`中的`index`是该项目的索引。 | | `wx.LIST_HITTEST_ONITEMRIGHT` | 位置点在项目右边的空白区域中。 | | `wx.LIST_HITTEST_ONITEMSTATEICON` | 位置点在一个项目的状态图标中。我们这里假设列表是树形的模式,并且存在一个用户定义的状态。 | | `wx.LIST_HITTEST_TOLEFT` | 位置点在列表的客户区域的左边。 | | `wx.LIST_HITTEST_TORIGHT` | 位置点在列表的客户区域的右边。 | 至于其它的方面,还有几个方法,它们将为你提供关于所指定的项目的一些信息。方法`GetItem()`和`GetItemText()`方法我们早先已说过了,其它的见表13.9 **表13.9** **获得列表控件的项目信息的方法** | | | | --- | --- | | `GetItemPosition(item)` | 返回一个`wx.Point`,它是指定项目的位置。只用于图标或小图标模式。所返回的位置点是该项目位置的左上角。 | | `GetItemRect(item,code= wx.LIST_RECT_BOUNDS)` | 返回`item`所指定的项目的矩形区域 | `wx.Rect`。参数`code`是可选的。`code`的默认值是`wx.LIST_RECT_BOUNDS`,这使得`wxPython`返回项目的整个矩形区域。`code`的其它取值还有 | `wx.LIST_RECT_ICON`,它导致返回的只是项目的图标部分的矩形区域,`wx.LIST_RECT_LABEL`,它导致返回的只是项目的标签部分的矩形区域。 | | `GetNextItem(item, geometry=wx.LIST_ALL, state=wx.LIST_STATE_DONTCARE )` | 根据`geometry`和`state`参数,返回列表中位于`item`所指定的项目之后的下一个项目。其中的`geometry`和`state`参数,它们都有自己的取值,后面的列表将有说明。 | | `SetItemPosition(item, pos)` | 将`item`所指定的项目移动到`pos`所指定的位置处。只对图标或小图标模式的列表有意义。 | 表13.10列出了用于`GetNextItem()`的`geometry`参数的取值。`geometry`参数只用于微软`Windows`下。 **表13.10** **`GetNextItem()`的`geometry`参数的取值** | | | | --- | --- | | `wx.LIST_NEXT_ABOVE` | 查找显示上位于开始项目之上的下一个为指定状态的项目。 | | `wx.LIST_NEXT_ALL` | 在列表中按索引的顺序查找下一个为指定状态的项目。 | | `wx.LIST_NEXT_BELOW` | 查找显示上位于开始项目之下的下一个为指定状态的项目。 | | `wx.LIST_NEXT_LEFT` | 查找显示上位于开始项目左边的下一个为指定状态的项目。 | | `wx.LIST_NEXT_RIGHT` | 查找显示上位于开始项目右边的下一个为指定状态的项目。 | 表13.11列出了用于`GetNextItem()`的`state`参数的取值 **表13.11** **用于`GetNextItem()`的`state`参数的取值** | | | | --- | --- | | `wx.LIST_STATE_CUT` | 只查找所选择的用于剪贴板剪切和粘贴的项目。 | | `wx.LIST_STATE_DONTCARE` | 查找项目,不管它当前的状态。 | | `wx.LIST_STATE_DropHILITED` | 只查找鼠标要释放的项目。 | | `wx.LIST_STATE_FOCUSED` | 只查找当前有焦点的项目。 | | `wx.LIST_STATE_SelectED` | 只查找当前被选择的项目。 | 表13.12显示了用于改变一个项目的文本显示的方法以及用于控件项目的字体和颜色的方法。 **表13.2** **列表控件的显示属性** | | | | --- | --- | | `GetBackgroundColour()` | 处理整个列表控件的背景色。参数`col`是一个`wx.Colour`或颜色名。 | | `SetBackgroundColour(col)` | | `GetItemBackgroundColour(item)` | 处理索引`item`所指定的项目的背景色。这个属性只用于报告模式。 | | `SetItemBackgroundColour(item,col)` | | `GetItemTextColour(item)` | 处理索引`item`所指定的项目的文本的颜色。这个属性只用于报告模式。 | | `SetItemTextColour(item, col)` | | `GetTextColour()` | 处理整个列表的文本的颜色。 | | `SetTextColour(col)` | 表13.3显示了列表控件的其它的一些方法。 **表13.3** **列表控件的其它的一些方法** | | | | --- | --- | | `GetItemSpacing()` | 返回位于图标间的空白的`wx.Size`。单位为像素。 | | `GetSelectedItemCount()` | 返回列表中当前被选择的项目的数量。 | | `GetTopItem()` | 返回可见区域顶部的项目的索引。只在报告模式中有意义。 | | `GetViewRect()` | 返回一个`wx.Rect`,它是能够包含所有项目所需的最小矩形(没有滚动条)。只对图标或小图标模式有意义。 | | `ScrollList(dx, dy)` | 使用控件滚动。参数`dy`是垂直量,`dx`是水平量,单位是像素。对于图标、小图标或报告模式,单位是像素。如果是列表模式,那么单位是列数。 | 上面的这些表涉及了一个列表控件的大多数功能。然而到目前为止,我们所见过的所有的列表控件,它们被限制为:在程序的运行期间,它们的所有数据必须存在于内存中。在下一节,我们将讨论一个机制,这个机制仅在数据需要被显示时,才提供列表数据。 ## 创建一个虚列表控件 让我们设想你的`wxPython`应用程序需要去显示包含你所有客户的一个列表。开始时你使用一个标准的列表控件,并且它工作的很好。后来人的客户列表变得越来越大,太多的客户使得你的应用程序开始出现了效率问题。这时你的程序起动所需的时间变得较长了,并占用越来越多的内存。你怎么办呢?你可以创建一个虚的列表控件。 问题的实质就是列表控件的数据处理。通常,这些数据都是从数据产生的地方将数据拷贝到列表控件中。这是潜在地浪费资源,对于一个小的列表,这好象看不出任何问题,但对于创建一个较大的列表控件,这将占用很多的内存,并导致启动变慢。 为了将一个列表控件所占的内存和启动所需的时间降到最小化,`wxPython`允许你去声明一个虚的列表控件,这意味关于每项的信息只在控件需要去显示该项时才生成。这就防止了控件一开始就将每项存储到它的内存空间中,并且这也意味着在启动时,并没有声明完整的列表控件。同时这个方案的缺点就是虚列表中的列表项的恢复可能变得较慢。图13.6显示了一个虚列表。 例13.5显示了产生该虚列表控件的完整代码 **图13.6** ![](https://box.kancloud.cn/2016-08-21_57b99646be1a0.gif) **例13.5** **一个虚列表控件** ``` #!/usr/bin/python #-*- encoding:UTF-8 -*- import wx import sys, glob, random import data class DataSource:#数据源 """ A simple data source class that just uses our sample data items. A real data source class would manage fetching items from a database o similar. """ def GetColumnHeaders(self): return data.columns def GetCount(self): return len(data.rows) def GetItem(self, index): return data.rows[index] def UpdateCache(self, start, end): pass class VirtualListCtrl(wx.ListCtrl):#1 声明虚列表 """ A generic virtual listctrl that fetches data from a DataSource. """ def __init__(self, parent, dataSource): wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL)#使用wx.LC_VIRTUAL标记创建虚列表 self.dataSource = dataSource self.Bind(wx.EVT_LIST_CACHE_HINT, self.DoCacheItems) self.SetItemCount(dataSource.GetCount())#设置列表的大小 columns = dataSource.GetColumnHeaders() for col, text in enumerate(columns): self.InsertColumn(col, text) def DoCacheItems(self, evt): self.dataSource.UpdateCache( evt.GetCacheFrom(), evt.GetCacheTo()) def OnGetItemText(self, item, col):#得到需求时的文本 data = self.dataSource.GetItem(item) return data[col] def OnGetItemAttr(self, item): return None def OnGetItemImage(self, item): return -1 class DemoFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "Virtual wx.ListCtrl", size=(600,400)) self.list = VirtualListCtrl(self, DataSource()) app = wx.PySimpleApp() frame = DemoFrame() frame.Show() app.MainLoop() ``` 这个数据源的类是一个简单的例子,它存储了我们所需要的数据项。真实情况下的数据源类还会处理从一个数据库中获取数据之类的情况,这种情况下只需要重新实现本例中的同一接口。 要创建一个虚列表,第一步就是在初始化的时候对列表控件使用`wx.LC_VIRTUAL`标记如#1。通常使用子类`wx.ListCtrl`来创建你的虚列表控件,而非仅仅使用构造函数。这是因为你需要覆盖`wx.ListCtrl`的一些方法,以便扩展这个虚列表。虚列表的声明类似如下: ``` class MyVirtualList(wx.ListCtrl): def __init__(self, parent): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT|wx.LC_VIRTUAL) ``` 有时在虚列表的初始化期间,必须调用`SetItemCount()`方法。这将告诉控件在数据源中存在多少数据项,这样它就可以设置适当的限制并处理滚动条。如果数据源中的数据项的数量改变了,你可以再调用`SetItemCount()`一次。你所覆盖的任何以`On`开关的方法,必须能够处理[0,`SetItemCount()`-1]间的数据。 你的虚列表控件可以覆盖其父类的三个方法,以便决定在列表控件中显示些什么。最重要的要覆盖的方法是`OnGetItemText(item, col)`。其中的参数`item`和`col`是要绘制的单元格的行和列,方法的返回值是显示在该单元格中的文本字符串。例如,下面的方法将只显示相关单元格的坐标。 ``` def OnGetItemText(self, item, col): return "Item %d, column %d" % (item, col) ``` 如果你想在一行中显示一个图像,你需要覆盖`OnGetItemImage(item)`。它的返回值是较早声明的列表控件的图像列中的一个整数索引。如果你没有覆盖这个方法,那么基类版本的`OnGetItemImage`将返回-1,这表明不显示图像。如果你想改变行的一些显示属性,那么你可以覆盖`OnGetItemAttr(item)`方法,`item`是行的索引,该方法返回类`wx.ListItemAttr`的一个实例。该类有一些`get`*和`set`*方法可以用来设置行的颜色、对齐方式等等显示属性。 如果你的虚列表所基于的数据改变了,而你想更新显示,那么你可以使用该列表控件的`RefreshItem(item)`来重绘特定的行。相关的方法`RefreshItems(itemFrom,itemTo)`重绘位于索引`itemFrom`和`itemTo`间的所有行。 为了对数据源中的数据的获取提供优化帮助,对于要显示一页新的数据,虚列表控件会发送`EVT_LIST_CACHE_HINT`事件。这将给你的数据源一个时机用以从数据库(或另处)一次获取几个记录并保存它们。这样就使得随后的`OnGetItemText()`执行的更快。 ## 本章小结 1、列表控件是`wxPython`用于显示列表信息的窗口部件。它比简单的列表框部件要更复杂且有完整的特性。列表控件是类`wx.ListCtrl`的实例。列表控件可以显示为图标模式,每个图标下都有一个项目文本,也可以显示为带有小图标的小图标模式等等。在列表模式中,元素按列显示,在报告模式中,以多列的格式显示列表,每列都有列标签。 2、用于列表控件的图像是由一个图像列表管理的,图像列表是一个可经由索引来访问的一个图像的数组。列表控件可以为不同的列表模式维护各自的图像列表,这使得能够容易地在模式间切换。 3、你可以使用`InsertStringItem(index,label)`方法来插入文本到列表中,使用`InsertImageItem(index,  imageIndex)`方法插入图像到列表中。要一次做上面两件事,可以使用`InsertImageStringItem(index,label,` * `imageIndex)`。要对报告模式的列表添加列,可以使用`InsertColumn(col,  heading, format=`"`wx.LIST_FORMAT_LEFT,  width=`-1)方法。一旦已经添加了列后,你就可以使用`SetStringItem(index, col, label, imageId=`-1)方法为新的列增加文本。 4、列表控件产生的几个事件可以被绑到程序的动作。这些事件项属于类`wx.ListEvent`。通常的事件类型包括`EVT_LIST_Insert_ITEM, EVT_LIST_ITEM_ACTIVATED,`和`EVT_LIST_ ITEM_SelectED`。 5、如果列表控件声明时使用了`wx.LC_EDIT_LABELS`标记,那么用户就可以编辑列表项的文本。编辑的确认是通过按下回车键或在列表中敲击完成的,也可以通过按下`Esc`键来取消编辑。 6、你可以通过在声明列表时使用`wx.LC_SORT_ASCENDING`或`wx.LC_SORT_DESCENDING`来排序列表。这将按照项目的字符串的顺序来排序列表。在报告模式中,这将根据0列的字符串来排序。你也可以使用`SortItems(func)`方法来创建你自定义的排序方法。对于报告模式的列表,`mixin`类`wx.lib.mixins.listctrl.ColumnSorterMixin`给了你根据用户所选择的列来排序的能力。 7、使用了标记`wx.LC_VIRTUAL`声明的列表控件是一个虚列表控件。这意味着它的数据是当列表中的项目被显示时动态地确定的。对于虚列表控件,你必须覆盖`OnGetItemText(item, col)`方法以返回适当的文本给所显示的行和列。你也可以使用`OnGetItemImage(item)`和`OnGetItemAttr(item)`方法来返回关于每行的图像或列表的显示属性。如果数据源的数据改变了,你可以使用`RefreshItem(item)`方法来更新列表的某个行或使用`RefreshItems(itemFrom, itemTo)`方法来更新多个行。 最终,你的数据将变得复杂得不能放在一个简单的列表中。你将会需要类似二维的电子表格样式的东西,这就是网格控件,我们将在下一章进行讨论。