简体   繁体   中英

How to display/hide a control on mouse hover event

I have been developing an application lately, and found myself stuck on a simple but annoying problem.

I would like to make a specific control visible/not visible when I enter its parent, and being able to perform events (eg: click) on this control. The problem is, the mouse hover even does not work on the parent when I enter the very control I want to display. This result in a flickering of the control I want to display (mouse hover works -> control is displayed -> mouse hover does not work anymore -> control is hidden -> mouse hover works -> etc).

I have found this "solution" to help me have something "stable".

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

Basically, when I enter the object, a timer starts and checks every 50ms if the mouse is in the parent. If so, the control is displayed. If not, the timer is stopped and the control hidden.

This works. Yay. But I find this solution very ugly.

So my question is: is there another approach, another solution more beautiful than this one?

Tell me if I am not clear enough :)

Thanks in advance!

EDIT: Hey I think I have found it myself!

The trick is to override OnMouseLeave of the parent control with this:

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

This way, when entering the control I have displayed (entering the parent control), the mouse leave event is not triggered! It works!

Thanks for your answers. You can continue to post your ideas I guess, because I don't see a lot of solutions out there on the internet :)

You can make a control "transparent" to mouse events. So mouse events will just pass through it.

You have to write your own class inheriting your desired control. If, for example, a Label is your specific control, then create a new class inheriting Label - you get the point. :-)

In your new class you then make use of the window messages to make your control ignore mouse events:

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

You can read more about WndProc at MSDN .

You could register a message filter for your form and pre-process the mouse move events of your form. Thanks to this, you don't have to override your child controls etc.

The message filter , once registered in the parent form, will work for the child forms too, so even when a part of your form is covered by a child form, your target control should still appear/dissapear depending on the mouse position.

In the following example, there is a panel on a form, and that panel has a button inside.

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

I would do a trick here :) I would wrap the control into a new control :) check this out.

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>

Code behind:

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

it's light, easy and working . Enjoy

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM