如何在鼠标悬停事件中显示/隐藏控件

4
近期我在开发一个应用,却遇到了一个简单却令人困扰的问题。
我想要在进入其父控件时使特定控件可见/不可见,并且能够对该控件执行事件(例如:点击)。但问题是,当我进入我想要显示的控件时,鼠标悬停甚至无法作用于其父控件,这导致了我想要显示的控件闪烁不定(鼠标悬停有效 -> 控件显示 -> 鼠标悬停不再有效 -> 控件隐藏 -> 鼠标悬停有效 -> 等等)。
我已经找到了这个“解决方案”来帮助我保持“稳定”。
    // Timer to make the control appearing properly.
    private void Timer_Elapsed(object o, ElapsedEventArgs e)
    {
        try
        {
            ItemToHideDisplay.Visible = true;
            var mousePoint = this.PointToClient(Cursor.Position);
            if (mousePoint.X > this.Width ||
                mousePoint.X < 0 ||
                mousePoint.Y > this.Height ||
                mousePoint.Y < 0)
            {
                HideDisplayTimer.Stop();
                ItemToHideDisplay.Visible = false;
                base.OnMouseLeave(e);
            }
        }
        catch
        {
            // We don't want the application to crash...
        }
    }

    protected override void OnMouseEnter(EventArgs e)
    {
        HideDisplayTimer.Start();
        base.OnMouseEnter(e);
    }

基本上,当我进入对象时,一个计时器开始工作,并每50毫秒检查一次鼠标是否在父控件中。如果是,则显示控件。如果不是,则停止计时器并隐藏控件。

这个方法有效。太好了。但我认为这个解决方案非常丑陋。

所以我的问题是:有没有另一种方法,比这个更美观的解决方案?

如果我表述不清楚,请告诉我 :)

提前感谢!

编辑:嘿,我想我已经找到了答案!

诀窍是使用以下代码覆盖父控件的OnMouseLeave:

    protected override void OnMouseLeave(EventArgs e)
    {
        var mousePoint = this.PointToClient(Cursor.Position);
        if (mousePoint.X > this.Width ||
mousePoint.X < 0 ||
mousePoint.Y > this.Height ||
mousePoint.Y < 0)
        {
            base.OnMouseLeave(e);
        }
    }

这样,当进入我显示的控件(进入父控件)时,鼠标离开事件不会触发!它有效了!
谢谢你的回答。我想你可以继续发布你的想法,因为我在互联网上没有看到很多解决方案 :)
3个回答

0
你可以将一个控件“透明”到鼠标事件,鼠标事件会直接穿过它。
你需要编写自己的类来继承你想要的控件。例如,如果一个Label是你特定的控件,那么创建一个继承Label的新类-你懂的。 :-)
在你的新类中,你可以使用窗口消息使你的控件忽略鼠标事件:
protected override void WndProc(ref Message m)
{
    const int WM_NCHITTEST = 0x0084;
    const int HTTRANSPARENT = -1;

    switch(m.Msg)
    {
        case WM_NCHITTEST:
            m.Result = (IntPtr)HTTRANSPARENT;
            break;
        default:
            base.WndProc(ref m);
    }
}

您可以在 MSDN 上了解更多关于 WndProc 的信息。


但我仍然希望能够点击显示的控件。这种过滤方式可行吗? - Matthieu
要么你在问题中没有提到,要么我没找到。无论如何,这使得问题更加复杂,但是我已经做到了。我可以在几个小时内查看我的代码,但你也可以搜索 SetLayeredWindowAttributes。只要注意你正在深入研究 winforms... ;) - KeyNone
我没说过那个,抱歉。不过我已经找到解决方案了,你可以看一下:)。谢谢这个建议,我相信它将来会帮助我! - Matthieu

0

您可以为您的表单注册一个消息过滤器,预处理您的表单的鼠标移动事件。因此,您不必覆盖您的子控件等。

消息过滤器一旦在父表单中注册,也将适用于子表单,因此,即使您的表单的一部分被子表单覆盖,您的目标控件仍应根据鼠标位置出现或消失。

在以下示例中,有一个面板在表单上,并且该面板内有一个按钮。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    internal void CaptureMouseMove(Point location)
    {
        if (panel1.RectangleToScreen(panel1.ClientRectangle).Contains(location))
        {
            button1.Visible = true;
            Console.WriteLine(location + "in " + panel1.RectangleToScreen(panel1.ClientRectangle));
        }
        else
        {
            button1.Visible = false;
            Console.WriteLine(location + "out " + panel1.RectangleToScreen(panel1.ClientRectangle));
        }
    }

    internal bool Form1_ProcessMouseMove(Message m)
    {
        Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
        Control ctr = Control.FromHandle(m.HWnd);
        if (ctr != null)
        {
            pos = ctr.PointToScreen(pos);
        }
        else
        {
            pos = this.PointToScreen(pos);
        }
        this.CaptureMouseMove(pos);

        return false;
    }

    private MouseMoveMessageFilter mouseMessageFilter;

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        // add filter here
        this.mouseMessageFilter = new MouseMoveMessageFilter();
        this.mouseMessageFilter.TargetForm = this;
        this.mouseMessageFilter.ProcessMouseMove = this.Form1_ProcessMouseMove;
        Application.AddMessageFilter(this.mouseMessageFilter);
    }

    protected override void OnClosed(EventArgs e)
    {
        // remove filter here
        Application.RemoveMessageFilter(this.mouseMessageFilter);
        base.OnClosed(e);
    }

    private class MouseMoveMessageFilter : IMessageFilter
    {
        public Form TargetForm { get; set; }
        public Func<Message, bool> ProcessMouseMove;

        public bool PreFilterMessage(ref Message m)
        {
            if (TargetForm.IsDisposed) return false;

            //WM_MOUSEMOVE
            if (m.Msg == 0x0200)
            {
                if (ProcessMouseMove != null)
                   return ProcessMouseMove(m);
            }

            return false;
        }
    }
}

-1

我会在这里做一个小技巧 :) 我会将控件包装到一个新的控件中 :) 看看这个。

XAML:

<UserControl MouseEnter="Border_MouseEnter" MouseLeave="UserControl_MouseLeave" Margin="100" Background="Transparent">
    <UserControl x:Name="ControlToHide" Background="Red">
        <Button Content="hi"  Width="100" Height="100"/>
    </UserControl>
</UserControl>

代码后台:

private void Border_MouseEnter(object sender, MouseEventArgs e)
{
    this.ControlToHide.Visibility = System.Windows.Visibility.Hidden;
}

private void UserControl_MouseLeave(object sender, MouseEventArgs e)
{
    this.ControlToHide.Visibility = System.Windows.Visibility.Visible;        
}

它很轻巧、易用且运行良好。享受它吧。


2
这个问题被标记为winforms。看起来像是WPF。 - KeyNone
升级电源,真的。很抱歉,但这个想法可以被复制。 - Jarek
我已经尝试过包装的东西(在WinForms中)。它...有时候有效。但它并不是非常稳定。 - Matthieu
不一定。如果我没记错的话,当你进入子项时,Winforms会立即触发父级的“MouseLeave”事件 - 但我可能是错的。 - KeyNone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接