You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建下拉展开时可编辑并支持动态匹配的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()

关键功能说明

  1. 实时匹配逻辑:

    • 默认实现前缀匹配,若需要包含式通配符匹配,可将OnTextChanged中的匹配逻辑替换为fnmatch.filter(self.all_items, f"*{input_text}*")
    • 输入文本时自动筛选列表项,输入为空时恢复显示所有选项
  2. 文本高亮实现:

    • 自定义HighlightListCtrl重写OnPaint事件,分段绘制文本:匹配前缀用粗体,其余部分用常规字体
    • 通过SetMatchText传递当前输入文本,触发列表刷新高亮
  3. 交互优化:

    • 下拉展开时自动加载全量选项,输入时实时更新列表并滚动至首个匹配项
    • 选中列表项后自动同步到输入框并关闭下拉面板

内容的提问来源于stack exchange,提问作者linxpatrick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 11:13:11