GUI/GadgetWindow.cs
author paulwerelds
Mon, 04 Oct 2010 06:46:55 +0000
changeset 208 08635c9dac04
parent 202 551243a66b32
child 209 dee42c887ab0
permissions -rw-r--r--
Fixed an issue where the gadget would not be movable on an extra monitor positioned to the left of the primary monitor.
     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                 (int)((uint)message.LParam & 0xFFFF),
   144                 (int)(((uint)message.LParam >> 16) & 0xFFFF)));
   145             message.Result = IntPtr.Zero;
   146           } break;
   147         case WM_WINDOWPOSCHANGING: {
   148             WindowPos wp = (WindowPos)Marshal.PtrToStructure(
   149               message.LParam, typeof(WindowPos));
   150             
   151             if (!lockPositionAndSize) {
   152               // prevent the window from leaving the screen
   153               if ((wp.flags & SWP_NOMOVE) == 0) {
   154                 Rectangle rect = Screen.GetWorkingArea(new Point(wp.x, wp.y));
   155                 const int margin = 16;
   156                 wp.x = Math.Max(wp.x, rect.Left - wp.cx + margin);
   157                 wp.x = Math.Min(wp.x, rect.Right - margin);
   158                 wp.y = Math.Max(wp.y, rect.Top - wp.cy + margin);
   159                 wp.y = Math.Min(wp.y, rect.Bottom - margin);
   160               }
   161 
   162               // update location and fire event
   163               if ((wp.flags & SWP_NOMOVE) == 0) {
   164                 if (location.X != wp.x || location.Y != wp.y) {
   165                   location = new Point(wp.x, wp.y);
   166                   if (LocationChanged != null)
   167                     LocationChanged(this, EventArgs.Empty);
   168                 }
   169               }
   170 
   171               // update size and fire event
   172               if ((wp.flags & SWP_NOSIZE) == 0) {
   173                 if (size.Width != wp.cx || size.Height != wp.cy) {
   174                   size = new Size(wp.cx, wp.cy);
   175                   if (SizeChanged != null)
   176                     SizeChanged(this, EventArgs.Empty);
   177                 }
   178               } 
   179 
   180               // update the size of the layered window
   181               if ((wp.flags & SWP_NOSIZE) == 0) {
   182                 NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero,
   183                   IntPtr.Zero, ref size, IntPtr.Zero, IntPtr.Zero, 0,
   184                   IntPtr.Zero, 0);                
   185               }
   186 
   187               // update the position of the layered window
   188               if ((wp.flags & SWP_NOMOVE) == 0) {
   189                 NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   190                   location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | 
   191                   SWP_NOZORDER | SWP_NOSENDCHANGING);
   192               }
   193             }
   194             
   195             // do not forward any move or size messages
   196             wp.flags |= SWP_NOSIZE | SWP_NOMOVE;            
   197             Marshal.StructureToPtr(wp, message.LParam, false);                      
   198             message.Result = IntPtr.Zero;
   199           } break;
   200         default: {
   201             base.WndProc(ref message);
   202           } break;
   203       }      
   204     }
   205 
   206     private BlendFunction CreateBlendFunction() {
   207       BlendFunction blend = new BlendFunction();
   208       blend.BlendOp = AC_SRC_OVER;
   209       blend.BlendFlags = 0;
   210       blend.SourceConstantAlpha = opacity;
   211       blend.AlphaFormat = AC_SRC_ALPHA;
   212       return blend;
   213     }
   214 
   215     public void Update(Bitmap bitmap) {
   216       IntPtr screen = NativeMethods.GetDC(IntPtr.Zero);
   217       IntPtr memory = NativeMethods.CreateCompatibleDC(screen);
   218       IntPtr newHBitmap = IntPtr.Zero;
   219       IntPtr oldHBitmap = IntPtr.Zero;
   220 
   221       try {
   222         newHBitmap = bitmap.GetHbitmap(Color.Black);
   223         oldHBitmap = NativeMethods.SelectObject(memory, newHBitmap);
   224 
   225         Point pointSource = Point.Empty;
   226         BlendFunction blend = CreateBlendFunction();
   227 
   228         NativeMethods.UpdateLayeredWindow(Handle, screen, IntPtr.Zero,
   229           ref size, memory, ref pointSource, 0, ref blend, ULW_ALPHA);
   230 
   231         // make sure the window is at the right location
   232         NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   233           location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | 
   234           SWP_NOZORDER | SWP_NOSENDCHANGING);
   235 
   236       } finally {        
   237         if (newHBitmap != IntPtr.Zero) {
   238           NativeMethods.SelectObject(memory, oldHBitmap);
   239           NativeMethods.DeleteObject(newHBitmap);
   240         }
   241         NativeMethods.DeleteDC(memory);
   242         NativeMethods.ReleaseDC(IntPtr.Zero, screen);
   243       }
   244     }
   245 
   246     public byte Opacity {
   247       get {
   248         return opacity;
   249       }
   250       set {
   251         if (opacity != value) {
   252           opacity = value;
   253           BlendFunction blend = CreateBlendFunction();
   254           NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   255             IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, ref blend, ULW_ALPHA);
   256         }
   257       }
   258     }
   259 
   260     public bool Visible {
   261       get {
   262         return visible;
   263       }
   264       set {
   265         if (visible != value) {
   266           visible = value;
   267           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0,
   268             SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER |
   269             (value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
   270           if (value) {
   271             if (!alwaysOnTop)
   272               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   273           } else {
   274             if (!alwaysOnTop)
   275               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   276           }
   277         }
   278       }
   279     }
   280 
   281     // if locked, the window can not be moved or resized
   282     public bool LockPositionAndSize {
   283       get {
   284         return lockPositionAndSize;
   285       }
   286       set {
   287         lockPositionAndSize = value;
   288       }
   289     }
   290 
   291     public bool AlwaysOnTop {
   292       get {
   293         return alwaysOnTop;
   294       }
   295       set {
   296         if (value != alwaysOnTop) {
   297           alwaysOnTop = value;
   298           if (alwaysOnTop) {
   299             if (visible)
   300               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   301             MoveToTopMost(Handle);            
   302           } else {
   303             MoveToBottom(Handle);
   304             if (visible)
   305               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   306           }
   307         }
   308       }
   309     }
   310 
   311     public Size Size {
   312       get {
   313         return size; 
   314       }
   315       set {
   316         if (size != value) {
   317           size = value;
   318           NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   319             ref size, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, 0);                    
   320           if (SizeChanged != null)
   321             SizeChanged(this, EventArgs.Empty);
   322         }
   323       }
   324     }
   325 
   326     public event EventHandler SizeChanged;
   327 
   328     public Point Location {
   329       get {
   330         return location;
   331       }
   332       set {
   333         if (location != value) {
   334           location = value;
   335           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   336             location.X, location.Y, 0, 0, 
   337             SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING);          
   338           if (LocationChanged != null)
   339             LocationChanged(this, EventArgs.Empty);
   340         }
   341       }
   342     }
   343 
   344     public event EventHandler LocationChanged;
   345 
   346     public ContextMenu ContextMenu {
   347       get {
   348         return contextMenu;
   349       }
   350       set {
   351         this.contextMenu = value;
   352       }
   353     }
   354 
   355     public event HitTestEventHandler HitTest;
   356 
   357     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   358     private struct BlendFunction {
   359       public byte BlendOp;
   360       public byte BlendFlags;
   361       public byte SourceConstantAlpha;
   362       public byte AlphaFormat;
   363     }
   364 
   365     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   366     private struct WindowPos {
   367       public IntPtr hwnd;
   368       public IntPtr hwndInsertAfter;
   369       public int x;
   370       public int y;
   371       public int cx;
   372       public int cy;
   373       public uint flags;
   374     }
   375 
   376     public static readonly IntPtr HWND_BOTTOM = (IntPtr)1;
   377     public static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1);
   378 
   379     public const int WS_EX_LAYERED = 0x00080000;
   380     public const int WS_EX_TOOLWINDOW = 0x00000080;
   381 
   382     public const uint SWP_NOSIZE = 0x0001;
   383     public const uint SWP_NOMOVE = 0x0002;
   384     public const uint SWP_NOACTIVATE = 0x0010;
   385     public const uint SWP_HIDEWINDOW = 0x0080;
   386     public const uint SWP_SHOWWINDOW = 0x0040;
   387     public const uint SWP_NOZORDER = 0x0004;
   388     public const uint SWP_NOSENDCHANGING = 0x0400;
   389 
   390     public const int ULW_COLORKEY = 0x00000001;
   391     public const int ULW_ALPHA = 0x00000002;
   392     public const int ULW_OPAQUE = 0x00000004;
   393 
   394     public const byte AC_SRC_OVER = 0x00;
   395     public const byte AC_SRC_ALPHA = 0x01;
   396 
   397     public const int WM_NCHITTEST = 0x0084;
   398     public const int WM_NCLBUTTONDBLCLK = 0x00A3;
   399     public const int WM_NCLBUTTONDOWN = 0x00A1;
   400     public const int WM_NCLBUTTONUP = 0x00A2;
   401     public const int WM_NCRBUTTONDOWN = 0x00A4;
   402     public const int WM_NCRBUTTONUP = 0x00A5;
   403     public const int WM_WINDOWPOSCHANGING = 0x0046;
   404     public const int WM_COMMAND = 0x0111;
   405 
   406     public const int TPM_RIGHTBUTTON = 0x0002;
   407     public const int TPM_VERTICAL = 0x0040;
   408 
   409     private enum WindowAttribute : int {
   410       DWMWA_NCRENDERING_ENABLED = 1,
   411       DWMWA_NCRENDERING_POLICY,
   412       DWMWA_TRANSITIONS_FORCEDISABLED,
   413       DWMWA_ALLOW_NCPAINT,
   414       DWMWA_CAPTION_BUTTON_BOUNDS,
   415       DWMWA_NONCLIENT_RTL_LAYOUT,
   416       DWMWA_FORCE_ICONIC_REPRESENTATION,
   417       DWMWA_FLIP3D_POLICY,
   418       DWMWA_EXTENDED_FRAME_BOUNDS,
   419       DWMWA_HAS_ICONIC_BITMAP,
   420       DWMWA_DISALLOW_PEEK,
   421       DWMWA_EXCLUDED_FROM_PEEK,
   422       DWMWA_LAST
   423     }
   424 
   425     /// <summary>
   426     /// Some macros imported and converted from the Windows SDK
   427     /// </summary>
   428     private static class Macros {
   429       public static UInt16 LOWORD(IntPtr l) {
   430         return ((UInt16) (((UInt64) (l)) & 0xffff));
   431       }
   432       
   433       public static UInt16 HIWORD(IntPtr l) {
   434         return ((UInt16) ((((UInt64) (l)) >> 16) & 0xffff));
   435       }
   436 
   437       public static int GET_X_LPARAM(IntPtr lp) {
   438         return ((int) (short) LOWORD(lp));
   439       }
   440 
   441       public static int GET_Y_LPARAM(IntPtr lp) {
   442         return ((int) (short) HIWORD(lp));
   443       }
   444     }
   445 
   446     /// <summary>
   447     /// Imported native methods
   448     /// </summary>
   449     private static class NativeMethods {
   450       private const string USER = "user32.dll";
   451       private const string GDI = "gdi32.dll";
   452       public const string DWMAPI = "dwmapi.dll";
   453 
   454       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   455       [return: MarshalAs(UnmanagedType.Bool)]
   456       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
   457         IntPtr pptDst, ref Size psize, IntPtr hdcSrc, IntPtr pprSrc,
   458         int crKey, IntPtr pblend, int dwFlags);
   459 
   460       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   461       [return: MarshalAs(UnmanagedType.Bool)]
   462       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, 
   463         IntPtr pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, 
   464         int crKey, ref BlendFunction pblend, int dwFlags);
   465 
   466       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   467       [return: MarshalAs(UnmanagedType.Bool)]
   468       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
   469         IntPtr pptDst, IntPtr psize, IntPtr hdcSrc, IntPtr pprSrc,
   470         int crKey, ref BlendFunction pblend, int dwFlags);  
   471 
   472       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   473       public static extern IntPtr GetDC(IntPtr hWnd);
   474 
   475       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   476       public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
   477 
   478       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   479       public static extern bool SetWindowPos(IntPtr hWnd,
   480         IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
   481 
   482       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   483       public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags, 
   484         int x, int y, IntPtr hWnd, IntPtr tpmParams);
   485 
   486       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   487       public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
   488 
   489       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   490       [return: MarshalAs(UnmanagedType.Bool)]
   491       public static extern bool DeleteDC(IntPtr hdc);
   492       
   493       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   494       public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
   495 
   496       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   497       [return: MarshalAs(UnmanagedType.Bool)]
   498       public static extern bool DeleteObject(IntPtr hObject);
   499 
   500       [DllImport(DWMAPI, CallingConvention = CallingConvention.Winapi)]
   501       public static extern int DwmSetWindowAttribute(IntPtr hwnd,
   502         WindowAttribute dwAttribute, ref bool pvAttribute, int cbAttribute);
   503     }    
   504   }
   505 
   506   public enum HitResult {
   507     Transparent = -1,
   508     Nowhere = 0,
   509     Client = 1,
   510     Caption = 2,
   511     Left = 10,
   512     Right = 11,
   513     Top = 12,
   514     TopLeft = 13,
   515     TopRight = 14,
   516     Bottom = 15,
   517     BottomLeft = 16,
   518     BottomRight = 17,
   519     Border = 18
   520   }
   521 
   522   public delegate void HitTestEventHandler(object sender, HitTestEventArgs e);
   523 
   524   public class HitTestEventArgs : EventArgs {
   525     public HitTestEventArgs(Point location, HitResult hitResult) {
   526       Location = location;
   527       HitResult = hitResult;
   528     }
   529     public Point Location { get; private set; }
   530     public HitResult HitResult { get; set; }
   531   }
   532 }