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

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

用户控制

所以我终于明白,当用户点击它之外时你只想关闭它。 在这种情况下, Leave事件应该可以正常工作…出于某种原因,我得到的印象是,只要他们将鼠标移到您的自定义下拉列表之外,您就希望它关闭。 只要您的控件失去焦点,就会引发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 on命令的表单:

 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函数指示Windows在UserControl窗口上捕获鼠标。 控件的Capture属性提供了一种更简单的方法来执行相同的操作。

从技术上讲,您需要p / invoke SetCapture()才能接收在您的控件之外发生的点击事件。

但在你的情况下,正如@Martin所说,处理Leave事件应该足够了。

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

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

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

 enter code here 

然后:

 protected override void OnMouseDown(MouseEventArgs e) { if (!theListBox.Bounds.Contains(e.Location)) { theListBox.Visible = false; } } 

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

你可能正在寻找离开事件:

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

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

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

打开下拉列表时,在控件的父窗体上注册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); 

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