🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 第十四章 网格控件 **本章内容包括:** * 创建网格(`grid)` * 添加行和单元格(`cell),`并且处理列的首部 * 使用一个自定义的单元格(`cell)`描绘器(`renderer)` * 创建自定义的编辑器 * 捕获用户事件 网格控件大概是`wxPython`中最复杂和最灵活的一个窗口部件。在这一章,你将有机会接触到这个控件的许多特性。我们将讨论如何输入数据到网格控件以及如何处理该控件的显示属性,并且我们还将讨论自定义编辑器和描绘器。网格控件使你能够在一个类似电子表格格式的网格中显示表格数据。该控件允许你为行和列指定标签,以及通过拖动网格线来改变网格的大小,并且可以为每个单元格单独指定字体和颜色属性。 在最常见的情况下,你一般会显示一个简单的字符串值。然而,你也能为任一单元格指定一个自定义的描绘器,以使你能够显示不同的数据;你可以有编辑表中的单元格,并且对不同的数据使用不同类型的编辑器。你还能够创建你自己自定义的描绘器和编辑器,这使得你在单元格数据的显示和处理上可以非常的灵活,几乎没有什么限制。网格控件还有大量的鼠标和键盘事件,你可以程序中捕获它们并用来触发相关的代码。 我们将通过展示两个创建`wxPython`网格的方法来作为我们讨论的开始。 ## 创建你的网格 网格控件是用以显示一个二维的数据集的。要使用该控件显示有用的信息,你需要告诉该控件它工作所基于的是什么数据。在`wxPython`中,有两种不同的机制用于在网格控件中处理数据,它们之间在处理数据的添加,删除和编辑的方式上有些许的不同。 * 网格控件可以直接处理每行和每列中的值。 * 数据可以通过使用一个网格表(`grid table)`来间接地处理。 较简单的一种是使用网格控件直接处理值。在这种情况下,网格维护着数据的一份拷贝。在这种情况下,如果有大量的数据或你的应用程序已经有了一个现存的网格类的数据结构,那么这可能显得比较笨拙。如果是这样,你可以使用一个网格表来处理该网格的数据。参见第5章来回顾一下在`MVC`架构中,网格表是如何被作为一个模型的。 ### 如何创建一个简单的网格? 尽管网格控件有大量的方法用于控件精确的显示和数据的管理,但时开始使用一个网格控件是十分简单的。图14.1显示了一个简单的网格,其中的单元格中添加了一些字符串数据。 **图14.1** ![](https://box.kancloud.cn/2016-08-21_57b99646d9ffb.gif) 网格控件是类`wx.grid.Grid`的实例。由于网格类及相关类的尺寸的原因,实际中许多的程序都不使用它,`wxPython`的网格类存在于它们自己的模块中,它们不会被自动导入到核心的名字空间中。`wx.grid.Grid`的构造函数类似于其它的控件的构造函数。 ``` wx.grid.Grid(parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.WANTS_CHARS, name=wxPanelNameStr) ``` 其中的所有的参数与`wx.Window`的构造函数是相同的,并且有相同的意义。`wx.WANTS_CHARS`样式是网格的默认样式,除此之外,`wx.grid.Grid`没有为自己定义特别的样式。由于网格类的复杂性,所以在程序中,你一般要自定义网格类的一个子类来实现一个网格,而非直接使用`wx.grid.Grid`的一个实例。 和我们所见过的别的控件不同,调用该构造函数不足以创建一个可用的网格。有两个方法用以初始化网格 * `CreateGrid()` * `SetTable()` 在这一节,我们将讨论一个方法,第二个方法将在网格表的讨论中提及。 要显式地初始化网格,可以使用方法`CreateGrid(numRows, numCols, selmode=wx.grid.Grid.SelectCells)`。这个方法应该在构造函数之后被直接地调用,并用必须在网格被显示之前调用。参数`numRows, numCols`指定了网格的初始大小。参数`selmode`指定了网格中单元格的选择模式,默认值是`wx.grid.Grid.SelectCells`,意思是一次只选择一个单元格。其它的值有`wx.grid.Grid.SelectRows`,意思是一次选择整个行,`wx.grid.Grid.SelectionColumns`,意思是一次选择整个列。创建之后,你可以使用方法`GetSelectionMode()`来访问选择模式,并且你可以使用方法`SetSelectionMode(mode)`来重置模式。你还可以使用方法`GetNumberCols()`和`GetNumberRows()`来得到行和列数。 在内部,使用`CreateGrid()`初始化网格之后,`wxPython`设置了一个二维的字符串数组。一旦网格被初始化了,你就可以使用方法`SetCellValue(row, col, s)`来放置数据。其中参数`row, col`是要设置的单元格的坐标,s是要在该坐标处显示的字符串文本。如果你想获取特定坐标处的值,你可以使用函数`GetCellValue(row, col)`,该函数返回字符串。要一次清空整个网格,你可以使用方法`ClearGrid()`。例14.1显示了产生图14.1的代码。 **例14.1** **使用`ClearGrid()`创建的一个示例网格** ``` import wx import wx.grid class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Simple Grid", size=(640,480)) grid = wx.grid.Grid(self) grid.CreateGrid(50,50) for row in range(20): for col in range(6): grid.SetCellValue(row, col, "cell (%d,%d)" % (row, col)) app = wx.PySimpleApp() frame = TestFrame() frame.Show() app.MainLoop() ``` `CreateGrid()`和`SetCellValue()`仅限于你的网格数据是由简单字符串组成的情况。如果你的数据更加的复杂或表特别大的话,更好的方法是创建一个网格表,这将随后讨论。 ### 如何使用网格表来创建一个网格? 对于较复杂的情况,你可以将你的数据保存在一个网格表中,网格表是一个单独的类,它存储数据并与网格控件交互以显示数据。推荐在下列情况下使用网格表: * 网格的数据比较复杂 * 数据存储在你的系统中的另外的对象中 * 网格太大以致于不能一次整个被存储到内存中 在第5章中,我们在`MVC`设计模式中讨论了网格表以及在你的应用程序中实现一个网格表的不同方法。在本章,我们将重点放在对网格表的使用上。图14.2显示了使用网格表创建的一个网格。 **图14.2** ![](https://box.kancloud.cn/2016-08-21_57b9964703d0a.gif) 要使用一个网格表,你需要要创建`wx.grid.PyGridTableBase`的子类。该子类必须覆盖父类`wx.grid.GridTableBase`的一些方法。例14.2显示了用于创建图14.2的代码。 **例14.2** **关于使用网格表机制的代码** ``` #-*- encoding:UTF-8 -*- import wx import wx.grid class TestTable(wx.grid.PyGridTableBase):#定义网格表 def __init__(self): wx.grid.PyGridTableBase.__init__(self) self.data = { (1,1) : "Here", (2,2) : "is", (3,3) : "some", (4,4) : "data", } self.odd=wx.grid.GridCellAttr() self.odd.SetBackgroundColour("sky blue") self.odd.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) self.even=wx.grid.GridCellAttr() self.even.SetBackgroundColour("sea green") self.even.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) # these five are the required methods def GetNumberRows(self): return 50 def GetNumberCols(self): return 50 def IsEmptyCell(self, row, col): return self.data.get((row, col)) is not None def GetValue(self, row, col):#为网格提供数据 value = self.data.get((row, col)) if value is not None: return value else: return '' def SetValue(self, row, col, value):#给表赋值 self.data[(row,col)] = value # the table can also provide the attribute for each cell def GetAttr(self, row, col, kind): attr = [self.even, self.odd][row % 2] attr.IncRef() return attr class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Grid Table", size=(640,480)) grid = wx.grid.Grid(self) table = TestTable() grid.SetTable(table, True) app = wx.PySimpleApp() frame = TestFrame() frame.Show() app.MainLoop() ``` 在例14.2中,所有特定于应用程序的逻辑都已被移到了网格表类,所以这里就没有必须创建一个自定义的`wx.grid.Grid`的子类。 要使网格表有效,你必须覆盖5个方法。表14.1列出了这些方法。在这一章中,我们还会看到其它你能覆盖的方法,你可以覆盖它们以给于你的表更多的功能。 **表14.1** **`wx.grid.GridTableBase`中需要被覆盖的方法** | | | | --- | --- | | `GetNumberCols()` | 返回显示在网格中的列的数目 | | `GetNumberRows()` | 返回显示在网格中的行的数目 | | `GetValue(row, col)` | 返回坐标(`row, col)`处的值 | | `IsEmptyCell(row, col)` | 如果坐标(`row, col)`处的单元格为空的话,返回`True`。否则返回`False`。 | `SetValue(row, col, value)`:如果你需要的话,它使你能够更新你底层的数据结构以匹配用户的编辑。对于一个只读的表,你仍然需要声明该方法,但是你可以通过`pass`来使它什么也不做。该方法在当用户编辑一个单元格时自动被调用。 要将网格表实例附着于你的表的实例,要调用 `SetTable(table,takeOwnership=False,selmode=wx.grid.Grid.SelectCells)`方法。其中参数`table`是你的`wx.grid.PyGridTableBase`的实例。参数`takeOwnership`使得网格控件拥有这个表。如果`takeOwnership`为`True`,那么当网格被删除时,该表也被`wxPython`系统删除。参数`selmode`作用等同于在`CreateGrid()`中的作用。 还有一些其它的方法你可以覆盖,以处理网格的各部分,而非表的数据。在本章的稍后部分,我们将讨论这些方法中的一些。并且,我们将看到在某些情况中,使用`SetTable`创建的表的行为与使用`CreateGrid()`创建的表的行为是不同的。 你能够覆盖的另一个方法是`Clear()`,它在当对网格调用`ClearGrid()`时被调用,如果适当的话,你可以覆盖该方法来清除潜在的数据源。在网格中置入数据了以后,你现在可以开始对网格作各种有兴趣的事情了。在下一节,我们将给你展示如何处理网格的外观。 ## 使用网格工作 一旦网格被创建并初始化了,你就可以用很多不同的方法来处理它了。单元格、行或列可以被添加和删除。你可以增加首部,改变一行或一列的大小,并可以用代码的方式来改变网格的可见部分或被选择的部分。下面的几节,我们将涉及这些内容。 ### 如何添加、删除行,列和单元格? 在网格被创建之后,你仍然可以添加新的行和列。注意,依据网格的创建方法不同,该机制的工作也不同。你可以使用`AppendCols(numCols=1)`方法在你的网格的右边增加一列。使用`AppendRows(numRows=1)`在网格的底部增加一行。 如果不是想在网格的行或列的最后添加一行或一列,你可以使用方法`InsertCols(pos=0, numCols=1)`或`InsertRows(pos=1, numRows=1)`来在指定位置添加。其中参数`pos`代表被添加的新元素中第一个的索引。如果参数`numRows`或`numCols`大于1 ,那么有更多的元素被添加到起始位置的右边(对于列来说),或起始位置的下边(对于行来说)。 要删除一行或一列,你可以使用方法`DeleteCols(pos=0, numCols=1)`和`DeleteRows(pos=0, numRows=1)`。其中参数`pos`是要被删除的行或列的第一个的索引。 如果网格是使用`CreateGrid()`方法被初始化的,那么上面讨论的方法总是可以工作的,并且在新的行或列中创建的单元格是以一个空字符串从为初始值的。如果网是使用`SetTable()`方法被初始化的,那么网格表必须支持对表的改变。 要支持改变,你的网格表要对同样的改变方法进行覆盖。例如,如果你对你的网格调用了`InsertCols()`方法,那么网格表也必须声明一个`InsertCols(pos=0, numCols=1)`方法。该网格表的这个方法返回布尔值`True`表示支持改变,返回`False`则否决改变。例如,要创建一个只允许被扩展到50行的一个表,可以在你的网格表中写上下面的方法。 ``` def AppendRows(self, numRows=1): return (self.GetRowCount() + numRows) = 50 ``` 某些对网格的改变不会立即被显示出来,而是要等待网格被刷新。你可能通过使用`ForceRefresh()`方法来触发一个即时的刷新。在通常情况下,如果你用代码的方式来改变你的网格,则改变不会立即显示出来,那么插入对`ForceRefresh()`方法的调用可以确保你的改变即时的显示出来。 如果你在对一个网格作一个大量的改变,而你在改变期间不想让网格的显示产生闪烁的话,你可以通过使用`BeginBatch()`方法来告诉该网格去作一个批量的处理。该方法将针对网格作一个内在的增量计数。你也必须在批量的任务之后调用`EndBatch()`——该方法针对网格作一个内在的减量计数。当计数值比0大时,表明正处于开始和结束计数之间,网格这时不会重绘。如果必要的话,你还可以在批量处理中再嵌套批量处理。同样,在全部的批量处理没有完成时,网格不会重绘。 ### 如何处理一个网格的行和列的首部? 在一个`wxPython`的网格控件中,每行和每列都有它们自己的标签。默认情况下,行的标签是数字,从1开坮。列的标签是字母,从A开始。`wxPython`提供了一些方法来改变这些标签。图14.3显示了一个带有首部标签的网格。 **图14.3** ![](https://box.kancloud.cn/2016-08-21_57b9964717fe6.gif) 例子14.3显示了产生图14.3的代码。其中网格是用`CreateGrid()`初始化的。 **例14.3** **带自定义标签的一个非模式的网格** ``` import wx import wx.grid class TestFrame(wx.Frame): rowLabels = ["uno", "dos", "tres", "quatro", "cinco"] colLabels = ["homer", "marge", "bart", "lisa", "maggie"] def __init__(self): wx.Frame.__init__(self, None, title="Grid Headers", size=(500,200)) grid = wx.grid.Grid(self) grid.CreateGrid(5,5) for row in range(5): #1 start grid.SetRowLabelValue(row, self.rowLabels[row]) grid.SetColLabelValue(row, self.colLabels[row]) #1 end for col in range(5): grid.SetCellValue(row, col, "(%s,%s)" % (self.rowLabels[row], self.colLabels[col])) app = wx.PySimpleApp() frame = TestFrame() frame.Show() app.MainLoop() ``` 正如添加和删除行一样,改变标签也是根据网格的类型而不同的。对于使用`CreateGrid()`创建的网格,要使用`SetColLabelValue(col, value)`和`SetRowLabelValue(row, value)`方法来设置标签值,如#1所示。参数`col`和`row`是列和行的索引,`value`是要显示在标签中的字符串。要得到一行或一列的标签,使用`GetColLabelValue(col)`和`GetRowLabelValue(row)`方法。 对于使用外部网格表的一个网格控件,你可以通过覆盖网格表的`GetColLabelValue(col)`和`GetRowLabelValue(row)`方法来达到相同的作用。为了消除混淆,网格控件在当它需要显示标签并且网格有一个关联的表时,内在地调用这些方法。由于返回值是动态地由你在覆盖的方法中所写的代码决定的,所以这里不需要覆盖或调用`set`*方法。不过`set`*方法仍然存在——`SetColLabelValue(col, value)`和`SetRowLabelValue(row, value)`——但是你很少会使用到,除非你想让用户能够改变潜在的数据。通常,你不需要`set`*方法。例14.4显示了如何改变网格表中的标签——这个例子产生与上一例相同的输出。 **例14.4** **带有自定义标签的使用了网格表的网格** ``` import wx import wx.grid class TestTable(wx.grid.PyGridTableBase): def __init__(self): wx.grid.PyGridTableBase.__init__(self) self.rowLabels = ["uno", "dos", "tres", "quatro", "cinco"] self.colLabels = ["homer", "marge", "bart", "lisa", "maggie"] def GetNumberRows(self): return 5 def GetNumberCols(self): return 5 def IsEmptyCell(self, row, col): return False def GetValue(self, row, col): return "(%s,%s)" % (self.rowLabels[row], self.colLabels[col]) def SetValue(self, row, col, value): pass def GetColLabelValue(self, col):#列标签 return self.colLabels[col] def GetRowLabelValue(self, row):#行标签 return self.rowLabels[row] class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Grid Table", size=(500,200)) grid = wx.grid.Grid(self) table = TestTable() grid.SetTable(table, True) app = wx.PySimpleApp() frame = TestFrame() frame.Show() app.MainLoop() ``` 默认情况下,标签是居中显示的。但是你也可以使用`SetColumnLabelAlignment(horiz, vert)`和`SetRowLabelAlignment(horiz, vert)`来改变这个行为。其中参数`horiz`用以控制水平对齐方式,取值有`wx.ALIGN_LEFT, wx.ALIGN_CENTRE`或`wx.ALIGN_RIGHT`。参数`vert`用以控制垂直对齐方式,取值有`wx.ALIGN_TOP, wx.ALIGN_CENTRE,`或`wx.ALIGN_BOTTOM`。 行和列的标签区域共享一套颜色和字体属性。你可以使用`SetLabelBackgroundColour(colour)` , `SetLabelFont(font), and SetLabelTextColour(colour)`方法来处理这些属性。参数`colour`是`wx.Colour`的一个实例或`wxPython`会转换为颜色的东西,如颜色的字符串名。参数`font`是`wx.Font`的一个实例。与`set`*相应的`get`*方法有`GetLabelBackgoundColour(), GetLabelFont()`,和`GetLabelTextFont()`。 ### 如何管理网格元素的尺寸? 网格控件提供了几个不同的方法来管理单元格、行和列的尺寸。在这一节,我们将讨论这些方法。图14.4显示了一些用来改变一个特定的单元格的尺寸的方法。 例14.5显示了创建了一个带有可调节大小的单元格、行和列的网格。 **图14.4** ![](https://box.kancloud.cn/2016-08-21_57b9964732d63.gif) **例14.5** **可调整尺寸的单元格的示例代码** ``` import wx import wx.grid class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Grid Sizes", size=(600,300)) grid = wx.grid.Grid(self) grid.CreateGrid(5,5) for row in range(5): for col in range(5): grid.SetCellValue(row, col, "(%s,%s)" % (row, col)) grid.SetCellSize(2, 2, 2, 3) grid.SetColSize(1, 125) grid.SetRowSize(1, 100) app = wx.PySimpleApp() frame = TestFrame() frame.Show() app.MainLoop() ``` **改变单元格的尺寸** 一个作用于单元格尺寸的基本的方法是使它跨多行或多列,类似于`HTML`的`rowspan`和`colspan`。要达到这种效果,在`wxPython`中可以使用方法`SetCellSize(row, col, num_rows, num_cols)`。该方法设置坐标`row,col`处的单元格跨`num_rows`行和`num_cols`列。在通常的情形下,每个单元格占据一行和一列,要使用单元格不止占据一行或一列,你需要给参数`num_rows, num_cols`大于1的值。参数`num_rows, num_cols`的值小于等于0会导致错误。如果你的设置使得一个单元格的尺寸与另一个早先声明为跨越的单元格的尺寸相重叠时,早先的这个单元格的尺寸会重置为占据一行和一列。你也能够使用方法`SetCellOverflow(row, col, allow)`方法来关闭单元格的跨越显示。只要在该方法中使用`pass`就可以阻止单元格跨越了,即使已经使用了`SetCellSize()`方法来设置它的尺寸。 调整网格的尺寸的一个更加典型的方法是基于一行或一列来处理其像素尺寸。你可以使用`SetColSize(col, width)`和`SetRowSize(row, height)`方法来改变一列或一行的宽度。当然,你可以使用`GetColSize(col)`或`GetRowSize(row)`来确定一列或一行的当前尺寸。 **设置默认尺寸** 你可以通过改变所有的行和列的默认尺寸来改变整个网格的尺寸。方法如下: `SetDefaultColSize(width, resizeExistingCols=False) ` `SetDefaultRowSize(height, resizeExistingRows=False)` 其中的第一个参数是以像素为单位的新的默认尺寸。如果第二个布尔参数的值是`True`,那么当前存在的所有行或列立即被调整到新的默认尺寸。如果第二个参数的值为`False`,那么这个新的默认尺寸仅被应用于新添加的行或列。通常,设置新的默认值是在初始化的开头,甚至是在调用`CreateGrid()`或`SetTable()`之前。你可以使用`GetDefaultColSize()`和`GetDefaultRowSize()`方法来得到当前的默认尺寸。 设置默认尺寸与为单个行或列设置尺寸相比,有一个性能上的问题。对于存储默认值,`wxPython`只需要存储这两个整数。如果你将单个行或列设置到一个非默认的尺寸,`wxPython`切换并将每个行或列的尺寸存储到一个数组中。如果你的表是非常的大的话,这将明显地占用很多的内存,因此这是需要注意的。 有时,你想为一行或一列设置一个最小的尺寸,以便不用担心程序的某个方法的调用或用户对网格线的拖动会致使该行或列变得更小。 在`wxPython`中,你可以对一个网格的宽度设置最小值或为单独的行和列分别设置最小尺寸值。要改变整个网格的最小尺寸,可以使用方法`SetColMinimalAcceptableWidth(width)`或`SetRowMinimalAcceptableHeight(height)`。其中的参数是针对所有行或列的最小的像素尺寸。要一行一行的设置最小尺寸,使用方法`SetColMinimalWidth(col, width)`或`SetRowMinimalHeight(row, height)`。其中第一个参数是要调整尺寸的项目的索引,第二个参数是以像素为单位的新的尺寸。单个的行的最小尺寸必须比最小的网格尺寸大,如果单个的行的最小尺寸被设置了的话。上面的`set`*方法都有一个相应的`get`*方法: * `GetColMinimalAcceptableWidth()` * `GetRowMinimalAcceptableHeight()` * `GetColMinimalWidth(col)` * `GetRowMinimalHeight(row)` **设置标签的尺寸** 网格上的标签区域有一套单独的调整尺寸的函数。在这种情况下,你是在设置行标签的宽度和列标签的高度,意思就是,把列标签作为一个特殊的行,把行标签作为一个特殊的列。`set`*方法有`SetRowLabelSize(width)`,它设置行标签的宽度,`SetColLabelSize(height)`,它设置列标签的高度。你可以使用相应的`GetRowLabelSize()`和`GetColLabelSize()`方法来得到这些尺寸。 通常,你不会关心单元格的实际的像素尺寸,你希望它们被自动调整到足够显示你的数据的大小。在`wxPython`中,你可以通过使用`AutoSize()`方法来自动调整整个网格的尺寸。该方法使得所有的行和列的尺寸与它们中的内容相适应。你也可以对单个的行或列使用`AutoSizeColumn(col, setAsMin=True)`和`AutoSizeRow(row, setAsMin=True)`来使它们的尺寸自动与其中的内容相适应。如果参数`setAsMin`为`True`,那么新的自动的尺寸将作为该行或列的最小尺寸。`AutoSizeColumns(setAsMin=True)`和`AutoSizeRows(setAsMin=True)`自动调整所有的列和行的尺寸。 你也可以让用户通过拖动标签单元格的边框来调整行的尺寸。用于实现这种行为的主要的方法如下: * `EnableDragColSize(enable=True)`:控制用户能否通过拖动边框来改变标签的宽度 * `EnableDragRowSize(enable=True)`:控制用户能否通过拖动边框来改变标签的高度 * `EnableDragGridSize(enable=True)`:控制用户能否通过拖动边框一次性改变标签的宽度和高度 下面的方法是上面方法的相应的使拖动无效的简便的方法: * `DisableDragColSize()` * `DisableDragRowSize()` * `DisableDragGridSize()` 下面的一套方法用以判断能否拖动: * `CanDragColSize()` * `CanDragRowSize()` * `CanDragGridSize()` ### 如何管理哪些单元格处于选择或可见状态? 在网格控件中,用户可以选择一个或多个单元格。在`wxPython`中,有几个方法让你能够处理多选的情况。 在下面的几个情况中,网格控件中的被选择的项可以是0个或多个: * 单个的处于选择状态的单元格 * 被选择的行 * 被选择的行 * 被选择的由单元格组成的块 用户可以通过命令或在单元格、行或列标签上的敲击,或拖动鼠标来选择多组单元格。要确定网格中是否有被选择的单元格,可能使用方法`IsSelection()`,如果有则该方法返回`True`。你可以通过使用`IsInSelection(row, col)`方法来查询任意一个特定的单元格当前是否处于选择状态中,如果是则返回`True`。 表14.2显示了几个方法,它们得到当前被选择的内容并返回给你。 **表14.2** **返回当前被选择的单元格的集的方法** | | | | --- | --- | | `GetSelectedCells()` | 返回包含一些单个的处于选择状态的单元格的一个`Python`列表。在这个列表中的每个项目都是一个(`row, col)`元组。 | | `GetSelectedCols()` | 返回由通过敲击列的标签而被选择的列的索引组成的一个`Python`列表。 | | `GetSelectedRows()` | 返回由通过敲击行的标签而被选择的列的索引组成的一个`Python`列表。 | | `GetSelectionBlockTopLeft()` | 返回包含一些被选择的由单元格组成的块的一个`Python`列表。其中的每个元素都时一个(`row, col)`元组,(`row, col)`元组是每块的左上角。 | | `GetSelectionBlockBottomRight()` | 返回包含一些被选择的由单元格组成的块的一个`Python`列表。其中的每个元素都时一个(`row, col)`元组,(`row, col)`元组是每块的右下角。 | 这儿也有几个用于设置或修改选择状态的方法。第一个是`ClearSelection()`,它清除当有的被选状态。在该方法被调用以后,`IsSelection()`返回`False`。你也可以做一个相反的动作,就是使用`SelectAll()`选择所有的单元格。你也可以使用方法`SelectCol(col, addToSelected=False)`和`SelectRow(row, addToSelected=False)`来选择整列或整行。在这两个方法中,第一个参数是要选择的行或列的索引。如果参数`addToSelected`为`True`,所有另外被选择的单元格仍然处于被选状态,并且该行或列也被增加到已有的选择中。如果参数`addToSelected`为`False`,那么所有另外被选择的单元格解除被选状态,而新的行或列代替它们作为被选择对象。同样地,你也可以使用方法`SelectBlock(topRow,  leftCol,  bottomRow,  rightCol, addToSelected=False)`来增加一个对一块范围的选择,前面四个参数是所选的范围的对角,`addToSelected`参数的作用同前一样。 你也可以使用`IsVisible(row, col, wholeCellVisible=True)`方法来得到一个特定的单元格在当前的显示中是否是可见的。如果该单元格当前显示在屏幕上了(相对于处在一个可滚动的容器的不可见部分而言),那么该方法返回`True`。如果参数`wholeCellVisible`为`True`,那么单元格要整个都是可见的,方法才返回`True`,如果参数`wholeCellVisible`为`False`,则单元格部分可见,方法就会返回`True`。方法`MakeCellVisible(row, col)`通过滚动确保了指定位置的单元格是可见的。 除了被选的单元格外,网格控件也有一个光标单元格,它代表获得当前用户焦点的单元格。你可以使用`GetGridCursorCol()`和`GetGridCursorRow()`方法来确定光标的当前位置,这两个方法返回整数的索引值。你可以使用`SetGridCursor(row, col)`方法来显式地放置一个光标。该方法除了移到光标外,它还隐式地对新的光标位置调用了`MakeCellVisible`。 表14.3说明了在网格坐标和显示器坐标之间作转换的网格控件的方法。 **表14.3** **坐标转换方法** | | | | --- | --- | | `BlockToDeviceRect(topLeft, bottomRight)` | 参数`topLeft, bottomRight`是单元格的坐标((`row, col)`元组的形式)。返回值是一个`wx.Rect`,`wx.Rect`使用给定的网格坐标所包围的矩形的设备像素坐标。 | | `CellToRect(row, col)` | 返回一个`wx.Rect`,`wx.Rect`的坐标是相对网格坐标(`row, col)`处的单元格的容器的坐标。 | | `XToCol(x)` | 返回包含x坐标(该坐标是相对于容器的)的列的索引。如果没有这样的列,则返回`wx.NOT_FOUND`。 | | `XToEdgeOfCol(x)` | 返回右边缘最接近给定的x坐标的列的整数索引。如果没有这样的列,则返回`wx.NOT_FOUND`。 | | `YToRow(y)` | 返回包含y坐标(该坐标是相对于容器的)的行的索引。如果没有这样的行,则返回`wx.NOT_FOUND`。 | | `YToEdgeOfRow(y)` | 返回底边缘最接近给定的y坐标的行的整数索引。如果没有这样的行,则返回`wx.NOT_FOUND`。 | 你可以使用上面这些方法来对网格单元格上的鼠标敲击的位置作转换。 ### 如何改变一个网格的单元格的颜色和字体? 正如其它的控件一样,这儿也有一些属性方法,你可以用来改变每个单元格的显示属性。图14.5是个示例图片。例14.6显示了产生图14.5的代码。注意其中的针对特定单元格的网格方法和`wx.grid.GridCellAttr`对象的创建方法的用法。 **图14.5** ![](https://box.kancloud.cn/2016-08-21_57b9964748580.gif) **例14.6** **改变网格的单元格的颜色** ``` import wx import wx.grid class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Grid Attributes", size=(600,300)) grid = wx.grid.Grid(self) grid.CreateGrid(10,6) for row in range(10): for col in range(6): grid.SetCellValue(row, col, "(%s,%s)" % (row, col)) grid.SetCellTextColour(1, 1, "red") grid.SetCellFont(1,1, wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) grid.SetCellBackgroundColour(2, 2, "light blue") attr = wx.grid.GridCellAttr() attr.SetTextColour("navyblue") attr.SetBackgroundColour("pink") attr.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) grid.SetAttr(4, 0, attr) grid.SetAttr(5, 1, attr) grid.SetRowAttr(8, attr) app = wx.PySimpleApp() frame = TestFrame() frame.Show() app.MainLoop() ``` 我们将通过讨论用于设置整个网格默认值的方法作为开始。你可以使用`SetDefaultCellAlignment(horiz, vert)`方法来为网格中所有的单元格设置默认的对齐方式,其中`horiz`的取值有`wx.LEFT`、`wx.CENTRE`、`wx.RIGHT`,`vert`的取值有`wx.TOP,  wx.CENTRE, `和`wx.BOTTOM`。你可以使用`GetDefaultCellAlignment()`来得到这个默认的单元格对齐方式,该方法返回一个(`horiz, vert)`元组。 背景和文本的颜色可以使用`SetDefaultCellTextColour(colour)`和`SetDefaultCellBackgroundColour(colour)`方法来设置。同样,`colour`参数可以是一个`wx.Colour`实例或颜色名。相应的`get`*方法是`GetDefaultCellTextColour()`和`GetDefaultCellBackgroundColour()`。最后,你可以使用`SetDefaultCellFont(font)`和`GetDefaultCellFont()`来处理默认的字体。 使用下面的方法,你可以设置单个单元格的相关属性: ``` GetCellAlignment(row, col) SetCellAlignment(row, col, horiz, vert) GetCellBackgroundColour(row, col) SetCellBackgroundColour(row, col, colour) GetCellFont(row, col) SetCellFont(row, col, font) GetCellTextColour(row, col) SetCellTextColour(row, col, colour) ``` 也可使用`SetSelectionBackground(colour)`和`SetSelectionForeground(colour)`方法来使用被选的单元格有另外背景色和前景色,相应的`get`*方法是`GetSelectionBackground()`和`GetSelectionForeground()`。 你也可以使用`SetMargins(extraWidth, extraHeight)`方法来设置网格控件与它的容器的边距。 在内部,类`wx.grid.Grid`使用一个名为`wx.grid.GridCellAttr`类来管理每个单元格的属性。`wx.grid.GridCellAttr`类对于本节所讨论到的属性,也有`get`*和`set`*方法。你可以通过使用`GetOrCreateCellAttr(row, col)`方法来得到关于一个特定的单元格的`attr`对象,它是单元格的属性对象。一个单元格的属性对象仅在该单元格已定义了非默认的属性时才被创建。一旦你有了该单元格的属性对象,你就可以用它来定义该单元格的显示属性。 要创建你自己的单元格属性对象,这个构造函数是`wx.grid.GridCellAttr()`。你可以设置某些参数,然后将该对象传递给方法`SetColAttr(attr)`或`SetRowAttr(attr)`,这两个方法将将这些显示属性应用到该行或列中的每个单元格,如例14.6所示。 如果你在使用一个网格表,你可以覆盖方法`GetAttr(row, col)`来返回特定单元格的一个`wx.grid.GridCellAttr`实例。 你也可以改变网格线的颜色和显示。网格线的显示是由方法`EnableGridLines(enable)`来控制的。参数`enable`是一个布乐值。如果为`True`,网格线被显示,如果为`False`,则不显示。你可以使用方法`SetGridLineColor(colour)`来改变网格线的颜色。 ## 自定义描绘器和编辑器 是什么使得网格控件是如此的灵活和有用呢?它就是显示或编辑一个单元格的内容的机制可以被改变这一特性。在后面的几节中,我们将给你展示如何去使用预定义的描绘器和编辑器,以及如何写你自己的描绘器和编辑器。 ### 如何使用一个自定义的单元格描绘器? 默认情况下,网格将它的数据以简单字符串的形式显示,然而,你也可以以不同的格式显示你的数据。你可以想将布尔值数据显示为一个复选框,或以图片格式显示一个数字值,或将一个数据的列表以线条的方式显示。 在`wxPython`中,每个单元格都可以有它自己的描绘器,这使得它能够以不同的方式显示它的数据。下面的部分讨论几个`wxPython`中预定义的描绘器,以及如何定义你自己的描绘器。 **预定义的描绘器(`renderer)`** 一个网格描绘器是类`wx.grid.GridCellRenderer`的一个实例,`wx.grid.GridCellRenderer`是一个抽象的父类。一般,你会使用它的子类。表14.4说明了几个你可以用在你的单元格中的预定义的描绘器。它们都有一个构造函数和`get`*,`set`*方法。 **表14.4** **预定义的网格单元格描绘器** | | | | --- | --- | | `wx.grid.GridCellAutoWrapStringRenderer` | 显示文本化的数据,在单元格边界按词按行。 | | `wx.grid.GridCellBoolRenderer` | 使用一个复选框来描绘布尔数据——选中表示`True`,未选中表示`False`。 | | `wx.grid.GridCellDateTimeRenderer` | 使单元格能够显示一个格式化的日期或时间。 | | `wx.grid.GridCellEnumRenderer` | 文本形式。 | | `wx.grid.GridCellFloatRenderer` | 使用指定位数和精度来描绘浮点数。该类的构造函数要求两个参数(`width=`-1, `precision=`-1)。默认的对齐方式为右对齐。 | | `wx.grid.GridCellNumberRenderer` | 数字数据。默认为右对齐方式显示。 | | `wx.grid.GridCellStringRenderer` | 简单字符串的形式。 | 要得到一个特定单元格的描绘器,可以使用方法`GetCellRenderer(row, col)`,该方法返回指定坐标处的单元格的描绘器实例。要为一个单元格设置描绘器,可以使用`SetCellRenderer(row, col, renderer)`方法,其中`renderer`参数是用于指定单元格的新的描绘器。这些方法都简单地设置或得到存储在相关单元格属性对象中的描绘器,所以如果你愿意的话,你可以直接处理`GridCellAttr`。你可以通过使`GetDefaultRenderer`和 `SetDefaultRenderer(renderer)`来得到和设置用于整个网格的默认的描绘器。 你也可以为一行设置描绘器,这个的典型应用是电子表格中的某列总是显示特定类型的数据。实现的方法是`SetColFormatBool(col), SetColFormatNumber(col)`,以及`SetColFormatFloat(col,  width,  precision)`。 **创建一个自定义的描绘器** 要创建你自定义的单元格描绘器,需要创建`wx.grid.PyGridCellRenderer`的一个子类。创建自定义的单元格描绘器,使你能够以特定的格式显示相关的数据。 图14.6显示了一个自定义描绘器的示例,它随机地绘制单元格的背景色。 **图14.6** ![](https://box.kancloud.cn/2016-08-21_57b9964760dd0.gif) 例14.7是产生图14.6的代码,其中显示了相关的类和所覆盖的方法 **例14.7** ``` #-*- encoding:UTF-8 -*- import wx import wx.grid import random class RandomBackgroundRenderer(wx.grid.PyGridCellRenderer):#定义描绘器 def __init__(self): wx.grid.PyGridCellRenderer.__init__(self) def Draw(self, grid, attr, dc, rect, row, col, isSelected):#绘制 text = grid.GetCellValue(row, col) hAlign, vAlign = attr.GetAlignment() dc.SetFont( attr.GetFont() ) if isSelected: bg = grid.GetSelectionBackground() fg = grid.GetSelectionForeground() else: bg = random.choice(["pink", "sky blue", "cyan", "yellow", "plum"]) fg = attr.GetTextColour() dc.SetTextBackground(bg) dc.SetTextForeground(fg) dc.SetBrush(wx.Brush(bg, wx.SOLID)) dc.SetPen(wx.TRANSPARENT_PEN) dc.DrawRectangleRect(rect) grid.DrawTextRectangle(dc, text, rect, hAlign, vAlign) def GetBestSize(self, grid, attr, dc, row, col): text = grid.GetCellValue(row, col) dc.SetFont(attr.GetFont()) w, h = dc.GetTextExtent(text) return wx.Size(w, h) def Clone(self): return RandomBackgroundRenderer() class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Grid Renderer", size=(640,480)) grid = wx.grid.Grid(self) grid.CreateGrid(50,50) # Set this custom renderer just for row 4 attr = wx.grid.GridCellAttr() attr.SetRenderer(RandomBackgroundRenderer()) grid.SetRowAttr(4, attr)#赋于第5行 for row in range(10): for col in range(10): grid.SetCellValue(row, col, "cell (%d,%d)" % (row, col)) app = wx.PySimpleApp() frame = TestFrame() frame.Show() app.MainLoop() ``` 你的描绘器类必须覆盖基类的下面三个方法。 ``` Draw() GetBestSize() Clone() ``` 这三个方法中最重要的是`Draw(grid, attr, dc, rect, row, col, isSelected)`。其中参数`grid`是包含相应单元格的网格实例。参数`attr`是网格的属性实例。如果你需要使用基本的绘制方法的话,参数`dc`是用于绘制的设备上下文。参数`rect`是单元格的矩形区域。参数`row,col`是单元格的坐标,如果单元格当前处于被选状态的话,参数`isSelected`为`True`。在你的绘制方法中,你可以自由地做任何你想做的事情。 方法`GetBestSize(grid, attr, dc, row, col)`返回一个`wx.Size`实例,该实例代表单元格的首先尺寸。方法`Clone()`返回一个`wx.grid.GridCellRenderer`实例。一旦描绘器被定义了,你就可以像使用预定义的描绘器一样使用它。 ### 如何编辑一个单元格? `wxPython`的网格控件允许你编辑单元格中的值。敲击一个单元格,或开始键入一个新的数据值都将打开一个默认的字符串编辑器,让你可以输入不同的字符串。在这一节,我们将讨论多种修改此默认行为的方法。 你可以使用方法`EnableEditing(enable)`来开关整个网格的可编辑性——参数`enable`是一个布尔值。如果它是`False`,那么所有的单元格都不可编辑。如果关闭了网格的可编辑性,那么你就不能再设置单个单元格的编辑状态了。如果打开了网格的可编辑性的话,单个的单元格可以被指定为只读。你可以使用方法`IsEditable()`来确定网格是否可编辑。 你可以使用方法`SetReadOnly(row, col, isReadOnly=True)`来设置一个特定单元格的编辑状态。`isReadOnly=True`代表该单元格为只读,为`False`代表单元格可编辑。`SetReadOnly()`是类`wx.grid.GridCellAttr`中的同名方法的一个简捷方式。换句话说,你可以使用`GetCellAttr(row, col).SetReadOnly(isReadOnly)`之类的来将一个单元格设置为只读。使用单元格属性机制的好处就是你可以将`SetReadOnly`与`SetRowAttr()`和`SetColAttr()`方法结合起来,以一次性的将整个行或列设置为可编辑的或只读的。 你也可以使用方法`EnableCellEditControl(enable=True)`和`DisableCellEditControl()`来处理网格的可编辑性,第二个方法等同于`EnableCellEditControl(False)`。`Enable`*方法将在当前所选择的单元格中创建并显示该单元格的编辑器。`disable`*方法则相反。如果`enable`*方法将工作于当前单元格,那么`CanEnableCellControl()`返回`true`,这就意味该网格是可编辑并且单元格没有被指定为只读。如果当前单元格的编辑器被激活了,则方法`IsCellEditControlEnabled()`返回`true`。 这里还有一些内在的可用的方法,你可以用于对编辑进行更细致的处理。你可以使用方法`ShowCellEditControl()`来触发当前单元格的编辑,并且你也可以使用方法`HideCellEditControl()`该编辑。你可以使用方法`IsCurrentCellReadOnly()`来确定当前单元格可编辑的有效性。你可以使用方法`SaveEditControlValue()`来确保在编辑器中所输入的新值被存储。当焦点从被编辑的单元格上移走时,网格控件隐式地调用该方法,当在你的程序中所做的一些事情可能会导致值被丢失时(比如关闭网格所处的窗口时),隐式地调用该方法是一个好的方式。 每个单元格都有它自己特定的编辑器对象。你可以使用方法`GetCellEditor(row, col)`来得到相关单元格的编辑器的一个引用,返回值是是类`wx.grid.GridCellEditor`的一个实例。你可以使用方法`SetCellEditor(row,  col,  editor)`来设置该编辑器,其中的`editor`参数是一个`wx.grid.GridCellEditor`。你可以使用方法`GetDefaultEditor()`和`SetDefaultEditor(editor)`来为整个网格管理默认的编辑器。正如描绘器一样,编辑器对象作为与单元格、行或列相关的`wx.grid.GridCellAttr`的一部分被存储。 ### 如何使用一个自定义的单元格编辑器? 正如描绘器一样,`wxPython`提供了几个不同类型的标准编辑器,也让你可以创建你自己的编辑器。 **预定义的编辑器** 所有的`wxPython`编辑器都是类`wx.grid.GridCellEditor`的子类。表14.5说明了这些标准的编辑器。 在接下来的部分,我们将给你展示如何创建自定义的单元格编辑器。 **表14.5** **`wxPyhton`中的单元格编辑器** | | | | --- | --- | | `wx.grid.GridCellAutoWrapStringEditor` | 使用多行文本控件来编辑数据值。 | | `wx.grid.GridCellBooleanEditor` | 用于单元格布尔值的编辑器,由一个复选框构成必,双击显示该复选框。你不必将布尔值描绘器用于一个布尔值编辑器——你可以用1或0,或者`on`/`off`此类的东西来替代布尔值描绘器来显示被选或未选状态。 | | `wx.grid.GridCellChoiceEditor` | 复合框编辑器。这个构造函数要求两个参数 | (`choices,allowOthers=False)`,其中参数`choices`是字符串的选项列表。如果`allowOthers`为`True`,那么除了下拉列表中的选项外,用户可以自己键入一个字符串。 | | `wx.grid.GridCellEnumEditor` | 继承自`wx.grid.GridCellChoiceEditor`,将数字换成等同的字符串呈现给用户。 | | `wx.grid.GridCellFloatEditor ` | 用于输入指定精度的浮点数。这个构造函数要求的参数是(`width=`-1,`precision=`-1),参数的意义与相应描绘器中的意思一样。使用这个编辑器输入的数被转换到相应的位数和精度。 | | `wx.grid.GridCellNumberEditor` | 整数编辑器。该构造函数要求的参数是(`min=`-1, `max=`-1)。如果`min`和`max`设置了,那么这个编辑器将进行范围检查,并否决试图输入范围之外的数。单元格的右边会有一个`spinner`控件,使用户可以通过鼠标来改变单元格中的值。 | | `wx.grid.GridCellTextEditor` | 默认的文本编辑器。 | **创建自定义的编辑器** 你可以想创建一个自定义的编辑器自行处理输入的数据。要创建你自己的编辑器,你要创建`wx.grid.PyGridCellEditor`的一个子类。这比描绘器复杂些。表14.6显示了几个你需要覆盖的方法。 表14.7显示了父类的更多的方法,你可以覆盖它们以改进你的自定义编辑器的外观。 **表14.6** **你必须覆盖的`PyGridCellEditor`的方法** | | | | --- | --- | | `BeginEdit(row, col, grid)` | 参数`row,col`是单元格的坐标,`grid`是包含的单元格。该方法在编辑请求之初被调用。在该方法中,编辑器用于得到数据去编辑,并为编辑做准工作。 | | `Clone()` | 返回该编辑器的一个拷贝。 | | `Create(parent, id, evtHandler)` | 创建被编辑器使用的控件。参数`parent`是容器,`id`是要创建的控件的标识符,`evtHandler`是绑定到该新控件的事件处理器。 | | `EndEdit(row, col, grid)` | 如果编辑已经改变了单元格的值,则返回`True`。任何其它的必须的清除工作都应该在这里被执行。 | | `Reset()` | 如果编辑被取消了,则该方法被调用。此时应该将控件中的值还原为初始值。 | **表14.7** **可以覆盖的`PyGridCellEditor`的方法** | | | | --- | --- | | `Destroy()` | 当编辑被销毁时,执行任何最终的清除工作。 | | `IsAcceptedKey(evt)` | 如果`evt`中的键被按下会启动编辑器,则方法返回`True`。`F2`键始终都用于启动编辑器。蕨类假设任意键的按下都将启动编辑器,除非它被修改为通过`control,alt,`或`shift`来启动。 | | `PaintBackground(rect, attr) ` | 参数`rect`是一个`wx.Rect`(使用逻辑单位),`attr`是与单元格相关的`wc.grid.GridCellAttr`。该方法的目的是绘制没有被编辑器控件所覆盖的单元格的部分。基类通过属性得到背景色并使用得到的背景色填充矩形。 | | `SetSize(rect)` | `rect`是一个该控件在屏幕上的逻辑尺度的`wx.Rect`。如果必要的话,可以使用该方法来将控件定位在该矩形内。 | | `Show(show, attr)` | 参数`show`是一个布尔值,它决定是否显示编辑器,`attr`是相关单元格的属性实例。调用该方法来显示或隐藏编辑器。 | | `StartingClick()` | 当编辑器通过在单元格上的一个鼠标敲击被启动时,该方法被调用来允许编辑器将该敲击用于自己的目的。 | | `StartingKey(evt)` | 如果编辑通过一个按键的按压被启动了,那么该方法被调用来允许编辑器控件使用该按键,如果你想的话。(例如,通过使用它作为实际编辑器的一部分)。 | 一旦你的编辑器完成了,你就可以使用`SetCellEditor`方法将它设置为任何单元格的编辑器。例14.8显示了一个自定义编辑器的示例,这个例子自动将你输入的文本转换为大写。 **例14.8** **创建自定义的大写编辑器** ``` #-*- encoding:UTF-8 -*- import wx import wx.grid import string class UpCaseCellEditor(wx.grid.PyGridCellEditor):#声明编辑器 def __init__(self): wx.grid.PyGridCellEditor.__init__(self) def Create(self, parent, id, evtHandler):#创建 """ Called to create the control, which must derive from wx.Control. *Must Override* """ self._tc = wx.TextCtrl(parent, id, "") self._tc.SetInsertionPoint(0) self.SetControl(self._tc) if evtHandler: self._tc.PushEventHandler(evtHandler) self._tc.Bind(wx.EVT_CHAR, self.OnChar) def SetSize(self, rect): """ Called to position/size the edit control within the cell rectangle. If you don't fill the cell (the rect) then be sure to override PaintBackground and do something meaningful there. """ self._tc.SetDimensions(rect.x, rect.y, rect.width+2, rect.height+2, wx.SIZE_ALLOW_MINUS_ONE) def BeginEdit(self, row, col, grid): """ Fetch the value from the table and prepare the edit control to begin editing. Set the focus to the edit control. *Must Override* """ self.startValue = grid.GetTable().GetValue(row, col) self._tc.SetValue(self.startValue) self._tc.SetInsertionPointEnd() self._tc.SetFocus() self._tc.SetSelection(0, self._tc.GetLastPosition()) def EndEdit(self, row, col, grid): """ Complete the editing of the current cell. Returns True if the value has changed. If necessary, the control may be destroyed. *Must Override* """ changed = False val = self._tc.GetValue() if val != self.startValue: changed = True grid.GetTable().SetValue(row, col, val) # update the table self.startValue = '' self._tc.SetValue('') return changed def Reset(self): """ Reset the value in the control back to its starting value. *Must Override* """ self._tc.SetValue(self.startValue) self._tc.SetInsertionPointEnd() def Clone(self): """ Create a new object which is the copy of this one *Must Override* """ return UpCaseCellEditor() def StartingKey(self, evt): """ If the editor is enabled by pressing keys on the grid, this will be called to let the editor do something about that first key if desired. """ self.OnChar(evt) if evt.GetSkipped(): self._tc.EmulateKeyPress(evt) def OnChar(self, evt): key = evt.GetKeyCode() if key 255: evt.Skip() return char = chr(key) if char in string.letters: char = char.upper() self._tc.WriteText(char)#转换为大写 else: evt.Skip() class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Grid Editor", size=(640,480)) grid = wx.grid.Grid(self) grid.CreateGrid(50,50) grid.SetDefaultEditor(UpCaseCellEditor())#设置成默认编辑器 app = wx.PySimpleApp() frame = TestFrame() frame.Show() app.MainLoop() ``` ## 捕获用户事件 网格控件有许多的用户事件。我们将把它们分为鼠标事件和键盘事件。 ### 如何捕获用户的鼠标动作? 对于网格控件,除了不同的鼠标事件类型外,对于这些类型还有一些不同的事件类。最常用的事件类是`wx.grid.GridEvent`。网格的事件类是`wx.CommandEvent`的一个子类,它提供了用于获得事件详情的几个方法,如表14.8所示。 **表14.8** **`wx.grid.GridEvent`的方法** | | | | --- | --- | | `AltDown()` | 当事件被触发时,如果`alt`键被按下了,则返回`True`。 | | `ControlDown()` | 当事件被触发时,如果`control`键被按下了,则返回`True`。 | | `GetCol()` | 返回发生事件的单元格所在的列的索引。 | | `GetPosition()` | 返回返回一个`wx.Point`。它代表事件发生点的逻辑坐标(以像素为单位)。 | | `GetRow()` | 返回发生事件的单元格所在的行的索引。 | | `MetaDown()` | 当事件被触发时,如果`met`键被按下了,则返回`True`。 | | `Selecting()` | 如果事件是一个被选事件,则返回`True`,如果事件是取消选择事件,则返回`False`。 | | `ShiftDown()` | 当事件被触发时,如果`shift`键被按下了,则返回`True`。 | 与`wx.grid.GridEvent`相关的有几个不同的事件类型。如表14.9所示。 **表14.9** **关于网格鼠标事件的单元格事件类型** | | | | --- | --- | | `wx.grid.EVT_GRID_CELL_CHANGE` | 当用户通过编辑器改变单元格中的数据时触发该事件。 | | `wx.grid.EVT_GRID_CELL_LEFT_CLICK` | 当用户在一个单元格中敲击鼠标左键时触发该事件。 | | `wx.grid.EVT_GRID_CELL_LEFT_DCLICK` | 当用户在一个单元格中双击鼠标左键时触发该事件。 | | `wx.grid.EVT_GRID_CELL_RIGHT_CLICK` | 当用户在一个单元格中敲击鼠标右键时触发该事件。 | | `wx.grid.EVT_GRID_CELL_RIGHT_DCLICK` | 当用户在一个单元格中双击鼠标右键时触发该事件。 | | `wx.grid.EVT_GRID_EDITOR_HIDDEN` | 当在编辑会话结束时隐藏一个单元格编辑器则触发该事件。 | | `wx.grid.EVT_GRID_EDITOR_SHOWN` | 当在编辑会话结束时显示一个单元格编辑器则触发该事件。 | | `wx.grid.EVT_GRID_LABEL_LEFT_CLICK` | 当用户在行或列的标签区域敲击鼠标左键时触发该事件。 | | `wx.grid.EVT_GRID_LABEL_LEFT_DCLICK` | 当用户在行或列的标签区域双击鼠标左键时触发该事件。 | | `wx.grid.EVT_GRID_LABEL_RIGHT_CLICK` | 当用户在行或列的标签区域敲击鼠标右键时触发该事件。 | | `wx.grid.EVT_GRID_LABEL_RIGHT_DCLICK` | 当用户在行或列的标签区域双击鼠标右键时触发该事件。 | | `wx.grid.EVT_GRID_Select_CELL` | 当用户将焦点移到一个新的单元格,并选择它时触发该事件。 | 有两个事件类型,它们有一个`wx.grid.GridSizeEvent`实例。这两个事件类型分别是`wx.grid.EVT_GRID_COL_SIZE`:当列大小被改变时触发,`wx.grid.EVT_GRID_ROW_SIZE`:当行的大小被改变时触发。网格的尺寸事件有5个与`wx.GridEvent`!`AltDown(), ControlDown(), GetPosition(), MetaDow(), `和`ShiftDown`相同的方法。`wx.grid.GridSizeEvent`的最后的一个方法是`GetRowOrCol()`,该方法返回发生改变的列或行的索引,当然这依赖于具体的事件类型。 事件类型`wx.grid.EVT_GRID_RANGE_Select`有一个`wx.grid.GridRangeSelectEvent`的实例,该事件当用户选择连续矩形范围内的单元格中被触发。该事件的实例拥有的方法是`GetBottomRightCoords(),  GetBottomRow(),  GetLeftCol(),  GetRightCol(),  GetTopRightCoords()`和`GetTopRow()`,它们返回被选择区域的整数索引或(`row, col)`元组。 最后,事件类型`EVT_GRID_EDITOR_CreateD`有一个`wx.grid.GridEditorCreatedEvent`实例。这个事件在当通过一个编辑会话创建了一个编辑器时被触发。该事件实例的方法有`GetCol(), GetRow(), `和`GetControl()`,它们分别返回发生事件的列,行的索引和使用的编辑控件。 ### 如何捕获用户的键盘动作? 除了使用鼠标外,用户还可以使用键盘来在网格中移动。你可以通过代码的方法来使用表14.10中的移动方法改变光标。其中的许多方法都要求一个`expandSelection`参数。每个方法中的`expandSelection`的作用都相同。如果这个参数为`True`,那么当前的选项将被扩展以容纳这个新的光标位置。如果这个参数为`False`,那么当前的选项被新的光标所取代。 **表14.10** **网格光标移动方法** | | | | --- | --- | | `MoveCursorDown(expandSelection)` | 向下移动光标。如果`expandSelection`为`False`,等同于按下"下箭头键",如果为`True`,则等同于按下"`shift`-下箭头键"。 | | `MoveCursorDownBlock(expandSelection)` | 向下移动光标。如果`expandSelection`为`False`,则等同于"`ctrl`-下箭头键",如果为`True`,则等同于"`shift`-`ctrl`-下箭头键"。 | | `MoveCursorLeft(expandSelection)` | 向左移动光标。如果`expandSelection`为`False`,等同于按下"左箭头键",如果为`True`,则等同于按下"`shift`-左箭头键"。 | | `MoveCursorLeftBlock(expandSelection)` | 向左移动光标。如果`expandSelection`为`False`,则等同于"`ctrl`-左箭头键",如果为`True`,则等同于"`shift`-`ctrl`-左箭头键"。 | | `MoveCursorRight(expandSelection)` | 向右移动光标。如果`expandSelection`为`False`,等同于按下"右箭头键",如果为`True`,则等同于按下"`shift`-右箭头键"。 | | `MoveCursorRightBlock(expandSelection)` | 向右移动光标。如果`expandSelection`为`False`,则等同于"`ctrl`-右箭头键",如果为`True`,则等同于"`shift`-`ctrl`-右箭头键"。 | | `MoveCursorUp(expandSelection)` | 向上移动光标。如果`expandSelection`为`False`,等同于按下"上箭头键",如果为`True`,则等同于按下"`shift`-上箭头键"。 | | `MoveCursorUpBlock(expandSelection)` | 向上移动光标。如果`expandSelection`为`False`,则等同于"`ctrl`-上箭头键",如果为`True`,则等同于"`shift`-`ctrl`-上箭头键"。 | | `MovePageDown()` | 显示下一页的单元格。 | | `MovePageUp()` | 显示上一页的单元格。 | 我们已经涵盖了所有你需要了解的有关单元格的知识。在下一章中,我们将讨论树形控件。 ## 本章小结 1、网格控件使你能够创建像电子表格一样的网格表,并具有很大的可控性和灵活性。网格控件是类`wx.grid.Grid`的一个实例。通常,如果使用网格控件处理复杂的问题的话,你应该通过`__init__`方法来定义它的子类,这是值得的,而非仅仅创建基类的一个实例并在程序的其它地方调用它的方法。 2、有两种方法用来将数据放入一个网格控件中。网格控件可以使用`CreateGrid(numRows, numCols)`方法被显式创建,然后使用`SetCellValue(row, col, s)`方法来设置单个的单元格。另一种是,你可以创建一个网格表的实例,该网格表作为网格的一个模型,它使你可以很容易地使用另一数据源的数据并显示在网格中。网格表是`wx.grid.PyGridTableBase`的子类,`wx.grid.PyGridTableBase`的方法中,`GetValue(row, col)`可以被覆盖以在显示一个单元格时驱动网格的行为。网格表被连接到网格控件使用方法`SetTable(table)`。当使用网格表的方法创建了网格后,可以通过网格表的方法来改变网格的行和列数。 3、网格也有行和列标签,标签有默认的值,类似于电子表格。标签所显示的文本和标签的其它显示属性可以使用网格的方法来改变。每个项的行和列的尺寸可以被显式了设置,或者网格可以根据所显示的自动调整尺寸。用户也可通过拖动网格线来改变网格的尺寸。如果需要的话,你可以为每行或每列设置一个最小的尺寸,以防止单元格变得太小而不能显示相应的数据。另外,特定的单元格了能使用`SetCellSize(row, col, numrows, numcols)`方法来达到跨行或列的目的。 4、用户可以选择网格中的一个或多个矩形范围的网格,这也可以通过使用很多不同的`select`*方法以程序化的方式实现相同的效果。一个没有在显示区域中的网格单元,可能使用`MakeCellVisible(row, col)`方法来将它移到显示区域上。 5、网格控件的强大性和灵活性来源于可以为每个单元格创建自定义的描绘器和编辑器这一能力。描绘器用于控件单元格中的信息显示。默认的描绘器只是一个简单的字符串,但是还有用于布尔值、整数和浮点数的预先定义好(预定义)的描绘器。你可以通过子类化`wx.Grid.PyGridCellRenderer`创建你自己的描绘器并覆盖它的绘制方法。 6、默认情况下,网格允许就地编辑数据。你可以改变这个属性(针对单元格,或行或列,或整个网格)。当编辑时,编辑器对象控制显示给用户的东西。默认的编辑器是一个用以修改字符串的普通的文本编辑器控件。其它还有用于布尔值、整数和浮点数的预定义的编辑器。你可以通过子类化`wx.grid.GridCellEditor`并覆盖它的几个方法来创建自己的自定义的编辑器。 7、网格控件有许多你能捕获的不同的事件,分别包括单元格中的鼠标敲击和标签中的鼠标敲击事件,以及通过改变一个单元格的尺寸而触发的事件。另外,你能够以编程的方式在网格中移动光标。