如何在C#中检测鼠标点击窗体控件外部

3

我有一个自定义控件(自定义ComboBox)。 当我按“箭头按钮”时,它可以正常工作并展开,如果我再次按它,它也会展开,但是如果它已经展开并且我在表单中任何地方点击 - 它会关闭,但是当我尝试打开它时 - 我必须按两次“箭头按钮”。 因此,我需要检测这个时刻,即当我在combobox之外单击时。

打开ComboBox的代码(在ButtonClick中调用)

private void OpenComboBox()       
{
    if (drop_flag)
    {
        ...

        popup.Show(this);
    }
    else
    {
        drop_flag = true;
    }
}

关闭事件

private void popup_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
    drop_flag = false;
}

所以,我想要类似这样的东西。
private ClickedOutsideControl()
{
    dropflag = true;
}

这没有任何意义。drop_flag 的唯一逻辑用法是 if (!drop_flag) { /* show dropdown */ } - Hans Passant
您的选项将始终显示下拉菜单,永远不会通过单击按钮关闭它。 - Udjen
我观察到的行为与您描述的略有不同。当ComboBox下拉列表被打开并且您单击另一个控件时,该控件不会被激活。相反,下拉菜单关闭,您必须再次单击该控件才能激活它。如果这个控件恰好是一个ComboBox,这意味着您必须点击两次才能打开它:一次关闭第一个下拉菜单,第二次打开第二个下拉菜单。我很抱歉要说,但这种行为是“按设计要求”的。 - Olivier Jacot-Descombes
2个回答

0

这并不是那么容易。使用基本的 .net,您总是需要某种控件/引用来拦截绑定点击事件。例如,如果您有一个 MDI 父窗口(主窗口),则可以拦截其点击事件。

另一种方法(使用 Windows API)是钩入系统事件,请参见以下 stackoverflow 帖子 全局鼠标事件处理程序

但是,您应该三思而后行,看看自己是否真的需要这个功能。


如果我使用这个类,我可以检测鼠标点击,但它总是被检测到,我只需要获取外部点击。 - Udjen
您可以检查表单是否位于所放置的坐标下,以及/或者表单的点击事件是否也被触发。正如所说,这有点脆弱,您应该测试并考虑是否这将是清洁的解决方案。 - Boas Enkler
嘿,我认为这个解决方案是极端的措施)) - Udjen

0
在表单中,您可以使用消息过滤器预处理发送到控件的消息。以下是我实施所需功能时的失败尝试
public partial class frmAutoCloseDropDown : Form, IMessageFilter
{
    int _lastMsg;

    public frmAutoCloseDropDown()
    {
        InitializeComponent();
        Application.AddMessageFilter(this);
    }

    // THIS ATTEMPT DOES NOT WORK!
    public bool PreFilterMessage(ref Message m)
    {
        const int WM_LBUTTONDOWN = 0x0201;

        if (m.Msg!= _lastMsg) {
            _lastMsg = m.Msg;
        }
        if (m.Msg == WM_LBUTTONDOWN) {
            // You would have to do this recursively if the combo-boxes were nested inside other controls.
            foreach (ComboBox cbo in Controls.OfType<ComboBox>()) {
                cbo.DroppedDown = false;
            }
        }
        return false;
    }

    // Note: Dispose is created inside *.Designer.cs and you have to move it manually to *.cs
    protected override void Dispose(bool disposing)
    {
        if (disposing) {
            Application.RemoveMessageFilter(this);
            if (components != null) {
                components.Dispose();
            }
        }
        base.Dispose(disposing);
    }
}

为什么它不起作用?可能是因为由 cbo.DroppedDown = false 生成的新消息被附加到消息队列中,只有在左键单击被处理 之后 才会被处理。这意味着即使使用 PreFilterMessage 尝试关闭下拉菜单也太迟了。

一个可能的解决方案是重新向正确的组合框发送 WM_LBUTTONDOWN。您必须解释消息的参数以获取鼠标坐标并查看指向哪个组合框以获取其 HWnd。我的尝试还表明行为也取决于下拉样式。它还会有不良的影响,关闭您刚刚打开的下拉菜单。如果您在下拉列表本身中单击以选择条目会发生什么情况?

可能你可以做到这一点,但代码往往变得非常复杂。这不值得努力。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接