using System; using System.Windows.Forms; using System.Collections; using System.Drawing; namespace NFission.WinForms { public sealed class HighlightFilter : System.Windows.Forms.IMessageFilter { #region static add / remove monitor functionality private static HighlightFilter instance; private static Pen highlightPen; /// /// The System.Color to use when highlighting controls /// public static Color HighlightColor { get{ return highlightPen.Color;} set { highlightPen.Color = value; instance.HighlightControl(instance.lastControl); } } /// /// The width of the line used to highlight controls /// public static float HighlightWidth { get{ return highlightPen.Width;} set { highlightPen.Width = value; instance.HighlightControl(instance.lastControl); } } /// /// Monitors all controls on a form, and highlights controls when the mouse moves over them /// /// The form whose controls you wish to monitor public static void MonitorForm(Form monitorForm) { MonitorControl((Control)monitorForm); } /// /// Monitors a control and all child controls, highlighting them when the mouse moves over them. /// /// The control to monitor public static void MonitorControl(Control monitorControl) { if(instance == null) { instance = new HighlightFilter(); Application.AddMessageFilter( instance); } PruneTree(); foreach(WeakReference wr in instance.monitored) { if(wr.Target == monitorControl) { //already being monitored... don't double up. return; } if(((Control)wr.Target).Contains(monitorControl)) { //a parent is already being monitored.. don't add it?? return; } } WeakReference weak = new WeakReference( monitorControl); instance.monitored.Add(weak); } /// /// Stops monitoring a form or control /// /// The control or form to stop monitoring public static void RemoveMonitor(Control monitored) { if(instance == null) return; foreach(WeakReference wr in instance.monitored) { if(wr.Target == monitored) { instance.monitored.Remove(wr); if( ((Control)wr.Target).Contains(instance.lastControl)) { instance.RemoveHighlights(); } break; } } PruneTree(); if( instance.monitored.Count == 0 ) { instance.Dispose(); instance = null; } } private static void PruneTree() { WeakReference wr; for(int i=0; i< instance.monitored.Count; i++) { wr = (WeakReference)instance.monitored[i]; if(! wr.IsAlive) { instance.monitored.Remove(wr); i--; } } } private HighlightFilter() { monitored = new ArrayList(); highlightPen = new Pen(Color.Yellow, 2f); } #endregion private ArrayList monitored; private Control lastControl; private const int TRAPMESSAGE = 0x200; //0x200 is wm_mousemove. I've also used 0x204, rbuttondown bool IMessageFilter.PreFilterMessage(ref Message m) { if( m.Msg == TRAPMESSAGE) //trap wm_mousemove, wm_rbuttondown, whatever. { Control ctrl = GetMonitoredControl( m.HWnd); if(ctrl != lastControl) { RemoveHighlights(); } if( ctrl != null) { HighlightControl(ctrl); } } return false; } private void HighlightControl(Control child) { if( child != null) { lastControl = child; Graphics g = child.CreateGraphics(); g.DrawRectangle( highlightPen, Rectangle.Inflate(child.ClientRectangle, -1, -1)); g.Dispose(); } } private void RemoveHighlights() { if(lastControl != null) { //cheating is easy! lastControl.Invalidate(); } } #region Find the applicable control, if any //Given an hWnd, return a Control object that //should be highlighted. If no monitored control //matches the hWnd, return null private Control GetMonitoredControl(IntPtr hWnd) { Control ctrl = Control.FromHandle(hWnd); foreach( WeakReference wr in monitored) { if(wr.IsAlive) { Control parent = (Control)wr.Target; if( !parent.IsDisposed) { if(parent.Contains(ctrl)) return ctrl; if(parent == ctrl && !(parent is Form)) return ctrl; } } } return null; } #endregion #region Disposing of resources //note that this type does NOT implement IDisposable, as // .. no code should ever have an instance of the class. // Any resemblance to IDisposable.Dispose is coincidental private bool isDisposed = false ; private void Dispose() { if(!isDisposed) { isDisposed = true; Application.RemoveMessageFilter(this); highlightPen.Dispose(); } } #endregion } }