Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET C# MouseEnter listener on a Control WITH Scrollbar

Tags:

c#

.net

winforms

As long as the mouse is over a specific control, we show some form. When the mouse leaves the control, we hide the control after a small timeout. This is standard hover behavior.

However, when a control (for example a Treeview) has a scrollbar, and the mouse is ON or OVER the scrollbar, the events don't fire ...

If we could get a reference to the scrollbar control, this would solve our problem, as we would add the same listener events to the scrollbar. However, the scrollbar isn't accessible as far as I know ...

How can we solve this problem ?

like image 833
Run CMD Avatar asked Jun 09 '10 12:06

Run CMD


2 Answers

The scrollbar is in the tree view's non-client area. When the mouse moves there, it starts generating non-client messages like WM_NCMOUSEMOVE and WM_NCMOUSELEAVE. You would have to sub-class the TreeView and override WndProc() to detect these message.

This doesn't really solve your problem though, you'll still have a hard time with edge cases. A low-tech approach with a Timer always works:

    private Form frmPopup;

    private void treeView1_MouseEnter(object sender, EventArgs e) {
        timer1.Enabled = true;
        if (frmPopup == null) {
            frmPopup = new Form2();
            frmPopup.StartPosition = FormStartPosition.Manual;
            frmPopup.Location = PointToScreen(new Point(treeView1.Right + 20, treeView1.Top));
            frmPopup.FormClosed += (o, ea) => frmPopup = null;
            frmPopup.Show();
        }
    }

    private void timer1_Tick(object sender, EventArgs e) {
        Rectangle rc = treeView1.RectangleToScreen(new Rectangle(0, 0, treeView1.Width, treeView1.Height));
        if (!rc.Contains(Control.MousePosition)) {
            timer1.Enabled = false;
            if (frmPopup != null) frmPopup.Close();
        }
    }
like image 194
Hans Passant Avatar answered Oct 17 '22 10:10

Hans Passant


I think there are several different ways to do this, but the key is your desire to have a timeout on the action. I think a combination of two techniques might work:

Put the control on a panel, docked to fill, and use the MouseEnter of the panel to turn on your behavior -- this will include the control's scrollbar. You can use the MouseLeave event of the panel as well, but you'll have to check the cursor's position to ensure it hasn't moved into the contained control. This method is mostly reliable, but moving the mouse quickly can confuse it.

If you combine this with a timer that starts when your shown/hidden control is shown and check the cursor position periodically. This will work, but your timeout before hiding the control won't necessarily be consistent (because the timer starts when they enter the control). You could stop/start the timer on mousemoves in the control to alleviate this somewhat.

I put together a project of the different methods I tried here: http://lovethedot.s3.amazonaws.com/100609StackoverflowScrollbarQuestion.zip

By docking the control you want to track in the panel, it essentially wraps it and you'll get MouseEnter at the very edge of the tracked control:

    private void panel1_MouseEnter(object sender, EventArgs e)
    {
        this.Text = "in";
    }

    private void panel1_MouseLeave(object sender, EventArgs e)
    {
        if (!new Rectangle(new Point(0, 0), panel1.Size).Contains(panel1.PointToClient(Control.MousePosition)))
            this.Text = "out";
    }

You're tracking entry into the panel surrounding the control, and exit from that panel provided the cursor isn't inside the tracked control.

To get a better "leave" experience, it's combined with a Timer that checks to see where the cursor is as well:

        private void listBox3_MouseEnter(object sender, EventArgs e)
    {
        button1.Visible = true;
        visibleTimer.Stop();
        visibleTimer.Start();
    }

    void visibleTimer_Tick(object sender, EventArgs e)
    {
        if (!new Rectangle(new Point(0, 0), listBox3.Size).Contains(listBox3.PointToClient(Control.MousePosition)))
        {
            visibleTimer.Stop();
            button1.Visible = false;
        }
    }
like image 35
Ragoczy Avatar answered Oct 17 '22 09:10

Ragoczy