因工作原因使用了一下 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()