# 第十四章 網格控件
**本章內容包括:**
* 創建網格(`grid)`
* 添加行和單元格(`cell),`并且處理列的首部
* 使用一個自定義的單元格(`cell)`描繪器(`renderer)`
* 創建自定義的編輯器
* 捕獲用戶事件
網格控件大概是`wxPython`中最復雜和最靈活的一個窗口部件。在這一章,你將有機會接觸到這個控件的許多特性。我們將討論如何輸入數據到網格控件以及如何處理該控件的顯示屬性,并且我們還將討論自定義編輯器和描繪器。網格控件使你能夠在一個類似電子表格格式的網格中顯示表格數據。該控件允許你為行和列指定標簽,以及通過拖動網格線來改變網格的大小,并且可以為每個單元格單獨指定字體和顏色屬性。
在最常見的情況下,你一般會顯示一個簡單的字符串值。然而,你也能為任一單元格指定一個自定義的描繪器,以使你能夠顯示不同的數據;你可以有編輯表中的單元格,并且對不同的數據使用不同類型的編輯器。你還能夠創建你自己自定義的描繪器和編輯器,這使得你在單元格數據的顯示和處理上可以非常的靈活,幾乎沒有什么限制。網格控件還有大量的鼠標和鍵盤事件,你可以程序中捕獲它們并用來觸發相關的代碼。
我們將通過展示兩個創建`wxPython`網格的方法來作為我們討論的開始。
## 創建你的網格
網格控件是用以顯示一個二維的數據集的。要使用該控件顯示有用的信息,你需要告訴該控件它工作所基于的是什么數據。在`wxPython`中,有兩種不同的機制用于在網格控件中處理數據,它們之間在處理數據的添加,刪除和編輯的方式上有些許的不同。
* 網格控件可以直接處理每行和每列中的值。
* 數據可以通過使用一個網格表(`grid?table)`來間接地處理。
較簡單的一種是使用網格控件直接處理值。在這種情況下,網格維護著數據的一份拷貝。在這種情況下,如果有大量的數據或你的應用程序已經有了一個現存的網格類的數據結構,那么這可能顯得比較笨拙。如果是這樣,你可以使用一個網格表來處理該網格的數據。參見第5章來回顧一下在`MVC`架構中,網格表是如何被作為一個模型的。
### 如何創建一個簡單的網格?
盡管網格控件有大量的方法用于控件精確的顯示和數據的管理,但時開始使用一個網格控件是十分簡單的。圖14.1顯示了一個簡單的網格,其中的單元格中添加了一些字符串數據。
**圖14.1**

網格控件是類`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**

要使用一個網格表,你需要要創建`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**

例子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**

**例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**

**例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**

例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、網格控件有許多你能捕獲的不同的事件,分別包括單元格中的鼠標敲擊和標簽中的鼠標敲擊事件,以及通過改變一個單元格的尺寸而觸發的事件。另外,你能夠以編程的方式在網格中移動光標。
- 活學活用wxPython
- 前言
- 致謝
- 關于本書
- 第一部分
- 第一章 歡迎使用wxPython
- 第二章 給wxPython程序一個堅實的基礎
- 第三章 在事件驅動環境中開發
- 第四章 用PyCrust使得wxPython更易處理
- 第五章 繪制藍圖
- 第六章 使用wxPython基本構件
- 第二部分 基礎wxPython
- 第七章 使用基礎控件
- 第八章 將構件放入窗體中
- 第九章 通過對話框讓用戶選擇
- 第十章 創建和使用wxPython菜單
- 第十一章 使用sizer放置構件
- 第十二章 操作基本圖像
- 第三部分 高級wxPython
- 第十三章 建造列表控件并管理列表項
- 第十四章 網格控件
- 第十五章 樹形控件
- 第十六章 在應用程序中加入HTML
- 第十七章 wxPython的打印構架
- 第十八章 使用wxPython的其他功能