wxPython - dynamially update a listctrl depending on input into a textctrl
does anyone of you have an example how to make the following possible:
I have a listctrl that displays > 600 items. Now I need to search in these items for a text the user inputs and update the list to only show the items containing this string.
So let us say the list contains "Hello", "Hi" and "Morning". The list displays all three items. Now the user types "h" into the textctrl, and the listctrl is narrowed down to "Hello" and "Hi". If the user instead types "o", and the list becomes "Hello" and "Morning".
Is this possible? Or is there any other convenient way to find an item in a listctrl? The build in "find开发者_运维技巧 as you type" is only of real use if you do exactly know what you search for - and in my case this will not really be the case...
Thanks, Woodpicker
The wxPython demo has a pretty good "type-ahead" filter built into it. Looking at the source code to Main.py they do it the "manual way", loop and rebuild the list. They are using a treeview but the ideas are sound:
def OnSearch(self, event=None):
value = self.filter.GetValue()
if not value:
self.RecreateTree()
return
wx.BeginBusyCursor()
for category, items in _treeList:
self.searchItems[category] = []
for childItem in items:
if SearchDemo(childItem, value):
self.searchItems[category].append(childItem)
wx.EndBusyCursor()
self.RecreateTree()
I like the ObjectListView wrapper better than the straight wx.ListCtrl. It includes the ability to filter items in the control as a feature of the widget. You can read about it here: http://objectlistview.sourceforge.net/python/features.html#filtering and here's the main page for the control: http://objectlistview.sourceforge.net/python/
Here is an example of filtering an UltimateListCtrl. I know this is 2 years later but I've found other examples on stackoverflow really, really helpful. I'm new to python/wxpython but hopefully it will be useful. starting from http://www.blog.pythonlibrary.org/2011/11/02/wxpython-an-intro-to-the-ultimatelistctrl/
import wx
from wx.lib.agw import ultimatelistctrl as ULC
class ULC_Panel(wx.Panel):
""""""
def __init__(self, parent, col_headers=None, list_data=None, options=None, dlg=None,
selected_list=None):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.options = options
self.show_only_selected = False
self.filter_string = ""
hsizer = wx.BoxSizer(wx.HORIZONTAL)
okayButton = wx.Button(self, wx.ID_OK, "OK")
okayButton.SetToolTip(wx.ToolTip("Click to close this dialog and use the selections"))
self.Bind(wx.EVT_BUTTON, self.OnOkayCanButton, okayButton)
hsizer.Add(okayButton, 0, wx.ALL, 5)
canButton = wx.Button(self, wx.ID_CANCEL, "Cancel")
canButton.SetToolTip(wx.ToolTip("Click to close this dialog and cancel selections"))
self.Bind(wx.EVT_BUTTON, self.OnOkayCanButton, canButton)
hsizer.Add(canButton, 0, wx.ALL, 5)
cb_show_only = wx.CheckBox(self, -1, "Show only selected items?")
cb_show_only.SetValue(self.show_only_selected)
cb_show_only.SetToolTip(wx.ToolTip("Click to show only selected rows"))
self.Bind(wx.EVT_CHECKBOX, self.EvtShowOnly, cb_show_only)
hsizer.Add(cb_show_only, 0, wx.ALL, 5)
self.stext = wx.StaticText(self, -1, "Filter: ", style=wx.ALIGN_LEFT)
self.filtr = wx.TextCtrl(self, -1, "", style=wx.ALIGN_LEFT)
self.Bind(wx.EVT_TEXT, self.OnFiltr, self.filtr)
fsizer = wx.BoxSizer(wx.HORIZONTAL)
fsizer.Add(self.stext, 0, wx.ALL)
fsizer.Add(self.filtr, 1, wx.EXPAND)
font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
boldfont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
boldfont.SetWeight(wx.BOLD)
boldfont.SetPointSize(12)
self.ultimateList = ULC.UltimateListCtrl(self, agwStyle = wx.LC_REPORT
| wx.LC_VRULES | ULC.ULC_HAS_VARIABLE_ROW_HEIGHT
| wx.LC_HRULES)
self.checkbox = [None] * len(list_data)
if selected_list != None:
self.selected = selected_list
else:
self.selected = [False] * len(list_data)
self.rows_max = len(list_data)
self.rows_current = -1
self.cols_max = len(col_headers)
self.cols_extra = 1
if options & ULC.ULC_MASK_CHECK:
self.cols_extra += 1
for i in xrange(self.cols_max+self.cols_extra):
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
info._image = []
info._format = 0
info._kind = 1
width = 150
if i >= self.cols_extra:
info._text = col_headers[i-self.cols_extra]
elif i == 0:
info._text = "Row"
width = 35
elif i == 1 and options & ULC.ULC_MASK_CHECK:
info._text = "Select"
width = 50
self.ultimateList.InsertColumnInfo(i, info)
self.ultimateList.SetColumnWidth(i, width)
self.list_data = list_data
pos = self.populate_table("")
if pos != None:
self.sz = self.ultimateList.GetItemRect(pos)
self.width = self.sz[2] + self.sz[3]
self.height = self.sz[1]
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(hsizer, 0, wx.EXPAND)
sizer.Add(fsizer, 0, wx.EXPAND)
sizer.Add(self.ultimateList, 1, flag=wx.EXPAND)
self.SetSizer(sizer)
sizer.Fit(self)
def EvtShowOnly(self, event):
cb = event.GetEventObject()
val = cb.GetValue()
self.show_only_selected = val
pos = self.populate_table(self.filter_string)
print "show_only_selected val= ", val
def EvtCheckBox(self, event):
cb = event.GetEventObject()
id = event.GetId()
val = cb.GetValue()
self.selected[id] = val
print "id, val= ", id, val
def OnOkayCanButton(self, event):
id = event.GetId()
dlg.EndModal(id)
def myGetNeedWH(self):
return (self.width, self.height)
def myGetSelectedState(self):
return self.selected
def populate_table(self, str_in):
busy = wx.BusyCursor()
if str_in:
str_low = str_in.lower()
if self.options & ULC.ULC_MASK_CHECK:
# if we have widgets in the row then we have to delete 1 row
# at a time (or else it leaves some of the widgets)
i = self.rows_current
#print "i, self.rows_max= ", i, self.rows_max
while i >= 0:
#print "i= ", i
self.ultimateList.DeleteItem(i)
i -= 1
else:
self.ultimateList.DeleteAllItems()
row = -1
for i in xrange(len(self.list_data)):
tlwr = self.list_data[i][0].lower()
if not str_in or tlwr.find(str_low) >= 0:
if self.show_only_selected and self.selected[i] == False:
continue
row += 1
for j in xrange(self.cols_max+self.cols_extra):
if j == 0:
pos = self.ultimateList.InsertStringItem(row, str(row))
elif j == 1 and self.options & ULC.ULC_MASK_CHECK:
self.checkbox[i] = wx.CheckBox(self.ultimateList, id= i)
self.checkbox[i].SetValue(self.selected[i])
self.checkbox[i].SetToolTip(wx.ToolTip("Click to select this row"))
self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.checkbox[i])
self.ultimateList.SetItemWindow(pos, col=1, wnd=self.checkbox[i], expand=False)
else:
self.ultimateList.SetStringItem(row, j, self.list_data[i][j-self.cols_extra])
self.rows_current = row
return row
def OnFiltr(self, event):
str1 = event.GetString()
id = event.GetId()
#print "got txt_tval str[%s]= %s" % (id, str1)
self.filter_string = str1
pos = self.populate_table(str1)
event.Skip()
return
########################################################################
class FilterListDiag(wx.Dialog):
def __init__(self, parent, id, title, headers=None, data_table=None, options=None, selected_list=None):
wx.Dialog.__init__(self, parent, id, title="", style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
options_in = options
self.panel = ULC_Panel(self, col_headers=headers, list_data=data_table, options=options_in,
dlg=self, selected_list=selected_list)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(sizer)
(self.width, self.height) = self.panel.myGetNeedWH()
def myGetNeedWH(self):
return (self.width, self.height)
def myGetSelectedState(self):
return self.panel.myGetSelectedState()
class TestFrame(wx.Frame):
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="MvP UltimateListCtrl Demo", size=(850,600))
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = TestFrame()
col_headers = ['col0', 'col1', 'col2']
list_data = [
["Newsboys", "Go", "Rock"],
["Puffy", "Bring It!", "Pop"],
["Family Force 5", "III", "Pop"],
["Me2", "III", "Pop"],
["Duffy", "III", "Pop"],
["Fluffy", "III", "Pop"],
]
# sel_data passes in a list of which rows are already selected
sel_data = [
False,
False,
False,
False,
True,
False,
]
opt=ULC.ULC_MASK_CHECK # just reusing this to indicate I want a column of checkboxes.
dlg = FilterListDiag(frame, -1, "hi", headers=col_headers, data_table=list_data, options=opt, selected_list=sel_data)
(w, h) = dlg.myGetNeedWH()
print w,h
dlg.SetSizeWH(w, 300)
val = dlg.ShowModal()
selected = dlg.myGetSelectedState()
print "okay, can, val= ", wx.ID_OK, wx.ID_CANCEL, val
dlg.Destroy()
print 'selected=', selected
I have something different.
I created a dictionary and updated every time there is an entry in the list ctrl. Now, when I have a search box, which changes the list ctrl with each input from the keyboard and I clear and re-update my dictionary. This way, I always have the latest index for the values. Code is as below:
class ViewUserDialog(wx.Dialog):
def __init__(self):
title = 'View Users Records'
super().__init__(parent=None, size=(750, 600), title=title)
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.list_sizer = wx.BoxSizer(wx.VERTICAL)
self.row_obj_dict = {}
.
.
.
self.list_ctrl_View_User.InsertColumn(0, "ID", width=150)
self.list_ctrl_View_User.InsertColumn(1, "User Name", width=150)
for index, users in original_user_frame.iterrows():
self.list_ctrl_View_User.InsertItem(indexes, user_id_value)
self.list_ctrl_View_User.SetItem(indexes, 1, users.NAME)
user_objects.append(users)
self.row_obj_dict[indexes] = users
indexes += 1
Now on searching, and selecting an item in lstctrl, you will still get your results:
def on_view(self, event):
selection = self.list_ctrl_View_User.GetFocusedItem()
self.list_ctrl_View_User.ClearAll()
if selection >= 0:
user = self.row_obj_dict[selection]
print(user)
self.list_ctrl_View_User.InsertColumn(0, "ID", width=150)
self.list_ctrl_View_User.InsertColumn(1, "User Name", width=150)
for index, users in original_user_frame.iterrows():
self.list_ctrl_View_User.InsertItem(indexes, user_id_value)
self.list_ctrl_View_User.SetItem(indexes, 1, users.NAME)
This will always give the currect selected item from the lstctrl and update the list with every input.
精彩评论