wxPython Grid 表格控件的使用

因工作原因使用了一下 wxPython。总的来说不推荐。我的观点是干什么事情,要用那个领域最成熟的库,比如桌面软件,用 C#、Qt。不流行的的 wx 有 bug,参考文档少。下面贴出我的代码。这个代码展示了 Grid 的使用,可以“增删改”,按列排序,隐藏某一列的显示(就像在 Excel 里为了看东西方便)。界面设计可以先用 wxFormBuilder 设计好,把代码 copy 过来。有些问题不好解决,我给一些还在探索中的人一点帮助。整个代码加上数据库是能运行的,数据库结构从代码中也能看出来。

import wx
import wx.xrc
import wx.grid
import pymysql,re,time

connection = pymysql.connect(host='localhost',
                                    port=3306,
                                    user='root',
                                    password='password',
                                    db='ZuHaoManager',
                                    charset='utf8'
                                    )   
        # 获取游标
cursor = connection.cursor() 

class mData_Grid(wx.grid.Grid):
    def __init__(self, parent, dbTable, GridLabelToDBLabel):
    
        self.ColsNums = len(GridLabelToDBLabel)
        wx.grid.Grid.__init__(self, parent, id=wx.ID_ANY, pos=wx.Point(-1,-1), size=(82*(self.ColsNums+1),600), style=wx.WANTS_CHARS)      
        # 这一个类的存在会导致最后类计数器大于0,有未释放资源,原因不明
                                    
        self.dbTable=dbTable
        self.GridLabelToDBLabel = GridLabelToDBLabel
        self.currentlySelectedCell = (0, 0)
        
        # 执行查询 SQL
        cursor.execute('SELECT * FROM %s'% dbTable)      
        self.data = cursor.fetchall()
        # cursor.fetchone()获取单条数据,返回值是元组。cursor.fetchmany(3)获取多条数据。
        
        # Grid
        self.RowsNum = len(self.data)
        self.CreateGrid( self.RowsNum, self.ColsNums )  
        self.ShowSerial=[]
        for i in range(self.RowsNum):
            self.ShowSerial.append([i,"str"])    # 在页面中的显示顺序,排序的时候用。i表示行号,"str"字符串占位
        self.ShowCols = []
        for i in range(self.ColsNums):
            self.ShowCols.append(i)        # 表示数据表中第i列显示于表格中的第几列,负数表示不显示。
        self.EnableEditing( True )
        self.EnableGridLines( True )
        self.EnableDragGridSize( False )
        self.SetMargins( 0, 0 )
        
        # Columns
        self.EnableDragColMove( False )
        self.EnableDragColSize( True )
        self.SetColLabelSize( 30 )
        self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.OnLabelLeftClick)

        # 设置列名称
        for i,m_tuple in enumerate(GridLabelToDBLabel):
            self.SetColLabelValue( i, m_tuple[0])
        
        self.SetColLabelAlignment( wx.ALIGN_CENTRE, wx.ALIGN_CENTRE )
        
        # Rows
        self.EnableDragRowSize( True )
        self.SetRowLabelSize( 80 )
        self.SetRowLabelAlignment( wx.ALIGN_CENTRE, wx.ALIGN_CENTRE )
        # 设置列背景颜色
        oddStyle=wx.grid.GridCellAttr()
        oddStyle.SetBackgroundColour(wx.Colour(12578815))
        for i in range(self.RowsNum):
            if i%2==0:
                self.SetRowAttr(i, oddStyle)
        
        # Label Appearance
        
        # Cell Defaults
        self.SetDefaultCellAlignment( wx.ALIGN_LEFT, wx.ALIGN_TOP )
        self.DisplayGrid( -100 )

        self.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.CellContentsChanged)
        self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.onSingleSelect)
        
    def DisplayGrid( self, ColNum ):    
        # ColNum 指示根据哪一列进行排序。
        """
        if self.ShowCols[0] > -1 and ColNum!=-100 :
            IDColAttrROF = wx.grid.GridCellAttr() # 设置ID列为可编辑
            IDColAttrROF.SetReadOnly(isReadOnly=False)
            # 排序完成以后,Grid的值需要重新设置,要先解除 ReadOnly 才能设置。
            # (之前程序在运行时,重新设置一行可以,第二行就不行,也很奇怪。)
            for row in range(self.RowsNum):
                self.SetAttr( row, 0, IDColAttrROF )
            # !!接触设置代码有问题!
        """
        for row,ele in enumerate(self.data):
            l_ShowRow = row
            if ColNum!=-100:
                l_ShowRow = self.ShowSerial.index([row,ele[ColNum]])     # 排序完以后这行代码就起作用了
            for col,value in enumerate(ele):
                l_ShowCol = self.ShowCols[col]   # 有些列是不显示的,>=0的要显示。
                if l_ShowCol>=0:
                    if type(value) is not str:
                        value = str(value)
                    self.SetCellValue(l_ShowRow,l_ShowCol,value)
        """
        if self.ShowCols[0] > -1:
            IDColAttrRO = wx.grid.GridCellAttr() # 设置ID列为不可编辑
            IDColAttrRO.SetReadOnly(isReadOnly=True)
            for row in range(self.RowsNum):
                self.SetAttr( row, 0, IDColAttrRO )
        """

    def CellContentsChanged(self, event):
        x = event.GetRow()
        y = event.GetCol()
        val = self.GetCellValue(x, y)
        if val == "":
            val = "null"
        inDataCol = self.ShowCols.index(y)
        ColLabel = self.GridLabelToDBLabel[inDataCol][1]
        AccountID_new = self.data[x,0]
        InsertCell = "UPDATE %s SET %s = \"%s\" WHERE AccountID = \"%s\";"%(self.dbTable, ColLabel ,val, AccountID_new)
        cursor.execute(InsertCell) 
        connection.commit() # do commit here to ensure data persistence
        # also, retrieve formatting for variable and format the output
        # self.SetCellValue(x, y, val)
        event.Skip()
        
    def OnLabelLeftClick(self, event):
        # print ("OnLabelLeftClick: (%d,%d) %s\n" % (event.GetRow(),
                                                  # event.GetCol(),
                                                  # event.GetPosition()))   # 输出  (-1,1) (196, 9)  or OnLabelLeftClick: (0,-1) (51, 45)
                      
        ColNum = event.GetCol()
        if event.GetRow()==-1 and self.GridLabelToDBLabel[ColNum][2]==1 and ColNum!=-1:
            for i in range(self.RowsNum):
                self.ShowSerial[i][1] = self.GetCellValue(i,ColNum)   # 把表格数据填充进来
            self.ShowSerial.sort(key=lambda x:x[1])
            self.DisplayGrid( ColNum )
            self.ForceRefresh()
        event.Skip()
        
    def onSingleSelect(self, event):        
        self.currentlySelectedCell = (event.GetRow(),
                                      event.GetCol())
        event.Skip()

# Define the tab content as classes:
class TabOne(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent) 
        #以下元组:第三位是是否支持排序。第四位是在“添加”的弹出对话框中如何显示:“-2”不显示,“-1”文本框,其他自然数是选项在字典中的索引号。
        self.GridLabelToDBLabel=[("账号ID","AccountID",1, -2),      
                        ("手机号","PhoneNum",1, -1),
                        ("名字","Name",1, -1),
                        ("话费","Fee",1, -1),
                        ("位置","Location",1, -1),
                        ("账号","Account",1, -1),
                        ("密码","Password",0, -1),
                        ("什么数量","PropertyNum",1, -1),
                        ("什么数量","CouponNum",1, -1),
                        ("什么时间","Mature",1, -1),
                        ("什么和","PaySum",1, -1),
                        ("什么","Remark",0, -1), 
                        ("什么什么","State",1, 0)]   # 此处不能用字典,因为字典没有顺序
        self.ChoiceList = [[ u"空闲", u"出租中", u"次日可用", wx.EmptyString ]]
        self.m_grid = mData_Grid( self, "AccountT", self.GridLabelToDBLabel )           

class TabTwo(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        #以下元组:第三位是是否支持排序。第四位是在“添加”的弹出对话框中如何显示:“-2”不显示,“-1”文本框,其他自然数是选项在字典中的索引号。
        self.GridLabelToDBLabel=[("用户ID","UserID",1, -2),      
                        ("用户名","Name",1, -1),
                        ("一个字段","VIPGrade",1, 0),
                        ("一个字段","CreditGrade",1, 1),
                        ("一个字段","RegisterDate",1, -1),
                        ("一个字段","State",1, 2),
                        ("一个字段","LastDays",1, -1),
                        ("一个字段","RentNum",1, -1),
                        ("一个字段","Remark ",0, -1)]   # 此处不能用字典,因为字典没有顺序
        self.ChoiceList = [[ u"普通", u"VIP", u"超级VIP", wx.EmptyString ],
                            [ u"优", u"良", u"中", u"差", wx.EmptyString ],
                            [ u"在用", u"封号", wx.EmptyString ]]
        self.m_grid = mData_Grid( self, "UserT", self.GridLabelToDBLabel )     

class TabThree(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        #以下元组:第三位是是否支持排序。第四位是在“添加”的弹出对话框中如何显示:“-2”不显示,“-1”文本框,其他自然数是选项在字典中的索引号。
        self.GridLabelToDBLabel=[("流水号","LSH",1, -2),   
                        ("账号ID","AccountID",1, -1),
                        ("用户ID","UserID",1, -1),
                        ("用户名","name",1, -1),
                        ("一个字段","RentDate",1, -1),
                        ("一个字段","RentPropertyNum",0, -1),
                        ("一个字段数","RentCouponNum",0, -1),
                        ("一个字段","RentDeposit",0, -1),
                        ("一个字段","Return_ChangedPwd",1, -1),
                        ("一个字段","Return_NewPwd",0, -1),
                        ("一个字段","ReturnDate",1, -1),
                        ("一个字段数","ReturnPropertyNum",0, -1),
                        ("一个字段数","ReturnCouponNum",0, -1),
                        ("一个字段","ReturnDeposit",0, -1),
                        ("一个字段","RentDays",1, -1),
                        ("一个字段","HaveBadRecord",1, 0),
                        ("一个字段","Active",1, -1),
                        ("一个字段","ActReward",0, -1),
                        ("一个字段","Remark",0, -1)]   # 此处不能用字典,因为字典没有顺序
        self.ChoiceList = [[ u"有", u"无", u"未检查", wx.EmptyString ]]
        self.m_grid = mData_Grid( self, "UserT", self.GridLabelToDBLabel )   

class TabFour(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        t = wx.StaticText(self, -1, "This is the last tab", (20,20))
        
class AddDialog ( wx.Dialog ):
    
    def __init__( self, parent, GridLabelToDBLabel, dbTable, targetGrid, ChoiceList ):
        wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = "添加", 
            pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_DIALOG_STYLE )
        self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
        self.GridLabelToDBLabel=GridLabelToDBLabel
        self.dbTable=dbTable
        self.targetGrid = targetGrid
        self.ChoiceList=ChoiceList
        fgSizer = wx.FlexGridSizer( 0, 2, 0, 0 )
        fgSizer.SetFlexibleDirection( wx.BOTH )
        fgSizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
        
        self.textCtrlList=[]        
        for t_label in GridLabelToDBLabel:
            if t_label[3]=="-2":    # 这一位数据库自增
                continue
            m_staticText = wx.StaticText( self, wx.ID_ANY, t_label[0], wx.DefaultPosition, wx.DefaultSize, 0 )
            m_staticText.Wrap( -1 )
            fgSizer.Add( m_staticText, 0, wx.ALL, 5 )
            
            if t_label[3]>-1:
                m_comboBox1Choices = self.ChoiceList[t_label[3]]
                self.m_comboBox1 = wx.ComboBox( self, wx.ID_ANY, u"请选择", wx.DefaultPosition, wx.DefaultSize, m_comboBox1Choices, 0 )
                fgSizer.Add( self.m_comboBox1, 0, wx.ALL, 5 )
                self.textCtrlList.append([m_textCtrl,1])   # 第二位用于指示是否为下拉列表
            else:
                m_textCtrl = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
                fgSizer.Add( m_textCtrl, 0, wx.ALL, 5 )
                self.textCtrlList.append([m_textCtrl,0])
        
        self.m_button1 = wx.Button( self, wx.ID_ANY, u"添加", wx.DefaultPosition, wx.DefaultSize, 0 )
        fgSizer.Add( self.m_button1, 0, wx.ALL, 5 )    
        self.m_button1.Bind( wx.EVT_BUTTON, self.addItem )
        
        self.SetSizer( fgSizer )
        self.Layout()
        fgSizer.Fit( self )
        
        self.Centre( wx.BOTH )
    
    def addItem(self, event):
        NewLinePara=[]  # 此处不能用字典,字典是无顺序的
        # re.match("1[2-9]{1}[0-9]{9}", self.textCtrlList[1].GetLineText(0))==None    # 判断输入是否为手机号
        for [m_textCtrl,i] in self.textCtrlList:
            if i==1:
                NewLinePara.append(m_textCtrl.GetStringSelection())   # GetTextSelection()返回(0,0),GetSelection()返回选择的索引
            else:
                NewLinePara.append(m_textCtrl.GetLineText(0))
        InsertCell="INSERT INTO "+self.dbTable+"  VALUES ( null,"
        for para in NewLinePara:
            InsertCell=InsertCell+"\""+para+"\""+","
        InsertCell=InsertCell[:-1]+");"
        cursor.execute(InsertCell) 
        connection.commit()
        
        # 刷新显示界面
        self.targetGrid.RowsNum = self.targetGrid.RowsNum + 1
        self.targetGrid.AppendRows( numRows=1 )
        for col,para in enumerate(NewLinePara):
            self.targetGrid.SetCellValue(self.targetGrid.RowsNum-1,col+1,para)   # 之所以 +1 是因为前面还有个 id。
        # 设置第一列为不可编辑
        IDColAttr = wx.grid.GridCellAttr() 
        IDColAttr.SetReadOnly(isReadOnly=True)
        self.targetGrid.SetAttr( self.targetGrid.RowsNum-1,0, IDColAttr)
        
        self.targetGrid.ForceRefresh()
        event.Skip()
        self.Destroy()
    
    def __del__( self ):
        pass
        
class DelDialog ( wx.Dialog ):
    
    def __init__( self, parent, dbTable, dbTableIDName, dbTableID, targetGrid, targetRow ):
        wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = "删除", 
            pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_DIALOG_STYLE )
        self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
        self.dbTable=dbTable        
        self.dbTableIDName=dbTableIDName      
        self.dbTableID=dbTableID      
        self.targetGrid = targetGrid
        self.targetRow = targetRow
        bSizer = wx.BoxSizer( wx.VERTICAL )
        
        self.m_staticText = wx.StaticText( self, wx.ID_ANY, u"确定删除?", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText.Wrap( -1 )
        bSizer.Add( self.m_staticText, 0, wx.ALL, 5 )
        
        self.m_button = wx.Button( self, wx.ID_ANY, u"确定", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer.Add( self.m_button, 0, wx.ALL, 5 )    
        self.m_button.Bind( wx.EVT_BUTTON, self.delItem )
        
        self.SetSizer( bSizer )
        self.Layout()
        bSizer.Fit( self )
        
        self.Centre( wx.BOTH )
        
    def delItem(self, event):
        DelCell="DELETE FROM "+self.dbTable+" WHERE "+self.dbTableIDName+" = "+"\""+self.dbTableID+"\";"
        cursor.execute(DelCell) 
        connection.commit()
        
        # 刷新显示界面
        self.targetGrid.RowsNum = self.targetGrid.RowsNum - 1
        self.targetGrid.DeleteRows( pos=self.targetRow, numRows=1)
        self.targetGrid.ForceRefresh()
        
        self.targetGrid.ShowSerial[self.targetRow] = -5
        event.Skip()
        self.Destroy()
    
    def __del__( self ):
        pass
        
    
class MainFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None,id=wx.ID_ANY, title="窗口标题", \
            pos=wx.Point(20,20), size=wx.Size( 1160,650 ), \
            style=wx.DEFAULT_FRAME_STYLE, name="MainFrame")
        
        # 三张表的名字!
        self.dbTableList=["AccountT","UserT","RunningAccountT"] 
        self.dbTableID=["AccountID","UserID","LSH"]
        
        self.m_menubar = wx.MenuBar( 0 )
        self.m_menu1 = wx.Menu()        
        self.m_menuItem1 = wx.MenuItem( self.m_menu1, wx.ID_ANY, u"添加", wx.EmptyString, wx.ITEM_NORMAL )
        self.m_menu1.Append( self.m_menuItem1 )        
        self.m_menuItem2 = wx.MenuItem( self.m_menu1, wx.ID_ANY, u"删除行", wx.EmptyString, wx.ITEM_NORMAL )
        self.m_menu1.Append( self.m_menuItem2 )         
        self.m_menuItem3 = wx.MenuItem( self.m_menu1, wx.ID_ANY, u"隐藏列", wx.EmptyString, wx.ITEM_NORMAL )
        self.m_menu1.Append( self.m_menuItem3 )        
        self.m_menubar.Append( self.m_menu1, u"操作" )         
        self.SetMenuBar( self.m_menubar )
        # Connect Events
        self.Bind( wx.EVT_MENU, self.MenuAddItem, id = self.m_menuItem1.GetId() )        
        self.Bind( wx.EVT_MENU, self.MenuDelItem, id = self.m_menuItem2.GetId() )     
        self.Bind( wx.EVT_MENU, self.MenuHideCol, id = self.m_menuItem3.GetId() )

        # Create a panel and notebook (tabs holder)
        p = wx.Panel(self)
        self.nb = wx.Notebook(p)

        # Create the tab windows
        tab1 = TabOne(self.nb)
        tab2 = TabTwo(self.nb)
        tab3 = TabThree(self.nb)
        tab4 = TabFour(self.nb)
        self.tabList=[tab1,tab2,tab3,tab4]

        # Add the windows to tabs and name them.
        self.nb.AddPage(tab1, "账号管理1")
        self.nb.AddPage(tab2, "用户管理2")
        self.nb.AddPage(tab3, "选项卡3")
        self.nb.AddPage(tab4, "选项卡4")

        # Set noteboook in a sizer to create the layout
        sizer = wx.BoxSizer()
        sizer.Add(self.nb, 1, wx.EXPAND)
        p.SetSizer(sizer)
        
    def MenuAddItem(self, event):
        l_FocusTab = self.tabList[self.nb.GetSelection()]
        l_GridLabelToDBLabel = l_FocusTab.GridLabelToDBLabel
        l_FocusGrid = l_FocusTab.m_grid
        l_dbTable = self.dbTableList[self.nb.GetSelection()]
        dlg = AddDialog(self, l_GridLabelToDBLabel, l_dbTable, l_FocusGrid, l_FocusTab.ChoiceList)
        if dlg.ShowModal() == wx.ID_OK:
            # do something here
            print('Hello')
        else:
            # handle dialog being cancelled or ended by some other button
            print('Dialog create failed.')
        dlg.Destroy()
        event.Skip()
        
    def MenuDelItem(self, event):
        targerTabNum=self.nb.GetSelection()
        targetGrid=self.tabList[targerTabNum].m_grid
        dbTable = self.dbTableList[targerTabNum]
        SelectedRtn = targetGrid.GetSelectedRows()
        if SelectedRtn!=[]:
            targetRow = SelectedRtn[0]
            dbTableID = targetGrid.data[ targetRow,0 ]
            dbTableIDName=self.tabList[targerTabNum].GridLabelToDBLabel[0][1]
            dlg = DelDialog(self, dbTable, dbTableIDName, dbTableID, targetGrid, targetRow )
            if dlg.ShowModal() == wx.ID_OK:
                # do something here
                print('Hello')
            else:
                # handle dialog being cancelled or ended by some other button
                print('Dialog create failed.')
            dlg.Destroy()
        event.Skip()
        
    def MenuHideCol(self, event):
        
        #因为点击列头部是排序,所以这里以选择的单元格为准
        targerTabNum=self.nb.GetSelection()  # 第几个选项卡。
        targetGrid=self.tabList[targerTabNum].m_grid   
        # GetSelectedCells()并不是我们想的那样。所以还得自己写个事件处理函数获得选择的列号。
        SelectedCol = targetGrid.targetGrid.currentlySelectedCell[1]
        targetGrid.ShowCols[SelectedCol] = -20 
        for i in range(SelectedCol+1,targetGrid.ColsNums):
            targetGrid.ShowCols = targetGrid.ShowCols - 1
        targetGrid.DeleteCols( pos=targetCol, numCols=1 )
        event.Skip()

if __name__ == "__main__":
    app = wx.App()
    MainFrame().Show()
    app.MainLoop()
    cursor.close()
    connection.close()

 

你可能感兴趣的:(python)