wxPython - 高級控制項之表格Grid

愛好史地的coder 發佈 2024-04-28T17:45:27.712201+00:00

wx.grid.Grid是一個功能強大的但是又稍微有一些複雜的窗口類,它用來顯示表格類型的數據。前一篇:wxPython - 高級控制項之選項卡Notebook。

實戰wxPython系列-043

wx.grid.Grid及其相關類用於顯示和編輯表格數據。它們提供了一組豐富的功能,用於顯示、編輯和與各種數據源交互。

wx.grid.Grid是一個功能強大的但是又稍微有一些複雜的窗口類,它用來顯示表格類型的數據。可以使用wx.Grid來作為一個包含名稱和值兩欄的屬性編輯器。或者是通過代碼使其作為一個一般意義上的表格,用來顯示一個資料庫或者是應用程式產生的特定統計數據。

一、wx.grid相關類

對於簡單的應用程式,我們只需要簡單地使用wx.grid.Grid類,它將設置其他相關類的默認實例並自動管理它們。對於更複雜的應用,我們可以為自定義網格視圖、網格數據表、單元格編輯器和渲染器(Renderer)派生自己的類。wx.grid提供一組類來實現表格應用。

wx.grid.Grid:主網格控制項類本身。

wx.grid.GridTableBase:類控制要顯示的實際數據。

wx.grid.GridStringTable:簡單的GridTableBase實現,只支持字符串數據項,並將它們全部存儲在內存中(因此只適用於不太大的網格)。

wx.grid.GridCellAttr:保存用於渲染表格的屬性數據。可以顯式的通過類似SetCellTextColour這樣的函數更改表格的屬性。也可以通過SetAttr函數設置某個單獨的表格的屬性或者是通過SetRowAttr和SetColAttr函數設置某一列或者某一行的屬性。還可以在定義的表格類中通過GetAttr函數返回指定表格的屬性。

wx.grid.GridCellAttrProvider:負責存儲和檢索單元格屬性的對象。

wx.grid.GridColLabelWindow:顯示網格列標籤的窗口。

wx.grid.GridRowLabelWindow:顯示網格行標籤的窗口。

wx.grid.GridCornerLabelWindow:在左上角網格角中使用的窗口。

wx.grid.GridWindow:表示網格的主要部分窗口。

wx.grid.GridCellRenderer是用於在單元格中呈現內容的抽象基類,負責對單元格進行繪畫。wxPython默認提供了幾種派生類來實現具體的繪製。

  • wx.grid.GridCellBoolRenderer:顯示單元格為選中或未選中的框。
  • wx.grid.GridCellFloatRenderer:格式化輸出單元格中的浮點數數據。
  • wx.grid.GridCellNumberRenderer:格式化輸出單元格中的整數數據。
  • wx.grid.GridCellStringRenderer:格式化輸出單元格中的字符串數據。
  • wx.grid.GridCellDateRenderer:格式化輸出日期。
  • wx.grid.GridCellDateTimeRenderer:格式化輸出日期時間。

wx.grid.GridCellEditor是用於編輯單元格值的抽象基類。負責實現對表格數據的即時編輯功能的控制項。wxPytho 默認提供了幾種派生類來實現具體的即時編輯功能。

  • wx.grid.GridCellBoolEditor:布爾值單元格的編輯器。
  • wx.grid.GridCellChoiceEditor:允許選擇一個預定義的字符串(也可能輸入一個新的字符串)的編輯器。
  • wx.grid.GridCellFloatEditor:用於浮點數的單元格的編輯器。
  • wx.grid.GridCellNumberEditor:用於整數的單元格的編輯器。
  • wx.grid.GridCellTextEditor:用於文本的單元格的編輯器。
  • wx.grid.GridCellDateEditor:用於日期的單元格的編輯器。

wx.GridEvent這個類包含了各種網格相關事件的信息,比如滑鼠在表格上單擊事件,表格數據改變事件,表格被選中事件,表格編輯器被顯示或者隱藏事件等。

二、wx.grid.Grid成員函數

wx.grid.Grid的成員函數較多,下面按相關功能列舉了一些常用的成員函數。

用於創建,刪除和數據交互的函數

  • AppendCols(self, numCols=1, updateLabels=True):將一個或多個新列追加到網格的右側。
  • AppendRows(self, numRows=1, updateLabels=True):將一個或多個新行追加到網格的底部。
  • InsertRows(self, pos=0, numRows=1, updateLabels=True):在網格指定位置插入一列或者多列。
  • InsertCols(self, pos=0, numCols=1, updateLabels=True):在網格指定位置插入一行或者多行。
  • GetNumberCols(self):返回網格的總列數。
  • GetNumberRows(self):返回網格的總行數。
  • CreateGrid(self, numRows, numCols, selmode=GridSelectCells):創建具有指定初始行數和列數的網格。直接在網格構造函數之後調用它。當您使用這個函數時,wx.grid.Grid將為您創建和管理一個簡單的字符串值表。所有網格數據都將存儲在內存中。
  • ClearGrid(self):清除所有的網格綁定表格中的數據並且刷新網格的顯示。表格本身並不會被釋放
  • DeleteCols(self, pos=0, numCols=1, updateLabels=True):從指定位置開始從網格中刪除一列或多列。
  • DeleteRows(self, pos=0, numRows=1, updateLabels=True):從指定位置開始從網格中刪除一行或多行。
  • GetColLabelValue(self, col):返回指定的列標籤。默認的網格表類提供了A,B…Z,AA,AB…ZZ,AAA…這種形式的列標籤。如果使用的是自定義網格表,則可以重寫wx.grid.GridTableBase.GetColLabelValue來提供自己的標籤。
  • GetCellValue (self, row, col):返回單元格中包含的指定位置的字符串。

界面相關函數:

  • BeginBatch(self):增加網格的批處理計數。當計數大於零時,網格的重繪將被抑制。每個對BeginBatch的調用都必須與後面對EndBatch的調用相匹配。做大量網格修改的代碼可以被封裝在BeginBatch和EndBatch調用之間,以避免屏幕閃爍。最後的EndBatch調用將導致網格被重新繪製。
  • EndBatch(self):減少網格的批處理計數。當計數大於零時,網格的重繪將被抑制。每個之前對BeginBatch的調用都必須與後面對EndBatch的調用相匹配。做大量網格修改的代碼可以被封裝在BeginBatch和EndBatch調用之間,以避免屏幕閃爍。最後的EndBatch將導致網格被重新繪製。
  • GetBatchCount(self):返回BeginBatch被調用的次數。網格的批處理計數等零時,網格將刷新顯示。
  • EnableGridLines(self, enable=True):打開或關閉網格線的繪製。
  • GridLinesEnabled(self):如果打開繪製網格線,則返回True,否則返回False。
  • ForceRefresh(self):強制立即重新繪製網格。
  • Fit(self):使網格控制項將自己的大小更改為當前行數和列數所要求的最小大小。
  • GetCellAlignment(self, row, col):返回指定單元格在垂直和水平方向上的對齊方式。
  • GetColLabelAlignment(self):返回列標籤的對齊方式。
  • GetRowLabelAlignment(self):返回行標籤的對齊方式。
  • GetDefaultCellAlignment(self):返回默認單元格的對齊方式。
  • GetCellBackgroundColour(self, row, col):返回指定單元格背景顏色。
  • GetDefaultCellBackgroundColour(self):返回單元格的當前默認背景色。
  • GetLabelBackgroundColour(self):返回行標籤和列標籤的背景顏色。
  • GetCellFont(self, row, col):返回指定單元格的字體。
  • GetDefaultCellFont(self):返回單元格文本的當前默認字體。
  • GetLabelFont(self):返回用於行和列標籤的字體。
  • GetCellTextColour(self, row, col):返回指定單元格的文本顏色。
  • GetDefaultCellFont(self):返回單元格文本的當前默認字體。
  • GetLabelTextColour(self):返回用於行和列標籤文本的顏色。
  • GetGridLineColour(self):返回網格線使用的顏色。

註:以上Get方法都有相應的Set方法。

  • SetColAttr(self, col, attr):為指定列中的所有單元格設置單元格屬性。
  • SetRowAttr(self, row, attr):為指定行中的所有單元格設置單元格屬性。

尺寸相關函數

  • AutoSize(self):自動設置所有行和列的高度和寬度以適應其內容。
  • AutoSizeColumn(self, col, setAsMin=True):自動調整列的大小以適應其內容。
  • AutoSizeColumns(self, setAsMin=True):自動調整所有列的大小以適應其內容。
  • AutoSizeRow(self, row, setAsMin=True):自動調整行的大小以適應其內容。
  • AutoSizeRows(self, setAsMin=True):自動調整所有行的大小以適應其內容。
  • CellTorect (self, row, col):返回與網格單元格的大小和邏輯坐標位置對應的矩形。
  • GetColMinimalWidth(self, col):獲取給定列的最小寬度。
  • GetRowMinimalHeight(self, row) :獲取給定行的最小高度。
  • GetColLabelSize(self):返回列標籤的當前高度。
  • GetDefaultColLabelSize(self):返回列標籤的默認高度。
  • GetDefaultColSize(self):返回網格列的當前默認寬度。
  • GetColSize(self, col):返回指定列的寬度。
  • GetRowLabelSize(self):返回行標籤的當前寬度。
  • GetDefaultRowLabelSize(self):返回行標籤的默認寬度。
  • GetDefaultRowSize(self):返回網格行的當前默認高度。
  • GetRowSize(self, row):返回指定行的高度。

註:以上Get方法都有相應的Set方法。

選擇和光標函數:

  • GetGridCursorCol(self):返回當前單元格列的位置。
  • GetGridCursorRow(self):返回當前單元格行的位置。
  • MoveCursorDown, MoveCursorLeft, MoveCursorRightMoveCursorUp:以每次一格的方式移動光標。
  • MoveCursorDownBlock, MoveCursorLeftBlock, MoveCursorRightBlockMoveCursorUpBlock:移動的時候跳到第一個非空單元格。
  • MovePageDownMovePageUp:一次移動一頁,頁大小由網格窗口的大小決定。
  • GetSelectionMode(self):返回當前選擇模式。
  • GetSelectedCells(self):返回由單獨選擇的單元格組成的數組。
  • GetSelectedCols(self):返回所選列的數組。請注意,這個方法本身並不足以找到所有被選中的列,因為它只包含被單獨選中的列。
  • GetSelectedRows(self):返回選定行的數組。請注意,這個方法本身並不足以找到所有被選中的行,因為它只包含被單獨選中的行。
  • GetSelectionBlockTopLeft(self):返回所選單元格塊的左上角數組。
  • GetSelectionBlockBottomRight(self):返回所選單元格塊的右下角的數組。
  • SelectAll(self):選擇網格中的所有單元格。

其他函數:

  • GetTable(self):返回網格用於保存內部數據的綁定表格對象。
  • GetCellEditor(self, row, col):返回一個指向指定位置單元格的編輯器指針。
  • GetDefaultEditor(self):返回指向當前默認網格單元格編輯器的指針。
  • GetCellRenderer(self, row, col):返回指向指定位置網格單元格的渲染器的指針。
  • GetDefaultRenderer(self):返回指向當前默認網格單元格渲染器的指針。

註:以上Get方法都有相應的Set方法。

  • ShowCellEditControl(self):顯示當前單元格被隱藏的編輯器。
  • HideCellEditControl(self):隱藏當前單元格被隱藏的編輯器。
  • SetReadOnly(self, row, col, isReadOnly=True):將指定單元格設置為可讀或者可編輯。

三、wx.grid.Grid演示

#網格控制項(wx.grid)

import wx
import wx.grid

#grid column type
class GridColumnControlKind:
    Text = "Text"
    CheckBox = "CheckBox"
    Colour = "Colour"

class GridCellColorEditor(wx.grid.GridCellEditor):
    def Create(self, parent, id, evtHandler):
        """
        創建一個控制項, 該控制項必須繼承自wx.Control
        *必須重載*
        """

        self.__parent = parent
        self.__dlgColor = None
        self.__btnColor = wx.Button(parent, id, "")
        self.SetControl(self.__btnColor)

        #添加新的事件句柄,防止窗口彈出後,單元格自動調用編輯器
        newEventHandler = wx._core.EvtHandler()
        if evtHandler:
            self.__btnColor.PushEventHandler(newEventHandler)
        self.__btnColor.Bind(wx.EVT_BUTTON, self.OnClick)

    def OnClick(self, e):
        self.__btnColor.SetFocus()
        self.ShowColorDialog()

    def SetSize(self, rect):
        """
        用於在單元格矩形中定位/調整編輯控制項的大小。
        如果沒有填充單元格(矩形),那麼一定要重寫
        PaintBackground做一些必要的事情。
        """
        self.__btnColor.SetDimensions(rect.x, rect.y, rect.width + 2, rect.height + 2, wx.SIZE_ALLOW_MINUS_ONE)

    def Clone(self):
        """
        創建一個新對象,它是這個對象的副本
        *必須重載*
        """
        return GridCellColorEditor()

    def BeginEdit(self, row, col, grid):
        """
        從表中獲取值並準備編輯控制項開始編輯。將焦點設置為編輯控制項。
        *必須重載*
        """
        self.startValue = grid.GetTable().GetValue(row, col)
        self.endValue = self.startValue
        self.__btnColor.SetBackgroundColour(self.startValue)

    def EndEdit(self, row, col, grid):
        """
        完成當前單元格的編輯。如果已發生改變,則返回True
        如有必要,可以銷毀控制項。
        *必須重載*
        """
        changed = False
        if self.endValue != self.startValue:
            changed = True
            grid.GetTable().SetValue(row, col, self.endValue) # 更新該表
            self.startValue = ""
        return changed

    def ShowColorDialog(self):
        colorDlg = wx.ColourDialog(self.__parent)
        self.__dlgColor = colorDlg
        colorDlg.GetColourData().SetColour(self.startValue)
        if wx.ID_OK == colorDlg.ShowModal():
            data = colorDlg.GetColourData()
            colour = data.GetColour()
            self.__btnColor.SetBackgroundColour(colour)
            self.endValue = colour

        del self.__dlgColor
        self.__dlgColor = None

class GridCellColorRender(wx.grid.GridCellRenderer):
    def __init__(self):
        wx.grid.GridCellRenderer.__init__(self)

    def Draw(self, grid, attr, dc, rect, row, col, isSelected):
        color = grid.GetTable().GetValue(row, col)
        dc.SetBrush(wx.Brush(color, wx.BRUSHSTYLE_SOLID))
        dc.SetPen(wx.TRANSPARENT_PEN)
        dc.DrawRectangle(rect)
        dc.SetBackgroundMode(wx.BRUSHSTYLE_TRANSPARENT)

    def GetBestSize(self, grid, attr, dc, row, col):
        return wx.Size(-1, -1)

    def Clone(self):
        return GridCellColorRender()

#根據具體業務邏輯定製grid的 table
class CustomGridTable(wx.grid.GridTableBase):
    def __init__(self):
        wx.grid.GridTableBase.__init__(self)

        #添加表的列的頭
        self.colLabels = ["名字", "可見", "最小門限", "最大門限", "顏色"]
        #指定一個列的控制類型
        self.colControlKinds = [GridColumnControlKind.Text, 
                                GridColumnControlKind.CheckBox, 
                                GridColumnControlKind.Text,
                                GridColumnControlKind.Text,
                                GridColumnControlKind.Colour]
        self.colControlEditorEnableStatus = [True, True, False, False, True]
        self.rowLabels = ["", "", "", "", ""]

        #添加數據源
        self.data = [ 
            ['Mask 1', 1, "2.5","320.6",(200,20,100)]
            ,['Mask 2', 1, "2.5","320.6",(50,0,200)]
        ]

    def GetNumberRows(self):
        return len(self.data)

    def GetNumberCols(self):
        return len(self.colLabels)

    def IsEmptyCell(self, row, col):
        return False

    def GetValue(self, row, col):
        return self.data[row][col]

    def SetValue(self, row, col, value):
        self.data[row][col] = value

    def GetColLabelValue(self, col):
        return self.colLabels[col]

    def GetRowLabelValue(self, row):
        return self.rowLabels[row]

    def InsertRow(self, index, row):
        if len(self.data) < index:
            return

        self.data.insert(index, row)
        print(self.data)
        self.GetView().BeginBatch()

        msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_INSERTED, index, 1)
        self.GetView().ProcessTableMessage(msg)

        # ... same thing for columns ....

        self.GetView().EndBatch()
        msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
        self.GetView().ProcessTableMessage(msg)

    def DeleteRow(self, row):
        rowIndex = self.data.index(row)
        if rowIndex < 0:
            return
        
        self.data.remove(row)

        self.GetView().BeginBatch()

        msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, rowIndex, 1)
        self.GetView().ProcessTableMessage(msg)

        # ... same thing for columns ....
        self.GetView().EndBatch()
        msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
        self.GetView().ProcessTableMessage(msg)

    def Clear(self):
        self.GetView().BeginBatch()

        msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, 0, self.GetNumberRows())
        self.GetView().ProcessTableMessage(msg)

        # ... same thing for columns ....
        self.GetView().EndBatch()
        self.data = []
        msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
        self.GetView().ProcessTableMessage(msg)
        

    def AppendRow(self, row):
        self.data.append(row)

        self.GetView().BeginBatch()
        msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED)
        self.GetView().ProcessTableMessage(msg)

        # ... same thing for columns ....
        self.GetView().EndBatch()
        msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
        self.GetView().ProcessTableMessage(msg)

#對grid的功能進行封裝 以方便處理
class CustomGrid(wx.grid.Grid):
    def __init__(self, parent, id, rowLabelSize = 0, customGridTable = None):
        wx.grid.Grid.__init__(self, parent, id)

        self.rowLabelSize = rowLabelSize
        self.__customTableSource = customGridTable
        self.SetTable(self.__customTableSource, True)

        self.__InitStyle()

        #設置column 對應的 editor
        self.__InitColumnsEditor()

        # self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK,self.__OnMouse)
        self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.__OnCellSelected)
        self.Bind(wx.grid.EVT_GRID_EDITOR_CREATED, self.__OnEditorCreated)

    def __InitStyle(self):
        #設置選中後的背景色
        self.SetSelectionBackground(wx.Colour(237, 145, 33))

    def __InitColumnsEditor(self):
        index = -1
        for columnKind in self.__customTableSource.colControlKinds:
            index += 1
            if columnKind == GridColumnControlKind.CheckBox:
                self.__InitCheckBoxColumnEditor(index)
            elif columnKind == GridColumnControlKind.Colour:
                self.__InitColorColumnEditor(index)

    def __InitCheckBoxColumnEditor(self, columnIndex):
        attr = wx.grid.GridCellAttr()
        attr.SetEditor(wx.grid.GridCellBoolEditor())
        attr.SetRenderer(wx.grid.GridCellBoolRenderer())
        self.SetColAttr(columnIndex, attr)

    def __InitColorColumnEditor(self, columnIndex):
        attr = wx.grid.GridCellAttr()
        attr.SetEditor(GridCellColorEditor())
        attr.SetRenderer(GridCellColorRender())
        self.SetColAttr(columnIndex, attr)

    def __OnCellSelected(self, e):
        if self.__customTableSource.colControlEditorEnableStatus[e.Col]:
            wx.CallAfter(self.EnableCellEditControl)
            e.Skip()

        #設置行為選中狀態 
        self.SelectRow(e.Row)

    def __OnEditorCreated(self, event):
        pass

    def ForceRefresh(self):
        wx.grid.Grid.ForceRefresh(self)

class SampleGrid(wx.Frame):

    def __init__(self, *args, **kw):
        super(SampleGrid, self).__init__(*args, **kw)

        self.InitUi()

    def InitUi(self):
        self.SetTitle("實戰wxPython: Gird演示")
        self.SetSize(500, 300)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        addButton = wx.Button(self, -1, "添加")
        deleteButton = wx.Button(self, -1, "刪除")
        clearButton = wx.Button(self, -1, "清理")
        sizer.Add(addButton, 0, wx.SHAPED)
        sizer.Add(deleteButton, 0, wx.SHAPED)
        sizer.Add(clearButton, 0, wx.SHAPED)

        table = CustomGridTable()
        grid = CustomGrid(self, id = -1, customGridTable = table)
        self.__grid = grid

        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(sizer)
        mainSizer.Add(grid, 1, wx.EXPAND)
        self.SetSizerAndFit(mainSizer)

        addButton.Bind(wx.EVT_BUTTON, self.OnAddClick)
        deleteButton.Bind(wx.EVT_BUTTON, self.OnDeleteClick)
        clearButton.Bind(wx.EVT_BUTTON, self.OnClearClick)

        self.Centre()

    def OnClearClick(self, e):
        table  = self.__grid.GetTable()
        table.Clear()
        print(self.__grid.GetTable().data)

    def OnDeleteClick(self, e):
        table  = self.__grid.GetTable()
        firstRow = table.data[0]
        table.DeleteRow(firstRow)
        print(self.__grid.GetTable().data)

    def OnAddClick(self, e):
        table  = self.__grid.GetTable()
        table.InsertRow(0, ['insert index ', 1, "2.5","110.6",(50,200,30)])
        print(self.__grid.GetTable().data)

def main():
    app = wx.App()
    sample = SampleGrid(None)
    sample.Show()
    app.MainLoop()

if __name__ == "__main__":
    main()

上面的代碼演示了如何從基類實現自定義的單元編輯器和渲染器,是一個使用wx.grid比較完整的例子,可以嘗試修改其中的代碼,觀察如何操控wx.grid.Grid。

四、本文知識點

  • 了解wx.gird相關類。
  • 掌握如何使用wx.grid.Grid網格控制項。

前一篇:wxPython - 高級控制項之選項卡Notebook

關鍵字: