UltimateListCtrl - 选择和排序

UltimateListCtrl - selections and sorting

我用 wxpython 和 ulc 为个人数据库编写了一个 python 小程序。 我以为我已经完成并最终从原始访问数据库导入了所有数据,现在遇到了问题。在 ulc 中有将近 1000 个条目(而不是我在开发中的 50 个条目)会减慢它的速度,以至于变得无法使用。 所以我开始使用虚拟模式重写所有内容来解决这个问题。但现在我面临另一个问题。

我在 listctrl 中有一个 select 多个人的复选框,我也在使用 ColumnSorterMixin。在正常模式下,当以不同的方式对列表进行排序时,列表中的 selection 会保持正确,但在虚拟模式下,selection 会留在行上而不是实际项目上。

例如,如果您 select 编辑了第三行然后对列表进行排序,则第三行中的项目现在位于第四行中,但第三行仍然是 select编辑。难道我做错了什么?我使用 ctrl 键尝试了复选框和 selections。两者都有同样的问题。

这是我的问题测试代码...

import wx
from wx.lib.agw import ultimatelistctrl as ULC
import wx.lib.mixins.listctrl  as  listmix

data = {
1 : ("John Doe", "Madeup Road 11\nFakeTown", "000-383783763"),
2 : ("Jane Doe", "Madeup Road 11\nFakeTown", "000-383783763"),
3 : ("Max Mustermann", "Madeup Road 16\nFakeTown", "043-3434763"),
4 : ("Myself", "Fake Road 9\nFakeTown", "323-3843457773"),
}


class mylist(ULC.UltimateListCtrl, listmix.ColumnSorterMixin):
    def __init__(self, parent):
        ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_VIRTUAL|ULC.ULC_REPORT|ULC.ULC_USER_ROW_HEIGHT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)
        self.SetUserLineHeight(40)
        self.itemDataMap = data
        self.itemIndexMap = data.keys()
        self.SetItemCount(len(data))
        self.table_fields=['Name','Street','Phone']
        field_index=0

        for field in self.table_fields:
            info = ULC.UltimateListItem()
            info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
            info._image = []
            info._format = wx.LIST_FORMAT_LEFT
            info._kind = 1
            info._text = field
            info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
            self.InsertColumnInfo(field_index, info)
            self.SetColumnWidth(field_index,175)
            field_index += 1


        self.checked = []
        self.Bind(ULC.EVT_LIST_ITEM_CHECKING, self.OnCheck)
        self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColumn)

        #mixins
        listmix.ColumnSorterMixin.__init__(self, 3)

        #sort by column (2), A->Z ascending order (1)
        self.SortListItems(0, 1)


    def SortItems(self,sorter=cmp):
        items = list(self.itemDataMap.keys())
        items.sort(sorter)
        self.itemIndexMap = items

        # redraw the list
        self.Refresh()

    def OnColumn(self, e):
        self.Update()
        e.Skip()

    def GetListCtrl(self):
        return self

    def OnCheck(self, event):
        item_column = (event.m_itemIndex, event.m_item.GetColumn())
        print item_column
        try:
            idx = self.checked.index(item_column)
        except ValueError:
            idx = None

        if idx == None:
            self.checked.append(item_column)
        else:
            del(self.checked[idx])
        self.Refresh()

    def getColumnText(self, index, col):
        item = self.GetItem(index, col)
        return item.GetText()

    def OnGetItemText(self, item, col):
        index=self.itemIndexMap[item]
        s = self.itemDataMap[index][col]
        return s

    def OnGetItemColumnImage(self, item, col):
        return []

    def OnGetItemImage(self, item):
        return []

    def OnGetItemAttr(self, item):
        return None

    def OnGetItemTextColour(self, item, col):
        return None

    def OnGetItemToolTip(self, item, col):
        return None

    def OnGetItemKind(self, item):
        return 1

    def OnGetItemColumnKind(self, item, col):
        if col==0:
            return self.OnGetItemKind(item)
        return 0

    def OnGetItemColumnCheck(self, item, column):
        item_column = (item, column)
        if item_column in self.checked:
            return True
        else:
            return False

class TestFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
        panel = wx.Panel(self, -1)
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.listctrl = listCtrl = mylist(panel)
        sizer.Add(listCtrl, 1, wx.EXPAND)
        panel.SetSizer(sizer)
        sizer.Layout()
        self.CenterOnScreen()
        self.Show()

class MyApp(wx.App):
    def OnInit(self):
        frame = TestFrame(None)
        frame.Show(True)
        return True

app = MyApp(0)
app.MainLoop()

这在虚拟 ListCtrl 中是否可行,还是我做错了什么?

我正在 Windows 8.1、Python 2.7.10 和 wxPython 3.0.2.0.

上进行测试

edit 注意到你在 3.0.2 上测试,所以我更新了之前的演示并使用相同的 wx 版本(和 Python 2.7.11)进行了测试而不是我使用的 wx 3.0.3 'Phoenix'。
你的问题的答案是:是的,这是可能的。
不止一种方法可以满足,但这里有一个工作演示,您可以将其完整粘贴并 运行 到它自己的 py 文件中进行测试。
本demo在wx版本3.0.2.0 osx-cocoa(经典版)上测试,满足以下条件:

Lists a thousand records, uses ULC in virtual mode, sorts on any column, checks multiple items, can check/uncheck using select-all box, preserves item checks when sorting rather than staying in position, doesn't become slow/unusable like your original version that led you here.

    import wxversion
    wxversion.select(['3.0'])  #for me this switches from 'Phoenix' to 'Classic'
    import wx; print wx.version();
    from wx.lib.agw import ultimatelistctrl as ULC
    from wx.lib.mixins import listctrl as listmix


    class mylist(ULC.UltimateListCtrl, listmix.ColumnSorterMixin):
        """
        Sortable list with check box for each row and a 'select all' box
        When sorting, the checkboxes follow the data, rather than staying on their row
        """
        def __init__(self, parent):
            ULC.UltimateListCtrl.__init__(self, parent, -1,
                                          agwStyle=ULC.ULC_VIRTUAL | ULC.ULC_REPORT# | ULC_SINGLE_SEL
                                          )
            datasource = Records()
            self.itemDataMap = datasource.data
            self.itemIndexMap = self.itemDataMap.keys()
            self.itemCheckedMap1 = set()
            self.itemCheckedMap2 = set()
            self.table_fields = ["id", "rockcol", "papercol", "scissorscol", "lizardcol"]
            field_index = 0
            for field in self.table_fields:
                info = ULC.UltimateListItem()
                masktoset = ULC.ULC_MASK_TEXT
                if field_index == 0:
                    masktoset |= ULC.ULC_MASK_KIND | ULC.ULC_MASK_CHECK
                    kindtoset = ULC.ULC_KIND_CHECK = wx.ITEM_CHECK
                    widthtoset = 60
                    info.Check(False)
                else:
                    widthtoset = ULC.ULC_AUTOSIZE
                info.SetText(field)
                info.SetKind(kindtoset)
                info.SetMask(masktoset)
                self.InsertColumnInfo(field_index, info)
                self.SetItemCount(len(datasource.data))
                self.SetColumnWidth(field_index, widthtoset)
                field_index += 1
            self.Bind(ULC.EVT_LIST_ITEM_CHECKING, self.OnSetItemCheck)
            self.Bind(ULC.EVT_LIST_COL_CHECKING, self.OnSetColumnCheck)
            self.Bind(ULC.EVT_LIST_COL_CLICK, self.OnColClick, self)
            listmix.ColumnSorterMixin.__init__(self, len(self.table_fields))  #binds col click event
            #self.SortListItems(1, 1)


    ####
    # ----------------------------------------------------------------------------
    # bound event handling
    # ----------------------------------------------------------------------------
        def OnSetItemCheck(self, e):
            x1 = e.m_itemIndex
            y1 = e.EventObject.itemCheckedMap1
            x2 = int(e.m_item._text)
            y2 = e.EventObject.itemCheckedMap2
            for x, y in (x1, y1), (x2, y2):
                if x not in y:
                    y.add(x)
                else:
                    y.remove(x)
            self.Refresh()

        def OnSetColumnCheck(self, e):
            e.EventObject.GetColumn(e.m_col)._checked = x = not e.EventObject.GetColumn(e.m_col)._checked
            if x:
                e.EventObject.itemCheckedMap1 = set(e.EventObject.itemIndexMap)
            else:
                e.EventObject.itemCheckedMap1.clear()
            self.Refresh()

        def OnColClick(self, e):
            self.Refresh()
            e.Skip()

        def GetListCtrl(self): #for sorter init
            return self

        def SortItems(self, sorter=cmp):
            self.itemIndexMap.sort(sorter)
            self.Refresh()

        def SortListItems(self, col=-1, ascending=1):
            self.fix()
            super(mylist, self).SortListItems(col, ascending)

        def fix(self):
            pass

        def OnSortOrderChanged(self):
            self.itemCheckedMap1 = set(i for i, x in enumerate(self.itemIndexMap) if x in self.itemCheckedMap2)


    ####
    # ----------------------------------------------------------------------------
    # overrides for 'virtual' mode support
    # ----------------------------------------------------------------------------
        def OnGetItemText(self, item, col):
            idx = self.itemIndexMap[item]
            return str(self.itemDataMap[idx][col])

        def OnGetItemTextColour(self, item, col):
            return None

        def OnGetItemToolTip(self, item, col):
            return None

        def OnGetItemColumnImage(self, item, column=0):
            return []

        def OnGetItemCheck(self, idx):
            return idx in self.itemCheckedMap1

        def OnGetItemColumnCheck(self, item, column=0):
            return self.OnGetItemCheck(item)

        def OnGetItemKind(self, itemidx):
            return wx.ITEM_CHECK

        def OnGetItemColumnKind(self, itemidx, columnidx):
            if self.GetColumn(columnidx)._kind == wx.ITEM_CHECK:
                return self.OnGetItemKind(itemidx)
            return wx.ITEM_NORMAL


    class Records:
        """
        Generates some unsorted data for testing sort and checkbox preservation
        """
        def __init__(self):
            self.data = {}
            self.makedataset(1000)

        def makedataset(self, rcount):
            for ci in xrange(rcount):
                c1 = str(hash(ci^2*2))
                c2 = str(hash(ci^3*2))
                c3 = str(hash(ci^4*2))
                c4 = str(hash(ci^1*2))
                self.data[ci] = ci, c1, c2, c3, c4


    class TestFrame(wx.Frame):
        def __init__(self):
            wx.Frame.__init__(self, None, -1, "virtual-mode ULC", size=(800, 300))
            panel = wx.Panel(self, -1)
            sizer = wx.BoxSizer(wx.VERTICAL)
            self.listctrl = mylist(panel)
            sizer.Add(self.listctrl, 1, wx.EXPAND)
            panel.SetSizer(sizer)
            sizer.Layout()
            self.Show()

    mySandbox = wx.App()
    mySandbox.SetTopWindow(TestFrame())
    mySandbox.MainLoop()

    exit()

使用的策略是每次更改排序顺序时更新选中框的数组。还有其他方法,但这个方法依赖于显示数据中的 id 列。