GUI/GadgetWindow.cs
author moel.mich
Mon, 25 Jul 2011 21:09:21 +0000
changeset 316 82bfd8af42a7
parent 244 99f16e21cdc8
child 344 3145aadca3d2
permissions -rw-r--r--
Buffered the gadget background drawing in order to improve drawing performance.
     1 /*
     2   
     3   Version: MPL 1.1/GPL 2.0/LGPL 2.1
     4 
     5   The contents of this file are subject to the Mozilla Public License Version
     6   1.1 (the "License"); you may not use this file except in compliance with
     7   the License. You may obtain a copy of the License at
     8  
     9   http://www.mozilla.org/MPL/
    10 
    11   Software distributed under the License is distributed on an "AS IS" basis,
    12   WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
    13   for the specific language governing rights and limitations under the License.
    14 
    15   The Original Code is the Open Hardware Monitor code.
    16 
    17   The Initial Developer of the Original Code is 
    18   Michael Möller <m.moeller@gmx.ch>.
    19   Portions created by the Initial Developer are Copyright (C) 2010-2011
    20   the Initial Developer. All Rights Reserved.
    21 
    22   Contributor(s):
    23 
    24   Alternatively, the contents of this file may be used under the terms of
    25   either the GNU General Public License Version 2 or later (the "GPL"), or
    26   the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
    27   in which case the provisions of the GPL or the LGPL are applicable instead
    28   of those above. If you wish to allow use of your version of this file only
    29   under the terms of either the GPL or the LGPL, and not to allow others to
    30   use your version of this file under the terms of the MPL, indicate your
    31   decision by deleting the provisions above and replace them with the notice
    32   and other provisions required by the GPL or the LGPL. If you do not delete
    33   the provisions above, a recipient may use your version of this file under
    34   the terms of any one of the MPL, the GPL or the LGPL.
    35  
    36 */
    37 
    38 using System;
    39 using System.Drawing;
    40 using System.Drawing.Drawing2D;
    41 using System.Drawing.Text;
    42 using System.Reflection;
    43 using System.Runtime.InteropServices;
    44 using System.Windows.Forms;
    45 
    46 namespace OpenHardwareMonitor.GUI {
    47 
    48   public class GadgetWindow : NativeWindow, IDisposable {
    49 
    50     private bool visible = false;
    51     private bool lockPositionAndSize = false;
    52     private bool alwaysOnTop = false;
    53     private byte opacity = 255;
    54     private Point location = new Point(100, 100);
    55     private Size size = new Size(130, 84);
    56     private ContextMenu contextMenu = null;
    57     private MethodInfo commandDispatch;
    58     private IntPtr handleBitmapDC;
    59     private Size bufferSize;
    60     private Graphics graphics;
    61 
    62     public GadgetWindow() {
    63       Type commandType = 
    64         typeof(Form).Assembly.GetType("System.Windows.Forms.Command");
    65       commandDispatch = commandType.GetMethod("DispatchID", 
    66         BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, 
    67         null, new Type[]{ typeof(int) }, null);
    68 
    69       this.CreateHandle(CreateParams);
    70 
    71       // move window to the bottom
    72       MoveToBottom(Handle);
    73 
    74       // prevent window from fading to a glass sheet when peek is invoked
    75       try {
    76         bool value = true;
    77         NativeMethods.DwmSetWindowAttribute(Handle,
    78           WindowAttribute.DWMWA_EXCLUDED_FROM_PEEK, ref value,
    79           Marshal.SizeOf(value));
    80       } catch (DllNotFoundException) { } catch (EntryPointNotFoundException) { }
    81 
    82       CreateBuffer();
    83     }
    84 
    85     private void ShowDesktopChanged(bool showDesktop) {
    86       if (showDesktop) {
    87         MoveToTopMost(Handle);
    88       } else {
    89         MoveToBottom(Handle);
    90       }
    91     }
    92 
    93     private void MoveToBottom(IntPtr handle) {
    94       NativeMethods.SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0,
    95         SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
    96     }
    97 
    98     private void MoveToTopMost(IntPtr handle) {
    99       NativeMethods.SetWindowPos(handle, HWND_TOPMOST, 0, 0, 0, 0,
   100         SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
   101     }
   102 
   103     private void ShowContextMenu(Point position) {
   104       NativeMethods.TrackPopupMenuEx(contextMenu.Handle, 
   105         TPM_RIGHTBUTTON | TPM_VERTICAL, position.X,
   106         position.Y, Handle, IntPtr.Zero);
   107     }
   108 
   109     protected virtual CreateParams CreateParams {
   110       get {
   111         CreateParams cp = new CreateParams();        
   112         cp.Width = 4096;
   113         cp.Height = 4096;
   114         cp.X = location.X;
   115         cp.Y = location.Y;
   116         cp.ExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW;
   117         return cp;
   118       }
   119     }
   120 
   121     protected override void WndProc(ref Message message) {
   122       switch (message.Msg) {
   123         case WM_COMMAND: {
   124             // need to dispatch the message for the context menu
   125             if (message.LParam == IntPtr.Zero)
   126               commandDispatch.Invoke(null, new object[] { 
   127               message.WParam.ToInt32() & 0xFFFF });
   128           } break;
   129         case WM_NCHITTEST: {
   130             message.Result = (IntPtr)HitResult.Caption;
   131             if (HitTest != null) {
   132               Point p = new Point(
   133                 Macros.GET_X_LPARAM(message.LParam) - location.X,
   134                 Macros.GET_Y_LPARAM(message.LParam) - location.Y
   135               );
   136               HitTestEventArgs e = new HitTestEventArgs(p, HitResult.Caption);
   137               HitTest(this, e);
   138               message.Result = (IntPtr)e.HitResult;
   139             }
   140           } break;
   141         case WM_NCLBUTTONDBLCLK: {
   142             if (MouseDoubleClick != null) {
   143               MouseDoubleClick(this, new MouseEventArgs(MouseButtons.Left, 2,
   144                 Macros.GET_X_LPARAM(message.LParam) - location.X,
   145                 Macros.GET_Y_LPARAM(message.LParam) - location.Y, 0));
   146             }
   147             message.Result = IntPtr.Zero;
   148           } break;
   149         case WM_NCRBUTTONDOWN: {
   150             message.Result = IntPtr.Zero;
   151           } break;
   152         case WM_NCRBUTTONUP: {
   153             if (contextMenu != null)
   154               ShowContextMenu(new Point(
   155                 Macros.GET_X_LPARAM(message.LParam),
   156                 Macros.GET_Y_LPARAM(message.LParam)
   157               ));
   158             message.Result = IntPtr.Zero;
   159           } break;
   160         case WM_WINDOWPOSCHANGING: {
   161             WindowPos wp = (WindowPos)Marshal.PtrToStructure(
   162               message.LParam, typeof(WindowPos));
   163             
   164             if (!lockPositionAndSize) {
   165               // prevent the window from leaving the screen
   166               if ((wp.flags & SWP_NOMOVE) == 0) {
   167                 Rectangle rect = Screen.GetWorkingArea(
   168                   new Rectangle(wp.x, wp.y, wp.cx, wp.cy));
   169                 const int margin = 16;
   170                 wp.x = Math.Max(wp.x, rect.Left - wp.cx + margin);
   171                 wp.x = Math.Min(wp.x, rect.Right - margin);
   172                 wp.y = Math.Max(wp.y, rect.Top - wp.cy + margin);
   173                 wp.y = Math.Min(wp.y, rect.Bottom - margin);
   174               }
   175 
   176               // update location and fire event
   177               if ((wp.flags & SWP_NOMOVE) == 0) {
   178                 if (location.X != wp.x || location.Y != wp.y) {
   179                   location = new Point(wp.x, wp.y);
   180                   if (LocationChanged != null)
   181                     LocationChanged(this, EventArgs.Empty);
   182                 }
   183               }
   184 
   185               // update size and fire event
   186               if ((wp.flags & SWP_NOSIZE) == 0) {
   187                 if (size.Width != wp.cx || size.Height != wp.cy) {
   188                   size = new Size(wp.cx, wp.cy);
   189                   if (SizeChanged != null)
   190                     SizeChanged(this, EventArgs.Empty);
   191                 }
   192               } 
   193 
   194               // update the size of the layered window
   195               if ((wp.flags & SWP_NOSIZE) == 0) {
   196                 NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero,
   197                   IntPtr.Zero, ref size, IntPtr.Zero, IntPtr.Zero, 0,
   198                   IntPtr.Zero, 0);                
   199               }
   200 
   201               // update the position of the layered window
   202               if ((wp.flags & SWP_NOMOVE) == 0) {
   203                 NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   204                   location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | 
   205                   SWP_NOZORDER | SWP_NOSENDCHANGING);
   206               }
   207             }
   208             
   209             // do not forward any move or size messages
   210             wp.flags |= SWP_NOSIZE | SWP_NOMOVE;
   211 
   212             // suppress any frame changed events
   213             wp.flags &= ~SWP_FRAMECHANGED;
   214 
   215             Marshal.StructureToPtr(wp, message.LParam, false);                      
   216             message.Result = IntPtr.Zero;
   217           } break;
   218         default: {
   219             base.WndProc(ref message);
   220           } break;
   221       }      
   222     }
   223 
   224     private BlendFunction CreateBlendFunction() {
   225       BlendFunction blend = new BlendFunction();
   226       blend.BlendOp = AC_SRC_OVER;
   227       blend.BlendFlags = 0;
   228       blend.SourceConstantAlpha = opacity;
   229       blend.AlphaFormat = AC_SRC_ALPHA;
   230       return blend;
   231     }
   232 
   233     private void CreateBuffer() {      
   234       IntPtr handleScreenDC = NativeMethods.GetDC(IntPtr.Zero);
   235       handleBitmapDC = NativeMethods.CreateCompatibleDC(handleScreenDC);
   236       NativeMethods.ReleaseDC(IntPtr.Zero, handleScreenDC);
   237       bufferSize = size;
   238 
   239       BitmapInfo info = new BitmapInfo();
   240       info.Size = Marshal.SizeOf(info);
   241       info.Width = size.Width;
   242       info.Height = -size.Height;
   243       info.BitCount = 32;
   244       info.Planes = 1;
   245 
   246       IntPtr ptr;
   247       IntPtr hBmp = NativeMethods.CreateDIBSection(handleBitmapDC, ref info, 0, 
   248         out ptr, IntPtr.Zero, 0);
   249       IntPtr hBmpOld = NativeMethods.SelectObject(handleBitmapDC, hBmp);
   250       NativeMethods.DeleteObject(hBmpOld);
   251       
   252       graphics = Graphics.FromHdc(handleBitmapDC);
   253 
   254       if (Environment.OSVersion.Version.Major > 5) {
   255         this.graphics.TextRenderingHint = TextRenderingHint.SystemDefault;
   256         this.graphics.SmoothingMode = SmoothingMode.HighQuality;
   257       } 
   258     }
   259 
   260     private void DisposeBuffer() {
   261       graphics.Dispose();
   262       NativeMethods.DeleteDC(handleBitmapDC);
   263     }
   264 
   265     public virtual void Dispose() {
   266       DisposeBuffer();
   267     } 
   268 
   269     public PaintEventHandler Paint; 
   270 
   271     public void Redraw() {
   272       if (!visible || Paint == null)
   273         return;
   274 
   275       if (size != bufferSize) {
   276         DisposeBuffer();
   277         CreateBuffer();
   278       }
   279 
   280       Paint(this, 
   281         new PaintEventArgs(graphics, new Rectangle(Point.Empty, size))); 
   282 
   283         Point pointSource = Point.Empty;
   284         BlendFunction blend = CreateBlendFunction();
   285 
   286         NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   287           ref size, handleBitmapDC, ref pointSource, 0, ref blend, ULW_ALPHA);
   288 
   289         // make sure the window is at the right location
   290         NativeMethods.SetWindowPos(Handle, IntPtr.Zero,
   291           location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE |
   292           SWP_NOZORDER | SWP_NOSENDCHANGING);
   293     }
   294 
   295     public byte Opacity {
   296       get {
   297         return opacity;
   298       }
   299       set {
   300         if (opacity != value) {
   301           opacity = value;
   302           BlendFunction blend = CreateBlendFunction();
   303           NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   304             IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, ref blend, ULW_ALPHA);
   305         }
   306       }
   307     }
   308 
   309     public bool Visible {
   310       get {
   311         return visible;
   312       }
   313       set {
   314         if (visible != value) {
   315           visible = value;
   316           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0,
   317             SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER |
   318             (value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
   319           if (value) {
   320             if (!alwaysOnTop)
   321               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   322           } else {
   323             if (!alwaysOnTop)
   324               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   325           }
   326         }
   327       }
   328     }
   329 
   330     // if locked, the window can not be moved or resized
   331     public bool LockPositionAndSize {
   332       get {
   333         return lockPositionAndSize;
   334       }
   335       set {
   336         lockPositionAndSize = value;
   337       }
   338     }
   339 
   340     public bool AlwaysOnTop {
   341       get {
   342         return alwaysOnTop;
   343       }
   344       set {
   345         if (value != alwaysOnTop) {
   346           alwaysOnTop = value;
   347           if (alwaysOnTop) {
   348             if (visible)
   349               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   350             MoveToTopMost(Handle);            
   351           } else {
   352             MoveToBottom(Handle);
   353             if (visible)
   354               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   355           }
   356         }
   357       }
   358     }
   359 
   360     public Size Size {
   361       get {
   362         return size; 
   363       }
   364       set {
   365         if (size != value) {
   366           size = value;
   367           NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   368             ref size, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, 0);                    
   369           if (SizeChanged != null)
   370             SizeChanged(this, EventArgs.Empty);
   371         }
   372       }
   373     }
   374 
   375     public event EventHandler SizeChanged;
   376 
   377     public Point Location {
   378       get {
   379         return location;
   380       }
   381       set {
   382         if (location != value) {
   383           location = value;
   384           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   385             location.X, location.Y, 0, 0, 
   386             SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING);          
   387           if (LocationChanged != null)
   388             LocationChanged(this, EventArgs.Empty);
   389         }
   390       }
   391     }
   392 
   393     public event EventHandler LocationChanged;
   394 
   395     public ContextMenu ContextMenu {
   396       get {
   397         return contextMenu;
   398       }
   399       set {
   400         this.contextMenu = value;
   401       }
   402     }
   403 
   404     public event HitTestEventHandler HitTest;
   405 
   406     public event MouseEventHandler MouseDoubleClick;
   407 
   408     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   409     private struct BlendFunction {
   410       public byte BlendOp;
   411       public byte BlendFlags;
   412       public byte SourceConstantAlpha;
   413       public byte AlphaFormat;
   414     }
   415 
   416     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   417     private struct WindowPos {
   418       public IntPtr hwnd;
   419       public IntPtr hwndInsertAfter;
   420       public int x;
   421       public int y;
   422       public int cx;
   423       public int cy;
   424       public uint flags;
   425     }
   426 
   427     [StructLayout(LayoutKind.Sequential)]
   428     public struct BitmapInfo {
   429       public Int32 Size;
   430       public Int32 Width;
   431       public Int32 Height;
   432       public Int16 Planes;
   433       public Int16 BitCount;
   434       public Int32 Compression;
   435       public Int32 SizeImage;
   436       public Int32 XPelsPerMeter;
   437       public Int32 YPelsPerMeter;
   438       public Int32 ClrUsed;
   439       public Int32 ClrImportant;
   440       public Int32 Colors;
   441     }
   442 
   443     public static readonly IntPtr HWND_BOTTOM = (IntPtr)1;
   444     public static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1);
   445 
   446     public const int WS_EX_LAYERED = 0x00080000;
   447     public const int WS_EX_TOOLWINDOW = 0x00000080;
   448 
   449     public const uint SWP_NOSIZE = 0x0001;
   450     public const uint SWP_NOMOVE = 0x0002;
   451     public const uint SWP_NOACTIVATE = 0x0010;
   452     public const uint SWP_FRAMECHANGED = 0x0020;
   453     public const uint SWP_HIDEWINDOW = 0x0080;
   454     public const uint SWP_SHOWWINDOW = 0x0040;
   455     public const uint SWP_NOZORDER = 0x0004;
   456     public const uint SWP_NOSENDCHANGING = 0x0400;
   457 
   458     public const int ULW_COLORKEY = 0x00000001;
   459     public const int ULW_ALPHA = 0x00000002;
   460     public const int ULW_OPAQUE = 0x00000004;
   461 
   462     public const byte AC_SRC_OVER = 0x00;
   463     public const byte AC_SRC_ALPHA = 0x01;
   464 
   465     public const int WM_NCHITTEST = 0x0084;
   466     public const int WM_NCLBUTTONDBLCLK = 0x00A3;
   467     public const int WM_NCLBUTTONDOWN = 0x00A1;
   468     public const int WM_NCLBUTTONUP = 0x00A2;
   469     public const int WM_NCRBUTTONDOWN = 0x00A4;
   470     public const int WM_NCRBUTTONUP = 0x00A5;
   471     public const int WM_WINDOWPOSCHANGING = 0x0046;
   472     public const int WM_COMMAND = 0x0111;
   473 
   474     public const int TPM_RIGHTBUTTON = 0x0002;
   475     public const int TPM_VERTICAL = 0x0040;
   476 
   477     private enum WindowAttribute : int {
   478       DWMWA_NCRENDERING_ENABLED = 1,
   479       DWMWA_NCRENDERING_POLICY,
   480       DWMWA_TRANSITIONS_FORCEDISABLED,
   481       DWMWA_ALLOW_NCPAINT,
   482       DWMWA_CAPTION_BUTTON_BOUNDS,
   483       DWMWA_NONCLIENT_RTL_LAYOUT,
   484       DWMWA_FORCE_ICONIC_REPRESENTATION,
   485       DWMWA_FLIP3D_POLICY,
   486       DWMWA_EXTENDED_FRAME_BOUNDS,
   487       DWMWA_HAS_ICONIC_BITMAP,
   488       DWMWA_DISALLOW_PEEK,
   489       DWMWA_EXCLUDED_FROM_PEEK,
   490       DWMWA_LAST
   491     }
   492 
   493     /// <summary>
   494     /// Some macros imported and converted from the Windows SDK
   495     /// </summary>
   496     private static class Macros {
   497       public static ushort LOWORD(IntPtr l) {
   498         return (ushort) ((ulong)l & 0xFFFF);
   499       }
   500       
   501       public static UInt16 HIWORD(IntPtr l) {
   502         return (ushort) (((ulong)l >> 16) & 0xFFFF);
   503       }
   504 
   505       public static int GET_X_LPARAM(IntPtr lp) {
   506         return (short) LOWORD(lp);
   507       }
   508 
   509       public static int GET_Y_LPARAM(IntPtr lp) {
   510         return (short) HIWORD(lp);
   511       }
   512     }
   513 
   514     /// <summary>
   515     /// Imported native methods
   516     /// </summary>
   517     private static class NativeMethods {
   518       private const string USER = "user32.dll";
   519       private const string GDI = "gdi32.dll";
   520       public const string DWMAPI = "dwmapi.dll";
   521 
   522       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   523       [return: MarshalAs(UnmanagedType.Bool)]
   524       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
   525         IntPtr pptDst, ref Size psize, IntPtr hdcSrc, IntPtr pprSrc,
   526         int crKey, IntPtr pblend, int dwFlags);
   527 
   528       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   529       [return: MarshalAs(UnmanagedType.Bool)]
   530       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, 
   531         IntPtr pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, 
   532         int crKey, ref BlendFunction pblend, int dwFlags);
   533 
   534       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   535       [return: MarshalAs(UnmanagedType.Bool)]
   536       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
   537         IntPtr pptDst, IntPtr psize, IntPtr hdcSrc, IntPtr pprSrc,
   538         int crKey, ref BlendFunction pblend, int dwFlags);  
   539 
   540       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   541       public static extern IntPtr GetDC(IntPtr hWnd);
   542 
   543       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   544       public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
   545 
   546       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   547       public static extern bool SetWindowPos(IntPtr hWnd,
   548         IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
   549 
   550       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   551       public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags, 
   552         int x, int y, IntPtr hWnd, IntPtr tpmParams);
   553 
   554       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   555       public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
   556 
   557       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   558       public static extern IntPtr CreateDIBSection(IntPtr hdc, 
   559         [In] ref BitmapInfo pbmi, uint pila, out IntPtr ppvBits, 
   560         IntPtr hSection, uint dwOffset);
   561 
   562       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   563       [return: MarshalAs(UnmanagedType.Bool)]
   564       public static extern bool DeleteDC(IntPtr hdc);
   565       
   566       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   567       public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
   568 
   569       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   570       [return: MarshalAs(UnmanagedType.Bool)]
   571       public static extern bool DeleteObject(IntPtr hObject);
   572 
   573       [DllImport(DWMAPI, CallingConvention = CallingConvention.Winapi)]
   574       public static extern int DwmSetWindowAttribute(IntPtr hwnd,
   575         WindowAttribute dwAttribute, ref bool pvAttribute, int cbAttribute);
   576     }    
   577   }
   578 
   579   public enum HitResult {
   580     Transparent = -1,
   581     Nowhere = 0,
   582     Client = 1,
   583     Caption = 2,
   584     Left = 10,
   585     Right = 11,
   586     Top = 12,
   587     TopLeft = 13,
   588     TopRight = 14,
   589     Bottom = 15,
   590     BottomLeft = 16,
   591     BottomRight = 17,
   592     Border = 18
   593   }
   594 
   595   public delegate void HitTestEventHandler(object sender, HitTestEventArgs e);
   596 
   597   public class HitTestEventArgs : EventArgs {
   598     public HitTestEventArgs(Point location, HitResult hitResult) {
   599       Location = location;
   600       HitResult = hitResult;
   601     }
   602     public Point Location { get; private set; }
   603     public HitResult HitResult { get; set; }
   604   }
   605 }