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 列。
我用 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 列。