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