GUI/GadgetWindow.cs
author sl
Sun, 03 Feb 2013 18:01:50 +0100
changeset 391 ca4c0e7ae75d
parent 302 44c0e7f76e9e
permissions -rw-r--r--
Converted project to VisualStudio 2012.
Adding SoundGraphDisplay and SensorFrontView classes.
They were respectively based on SystemTray and SensorNotifyIcon.
SoundGraphDisplay is now able to load iMONDisplay.dll providing it lives on your PATH.
Adding option to sensor context menu for adding it into FrontView.
     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 }