這個功能真的是讓我費了好多功夫才完成。
原本已經用 listctrl 完成了排序的功能,但是被要求要在每個row前方加上checkbox。
所以就開啟了不斷的 Google之旅 Orz
中間實在遭遇了很多的狀況,寫出了可以加上checkbox的功能,結果排序卻失效。
一直無法完整的把這兩個功能combine在一起。
好不容易完成了,卻默默的發現其他Bug。
例如:table上排序完成,但itemDataMap卻沒有同步更改。
另外如果欄位裡放的是數字,它會使用字串的排序,而造成排序結果為1, 11, 12, 13, 2, 3, 4。
這不是我要的排序阿!!!!!! (*瘋狂吶喊*)
最後參考了很多的分享資料,終於完成了我要的功能。
上面兩張是以Index為排序(比對數字)的結果,下面兩張是以Name為排序(比對字串)的結果。
寫了一個簡單的sample code分享給大家,希望和我遇到一樣困擾的朋友可以順利的解決。
import wx
import wx.lib.mixins.listctrl as listmix
import sys
import time
import locale
import re
musicdata = {
1 : ("Jerry", "Man", "Single"),
2 : ("Annie", "Woman", "Married"),
3 : ("Nick", "Man", "Single"),
4 : ("Cosmo", "Man", "Single"),
5 : ("Always", "Man", "Married"),
6 : ("Jason", "Man", "Married"),
7 : ("James", "Man", "Single"),
8 : ("Andrew", "Man", "Married"),
9 : ("Ashley", "Woman", "Married"),
10: ("Stephanie", "Woman", "Single"),
11: ("Claire", "Woman", "Single"),
12: ("Fran", "Man", "Married")
}
# added the listmix.CheckListCtrlMixin to add checkbox on eveny row
class TestVirtualList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.CheckListCtrlMixin):
def __init__(self, parent):
wx.ListCtrl.__init__( self, parent, -1, style=wx.LC_REPORT | wx.BORDER_SUNKEN | wx.LC_VRULES | wx.LC_HRULES)
listmix.CheckListCtrlMixin.__init__(self)
listmix.ListCtrlAutoWidthMixin.__init__(self)
# created the columns
self.InsertColumn(0, "Index", width=75)
self.InsertColumn(1, "Name", width=100)
self.InsertColumn(2, "Sex", width=150)
self.InsertColumn(3, "Status", width=150)
# --------------------------------------------------
# Print the status of the pressed checkbox
# --------------------------------------------------
def OnCheckItem(self, index, flag):
print(index, flag)
# added the listmix.ColumnSorterMixin to do the sort function
class TestFrame(wx.Frame, listmix.ColumnSorterMixin):
def __init__(self, parent, id):
self.frame = wx.Frame.__init__(self, parent, id, 'Sample List', size=(600, 500))
self.panel = wx.Panel(self, wx.ID_ANY)
self.list = TestVirtualList(self.panel)
self.list.foundList = {}
self.sortList = []
self.readButton = wx.Button(self.panel, -1, "Get Sort List")
self.readButton.SetMaxSize((25,250))
self.Bind(wx.EVT_BUTTON, self.onGetSortList, self.readButton)
# put the musicdata into the foundList
# the foundlist is same with the itemDataMap
items = musicdata.items()
index = 0
for key, data in items:
self.list.InsertStringItem(index, str(key))
self.list.SetStringItem(index, 1, str(data[0]))
self.list.SetStringItem(index, 2, str(data[1]))
self.list.SetStringItem(index, 3, str(data[2]))
self.list.SetItemData(index, key)
self.list.CheckItem(index)
tmpNode = {key:(str(index+1),str(data[0]), str(data[1]), str(data[2]))}
self.list.foundList.update(tmpNode)
index += 1
# need added the itemDataMap due to use the ColumnSorterMixin
self.itemDataMap = self.list.foundList
listmix.ColumnSorterMixin.__init__(self, 4)
self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.list, 1, wx.EXPAND)
self.vbox.Add(self.readButton)
self.panel.SetSizer(self.vbox)
# --------------------------------------------------
# Used by the ColumnSorterMixin
# --------------------------------------------------
def GetListCtrl(self):
return self.list
# --------------------------------------------------
# Used by the ColumnSorterMixin
# --------------------------------------------------
def OnColClick(self, event):
event.Skip()
# --------------------------------------------------
# Return customSorter
# --------------------------------------------------
def GetColumnSorter(self):
return self.CustColumnSorter
# --------------------------------------------------
# Sorted the list
# --------------------------------------------------
def CustColumnSorter(self, key1, key2):
col = self._col
ascending = self._colSortFlag[col]
item1 = self.itemDataMap[key1][col]
item2 = self.itemDataMap[key2][col]
if str(item1).isdigit() and str(item2).isdigit():
# sort digital value
cmpVal = cmp(int(item1), int(item2))
else:
# sort string
cmpVal = locale.strcoll(str(item1), str(item2))
# If the items are equal then pick something else to make the sort value unique
if cmpVal == 0:
cmpVal = cmp(*self.GetSecondarySortValues(col, key1, key2))
if ascending:
return cmpVal
else:
return -cmpVal
# --------------------------------------------------
# Get the sorted list
# --------------------------------------------------
def onGetSortList(self, event):
# list table already sorted, but itemDataMap didn't sorted
# get the item text at every row to grab the sorted list
self.sortList = []
# get the item text and compared with itemDataMap
for idx in range(len(self.itemDataMap)):
if self.list.IsChecked(idx):
item = self.list.GetItem(idx, 0)
findIdx = item.Text
for getIdx in range(len(self.itemDataMap)):
if self.itemDataMap[getIdx+1][0] == findIdx:
s = self.itemDataMap[getIdx+1][1]
self.sortList.append(s)
print self.sortList
if __name__ == '__main__':
app = wx.PySimpleApp()
f = TestFrame(None, id=1)
f.Show()
app.MainLoop()
稍微說明一下我的程式碼:
我在TestFrame中新增了一個self.list,利用CheckListCtrlMixin在每個row前方加上checkbox讓使用者可以做選取的工作,利用ColumnSorterMixin完成按下column就可以依那行column為key的排序動作。
Ps: 如果你的每個欄位填入的資料都是字串,即你不需要任何數字排序功能的話。
可以直接把這兩個method註解掉 => GetColumnSorter, CustColumnSorter。
文字排序的功能仍然會正常喔! XD
由於self.list所顯示的排序結果並沒有同步更新到itemDataMap
所以我加了一個onGetSortList的event,會依序取得self.list上的index
再去itemDataMap比對index而抓取此index的其他資料。
當然,你也可以全部資料都直接從self.list上顯示的文字抓下來。
不過,我還是有一個功能一直無法順利整進目前的程式碼裡。
就是在每個column上增加排序的箭頭圖標,顯示目前是遞增還是遞減。
參考了網路上分享的方法,卻會把圖標蓋在每一行的checkbox上,苦惱。
如果有人找到方法,也歡迎分享給我。我會大力感激你的。
沒有留言:
張貼留言