C# 父控件鼠标进入/离开事件与子控件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1161280/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Parent Control Mouse Enter/Leave Events With Child Controls
提问by Paul Williams
I have a C# .NET 2.0 WinForms app. My app has a control that is a container for two child controls: a label, and some kind of edit control. You can think of it like this, where the outer box is the parent control:
我有一个 C# .NET 2.0 WinForms 应用程序。我的应用程序有一个控件,它是两个子控件的容器:一个标签和某种编辑控件。你可以这样想,其中外框是父控件:
+---------------------------------+ | [Label Control] [Edit Control] | +---------------------------------+
I am trying to do something when the mouse enters or leaves the parent control, but I don't care if the mouse moves into one of its children. I want a single flag to represent "the mouse is somewhere inside the parent or children" and "the mouse has moved outside of the parent control bounds".
当鼠标进入或离开父控件时,我试图做一些事情,但我不在乎鼠标是否移动到它的一个子控件中。我想要一个标志来表示“鼠标位于父级或子级内的某处”和“鼠标已移出父级控件边界”。
I've tried handling MouseEnter and MouseLeave on the parent and both child controls, but this means the action begins and ends multiple times as the mouse moves across the control. In other words, I get this:
我已经尝试在父控件和两个子控件上处理 MouseEnter 和 MouseLeave,但这意味着随着鼠标在控件上移动,动作开始和结束多次。换句话说,我明白了:
Parent.OnMouseEnter (start doing something) Parent.OnMouseLeave (stop) Child.OnMouseEnter (start doing something) Child.OnMouseLeave (stop) Parent.OnMouseEnter (start doing something) Parent.OnMouseLeave (stop)
The intermediate OnMouseLeave events cause some undesired effects as whatever I'm doing gets started and then stopped. I want to avoid that.
中间的 OnMouseLeave 事件会导致一些不良影响,因为我正在做的任何事情都会开始然后停止。我想避免这种情况。
I don't want to capture the mouse as the parent gets the mouse over, because the child controls need their mouse events, and I want menu and other shortcut keys to work.
我不想在父级将鼠标悬停时捕获鼠标,因为子控件需要它们的鼠标事件,并且我希望菜单和其他快捷键起作用。
Is there a way to do this inside the .NET framework? Or do I need to use a Windows mouse hook?
有没有办法在 .NET 框架内做到这一点?还是我需要使用 Windows 鼠标挂钩?
采纳答案by Paul Williams
After more research, I discovered the Application.AddMessageFilter method. Using this, I created a .NET version of a mouse hook:
经过更多研究,我发现了Application.AddMessageFilter 方法。使用这个,我创建了一个 .NET 版本的鼠标钩子:
class MouseMessageFilter : IMessageFilter, IDisposable
{
public MouseMessageFilter()
{
}
public void Dispose()
{
StopFiltering();
}
#region IMessageFilter Members
public bool PreFilterMessage(ref Message m)
{
// Call the appropriate event
return false;
}
#endregion
#region Events
public class CancelMouseEventArgs : MouseEventArgs
{...}
public delegate void CancelMouseEventHandler(object source, CancelMouseEventArgs e);
public event CancelMouseEventHandler MouseMove;
public event CancelMouseEventHandler MouseDown;
public event CancelMouseEventHandler MouseUp;
public void StartFiltering()
{
StopFiltering();
Application.AddMessageFilter(this);
}
public void StopFiltering()
{
Application.RemoveMessageFilter(this);
}
}
Then, I can handle the MouseMove event in my container control, check to see if the mouse is inside my parent control, and start the work. (I also had to track the last moused over parent control so I could stop the previously started parent.)
然后,我可以在我的容器控件中处理 MouseMove 事件,检查鼠标是否在我的父控件内,然后开始工作。(我还必须跟踪最后一次鼠标悬停在父控件上,以便我可以停止之前启动的父控件。)
---- Edit ----
- - 编辑 - -
In my form class, I create and hookup the filter:
在我的表单类中,我创建并连接了过滤器:
public class MyForm : Form
{
MouseMessageFilter msgFilter;
public MyForm()
{...
msgFilter = new MouseMessageFilter();
msgFilter.MouseDown += new MouseMessageFilter.CancelMouseEventHandler(msgFilter_MouseDown);
msgFilter.MouseMove += new MouseMessageFilter.CancelMouseEventHandler(msgFilter_MouseMove);
}
private void msgFilter_MouseMove(object source, MouseMessageFilter.CancelMouseEventArgs e)
{
if (CheckSomething(e.Control)
e.Cancel = true;
}
}
回答by Paul Sasik
i don't think you need to hook the message pump to solve this. Some flagging in your UI should do the trick. i'm thinking that you create a member variable, something like Control _someParent, in your controlling class which will take the reference of the parent control when one of your OnMouseEnter handlers is called. Then, in OnMouseLeave, check the value of the _someParent "flag" and if it's the same as the current sender's then do not actually stop your processing, just return. Only when the parent is different do you stop and reset _someParent to null.
我认为你不需要挂钩消息泵来解决这个问题。用户界面中的一些标记应该可以解决问题。我在想你在你的控制类中创建了一个成员变量,比如 Control _someParent,当你的 OnMouseEnter 处理程序之一被调用时,它会引用父控件。然后,在 OnMouseLeave 中,检查 _someParent “标志”的值,如果它与当前发件人的相同,则实际上不要停止处理,只需返回即可。只有当父对象不同时,您才停止并将 _someParent 重置为 null。
回答by Fredrik M?rk
You can find out whether the mouse is within the bounds of your control like this (assuming this code resides in your container control; if not, replace this
with a reference to the container control):
您可以像这样找出鼠标是否在您的控件范围内(假设此代码驻留在您的容器控件中;如果不是,则替换this
为对容器控件的引用):
private void MyControl_MouseLeave(object sender, EventArgs e)
{
if (this.ClientRectangle.Contains(this.PointToClient(Cursor.Position)))
{
// the mouse is inside the control bounds
}
else
{
// the mouse is outside the control bounds
}
}
回答by Eric
I feel I found a much better solution than the currently top accepted solution.
我觉得我找到了一个比目前最受认可的解决方案更好的解决方案。
The problem with other proposed solutions is that they are either fairly complex (directly handling lower level messages).
其他提议的解决方案的问题在于它们要么相当复杂(直接处理较低级别的消息)。
Or they fail corner cases: relying on the mouse position on MouseLeave can cause you to miss the mouse exiting if the mouse goes straight from inside a child control to outside the container.
或者它们在极端情况下失败:如果鼠标直接从子控件内部移动到容器外部,则依赖 MouseLeave 上的鼠标位置可能会导致您错过鼠标退出。
While this solution isn't entirely elegant, it is straightforward and works:
虽然这个解决方案并不完全优雅,但它很简单并且有效:
Add a transparent control that takes up the entire space of the container that you want to receive MouseEnter and MouseLeave events for.
添加一个透明控件,该控件占据要为其接收 MouseEnter 和 MouseLeave 事件的容器的整个空间。
I found a good transparent control in Amed's answer here: Making a control transparent
我在 Amed 的回答中找到了一个很好的透明控件:Making a control transparent
Which I then stripped down to this:
然后我将其精简为:
public class TranspCtrl : Control
{
public TranspCtrl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, true);
this.BackColor = Color.Transparent;
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | 0x20;
return cp;
}
}
}
Example usage:
用法示例:
public class ChangeBackgroundOnMouseEnterAndLeave
{
public Panel Container;
public Label FirstLabel;
public Label SecondLabel;
public ChangeBackgroundOnMouseEnterAndLeave()
{
Container = new Panel();
Container.Size = new Size(200, 60);
FirstLabel = new Label();
FirstLabel.Text = "First Label";
FirstLabel.Top = 5;
SecondLabel = new Label();
SecondLabel.Text = "Second Lable";
SecondLabel.Top = 30;
FirstLabel.Parent = Container;
SecondLabel.Parent = Container;
Container.BackColor = Color.Teal;
var transparentControl = new TranspCtrl();
transparentControl.Size = Container.Size;
transparentControl.MouseEnter += MouseEntered;
transparentControl.MouseLeave += MouseLeft;
transparentControl.Parent = Container;
transparentControl.BringToFront();
}
void MouseLeft(object sender, EventArgs e)
{
Container.BackColor = Color.Teal;
}
void MouseEntered(object sender, EventArgs e)
{
Container.BackColor = Color.Pink;
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var test = new ChangeBackgroundOnMouseEnterAndLeave();
test.Container.Top = 20;
test.Container.Left = 20;
test.Container.Parent = this;
}
}
Enjoy proper MouseLeave and MouseEnter events!
享受适当的 MouseLeave 和 MouseEnter 事件!
回答by Darryl
I had the exact same need. Paul Williams' answer provided me with the core idea, but I had difficulty understanding the code. I found another take here, and together, the two examples helped me develop my own version.
我有完全相同的需求。保罗威廉姆斯的回答为我提供了核心思想,但我很难理解代码。我在这里找到了另一个例子,这两个例子一起帮助我开发了我自己的版本。
To initialize, you pass the container control of interest into the ContainerMessageFilter
constructor. The class collects the window handles of the container and all child controls within it.
要初始化,您将感兴趣的容器控件传递给ContainerMessageFilter
构造函数。该类收集容器的窗口句柄以及其中的所有子控件。
Then, during operation, the class filters the WM_MOUSEMOVE
message, checking the messages's HWnd
to determine what control the mouse is moving within. In this way, it determines when the mouse has moved within or outside the set of controls within the container that it is watching.
然后,在操作期间,该类过滤WM_MOUSEMOVE
消息,检查消息HWnd
以确定鼠标在哪个控件内移动。通过这种方式,它确定鼠标何时在它所观察的容器内的控件组内或外移动。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
public class ContainerMessageFilter : IMessageFilter {
private const int WM_MOUSEMOVE = 0x0200;
public event EventHandler MouseEnter;
public event EventHandler MouseLeave;
private bool insideContainer;
private readonly IEnumerable<IntPtr> handles;
public ContainerMessageFilter( Control container ) {
handles = CollectContainerHandles( container );
}
private static IEnumerable<IntPtr> CollectContainerHandles( Control container ) {
var handles = new List<IntPtr> { container.Handle };
RecurseControls( container.Controls, handles );
return handles;
}
private static void RecurseControls( IEnumerable controls, List<IntPtr> handles ) {
foreach ( Control control in controls ) {
handles.Add( control.Handle );
RecurseControls( control.Controls, handles );
}
}
public bool PreFilterMessage( ref Message m ) {
if ( m.Msg == WM_MOUSEMOVE ) {
if ( handles.Contains( m.HWnd ) ) {
// Mouse is inside container
if ( !insideContainer ) {
// was out, now in
insideContainer = true;
OnMouseEnter( EventArgs.Empty );
}
}
else {
// Mouse is outside container
if ( insideContainer ) {
// was in, now out
insideContainer = false;
OnMouseLeave( EventArgs.Empty );
}
}
}
return false;
}
protected virtual void OnMouseEnter( EventArgs e ) {
var handler = MouseEnter;
handler?.Invoke( this, e );
}
protected virtual void OnMouseLeave( EventArgs e ) {
var handler = MouseLeave;
handler?.Invoke( this, e );
}
}
In the following usage example, we want to monitor mouse entry and exit for a Panel
and the child controls that it contains:
在以下使用示例中,我们要监视 aPanel
及其包含的子控件的鼠标进入和退出:
public partial class Form1 : Form {
private readonly ContainerMessageFilter containerMessageFilter;
public Form1() {
InitializeComponent();
containerMessageFilter = new ContainerMessageFilter( panel1 );
containerMessageFilter.MouseEnter += ContainerMessageFilter_MouseEnter;
containerMessageFilter.MouseLeave += ContainerMessageFilter_MouseLeave;
Application.AddMessageFilter( containerMessageFilter );
}
private static void ContainerMessageFilter_MouseLeave( object sender, EventArgs e ) {
Console.WriteLine( "Leave" );
}
private static void ContainerMessageFilter_MouseEnter( object sender, EventArgs e ) {
Console.WriteLine( "Enter" );
}
private void Form1_FormClosed( object sender, FormClosedEventArgs e ) {
Application.RemoveMessageFilter( containerMessageFilter );
}
}