GUI/GadgetWindow.cs
author paulwerelds
Mon, 04 Oct 2010 16:56:42 +0000
changeset 209 dee42c887ab0
parent 208 08635c9dac04
child 210 be7dc4a856aa
permissions -rw-r--r--
Fixed the context menu positioning as well, following the previous fix to multimonitor setups.
     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
    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.Reflection;
    41 using System.Runtime.InteropServices;
    42 using System.Windows.Forms;
    43 
    44 namespace OpenHardwareMonitor.GUI {
    45 
    46   public class GadgetWindow : NativeWindow {
    47 
    48     private bool visible = false;
    49     private bool lockPositionAndSize = false;
    50     private bool alwaysOnTop = false;
    51     private byte opacity = 255;
    52     private Point location = new Point(100, 100);
    53     private Size size = new Size(130, 84);
    54     private ContextMenu contextMenu = null;
    55     private MethodInfo commandDispatch;
    56 
    57     public GadgetWindow() {
    58       Type commandType = 
    59         typeof(Form).Assembly.GetType("System.Windows.Forms.Command");
    60       commandDispatch = commandType.GetMethod("DispatchID", 
    61         BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, 
    62         null, new Type[]{ typeof(int) }, null);
    63 
    64       this.CreateHandle(CreateParams);
    65 
    66       // move window to the bottom
    67       MoveToBottom(Handle);
    68 
    69       // prevent window from fading to a glass sheet when peek is invoked
    70       try {
    71         bool value = true;
    72         NativeMethods.DwmSetWindowAttribute(Handle,
    73           WindowAttribute.DWMWA_EXCLUDED_FROM_PEEK, ref value,
    74           Marshal.SizeOf(value));
    75       } catch (DllNotFoundException) { } catch (EntryPointNotFoundException) { }
    76     }
    77 
    78     private void ShowDesktopChanged(bool showDesktop) {
    79       if (showDesktop) {
    80         MoveToTopMost(Handle);
    81       } else {
    82         MoveToBottom(Handle);
    83       }
    84     }
    85 
    86     private void MoveToBottom(IntPtr handle) {
    87       NativeMethods.SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0,
    88         SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
    89     }
    90 
    91     private void MoveToTopMost(IntPtr handle) {
    92       NativeMethods.SetWindowPos(handle, HWND_TOPMOST, 0, 0, 0, 0,
    93         SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
    94     }
    95 
    96     private void ShowContextMenu(Point position) {
    97       NativeMethods.TrackPopupMenuEx(contextMenu.Handle, 
    98         TPM_RIGHTBUTTON | TPM_VERTICAL, position.X,
    99         position.Y, Handle, IntPtr.Zero);
   100     }
   101 
   102     protected virtual CreateParams CreateParams {
   103       get {
   104         CreateParams cp = new CreateParams();        
   105         cp.Width = 4096;
   106         cp.Height = 4096;
   107         cp.X = location.X;
   108         cp.Y = location.Y;
   109         cp.ExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW;
   110         return cp;
   111       }
   112     }
   113 
   114     protected override void WndProc(ref Message message) {
   115       switch (message.Msg) {
   116         case WM_COMMAND: {
   117             // need to dispatch the message for the context menu
   118             if (message.LParam == IntPtr.Zero)
   119               commandDispatch.Invoke(null, new object[] { 
   120               message.WParam.ToInt32() & 0xFFFF });
   121           } break;
   122         case WM_NCHITTEST: {
   123             message.Result = (IntPtr)HitResult.Caption;
   124             if (HitTest != null) {
   125               Point p = new Point(
   126                 Macros.GET_X_LPARAM(message.LParam) - location.X,
   127                 Macros.GET_Y_LPARAM(message.LParam) - location.Y
   128               );
   129               HitTestEventArgs e = new HitTestEventArgs(p, HitResult.Caption);
   130               HitTest(this, e);
   131               message.Result = (IntPtr)e.HitResult;
   132             }
   133           } break;
   134         case WM_NCLBUTTONDBLCLK: {
   135             message.Result = IntPtr.Zero;
   136           } break;
   137         case WM_NCRBUTTONDOWN: {
   138             message.Result = IntPtr.Zero;
   139           } break;
   140         case WM_NCRBUTTONUP: {
   141             if (contextMenu != null)
   142               ShowContextMenu(new Point(
   143                 Macros.GET_X_LPARAM(message.LParam),
   144                 Macros.GET_Y_LPARAM(message.LParam)
   145               ));
   146             message.Result = IntPtr.Zero;
   147           } break;
   148         case WM_WINDOWPOSCHANGING: {
   149             WindowPos wp = (WindowPos)Marshal.PtrToStructure(
   150               message.LParam, typeof(WindowPos));
   151             
   152             if (!lockPositionAndSize) {
   153               // prevent the window from leaving the screen
   154               if ((wp.flags & SWP_NOMOVE) == 0) {
   155                 Rectangle rect = Screen.GetWorkingArea(new Point(wp.x, wp.y));
   156                 const int margin = 16;
   157                 wp.x = Math.Max(wp.x, rect.Left - wp.cx + margin);
   158                 wp.x = Math.Min(wp.x, rect.Right - margin);
   159                 wp.y = Math.Max(wp.y, rect.Top - wp.cy + margin);
   160                 wp.y = Math.Min(wp.y, rect.Bottom - margin);
   161               }
   162 
   163               // update location and fire event
   164               if ((wp.flags & SWP_NOMOVE) == 0) {
   165                 if (location.X != wp.x || location.Y != wp.y) {
   166                   location = new Point(wp.x, wp.y);
   167                   if (LocationChanged != null)
   168                     LocationChanged(this, EventArgs.Empty);
   169                 }
   170               }
   171 
   172               // update size and fire event
   173               if ((wp.flags & SWP_NOSIZE) == 0) {
   174                 if (size.Width != wp.cx || size.Height != wp.cy) {
   175                   size = new Size(wp.cx, wp.cy);
   176                   if (SizeChanged != null)
   177                     SizeChanged(this, EventArgs.Empty);
   178                 }
   179               } 
   180 
   181               // update the size of the layered window
   182               if ((wp.flags & SWP_NOSIZE) == 0) {
   183                 NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero,
   184                   IntPtr.Zero, ref size, IntPtr.Zero, IntPtr.Zero, 0,
   185                   IntPtr.Zero, 0);                
   186               }
   187 
   188               // update the position of the layered window
   189               if ((wp.flags & SWP_NOMOVE) == 0) {
   190                 NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   191                   location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | 
   192                   SWP_NOZORDER | SWP_NOSENDCHANGING);
   193               }
   194             }
   195             
   196             // do not forward any move or size messages
   197             wp.flags |= SWP_NOSIZE | SWP_NOMOVE;            
   198             Marshal.StructureToPtr(wp, message.LParam, false);                      
   199             message.Result = IntPtr.Zero;
   200           } break;
   201         default: {
   202             base.WndProc(ref message);
   203           } break;
   204       }      
   205     }
   206 
   207     private BlendFunction CreateBlendFunction() {
   208       BlendFunction blend = new BlendFunction();
   209       blend.BlendOp = AC_SRC_OVER;
   210       blend.BlendFlags = 0;
   211       blend.SourceConstantAlpha = opacity;
   212       blend.AlphaFormat = AC_SRC_ALPHA;
   213       return blend;
   214     }
   215 
   216     public void Update(Bitmap bitmap) {
   217       IntPtr screen = NativeMethods.GetDC(IntPtr.Zero);
   218       IntPtr memory = NativeMethods.CreateCompatibleDC(screen);
   219       IntPtr newHBitmap = IntPtr.Zero;
   220       IntPtr oldHBitmap = IntPtr.Zero;
   221 
   222       try {
   223         newHBitmap = bitmap.GetHbitmap(Color.Black);
   224         oldHBitmap = NativeMethods.SelectObject(memory, newHBitmap);
   225 
   226         Point pointSource = Point.Empty;
   227         BlendFunction blend = CreateBlendFunction();
   228 
   229         NativeMethods.UpdateLayeredWindow(Handle, screen, IntPtr.Zero,
   230           ref size, memory, ref pointSource, 0, ref blend, ULW_ALPHA);
   231 
   232         // make sure the window is at the right location
   233         NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   234           location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | 
   235           SWP_NOZORDER | SWP_NOSENDCHANGING);
   236 
   237       } finally {        
   238         if (newHBitmap != IntPtr.Zero) {
   239           NativeMethods.SelectObject(memory, oldHBitmap);
   240           NativeMethods.DeleteObject(newHBitmap);
   241         }
   242         NativeMethods.DeleteDC(memory);
   243         NativeMethods.ReleaseDC(IntPtr.Zero, screen);
   244       }
   245     }
   246 
   247     public byte Opacity {
   248       get {
   249         return opacity;
   250       }
   251       set {
   252         if (opacity != value) {
   253           opacity = value;
   254           BlendFunction blend = CreateBlendFunction();
   255           NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   256             IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, ref blend, ULW_ALPHA);
   257         }
   258       }
   259     }
   260 
   261     public bool Visible {
   262       get {
   263         return visible;
   264       }
   265       set {
   266         if (visible != value) {
   267           visible = value;
   268           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0,
   269             SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER |
   270             (value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
   271           if (value) {
   272             if (!alwaysOnTop)
   273               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   274           } else {
   275             if (!alwaysOnTop)
   276               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   277           }
   278         }
   279       }
   280     }
   281 
   282     // if locked, the window can not be moved or resized
   283     public bool LockPositionAndSize {
   284       get {
   285         return lockPositionAndSize;
   286       }
   287       set {
   288         lockPositionAndSize = value;
   289       }
   290     }
   291 
   292     public bool AlwaysOnTop {
   293       get {
   294         return alwaysOnTop;
   295       }
   296       set {
   297         if (value != alwaysOnTop) {
   298           alwaysOnTop = value;
   299           if (alwaysOnTop) {
   300             if (visible)
   301               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   302             MoveToTopMost(Handle);            
   303           } else {
   304             MoveToBottom(Handle);
   305             if (visible)
   306               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   307           }
   308         }
   309       }
   310     }
   311 
   312     public Size Size {
   313       get {
   314         return size; 
   315       }
   316       set {
   317         if (size != value) {
   318           size = value;
   319           NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   320             ref size, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, 0);                    
   321           if (SizeChanged != null)
   322             SizeChanged(this, EventArgs.Empty);
   323         }
   324       }
   325     }
   326 
   327     public event EventHandler SizeChanged;
   328 
   329     public Point Location {
   330       get {
   331         return location;
   332       }
   333       set {
   334         if (location != value) {
   335           location = value;
   336           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   337             location.X, location.Y, 0, 0, 
   338             SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING);          
   339           if (LocationChanged != null)
   340             LocationChanged(this, EventArgs.Empty);
   341         }
   342       }
   343     }
   344 
   345     public event EventHandler LocationChanged;
   346 
   347     public ContextMenu ContextMenu {
   348       get {
   349         return contextMenu;
   350       }
   351       set {
   352         this.contextMenu = value;
   353       }
   354     }
   355 
   356     public event HitTestEventHandler HitTest;
   357 
   358     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   359     private struct BlendFunction {
   360       public byte BlendOp;
   361       public byte BlendFlags;
   362       public byte SourceConstantAlpha;
   363       public byte AlphaFormat;
   364     }
   365 
   366     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   367     private struct WindowPos {
   368       public IntPtr hwnd;
   369       public IntPtr hwndInsertAfter;
   370       public int x;
   371       public int y;
   372       public int cx;
   373       public int cy;
   374       public uint flags;
   375     }
   376 
   377     public static readonly IntPtr HWND_BOTTOM = (IntPtr)1;
   378     public static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1);
   379 
   380     public const int WS_EX_LAYERED = 0x00080000;
   381     public const int WS_EX_TOOLWINDOW = 0x00000080;
   382 
   383     public const uint SWP_NOSIZE = 0x0001;
   384     public const uint SWP_NOMOVE = 0x0002;
   385     public const uint SWP_NOACTIVATE = 0x0010;
   386     public const uint SWP_HIDEWINDOW = 0x0080;
   387     public const uint SWP_SHOWWINDOW = 0x0040;
   388     public const uint SWP_NOZORDER = 0x0004;
   389     public const uint SWP_NOSENDCHANGING = 0x0400;
   390 
   391     public const int ULW_COLORKEY = 0x00000001;
   392     public const int ULW_ALPHA = 0x00000002;
   393     public const int ULW_OPAQUE = 0x00000004;
   394 
   395     public const byte AC_SRC_OVER = 0x00;
   396     public const byte AC_SRC_ALPHA = 0x01;
   397 
   398     public const int WM_NCHITTEST = 0x0084;
   399     public const int WM_NCLBUTTONDBLCLK = 0x00A3;
   400     public const int WM_NCLBUTTONDOWN = 0x00A1;
   401     public const int WM_NCLBUTTONUP = 0x00A2;
   402     public const int WM_NCRBUTTONDOWN = 0x00A4;
   403     public const int WM_NCRBUTTONUP = 0x00A5;
   404     public const int WM_WINDOWPOSCHANGING = 0x0046;
   405     public const int WM_COMMAND = 0x0111;
   406 
   407     public const int TPM_RIGHTBUTTON = 0x0002;
   408     public const int TPM_VERTICAL = 0x0040;
   409 
   410     private enum WindowAttribute : int {
   411       DWMWA_NCRENDERING_ENABLED = 1,
   412       DWMWA_NCRENDERING_POLICY,
   413       DWMWA_TRANSITIONS_FORCEDISABLED,
   414       DWMWA_ALLOW_NCPAINT,
   415       DWMWA_CAPTION_BUTTON_BOUNDS,
   416       DWMWA_NONCLIENT_RTL_LAYOUT,
   417       DWMWA_FORCE_ICONIC_REPRESENTATION,
   418       DWMWA_FLIP3D_POLICY,
   419       DWMWA_EXTENDED_FRAME_BOUNDS,
   420       DWMWA_HAS_ICONIC_BITMAP,
   421       DWMWA_DISALLOW_PEEK,
   422       DWMWA_EXCLUDED_FROM_PEEK,
   423       DWMWA_LAST
   424     }
   425 
   426     /// <summary>
   427     /// Some macros imported and converted from the Windows SDK
   428     /// </summary>
   429     private static class Macros {
   430       public static UInt16 LOWORD(IntPtr l) {
   431         return ((UInt16) (((UInt64) (l)) & 0xffff));
   432       }
   433       
   434       public static UInt16 HIWORD(IntPtr l) {
   435         return ((UInt16) ((((UInt64) (l)) >> 16) & 0xffff));
   436       }
   437 
   438       public static int GET_X_LPARAM(IntPtr lp) {
   439         return ((int) (short) LOWORD(lp));
   440       }
   441 
   442       public static int GET_Y_LPARAM(IntPtr lp) {
   443         return ((int) (short) HIWORD(lp));
   444       }
   445     }
   446 
   447     /// <summary>
   448     /// Imported native methods
   449     /// </summary>
   450     private static class NativeMethods {
   451       private const string USER = "user32.dll";
   452       private const string GDI = "gdi32.dll";
   453       public const string DWMAPI = "dwmapi.dll";
   454 
   455       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   456       [return: MarshalAs(UnmanagedType.Bool)]
   457       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
   458         IntPtr pptDst, ref Size psize, IntPtr hdcSrc, IntPtr pprSrc,
   459         int crKey, IntPtr pblend, int dwFlags);
   460 
   461       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   462       [return: MarshalAs(UnmanagedType.Bool)]
   463       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, 
   464         IntPtr pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, 
   465         int crKey, ref BlendFunction pblend, int dwFlags);
   466 
   467       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   468       [return: MarshalAs(UnmanagedType.Bool)]
   469       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
   470         IntPtr pptDst, IntPtr psize, IntPtr hdcSrc, IntPtr pprSrc,
   471         int crKey, ref BlendFunction pblend, int dwFlags);  
   472 
   473       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   474       public static extern IntPtr GetDC(IntPtr hWnd);
   475 
   476       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   477       public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
   478 
   479       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   480       public static extern bool SetWindowPos(IntPtr hWnd,
   481         IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
   482 
   483       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   484       public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags, 
   485         int x, int y, IntPtr hWnd, IntPtr tpmParams);
   486 
   487       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   488       public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
   489 
   490       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   491       [return: MarshalAs(UnmanagedType.Bool)]
   492       public static extern bool DeleteDC(IntPtr hdc);
   493       
   494       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   495       public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
   496 
   497       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   498       [return: MarshalAs(UnmanagedType.Bool)]
   499       public static extern bool DeleteObject(IntPtr hObject);
   500 
   501       [DllImport(DWMAPI, CallingConvention = CallingConvention.Winapi)]
   502       public static extern int DwmSetWindowAttribute(IntPtr hwnd,
   503         WindowAttribute dwAttribute, ref bool pvAttribute, int cbAttribute);
   504     }    
   505   }
   506 
   507   public enum HitResult {
   508     Transparent = -1,
   509     Nowhere = 0,
   510     Client = 1,
   511     Caption = 2,
   512     Left = 10,
   513     Right = 11,
   514     Top = 12,
   515     TopLeft = 13,
   516     TopRight = 14,
   517     Bottom = 15,
   518     BottomLeft = 16,
   519     BottomRight = 17,
   520     Border = 18
   521   }
   522 
   523   public delegate void HitTestEventHandler(object sender, HitTestEventArgs e);
   524 
   525   public class HitTestEventArgs : EventArgs {
   526     public HitTestEventArgs(Point location, HitResult hitResult) {
   527       Location = location;
   528       HitResult = hitResult;
   529     }
   530     public Point Location { get; private set; }
   531     public HitResult HitResult { get; set; }
   532   }
   533 }