有没有办法检测用户控件外的鼠标点击?

时间:2011-04-18 10:04:24

标签: c# winforms user-controls mouseevent

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

user control

7 个答案:

答案 0 :(得分:13)

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

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

  

EnterLeave事件是分层的,将在父链上下级联,直到达到适当的控制。例如,假设您有一个带有两个GroupBox控件的Form,并且每个GroupBox控件都有一个TextBox控件。当插入符号从一个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();
}

然后出于测试目的,我创建了一个显示命令下拉UserControl的表单:

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不会关闭。嗯,为什么不呢?好吧,因为表单本身并不想要关注。只有控件才能获得焦点,我们没有点击控件。并且因为没有其他东西偷走焦点,Leave事件从未被提升,这意味着UserControl不知道它应该关闭它。

如果您需要UserControl在用户单击表单中的空白区域时自行关闭,则需要对其进行一些特殊处理。由于您说您只关心点击,因此您只需处理表单的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);
   }
}

是的,这最后一部分感觉像是黑客。正如其他人所提到的,更好的解决方案是使用SetCapture function指示Windows在UserControl窗口上捕获鼠标。控件的Capture property提供了一种更简单的方法来执行相同的操作。

答案 1 :(得分:2)

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

但是在你的情况下,正如@Martin建议的那样,处理Leave事件就足够了。

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

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

答案 2 :(得分:2)

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

enter code here

然后:

protected override void OnMouseDown(MouseEventArgs e)
{

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

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

答案 3 :(得分:0)

您可能正在寻找休假活动:

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

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

答案 4 :(得分:0)

我只想分享这个。这样做可能不是一个好方法,但是看起来它适用于在假的“ 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 
        }
    }

答案 5 :(得分:-1)

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

打开下拉列表时,在控件的父窗体上注册一个click事件:

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);

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

答案 6 :(得分:-1)

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

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