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

C# WinForms中点击用户控件外部关闭下拉状态的实现方案

解决WinForms自定义ComboBox控件点击外部收起下拉的问题

我在C# WinForms项目中实现了ComboBoxSearchVendor用户控件,通过IsDroppedDown属性控制下拉展开/收起状态。需求是点击控件外部时自动收起下拉,但尝试添加Leave事件没有效果,现寻求可行实现方案。

控件完整代码:

using Common.Cache;
using Common.Helpers;
using System;
using System.Drawing;
using System.Windows.Forms;

namespace Presentation.UI.Components
{
    public partial class ComboBoxSearchVendor : UserControl
    {
        private bool _IsDroppedDown = false;
        private string _SelectedValue = null;

        public ComboBoxSearchVendor()
        {
            InitializeComponent();
            Height = TBToggle.Height;
            Theme.DataGridView(Data);
            Data.DataSource = VendorCache.vendors;
            BringToFront();
        }

        public bool IsDroppedDown
        {
            get { return _IsDroppedDown; }
            set { _IsDroppedDown = value; }
        }

        public string SelectedValue
        {
            get { return _SelectedValue; }
            set {
                _SelectedValue = value;
                if (_SelectedValue != null)
                {
                    var vendor = VendorCache.Find(_SelectedValue);
                    if (vendor != null) TBToggle.Text = $"{vendor.Ruc} - {vendor.BusinessName}";
                }
            }
        }

        private void DropDown_Click(object sender, EventArgs e)
        {
            DropDownAndUp();
            TBSearch.Focus();
        }

        private void DropDownAndUp()
        {
            _IsDroppedDown = !_IsDroppedDown;
            if (_IsDroppedDown)
            {
                Height = 200;
                DropDownIcon.IconChar = FontAwesome.Sharp.IconChar.ChevronUp;
                DropDownIcon.BackColor = Color.AliceBlue;
                TBToggle.BackColor = Color.AliceBlue;
            }
            else
            {
                Height = TBToggle.Height;
                DropDownIcon.IconChar = FontAwesome.Sharp.IconChar.ChevronDown;
                DropDownIcon.BackColor = Color.White;
                TBToggle.BackColor = Color.White;
            }
        }

        private void TBSearch_KeyUp(object sender, KeyEventArgs e)
        {
            Data.DataSource = VendorCache.Filter(TBSearch.Text);
        }

        private void Data_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            try
            {
                DataGridViewRow Row = Data.CurrentRow;
                _SelectedValue = Row.Cells["Ruc"].Value.ToString();
                TBToggle.Text = $"{Row.Cells["Ruc"].Value} - {Row.Cells["BusinessName"].Value}";
                TBSearch.Clear();
                DropDownAndUp();   
            } catch (Exception ex)
            {
                Helpers.Log(this, ex);
            }
        }

        private void Data_DataError(object sender, DataGridViewDataErrorEventArgs e) { }
    }
}

尝试的Leave事件代码(未生效):

private void ComboBoxSearchVendor_Leave(object sender, EventArgs e)
{
   if (!ContainsFocus)
       IsDroppedDown = false;
}

问题原因

Leave事件失效是因为控件包含子控件(如TBToggle、TBSearch、DataGridView),当焦点移到子控件上时,父控件的ContainsFocus仍为true,导致Leave事件的判断条件不成立,无法触发收起逻辑。

可行解决方案:监听全局鼠标点击

通过注册全局消息过滤器,监听鼠标左键点击事件,判断点击位置是否在控件范围外,若是则收起下拉。

  1. 在控件构造函数中添加消息过滤器:
public ComboBoxSearchVendor()
{
    InitializeComponent();
    Height = TBToggle.Height;
    Theme.DataGridView(Data);
    Data.DataSource = VendorCache.vendors;
    BringToFront();
    // 添加消息过滤器
    Application.AddMessageFilter(new MouseClickFilter(this));
}
  1. 实现消息过滤器类:
private class MouseClickFilter : IMessageFilter
{
    private readonly ComboBoxSearchVendor _control;
    private const int WM_LBUTTONDOWN = 0x0201;

    public MouseClickFilter(ComboBoxSearchVendor control)
    {
        _control = control;
    }

    public bool PreFilterMessage(ref Message m)
    {
        // 仅处理左键点击且下拉处于展开状态的情况
        if (m.Msg == WM_LBUTTONDOWN && _control.IsDroppedDown)
        {
            Point clickScreenPos = Control.MousePosition;
            // 判断点击位置是否在控件的屏幕区域之外
            if (!_control.RectangleToScreen(_control.ClientRectangle).Contains(clickScreenPos))
            {
                // 调用收起方法
                _control.DropDownAndUp();
            }
        }
        return false; // 允许消息继续传递
    }
}
  1. 在控件销毁时移除消息过滤器,避免内存泄漏:
protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        // 移除消息过滤器
        Application.RemoveMessageFilter(new MouseClickFilter(this));
        components?.Dispose();
    }
    base.Dispose(disposing);
}

这样,当用户点击控件外部区域时,下拉菜单会自动收起,同时保留控件内部操作的正常逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 12:28:17