wxPython - динамически обновлять listctrl в зависимости от ввода в textctrl

У кого-нибудь из вас есть пример, как сделать следующее возможным:

У меня есть listctrl, который отображает> 600 элементов. Теперь мне нужно найти в этих элементах текст, введенный пользователем, и обновить список, чтобы отображались только элементы, содержащие эту строку.

Итак, допустим, список содержит "Hello", "Hi" и "Morning". В списке отображаются все три элемента. Теперь пользователь вводит "h" в textctrl, а listctrl сужается до "Hello" и "Hi". Если пользователь вместо этого вводит "o", и список становится "Привет" и "Утро".

Это возможно? Или есть какой-нибудь другой удобный способ найти элемент в listctrl? Сборка "находи по мере ввода" полезна только в том случае, если вы точно знаете, что ищете, и в моем случае это не совсем так...

Спасибо, Дятел

3 ответа

Решение

Демонстрация wxPython имеет довольно хороший встроенный фильтр. Глядя на исходный код Main.py, они делают это "вручную", зацикливаются и перестраивают список. Они используют древовидную структуру, но идеи здравы:

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()    

Мне нравится оболочка ObjectListView лучше, чем прямая wx.ListCtrl. Он включает в себя возможность фильтровать элементы в элементе управления как функцию виджета. Вы можете прочитать об этом здесь: http://objectlistview.sourceforge.net/python/features.html и вот главная страница для элемента управления: http://objectlistview.sourceforge.net/python/

Вот пример фильтрации UltimateListCtrl. Я знаю, что это 2 года спустя, но я нашел другие примеры действительно очень полезными. Я новичок в python / wxpython, но, надеюсь, это будет полезно. начиная с 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

У меня что-то другое.

Я создал словарь и обновлялся каждый раз, когда в списке есть запись ctrl. Теперь, когда у меня есть поле поиска, которое меняет ctrl списка при каждом вводе с клавиатуры, я очищаю и повторно обновляю свой словарь. Таким образом, у меня всегда будет последний индекс значений. Код выглядит следующим образом:

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

Теперь при поиске и выборе элемента в lstctrl вы все равно получите свои результаты:

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)

Это всегда будет выдавать текущий выбранный элемент из lstctrl и обновлять список при каждом вводе.

Другие вопросы по тегам