GUI/GadgetWindow.cs
author moel.mich
Tue, 07 Sep 2010 18:51:42 +0000
changeset 180 d40f49d45614
parent 176 c16fd81b520a
child 183 3096735e99b2
permissions -rw-r--r--
Fixed the GadgetWindow AlwaysOnTop option (could be lost by tracking "Show Desktop" events even when AlwaysOnTop = true).
     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   public class GadgetWindow : NativeWindow {
    46 
    47     private bool visible = false;
    48     private bool lockPosition = false;
    49     private bool alwaysOnTop = false;
    50     private byte opacity = 255;
    51     private Point location = new Point(100, 100);
    52     private Size size = new Size(130, 84);
    53     private ContextMenu contextMenu = null;
    54     private MethodInfo commandDispatch;
    55 
    56     public GadgetWindow() {
    57       Type commandType = 
    58         typeof(Form).Assembly.GetType("System.Windows.Forms.Command");
    59       commandDispatch = commandType.GetMethod("DispatchID", 
    60         BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, 
    61         null, new Type[]{ typeof(int) }, null);
    62 
    63       this.CreateHandle(CreateParams);
    64 
    65       // move window to the bottom
    66       MoveToBottom(Handle);
    67 
    68       // prevent window from fading to a glass sheet when peek is invoked
    69       try {
    70         bool value = true;
    71         int r = NativeMethods.DwmSetWindowAttribute(Handle,
    72           WindowAttribute.DWMWA_EXCLUDED_FROM_PEEK, ref value,
    73           Marshal.SizeOf(value));
    74       } catch (DllNotFoundException) { } catch (EntryPointNotFoundException) { }
    75     }
    76 
    77     private void ShowDesktopChanged(bool showDesktop) {
    78       if (showDesktop) {
    79         MoveToTopMost(Handle);
    80       } else {
    81         MoveToBottom(Handle);
    82       }
    83     }
    84 
    85     private void MoveToBottom(IntPtr handle) {
    86       NativeMethods.SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0,
    87         SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
    88     }
    89 
    90     private void MoveToTopMost(IntPtr handle) {
    91       NativeMethods.SetWindowPos(handle, HWND_TOPMOST, 0, 0, 0, 0,
    92         SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
    93     }
    94 
    95     private void ShowContextMenu(Point position) {
    96       NativeMethods.TrackPopupMenuEx(contextMenu.Handle, 
    97         TPM_RIGHTBUTTON | TPM_VERTICAL, position.X,
    98         position.Y, Handle, IntPtr.Zero);
    99     }
   100 
   101     protected virtual CreateParams CreateParams {
   102       get {
   103         CreateParams cp = new CreateParams();
   104         cp.Width = size.Width;
   105         cp.Height = size.Height;
   106         cp.X = location.X;
   107         cp.Y = location.Y;
   108         cp.ExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW;
   109         return cp;
   110       }
   111     }
   112 
   113     protected override void WndProc(ref Message message) {
   114       switch (message.Msg) {
   115         case WM_COMMAND:
   116           // need to dispatch the message for the context menu
   117           if (message.LParam == IntPtr.Zero) 
   118             commandDispatch.Invoke(null, new object[] { 
   119               message.WParam.ToInt32() & 0xFFFF });          
   120           break;
   121         case WM_NCHITTEST:           
   122           // all pixels of the form belong to the caption
   123           message.Result = HTCAPTION; 
   124           break;
   125         case WM_NCLBUTTONDBLCLK:  
   126           message.Result = IntPtr.Zero; break;
   127         case WM_NCRBUTTONDOWN:
   128           message.Result = IntPtr.Zero; break;
   129         case WM_NCRBUTTONUP:
   130           if (contextMenu != null)
   131             ShowContextMenu(new Point(
   132               (int)((uint)message.LParam & 0xFFFF), 
   133               (int)(((uint)message.LParam >>16) & 0xFFFF)));          
   134           message.Result = IntPtr.Zero; 
   135           break;
   136         case WM_WINDOWPOSCHANGING:         
   137           WindowPos wp = (WindowPos)Marshal.PtrToStructure(
   138             message.LParam, typeof(WindowPos));
   139 
   140           // add the nomove flag if position is locked
   141           if (lockPosition)
   142             wp.flags |= SWP_NOMOVE;
   143 
   144           // prevent the window from leaving the screen
   145           if ((wp.flags & SWP_NOMOVE) == 0) {            
   146             Rectangle rect = Screen.GetWorkingArea(new Point(wp.x, wp.y));
   147             const int margin = 20;
   148             wp.x = Math.Max(wp.x, rect.Left - wp.cx + margin);
   149             wp.x = Math.Min(wp.x, rect.Right - margin);
   150             wp.y = Math.Max(wp.y, rect.Top - wp.cy + margin);
   151             wp.y = Math.Min(wp.y, rect.Bottom - margin);
   152 
   153             // raise the event if location changed
   154             if (location.X != wp.x || location.Y != wp.y) {
   155               location = new Point(wp.x, wp.y);
   156               if (LocationChanged != null)
   157                 LocationChanged(this, EventArgs.Empty);
   158             }
   159           }          
   160 
   161           Marshal.StructureToPtr(wp, message.LParam, false);
   162           message.Result = IntPtr.Zero;
   163           break;           
   164         default:
   165           base.WndProc(ref message); break;
   166       }      
   167     }
   168 
   169     private BlendFunction CreateBlendFunction() {
   170       BlendFunction blend = new BlendFunction();
   171       blend.BlendOp = AC_SRC_OVER;
   172       blend.BlendFlags = 0;
   173       blend.SourceConstantAlpha = opacity;
   174       blend.AlphaFormat = AC_SRC_ALPHA;
   175       return blend;
   176     }
   177 
   178     public void Update(Bitmap bitmap) {
   179       IntPtr screen = NativeMethods.GetDC(IntPtr.Zero);
   180       IntPtr memory = NativeMethods.CreateCompatibleDC(screen);
   181       IntPtr newHBitmap = IntPtr.Zero;
   182       IntPtr oldHBitmap = IntPtr.Zero;
   183 
   184       try {
   185         newHBitmap = bitmap.GetHbitmap(Color.Black);
   186         oldHBitmap = NativeMethods.SelectObject(memory, newHBitmap);
   187 
   188         Size size = bitmap.Size;
   189         Point pointSource = Point.Empty;
   190         Point topPos = Location;
   191 
   192         BlendFunction blend = CreateBlendFunction();
   193         NativeMethods.UpdateLayeredWindow(Handle, screen, ref topPos,
   194           ref size, memory, ref pointSource, 0, ref blend, ULW_ALPHA);
   195       } finally {
   196         NativeMethods.ReleaseDC(IntPtr.Zero, screen);
   197         if (newHBitmap != IntPtr.Zero) {
   198           NativeMethods.SelectObject(memory, oldHBitmap);
   199           NativeMethods.DeleteObject(newHBitmap);
   200         }
   201         NativeMethods.DeleteDC(memory);
   202       }
   203     }
   204 
   205     public byte Opacity {
   206       get {
   207         return opacity;
   208       }
   209       set {
   210         if (opacity != value) {
   211           opacity = value;
   212           BlendFunction blend = CreateBlendFunction();
   213           NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   214             IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, ref blend, ULW_ALPHA);
   215         }
   216       }
   217     }
   218 
   219     public bool Visible {
   220       get {
   221         return visible;
   222       }
   223       set {
   224         if (visible != value) {
   225           visible = value;
   226           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0,
   227             SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER |
   228             (value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
   229           if (value) {
   230             if (!alwaysOnTop)
   231               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   232           } else {
   233             if (!alwaysOnTop)
   234               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   235           }
   236         }
   237       }
   238     }
   239 
   240     // if locked, the window can not be moved
   241     public bool LockPosition {
   242       get {
   243         return lockPosition;
   244       }
   245       set {
   246         lockPosition = value;
   247       }
   248     }
   249 
   250     public bool AlwaysOnTop {
   251       get {
   252         return alwaysOnTop;
   253       }
   254       set {
   255         if (value != alwaysOnTop) {
   256           alwaysOnTop = value;
   257           if (alwaysOnTop) {
   258             if (visible)
   259               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   260             MoveToTopMost(Handle);            
   261           } else {
   262             MoveToBottom(Handle);
   263             if (visible)
   264               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   265           }
   266         }
   267       }
   268     }
   269 
   270     public Size Size {
   271       get {
   272         return size; 
   273       }
   274       set {
   275         if (size != value) {
   276           size = value;
   277           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, size.Width,
   278             size.Height, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | 
   279             SWP_NOSENDCHANGING);
   280         }
   281       }
   282     }
   283 
   284     public Point Location {
   285       get {
   286         return location;
   287       }
   288       set {
   289         NativeMethods.SetWindowPos(Handle, IntPtr.Zero, value.X, value.Y, 0,
   290           0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING);
   291         location = value;
   292         if (LocationChanged != null)
   293           LocationChanged(this, EventArgs.Empty);
   294       }
   295     }
   296 
   297     public event EventHandler LocationChanged;
   298 
   299     public ContextMenu ContextMenu {
   300       get {
   301         return contextMenu;
   302       }
   303       set {
   304         this.contextMenu = value;
   305       }
   306     }
   307 
   308     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   309     private struct BlendFunction {
   310       public byte BlendOp;
   311       public byte BlendFlags;
   312       public byte SourceConstantAlpha;
   313       public byte AlphaFormat;
   314     }
   315 
   316     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   317     private struct WindowPos {
   318       public IntPtr hwnd;
   319       public IntPtr hwndInsertAfter;
   320       public int x;
   321       public int y;
   322       public int cx;
   323       public int cy;
   324       public uint flags;
   325     }
   326 
   327     public static readonly IntPtr HWND_BOTTOM = (IntPtr)1;
   328     public static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1);
   329 
   330     public const int WS_EX_LAYERED = 0x00080000;
   331     public const int WS_EX_TOOLWINDOW = 0x00000080;
   332 
   333     public const uint SWP_NOSIZE = 0x0001;
   334     public const uint SWP_NOMOVE = 0x0002;
   335     public const uint SWP_NOACTIVATE = 0x0010;
   336     public const uint SWP_HIDEWINDOW = 0x0080;
   337     public const uint SWP_SHOWWINDOW = 0x0040;
   338     public const uint SWP_NOZORDER = 0x0004;
   339     public const uint SWP_NOSENDCHANGING = 0x0400;
   340 
   341     public const int ULW_COLORKEY = 0x00000001;
   342     public const int ULW_ALPHA = 0x00000002;
   343     public const int ULW_OPAQUE = 0x00000004;
   344 
   345     public const byte AC_SRC_OVER = 0x00;
   346     public const byte AC_SRC_ALPHA = 0x01;
   347 
   348     public const int WM_NCHITTEST = 0x0084;
   349     public const int WM_NCLBUTTONDBLCLK = 0x00A3;
   350     public const int WM_NCLBUTTONDOWN = 0x00A1;
   351     public const int WM_NCLBUTTONUP = 0x00A2;
   352     public const int WM_NCRBUTTONDOWN = 0x00A4;
   353     public const int WM_NCRBUTTONUP = 0x00A5;
   354     public const int WM_WINDOWPOSCHANGING = 0x0046;
   355     public const int WM_COMMAND = 0x0111;
   356 
   357     public const int TPM_RIGHTBUTTON = 0x0002;
   358     public const int TPM_VERTICAL = 0x0040;
   359 
   360     public readonly IntPtr HTCAPTION = (IntPtr)2;
   361 
   362     private enum WindowAttribute : int {
   363       DWMWA_NCRENDERING_ENABLED = 1,
   364       DWMWA_NCRENDERING_POLICY,
   365       DWMWA_TRANSITIONS_FORCEDISABLED,
   366       DWMWA_ALLOW_NCPAINT,
   367       DWMWA_CAPTION_BUTTON_BOUNDS,
   368       DWMWA_NONCLIENT_RTL_LAYOUT,
   369       DWMWA_FORCE_ICONIC_REPRESENTATION,
   370       DWMWA_FLIP3D_POLICY,
   371       DWMWA_EXTENDED_FRAME_BOUNDS,
   372       DWMWA_HAS_ICONIC_BITMAP,
   373       DWMWA_DISALLOW_PEEK,
   374       DWMWA_EXCLUDED_FROM_PEEK,
   375       DWMWA_LAST
   376     }
   377 
   378     private static class NativeMethods {
   379       private const string USER = "user32.dll";
   380       private const string GDI = "gdi32.dll";
   381       public const string DWMAPI = "dwmapi.dll";
   382 
   383       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   384       [return: MarshalAs(UnmanagedType.Bool)]
   385       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, 
   386         ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, 
   387         int crKey, ref BlendFunction pblend, int dwFlags);
   388 
   389       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   390       [return: MarshalAs(UnmanagedType.Bool)]
   391       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
   392         IntPtr pptDst, IntPtr psize, IntPtr hdcSrc, IntPtr pprSrc,
   393         int crKey, ref BlendFunction pblend, int dwFlags);  
   394 
   395       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   396       public static extern IntPtr GetDC(IntPtr hWnd);
   397 
   398       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   399       public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
   400 
   401       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   402       public static extern bool SetWindowPos(IntPtr hWnd,
   403         IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
   404 
   405       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   406       public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags, 
   407         int x, int y, IntPtr hWnd, IntPtr tpmParams);
   408 
   409       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   410       public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
   411 
   412       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   413       [return: MarshalAs(UnmanagedType.Bool)]
   414       public static extern bool DeleteDC(IntPtr hdc);
   415       
   416       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   417       public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
   418 
   419       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   420       [return: MarshalAs(UnmanagedType.Bool)]
   421       public static extern bool DeleteObject(IntPtr hObject);
   422 
   423       [DllImport(DWMAPI, CallingConvention = CallingConvention.Winapi)]
   424       public static extern int DwmSetWindowAttribute(IntPtr hwnd,
   425         WindowAttribute dwAttribute, ref bool pvAttribute, int cbAttribute);
   426     }    
   427   }
   428 }