我有一个C#.NET 2.0 WinForms应用程序。我的应用程序有一个控件,它是两个子控件的容器:标签和某种编辑控件。您可以这样想,外框是父控件:
+---------------------------------+ | [Label Control] [Edit Control] | +---------------------------------+
我试图在鼠标进入或离开父控件时做某事,但我不在乎鼠标是否移动到其中一个子控件中。我想要一个标志来表示“鼠标在父或子的内部”和“鼠标移动到父控件边界之外”。
我尝试在父级和两个子控件上处理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)
中间的OnMouseLeave事件会导致一些不受欢迎的影响,因为我正在做的事情开始然后停止。我想避免这种情况。
我不想捕获鼠标,因为父控件需要鼠标移动,因为子控件需要鼠标事件,我希望菜单和其他快捷键能够工作。
有没有办法在.NET框架内执行此操作?或者我需要使用Windows鼠标挂钩吗?
答案 0 :(得分:8)
经过更多的研究,我发现了Application.AddMessageFilter method。使用它,我创建了一个鼠标钩子的.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);
}
}
然后,我可以在容器控件中处理MouseMove事件,检查鼠标是否在我的父控件中,然后开始工作。 (我还必须跟踪最后一个moused over parent控件,这样我就可以停止之前启动的父控件了。)
----编辑----
在我的表单类中,我创建并连接过滤器:
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;
}
}
答案 1 :(得分:7)
我觉得我找到了比当前最受欢迎的解决方案更好的解决方案。
其他提出的解决方案的问题是它们要么相当复杂(直接处理较低级别的消息)。
或者他们在角落情况下失败:如果鼠标直接从子控件内部移动到容器外部,依赖MouseLeave上的鼠标位置会导致您错过鼠标退出。
虽然这个解决方案并不完全优雅,但它很简单并且有效:
添加一个透明控件,占用要接收MouseEnter和MouseLeave事件的容器的整个空间。
我在Amed的答案中找到了一个很好的透明控制:Making a control transparent
然后我将其删除:
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;
}
}
}
使用示例:
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;
}
}
享受正确的MouseLeave和MouseEnter事件!
答案 2 :(得分:3)
你可以像这样找出鼠标是否在你的控件的范围内(假设这个代码驻留在你的容器控件中;如果没有,用引用容器控件替换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
}
}
答案 3 :(得分:2)
我认为你不需要挂钩消息泵来解决这个问题。您的UI中的某些标记应该可以解决问题。我在想你在你的控制类中创建一个成员变量,比如Control _someParent,当你的一个OnMouseEnter处理程序被调用时,它将引用父控件。然后,在OnMouseLeave中,检查_someParent“flag”的值,如果它与当前发送者的值相同,则不要实际停止处理,只需返回。只有当父级不同时,才会停止并将_someParent重置为null。
答案 4 :(得分:1)
我有完全相同的需求。 Paul Williams的回答为我提供了核心思想,但我很难理解代码。我找到了另一个here,这两个例子帮助我开发了自己的版本。
要进行初始化,请将感兴趣的容器控件传递给ContainerMessageFilter
构造函数。该类收集容器的窗口句柄及其中的所有子控件。
然后,在操作期间,该类过滤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 );
}
}
在以下用法示例中,我们要监视Panel
及其包含的子控件的鼠标进入和退出:
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 );
}
}