如何创建下拉展开时可编辑并支持动态匹配的wxPython ComboCtrl
实现带智能匹配与高亮的wxPython组合控件
核心实现思路
要复刻类似Microsoft Access的组合框功能,我们可以基于wx.ComboCtrl扩展,搭配自定义下拉列表面板(内含wx.ListCtrl)来实现:
- 利用
wx.ComboCtrl的可编辑特性,允许下拉展开时输入文本 - 监听输入框文本变化事件,实时做前缀/通配符匹配,更新下拉列表显示项
- 自定义
ListCtrl的绘制逻辑,将输入的匹配前缀设为粗体,剩余文本保持常规样式 - 匹配到结果后自动滚动至首个匹配项,提升操作效率
完整代码示例
import wx import fnmatch class HighlightListCtrl(wx.ListCtrl): def __init__(self, parent, match_text=""): super().__init__(parent, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_NO_HEADER) self.match_text = match_text self.InsertColumn(0, "") self.SetColumnWidth(0, wx.LIST_AUTOSIZE_USEHEADER) # 绑定绘制与选中事件 self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected) self.parent_combo = parent def SetMatchText(self, text): self.match_text = text.lower() self.Refresh() def OnPaint(self, event): dc = wx.PaintDC(self) self.PrepareDC(dc) # 获取当前可见项范围 first = self.GetTopItem() last = first + self.GetCountPerPage() if last > self.GetItemCount(): last = self.GetItemCount() for idx in range(first, last): rect = self.GetItemRect(idx) item_text = self.GetItemText(idx) item_text_lower = item_text.lower() # 绘制项背景 if self.GetItemState(idx, wx.LIST_STATE_SELECTED): wx.RendererNative.Get().DrawListItemSelection(self, dc, rect, wx.CONTROL_CURRENT) else: dc.SetBrush(wx.Brush(self.GetBackgroundColour())) dc.SetPen(wx.Pen(self.GetBackgroundColour())) dc.DrawRectangle(rect) # 处理文本高亮 if self.match_text and self.match_text in item_text_lower: match_start = item_text_lower.find(self.match_text) match_end = match_start + len(self.match_text) # 绘制非匹配前缀 dc.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) dc.SetTextForeground(wx.BLACK) dc.DrawText(item_text[:match_start], rect.x + 5, rect.y + 2) # 绘制粗体匹配部分 dc.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) dc.DrawText(item_text[match_start:match_end], rect.x + 5 + dc.GetTextExtent(item_text[:match_start])[0], rect.y + 2) # 绘制非匹配后缀 dc.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) dc.DrawText(item_text[match_end:], rect.x + 5 + dc.GetTextExtent(item_text[:match_end])[0], rect.y + 2) else: # 无匹配时正常绘制文本 dc.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) dc.SetTextForeground(wx.BLACK) dc.DrawText(item_text, rect.x + 5, rect.y + 2) event.Skip() def OnItemSelected(self, event): # 选中项后同步到ComboCtrl输入框并关闭下拉 selected_text = self.GetItemText(event.GetIndex()) self.parent_combo.SetValue(selected_text) self.parent_combo.Close() class CustomComboCtrl(wx.ComboCtrl): def __init__(self, parent, items=None): super().__init__(parent, style=wx.CB_READONLY | wx.TE_PROCESS_ENTER) self.all_items = items or [] self.dropdown_panel = None self.list_ctrl = None # 初始化下拉面板 self._create_dropdown() # 绑定输入与下拉展开事件 self.Bind(wx.EVT_TEXT, self.OnTextChanged) self.Bind(wx.EVT_COMBOBOX_DROPDOWN, self.OnDropdown) def _create_dropdown(self): self.dropdown_panel = wx.Panel(self, style=wx.BORDER_SUNKEN) sizer = wx.BoxSizer(wx.VERTICAL) self.list_ctrl = HighlightListCtrl(self.dropdown_panel) sizer.Add(self.list_ctrl, 1, wx.EXPAND) self.dropdown_panel.SetSizer(sizer) self.SetPopupControl(self.dropdown_panel) def OnDropdown(self, event): # 展开下拉时加载所有选项 self._update_list(self.all_items) self.dropdown_panel.SetSize((self.GetSize()[0], 200)) event.Skip() def OnTextChanged(self, event): input_text = event.GetString() if not input_text: # 输入为空时恢复显示所有项 self._update_list(self.all_items) return # 默认前缀匹配,如需通配符匹配可替换为fnmatch.filter(self.all_items, f"*{input_text}*") matched_items = [item for item in self.all_items if item.lower().startswith(input_text.lower())] self._update_list(matched_items) # 自动滚动到首个匹配项 if matched_items: self.list_ctrl.EnsureVisible(0) # 传递匹配文本用于高亮渲染 self.list_ctrl.SetMatchText(input_text) event.Skip() def _update_list(self, items): self.list_ctrl.DeleteAllItems() for idx, item in enumerate(items): self.list_ctrl.InsertItem(idx, item) self.list_ctrl.SetColumnWidth(0, wx.LIST_AUTOSIZE) # 测试示例 if __name__ == "__main__": app = wx.App(False) frame = wx.Frame(None, title="Custom Combo Demo", size=(400, 200)) panel = wx.Panel(frame) sizer = wx.BoxSizer(wx.VERTICAL) sample_items = ["Firehouse", "Fireplace", "Forest", "Furniture", "Fish", "Firework"] combo = CustomComboCtrl(panel, sample_items) sizer.Add(combo, 0, wx.EXPAND | wx.ALL, 10) panel.SetSizer(sizer) frame.Show() app.MainLoop()
关键功能说明
实时匹配逻辑:
- 默认实现前缀匹配,若需要包含式通配符匹配,可将
OnTextChanged中的匹配逻辑替换为fnmatch.filter(self.all_items, f"*{input_text}*") - 输入文本时自动筛选列表项,输入为空时恢复显示所有选项
- 默认实现前缀匹配,若需要包含式通配符匹配,可将
文本高亮实现:
- 自定义
HighlightListCtrl重写OnPaint事件,分段绘制文本:匹配前缀用粗体,其余部分用常规字体 - 通过
SetMatchText传递当前输入文本,触发列表刷新高亮
- 自定义
交互优化:
- 下拉展开时自动加载全量选项,输入时实时更新列表并滚动至首个匹配项
- 选中列表项后自动同步到输入框并关闭下拉面板
内容的提问来源于stack exchange,提问作者linxpatrick
相关产品推荐
相关产品推荐

