2013年11月28日 星期四

[紀錄] Using listmix to finish checkbox and sort function in Python


這個功能真的是讓我費了好多功夫才完成。

原本已經用 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上,苦惱。

如果有人找到方法,也歡迎分享給我。我會大力感激你的。

沒有留言:

張貼留言