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