第十三章 建造列表控件并管理列表项

本章内容:

  • 创建不同样式的列表控件
  • 处理列表中的项目
  • 响应列表中用户的选择
  • 编辑标签和对列表排序
  • 创建大的列表控件

wxPython中,有两个控件,你可以用来显示基于列表的信息。较简单的是列表框,它是可滚动的单列列表,类似于使用HTMLselect标记所得到的。列表框已经在第8章中讨论过了,本章不再作进一步的讨论。

本章讨论较为复杂的列表:列表控件,它是一个完整特性的列表窗口部件。这个列表控件可以每行显示多列信息,并可基于任一行进行排序,还能以不同的样式显示。对于列表控件的每个部分的细节显示,你有很大的灵活性。

建造一个列表控件

列表控件能够以下面四种不同模式建造:

  • 图标(icon)

  • 小图标(small icon)

  • 列表(list)

  • 报告(report)

如果你用过微软Windows的资源管理器(Explorer)MacFinder,那么这些方式你应该熟悉。我们将通过说明如何建立四种不同模式的列表作为开始来介绍列表控件。

什么是图标模式?

列表控件看起来类似于微软的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

在例13.1中,DemoFrame创建了一个“image list(图像列表)”来包含对要显示的图像的引用,然后它建造并扩充了这个列表控件。我们将在本章稍后的部分讨论“image list(图像列表)”。

什么是小图标模式?

小图标模式类似标准的图标模式,但是图标更小点。图13.2以小图标模式显示了相同的列表。

当你想在窗口部件中放入更多的显示项目时,小图标模式是最有用的,尤其是当图标不够精细时。

图13.2

产生图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

产生图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

报告模式与图标模式不尽相同。例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是容器部件,idwxPython标识符,使用-1表明自动创建标识符。具体的布局由参数possize来管理。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)

参数widthheight指定了添加到列表中的图像的像素尺寸。比指定大小大的图像是不允许的。参数mask是一个布尔值。如果为True,假如图像有遮罩,则使用遮罩绘制图像。参数initialCount设置列表的初始的内在尺寸。如果你知道列表会很大,那么指定初始量可以获得更多的内存分配以便稍后使用。

添加及移去图像

你可以使用方法Add(bitmap, mask=wx.NullBitmap)来将一个图像添加到列表,参数bitmapmask都是wx.Bitmap的实例。mask参数是一个单色位图,它代表该图像的透明部分,如果指定了mask参数的话。如果位图已经有一个与之相关的遮罩,那么该遮罩被默认使用。如果位图没有一个遮罩,并且你不使用单色透明映射,但设置了该位图的一个特定颜色作为这个透明色的话,那么你可以使用AddWithColourMask(bitmapcolour)方法,其中参数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是列表中要修改处的索引,bitmapmaskAdd()方法中的一样。如果要修改的项目是一个图标,可以使用方法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。如果solidBackgroundTrue,那么该绘制方法使用一个更快的算法工作。

一旦你有了一个图像列表,你就需要将它附给列表控件。这个以通过后面的任一个方法来实现:AssignImage(imageList, which)SetImage(imageList, which)imageList参数是一个图像列表,参数which是标记值:wx.IMAGE_LIST_NORMALwx.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),其中的itemwx.ListItem的一个实例。对于wx.ListItem,这里我们不将做很详细的说明,这是因为你几乎不会用到它并且该类也不很复杂——它几乎都是由getset方法组成的。一个列表项的几乎所有属性都可通过列表控件的方法来访问。

增加列

要增加报告模式的列表控件的列,先要创建列,然后设置每行/列对的单独的数据单元格。使用InsertColumn()方法创建列,它的语法如下:

InsertColumn(col, heading, format=wx.LIST_FORMAT_LEFT, width=-1)

在这个方法中,参数col是列表中的新列的索引,你必须提供这个值。参数heading是列标题。参数format控件列中文本的对齐方式,取值有:wx.LIST_FORMAT_CENTREwx.LIST_FORMAT_LEFT、和 wx.LIST_FORMAT_RIGHT。 参数width是列的初始显示宽度(像素单位)——用户可以通过拖动列的头部的边来改变它的宽度。要使用一个wx.ListItem对象来设置列的话,也有一个名为InsertColumnInfo(info)的方法,它要求一个列表项作为参数。

设置多列列表中的值

你可能已经注意到使用前面说明的行的方法来插入项目,对于一个多列的报告列表来说只能设置最初的那列。要在另外的列中设置字符串,可以使用方法SetStringItem()

SetStringItem(index, col, label, imageId=-1)

参数indexcol是你要设置的单元格的行和列的索引。你可以设定col为0来设置第一列,但是参数index必须对应列表控件中已有的行——换句话说,这个方法只能对已有的行使用。参数label是显示在单元格中文本,参数imageId是图像列表中的索引(如果你想在单元格中显示一个图像的话可以设置这个参数)。

SetStringItem()SetItem(info)方法的一种特殊情况,SetItem(info)方法要求一个wx.ListItem实例。要使用这个方法,在将wx.ListItem实例增加到一个列表之前,要先设置它行,列和其它的参数。你也可以使用GetItem(index,col=0)方法来得到单元格处的wx.ListItem实例,默认情况下,该方法返回一行的第一列,你可以通过设置参数col来选择其它列的一项。

项目属性

有许多的getset方法使你能够指定部分项目。通常这些方法工作在一行的第一列上。要得工作在其它的列上,你需要使用GetItem()来得到项目,并使用项目类的getset方法。你可以使用SetItemImage(item, image, selImage)来为一个项目设置图像,其中的item参数是该项目在列表中的索引,imageselImage都是图像列表中的索引,分别代表通常显示的图像和被选中时显示的图像。你可以通过使用GetItemText(item)SetItemText(item,text)方法来得到或设置一个项目的文本。

你可以使用GetItemState(item,stateMask)SetItemState(item, state, stateMask)来得到或设置单独一个项目的状态。statestateMask的取值见表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.ListEventwx.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类型的列表事件被触发,当会话结束时(通过使用EnterEsc),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文档字符串、高级的调试器和集成化的帮助系统。俨然一个用于PythonDelphi

对的Zope支持:对象的创建和编辑。剪切,复制,粘贴,导入和导出。检视器(inspector)Python脚本调试中的创建和编辑。

boa-constructor是用PythonwxPython库写成的。使用它之前,你必须安装了wxPython 2.4.0.7wxPython更高的版本以及Python 2.1Python的更高的版本。建议在使用boa-constructor之前,先入门wxPython

boa-constructor项目位于SourceForge中。 下载boa-constructor

如何对列表排序?

wxPython中有三个有用的方法可以对列表进行排序,在这一节,我们将按照从易到难的顺序来讨论。

在创建的时候告诉列表去排序

对一个列表控件排序的最容易的方法,是在构造函数中告诉该列表控件对项目进行排序。你可以通过使用样式标记wx.LC_SORT_ASCENDINGwx.LC_SORT_DESCENDING来实现。这两个标记导致了列表在初始显示的时候被排序,并且在Windows上,当新的项目被添加时,依然遵循所样式标记来排序。对于每个列表项的数据的排序,是基于其字符串文本的,只是简单的对字符串进行比较。如果列表是报告模式的,则排序是基于每行的最左边的列的字符串的。

基于数据而非所显示的文本来排序

有时,你想根据其它方面而非列表标签的字符串来对列表排序。在wxPython中,你可以做到这一点,但这是较为复杂的。首先,你需要为列表中的每个项目设置项目数据,这通过使用SetItemData(item, data)方法。参数item是项目在未排序的列表中的索引,参数data必须是一个整形或长整形的值(由于C++的数据类型的限制),这就有点限制了该机制的作用。如果要获取某行的项目数据,可以使用方法GetItemData(item)

一旦你设置了项目数据,你就可以使用方法SortItems(func)来排序项目。参数func是一个可调用的Python对象(函数),它需要两个整数。func函数对两个列表项目的数据进行比较——你不能得到行自身的引用。如果第一项比第二项大的话,函数将返回一个正整数,如果第一项比第二项小的话,返回一个负值,如果相等则返回0。尽管实现这个函数的最显而易见的方法是只对这两个项目做一个数字的比较就可以了,但是这并不唯一的排序方法。比如,数据项可能是外部字典或列表中的一个关键字,与该关键字相应的是一个更复杂的数据项,这种情况下,你可以通过比较与该关键字相应的数据项来排序。

使用mixin类进行列排序

关于对一个列表控件进行排序的常见的情况是,让用户能够通过在报告模式的列表的任一列上进行敲击来根据该列进行排序。你可以使用SortItems()机制来实现,但是它在保持到列的跟踪方面有点复杂。幸运的是,一个名为ColumnSorterMixinwxPythonmixin类可以为你处理这些信息,它位于wx.lib.mixins.listctrl模块中。图13.5显示了使用该mixin类对列进行的排序。

图13.5

声明这个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的指定的索引处开始。如果partialTrue,那么这个匹配是匹配以str头的字符串,而非完全匹配。返回值是所匹配的字符串的索引。
FindItemAtPos(startpoint, 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匹配的项目。参数startFindItem()
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 ) 根据geometrystate参数,返回列表中位于item所指定的项目之后的下一个项目。其中的geometrystate参数,它们都有自己的取值,后面的列表将有说明。
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

例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)。其中的参数itemcol是要绘制的单元格的行和列,方法的返回值是显示在该单元格中的文本字符串。例如,下面的方法将只显示相关单元格的坐标。

def OnGetItemText(self, item, col):
return "Item %d, column %d" % (item, col)

如果你想在一行中显示一个图像,你需要覆盖OnGetItemImage(item)。它的返回值是较早声明的列表控件的图像列中的一个整数索引。如果你没有覆盖这个方法,那么基类版本的OnGetItemImage将返回-1,这表明不显示图像。如果你想改变行的一些显示属性,那么你可以覆盖OnGetItemAttr(item)方法,item是行的索引,该方法返回类wx.ListItemAttr的一个实例。该类有一些getset方法可以用来设置行的颜色、对齐方式等等显示属性。

如果你的虚列表所基于的数据改变了,而你想更新显示,那么你可以使用该列表控件的RefreshItem(item)来重绘特定的行。相关的方法RefreshItems(itemFrom,itemTo)重绘位于索引itemFromitemTo间的所有行。

为了对数据源中的数据的获取提供优化帮助,对于要显示一页新的数据,虚列表控件会发送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_ASCENDINGwx.LC_SORT_DESCENDING来排序列表。这将按照项目的字符串的顺序来排序列表。在报告模式中,这将根据0列的字符串来排序。你也可以使用SortItems(func)方法来创建你自定义的排序方法。对于报告模式的列表,mixinwx.lib.mixins.listctrl.ColumnSorterMixin给了你根据用户所选择的列来排序的能力。

7、使用了标记wx.LC_VIRTUAL声明的列表控件是一个虚列表控件。这意味着它的数据是当列表中的项目被显示时动态地确定的。对于虚列表控件,你必须覆盖OnGetItemText(item, col)方法以返回适当的文本给所显示的行和列。你也可以使用OnGetItemImage(item)OnGetItemAttr(item)方法来返回关于每行的图像或列表的显示属性。如果数据源的数据改变了,你可以使用RefreshItem(item)方法来更新列表的某个行或使用RefreshItems(itemFrom, itemTo)方法来更新多个行。

最终,你的数据将变得复杂得不能放在一个简单的列表中。你将会需要类似二维的电子表格样式的东西,这就是网格控件,我们将在下一章进行讨论。

powered by Gitbook该教程制作时间: 2016-08-21 19:32:44