【问题标题】:Is there any way to detect a mouseclick outside a user control?有什么方法可以检测用户控件之外的鼠标点击?
【发布时间】:2022-04-11 03:48:25
【问题描述】:

我正在创建一个自定义下拉框,我想在鼠标在下拉框外单击时进行注册,以便将其隐藏。是否可以检测到控件外的点击?或者我应该在包含表单上创建一些机制并在任何下拉框打开时检查鼠标点击?

【问题讨论】:

  • 你不能使用控件的“离开”事件吗?我认为,当您的新控件失去焦点时,这将被触发。
  • 检测下拉菜单何时失去焦点?如果您离开控件,您可能还想关闭/隐藏下拉菜单。

标签: c# winforms user-controls mouseevent


【解决方案1】:

所以我终于明白,你只希望它在用户点击时关闭它。在这种情况下,Leave event 应该可以正常工作...出于某种原因,我的印象是,每当他们将鼠标移到自定义下拉列表之外时,您都希望它关闭。每当您的控件失去焦点时,就会引发Leave 事件,如果用户单击其他内容,它肯定会失去焦点,因为他们单击的内容获得焦点。

文档还说,此事件根据需要在控制链上下级联:

EnterLeave 事件是分层的,将在父链上下级联,直到达到适当的控制。例如,假设您有一个带有两个 GroupBox 控件的 Form,并且每个 GroupBox 控件都有一个 TextBox 控件。当插入符号从一个 TextBox 移动到另一个时,会为 TextBox 和 GroupBox 引发 Leave 事件,并为另一个 GroupBox 和 TextBox 引发 Enter 事件。

覆盖您的 UserControl 的 OnLeave 方法是处理此问题的最佳方法:

protected override void OnLeave(EventArgs e)
{
   // Call the base class
   base.OnLeave(e);

   // When this control loses the focus, close it
   this.Hide();
}

然后出于测试目的,我创建了一个表单,在命令中显示下拉用户控件:

public partial class Form1 : Form
{
   private UserControl1 customDropDown;

   public Form1()
   {
      InitializeComponent();

      // Create the user control
      customDropDown = new UserControl1();

      // Add it to the form's Controls collection
      Controls.Add(customDropDown);
      customDropDown.Hide();
   }

   private void button1_Click(object sender, EventArgs e)
   {         
      // Display the user control
      customDropDown.Show();
      customDropDown.BringToFront();   // display in front of other controls
      customDropDown.Select();         // make sure it gets the focus
   }
}

一切都与上面的代码完美配合,除了有一件事:如果用户点击表单的空白区域,UserControl 不会关闭。嗯,为什么不呢?好吧,因为表单本身不需要焦点。只有 controls 可以获得焦点,我们没有点击一个控件。而且因为没有其他东西偷走了焦点,Leave 事件从未被引发,这意味着 UserControl 不知道它应该自行关闭。

如果您需要 UserControl 在用户单击表单中的空白区域时自行关闭,则需要对此进行一些特殊情况处理。既然你说你只关心clicks,你可以只处理表单的Click 事件,并将焦点设置到不同的控件:

protected override void OnClick(EventArgs e)
{
   // Call the base class
   base.OnClick(e);

   // See if our custom drop-down is visible
   if (customDropDown.Visible)
   {
      // Set the focus to a different control on the form,
      // which will force the drop-down to close
      this.SelectNextControl(customDropDown, true, true, true, true);
   }
}

是的,这最后一部分感觉就像一个 hack。正如其他人所提到的,更好的解决方案是使用SetCapture function 来指示Windows 将鼠标捕获到您的UserControl 窗口上。控件的Capture property 提供了一种更简单的方法来做同样的事情。

【讨论】:

  • 谢谢,当鼠标离开用户控件时,我从不希望它关闭,其中一位海报只是建议将此作为替代解决方案。我将尝试实施您的答案。如果可行,这将是完美的。
【解决方案2】:

从技术上讲,您需要 p/invoke SetCapture() 才能接收您无法控制的点击事件。

但就您而言,按照@Martin 的建议处理Leave 事件就足够了。

编辑:在寻找SetCapture() 的用法示例时,我遇到了Control.Capture 属性,我并不知道。使用该属性意味着您不必 p/invoke 任何东西,这在我的书中总是一件好事。

因此,您必须在显示下拉菜单时将Capture 设置为true,然后确定鼠标指针是否位于单击事件处理程序中的控件内,如果不是,请设置Capturefalse 并关闭下拉菜单。

更新: 您还可以使用 Control.Focused 属性来确定在使用键盘或鼠标时控件是否获得或失去焦点,而不是使用 Capture 与 MSDN Capture 页面中提供的相同示例。

【讨论】:

  • 我在用户控件中有其他控件,当这些控件处于焦点时,会为用户控件触发离开事件。
  • @Bildsoe,如果我理解正确,当用户单击您的用户控件内的另一个控件时,您希望将下拉菜单保持打开状态,但如果她/他在您的用户控件之外单击,则将其关闭?
  • @Frédéric Hamidi - 当用户单击我的用户控件中的控件时,下拉菜单也应该关闭,但当用户将鼠标悬停在其中的另一个控件时不会关闭。
  • @Bildsoe,如果您只将鼠标悬停在另一个控件上,则不应引发 Leave 事件。这些控件是否强制将焦点放在悬停上?
  • Bildsoe:听起来您可能使用的是 control.mouseleave 而不是 control.leave。后者只会在另一个控件成为焦点时触发 - 即单击。当鼠标不再位于控件上方时,前者会触发。
【解决方案3】:

处理表单的MouseDown 事件,或覆盖表单的OnMouseDown 方法:

enter code here

然后:

protected override void OnMouseDown(MouseEventArgs e)
{

    if (!theListBox.Bounds.Contains(e.Location)) 
    {
        theListBox.Visible = false;
    }
}

Contains 方法旧的System.Drawing.Rectangle 可用于指示是否 一个点包含在一个矩形内。控件的 Bounds 属性是 由控件边缘定义的外部Rectangle。那个地点 MouseEventArgs 的属性是相对于 Control 的 Point 收到MouseDown 事件。窗体中控件的 Bounds 属性是 相对于窗体。

【讨论】:

  • 这实际上不起作用,因为如果您点击另一个控件,表单不会收到点击
【解决方案4】:

您可能正在寻找请假事件:

http://msdn.microsoft.com/en-us/library/system.windows.forms.control.leave.aspx

当输入焦点离开控件时发生离开。

【讨论】:

  • 但“正常”下拉菜单保持打开状态,直到单击鼠标才会关闭。你知道怎么做吗?
【解决方案5】:

我只是想分享这个。这样做可能不是一个好方法,但看起来它适用于在假“MouseLeave”上关闭的下拉面板,我试图将它隐藏在 Panel MouseLeave 上但它不起作用,因为从面板移动到按钮离开面板,因为按钮不是面板本身。可能有更好的方法可以做到这一点,但我分享这个是因为我花了大约 7 个小时来弄清楚如何让它工作。感谢@FTheGodfather

但它只有在鼠标在表单上移动时才有效。如果有面板,这将不起作用。

    private void click_to_show_Panel_button_MouseDown(object sender, MouseEventArgs e)
    {
        item_panel1.Visible = true; //Menu Panel
    }



    private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        if (!item_panel1.Bounds.Contains(e.Location))
        {
            item_panel1.Visible = false; // Menu panel 
        }
    }

【讨论】:

    【解决方案6】:

    这是我自己做的,我就是这样做的。

    当下拉菜单打开时,在控件的父窗体上注册一个点击事件:

    this.Form.Click += new EventHandler(CloseDropDown);
    

    但这只会把你带到一半。您可能希望您的下拉菜单在当前窗口被停用时也关闭。对我来说,最可靠的检测方法是通过一个计时器来检查当前处于活动状态的窗口:

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();
    

    var timer = new Timer();
    timer.Interval = 100;
    timer.Tick += (sender, args) =>
    {
        IntPtr f = GetForegroundWindow();
        if (this.Form == null || f != this.Form.Handle)
        {
            CloseDropDown();
        }
    };
    

    您当然应该只在下拉菜单可见时才让计时器运行。此外,在打开下拉菜单时,您可能希望在父表单上注册一些其他事件:

    this.Form.LocationChanged += new EventHandler(CloseDropDown);
    this.Form.SizeChanged += new EventHandler(CloseDropDown);
    

    别忘了在 CloseDropDown 方法中取消注册所有这些事件 :)

    编辑:

    我忘了,你还应该在你的控件上注册 Leave 事件,看看另一个控件是否被激活/点击:

    this.Leave += new EventHandler(CloseDropDown);
    

    我想我现在知道了,这应该涵盖所有基础。如果我遗漏了什么,请告诉我。

    【讨论】:

    • 嗯,不。不要定期轮询当前的前台窗口。这绝对是对 API 的滥用。你有一个非常好的方法来完成这里的要求。它涉及捕获鼠标。查找SetCapture
    • 很棒的建议@Cody,如果我需要更新此控件,我一定会检查出来。我建议你自己看看@Bildsoe。我还想指出,我通常将下拉菜单实现为单独的表单,因此您必须使用 GetForegroundWindow 方法将其考虑在内。
    【解决方案7】:

    如果你有表单,你可以像这样简单地使用Deactivate 事件:

    protected override void OnDeactivate(EventArgs e)
    {
        this.Dispose();
    }
    

    【讨论】:

    • 这个处理程序与其他事件相关,但与鼠标点击无关
    猜你喜欢
    • 1970-01-01
    • 2012-02-08
    • 1970-01-01
    • 1970-01-01
    • 2010-09-21
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多