Skip to content

Instantly share code, notes, and snippets.

@franzalex
Created May 18, 2017 00:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save franzalex/9658c101639af7da4ebd179efd560a7d to your computer and use it in GitHub Desktop.
Save franzalex/9658c101639af7da4ebd179efd560a7d to your computer and use it in GitHub Desktop.
This class allows a control to raise the MouseHover multiple times without having to move the mouse outside the control after each MouseHover event.
/****************************************************************************************
* Name: RepeatedHover.cs
* Author: Franz Alex Gaisie-Essilfie
* Description: Allows a control to raise the MouseHover event multiple times without
* having to move the mouse outside the control after each MouseHover event.
*
* Premise: From the documentation⁽¹⁾, a control's mouse events are as follows:
* > MouseEnter
* > MouseMove
* > MouseHover / MouseDown / MouseWheel
* > MouseUp
* > MouseLeave
* This makes room for the MouseHover event to be raised only once, even
* when the control's unobstructed DisplayRectangle is large enough to
* allow moving the pointer to a new location where a new MouseHover event
* could theoretically be raised.
* By using this class, the above-described constraint is bypassed, allowing
* a control to potentially raise the MouseHover event several times as
* long as the mouse pointer moves beyond the bounds after each MouseHover
* event has been raised.
*
* Change Log:
* Date | Description
* ------------|--------------------------------------------------------------
* 2017-05-18 | Initial design
*
****************************************************************************************
* ⁽¹⁾: https://msdn.microsoft.com/en-us/library/system.windows.forms.control.mousehover.aspx#Remarks
****************************************************************************************
*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
/// <summary>
/// Allows a control to raise the <see cref="Control.MouseHover">Hover</see> event multiple
/// times without having to move the mouse outside the control.
/// </summary>
public class RepeatedHover
{
private HashSet<Control> hookedControls;
private Rectangle hoverRect;
private Control lastControl;
private Point lastLocation;
private Timer hoverTimer;
/// <summary>Occurs when the mouse pointer rests on the control.</summary>
public event EventHandler<MouseEventArgs> MouseHover;
/// <summary>Initializes a new instance of the <see cref="RepeatedHover" /> class.</summary>
public RepeatedHover()
{
hookedControls = new HashSet<Control>();
hoverTimer = new Timer { Interval = SystemInformation.MouseHoverTime };
hoverTimer.Tick += hoverTimer_Tick;
}
/// <summary>
/// Gets or sets the time, in milliseconds, that the mouse pointer has to stay in the hover
/// rectangle before a mouse hover message is generated.
/// </summary>
/// <remarks>
/// <para>
/// The <strong>MouseHoverTime</strong> property indicates the time, in milliseconds,
/// that a mouse pointer must remain within an area the size of the
/// <see cref="MouseHoverSize" /> property in order to generate a mouse hover message.
/// </para>
/// <para>
/// The <see cref="MouseHoverSize" /> property indicates the size of the rectangle within
/// which the mouse pointer has to stay for the mouse hover time before a mouse hover
/// message is generated.
/// </para>
/// </remarks>
public int MouseHoverTime
{
get { return hoverTimer.Interval; }
set { hoverTimer.Interval = value; }
}
/// <summary>
/// Gets or sets the dimensions, in pixels, of the rectangle within which the mouse pointer
/// has to stay for the mouse hover time before a mouse hover message is generated.
/// </summary>
/// <remarks>
/// <para>
/// The <strong>MouseHoverSize</strong> property indicates the size of the rectangle within
/// which the mouse pointer has to stay for the mouse hover time before a mouse hover
/// message is generated.
/// </para>
/// <para>
/// The <see cref="MouseHoverTime" /> property indicates the time, in milliseconds,
/// that a mouse pointer must remain within an area the size of the
/// <strong>MouseHoverSize</strong> property in order to generate a mouse hover message.
/// </para>
/// </remarks>
public Size MouseHoverSize { get; set; } = SystemInformation.MouseHoverSize;
/// <summary>
/// Hooks the specified control to enable repeated raising of the
/// <see cref="MouseHover" /> event.
/// </summary>
/// <param name="ctrl">The control to be hooked.</param>
public void Hook(Control ctrl)
{
if (hookedControls.Add(ctrl))
{
ctrl.MouseMove += Control_MouseMove;
ctrl.Disposed += (o, e) => Unhook((Control)o); // to cleanly remove control on disposal
}
}
/// <summary>
/// Unhooks the specified control to stop repeated raising of the <see cref="MouseHover" /> event.
/// </summary>
/// <param name="ctrl">The control to be unhooked.</param>
public void Unhook(Control ctrl)
{
if (hookedControls.Remove(ctrl))
ctrl.MouseMove -= Control_MouseMove;
}
/// <summary>Raises the <see cref="E:MouseHover" /> event.</summary>
/// <param name="e">The <see cref="MouseEventArgs" /> instance containing the event data.</param>
protected void OnMouseHover(MouseEventArgs e)
{
MouseHover?.Invoke(lastControl, e);
}
private void Control_MouseMove(object sender, MouseEventArgs e)
{
var ctrl = (Control)sender;
/*
* Conditions for which the hover timer should be restarted:
* 1. The mouse is moving over a different control.
* 2. The mouse has moved outside the hover rectangle.
*/
if (ctrl != lastControl ||
!hoverRect.Contains(e.Location))
{
lastControl = ctrl; // update the hovered control
// adjust the position of the hover rectangle
hoverRect.Location = e.Location;
hoverRect.Size = Size.Empty;
hoverRect.Inflate(this.MouseHoverSize);
// restart hover timer
hoverTimer.Stop();
hoverTimer.Start();
}
lastLocation = e.Location;
}
private void hoverTimer_Tick(object sender, EventArgs e)
{
OnMouseHover(new MouseEventArgs(MouseButtons.None, 0, lastLocation.X, lastLocation.Y, 0));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment