Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 685
Woodpicker Avatar asked Aug 14 '11 19:08

Woodpicker


3 Answers

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()    
like image 166
Mark Avatar answered Oct 21 '22 17:10

Mark


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/

like image 42
Mike Driscoll Avatar answered Oct 21 '22 18:10

Mike Driscoll


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
like image 38
Pat Avatar answered Oct 21 '22 17:10

Pat