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