SharpDisplay: Migrating to new robust client scheme.
3 This Source Code Form is subject to the terms of the Mozilla Public
4 License, v. 2.0. If a copy of the MPL was not distributed with this
5 file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 Copyright (C) 2010-2011 Michael Möller <mmoeller@openhardwaremonitor.org>
8 Copyright (C) 2010 Paul Werelds <paul@werelds.net>
14 using System.Drawing.Drawing2D;
15 using System.Drawing.Text;
16 using System.Reflection;
17 using System.Runtime.InteropServices;
18 using System.Windows.Forms;
20 namespace OpenHardwareMonitor.GUI {
22 public class GadgetWindow : NativeWindow, IDisposable {
24 private bool visible = false;
25 private bool lockPositionAndSize = false;
26 private bool alwaysOnTop = false;
27 private byte opacity = 255;
28 private Point location = new Point(100, 100);
29 private Size size = new Size(130, 84);
30 private ContextMenu contextMenu = null;
31 private MethodInfo commandDispatch;
32 private IntPtr handleBitmapDC;
33 private Size bufferSize;
34 private Graphics graphics;
36 public GadgetWindow() {
38 typeof(Form).Assembly.GetType("System.Windows.Forms.Command");
39 commandDispatch = commandType.GetMethod("DispatchID",
40 BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
41 null, new Type[]{ typeof(int) }, null);
43 this.CreateHandle(CreateParams);
45 // move window to the bottom
48 // prevent window from fading to a glass sheet when peek is invoked
51 NativeMethods.DwmSetWindowAttribute(Handle,
52 WindowAttribute.DWMWA_EXCLUDED_FROM_PEEK, ref value,
53 Marshal.SizeOf(value));
54 } catch (DllNotFoundException) { } catch (EntryPointNotFoundException) { }
59 private void ShowDesktopChanged(bool showDesktop) {
61 MoveToTopMost(Handle);
67 private void MoveToBottom(IntPtr handle) {
68 NativeMethods.SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0,
69 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
72 private void MoveToTopMost(IntPtr handle) {
73 NativeMethods.SetWindowPos(handle, HWND_TOPMOST, 0, 0, 0, 0,
74 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
77 private void ShowContextMenu(Point position) {
78 NativeMethods.TrackPopupMenuEx(contextMenu.Handle,
79 TPM_RIGHTBUTTON | TPM_VERTICAL, position.X,
80 position.Y, Handle, IntPtr.Zero);
83 protected virtual CreateParams CreateParams {
85 CreateParams cp = new CreateParams();
90 cp.ExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW;
95 protected override void WndProc(ref Message message) {
96 switch (message.Msg) {
98 // need to dispatch the message for the context menu
99 if (message.LParam == IntPtr.Zero)
100 commandDispatch.Invoke(null, new object[] {
101 message.WParam.ToInt32() & 0xFFFF });
104 message.Result = (IntPtr)HitResult.Caption;
105 if (HitTest != null) {
107 Macros.GET_X_LPARAM(message.LParam) - location.X,
108 Macros.GET_Y_LPARAM(message.LParam) - location.Y
110 HitTestEventArgs e = new HitTestEventArgs(p, HitResult.Caption);
112 message.Result = (IntPtr)e.HitResult;
115 case WM_NCLBUTTONDBLCLK: {
116 if (MouseDoubleClick != null) {
117 MouseDoubleClick(this, new MouseEventArgs(MouseButtons.Left, 2,
118 Macros.GET_X_LPARAM(message.LParam) - location.X,
119 Macros.GET_Y_LPARAM(message.LParam) - location.Y, 0));
121 message.Result = IntPtr.Zero;
123 case WM_NCRBUTTONDOWN: {
124 message.Result = IntPtr.Zero;
126 case WM_NCRBUTTONUP: {
127 if (contextMenu != null)
128 ShowContextMenu(new Point(
129 Macros.GET_X_LPARAM(message.LParam),
130 Macros.GET_Y_LPARAM(message.LParam)
132 message.Result = IntPtr.Zero;
134 case WM_WINDOWPOSCHANGING: {
135 WindowPos wp = (WindowPos)Marshal.PtrToStructure(
136 message.LParam, typeof(WindowPos));
138 if (!lockPositionAndSize) {
139 // prevent the window from leaving the screen
140 if ((wp.flags & SWP_NOMOVE) == 0) {
141 Rectangle rect = Screen.GetWorkingArea(
142 new Rectangle(wp.x, wp.y, wp.cx, wp.cy));
143 const int margin = 16;
144 wp.x = Math.Max(wp.x, rect.Left - wp.cx + margin);
145 wp.x = Math.Min(wp.x, rect.Right - margin);
146 wp.y = Math.Max(wp.y, rect.Top - wp.cy + margin);
147 wp.y = Math.Min(wp.y, rect.Bottom - margin);
150 // update location and fire event
151 if ((wp.flags & SWP_NOMOVE) == 0) {
152 if (location.X != wp.x || location.Y != wp.y) {
153 location = new Point(wp.x, wp.y);
154 if (LocationChanged != null)
155 LocationChanged(this, EventArgs.Empty);
159 // update size and fire event
160 if ((wp.flags & SWP_NOSIZE) == 0) {
161 if (size.Width != wp.cx || size.Height != wp.cy) {
162 size = new Size(wp.cx, wp.cy);
163 if (SizeChanged != null)
164 SizeChanged(this, EventArgs.Empty);
168 // update the size of the layered window
169 if ((wp.flags & SWP_NOSIZE) == 0) {
170 NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero,
171 IntPtr.Zero, ref size, IntPtr.Zero, IntPtr.Zero, 0,
175 // update the position of the layered window
176 if ((wp.flags & SWP_NOMOVE) == 0) {
177 NativeMethods.SetWindowPos(Handle, IntPtr.Zero,
178 location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE |
179 SWP_NOZORDER | SWP_NOSENDCHANGING);
183 // do not forward any move or size messages
184 wp.flags |= SWP_NOSIZE | SWP_NOMOVE;
186 // suppress any frame changed events
187 wp.flags &= ~SWP_FRAMECHANGED;
189 Marshal.StructureToPtr(wp, message.LParam, false);
190 message.Result = IntPtr.Zero;
193 base.WndProc(ref message);
198 private BlendFunction CreateBlendFunction() {
199 BlendFunction blend = new BlendFunction();
200 blend.BlendOp = AC_SRC_OVER;
201 blend.BlendFlags = 0;
202 blend.SourceConstantAlpha = opacity;
203 blend.AlphaFormat = AC_SRC_ALPHA;
207 private void CreateBuffer() {
208 IntPtr handleScreenDC = NativeMethods.GetDC(IntPtr.Zero);
209 handleBitmapDC = NativeMethods.CreateCompatibleDC(handleScreenDC);
210 NativeMethods.ReleaseDC(IntPtr.Zero, handleScreenDC);
213 BitmapInfo info = new BitmapInfo();
214 info.Size = Marshal.SizeOf(info);
215 info.Width = size.Width;
216 info.Height = -size.Height;
221 IntPtr hBmp = NativeMethods.CreateDIBSection(handleBitmapDC, ref info, 0,
222 out ptr, IntPtr.Zero, 0);
223 IntPtr hBmpOld = NativeMethods.SelectObject(handleBitmapDC, hBmp);
224 NativeMethods.DeleteObject(hBmpOld);
226 graphics = Graphics.FromHdc(handleBitmapDC);
228 if (Environment.OSVersion.Version.Major > 5) {
229 this.graphics.TextRenderingHint = TextRenderingHint.SystemDefault;
230 this.graphics.SmoothingMode = SmoothingMode.HighQuality;
234 private void DisposeBuffer() {
236 NativeMethods.DeleteDC(handleBitmapDC);
239 public virtual void Dispose() {
243 public PaintEventHandler Paint;
245 public void Redraw() {
246 if (!visible || Paint == null)
249 if (size != bufferSize) {
255 new PaintEventArgs(graphics, new Rectangle(Point.Empty, size)));
257 Point pointSource = Point.Empty;
258 BlendFunction blend = CreateBlendFunction();
260 NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
261 ref size, handleBitmapDC, ref pointSource, 0, ref blend, ULW_ALPHA);
263 // make sure the window is at the right location
264 NativeMethods.SetWindowPos(Handle, IntPtr.Zero,
265 location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE |
266 SWP_NOZORDER | SWP_NOSENDCHANGING);
269 public byte Opacity {
274 if (opacity != value) {
276 BlendFunction blend = CreateBlendFunction();
277 NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
278 IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, ref blend, ULW_ALPHA);
283 public bool Visible {
288 if (visible != value) {
290 NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0,
291 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER |
292 (value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
295 ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
298 ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
304 // if locked, the window can not be moved or resized
305 public bool LockPositionAndSize {
307 return lockPositionAndSize;
310 lockPositionAndSize = value;
314 public bool AlwaysOnTop {
319 if (value != alwaysOnTop) {
323 ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
324 MoveToTopMost(Handle);
326 MoveToBottom(Handle);
328 ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
341 NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
342 ref size, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, 0);
343 if (SizeChanged != null)
344 SizeChanged(this, EventArgs.Empty);
349 public event EventHandler SizeChanged;
351 public Point Location {
356 if (location != value) {
358 NativeMethods.SetWindowPos(Handle, IntPtr.Zero,
359 location.X, location.Y, 0, 0,
360 SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING);
361 if (LocationChanged != null)
362 LocationChanged(this, EventArgs.Empty);
367 public event EventHandler LocationChanged;
369 public ContextMenu ContextMenu {
374 this.contextMenu = value;
378 public event HitTestEventHandler HitTest;
380 public event MouseEventHandler MouseDoubleClick;
382 [StructLayout(LayoutKind.Sequential, Pack = 1)]
383 private struct BlendFunction {
385 public byte BlendFlags;
386 public byte SourceConstantAlpha;
387 public byte AlphaFormat;
390 [StructLayout(LayoutKind.Sequential, Pack = 1)]
391 private struct WindowPos {
393 public IntPtr hwndInsertAfter;
401 [StructLayout(LayoutKind.Sequential)]
402 public struct BitmapInfo {
407 public Int16 BitCount;
408 public Int32 Compression;
409 public Int32 SizeImage;
410 public Int32 XPelsPerMeter;
411 public Int32 YPelsPerMeter;
412 public Int32 ClrUsed;
413 public Int32 ClrImportant;
417 public static readonly IntPtr HWND_BOTTOM = (IntPtr)1;
418 public static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1);
420 public const int WS_EX_LAYERED = 0x00080000;
421 public const int WS_EX_TOOLWINDOW = 0x00000080;
423 public const uint SWP_NOSIZE = 0x0001;
424 public const uint SWP_NOMOVE = 0x0002;
425 public const uint SWP_NOACTIVATE = 0x0010;
426 public const uint SWP_FRAMECHANGED = 0x0020;
427 public const uint SWP_HIDEWINDOW = 0x0080;
428 public const uint SWP_SHOWWINDOW = 0x0040;
429 public const uint SWP_NOZORDER = 0x0004;
430 public const uint SWP_NOSENDCHANGING = 0x0400;
432 public const int ULW_COLORKEY = 0x00000001;
433 public const int ULW_ALPHA = 0x00000002;
434 public const int ULW_OPAQUE = 0x00000004;
436 public const byte AC_SRC_OVER = 0x00;
437 public const byte AC_SRC_ALPHA = 0x01;
439 public const int WM_NCHITTEST = 0x0084;
440 public const int WM_NCLBUTTONDBLCLK = 0x00A3;
441 public const int WM_NCLBUTTONDOWN = 0x00A1;
442 public const int WM_NCLBUTTONUP = 0x00A2;
443 public const int WM_NCRBUTTONDOWN = 0x00A4;
444 public const int WM_NCRBUTTONUP = 0x00A5;
445 public const int WM_WINDOWPOSCHANGING = 0x0046;
446 public const int WM_COMMAND = 0x0111;
448 public const int TPM_RIGHTBUTTON = 0x0002;
449 public const int TPM_VERTICAL = 0x0040;
451 private enum WindowAttribute : int {
452 DWMWA_NCRENDERING_ENABLED = 1,
453 DWMWA_NCRENDERING_POLICY,
454 DWMWA_TRANSITIONS_FORCEDISABLED,
456 DWMWA_CAPTION_BUTTON_BOUNDS,
457 DWMWA_NONCLIENT_RTL_LAYOUT,
458 DWMWA_FORCE_ICONIC_REPRESENTATION,
460 DWMWA_EXTENDED_FRAME_BOUNDS,
461 DWMWA_HAS_ICONIC_BITMAP,
463 DWMWA_EXCLUDED_FROM_PEEK,
468 /// Some macros imported and converted from the Windows SDK
470 private static class Macros {
471 public static ushort LOWORD(IntPtr l) {
472 return (ushort) ((ulong)l & 0xFFFF);
475 public static UInt16 HIWORD(IntPtr l) {
476 return (ushort) (((ulong)l >> 16) & 0xFFFF);
479 public static int GET_X_LPARAM(IntPtr lp) {
480 return (short) LOWORD(lp);
483 public static int GET_Y_LPARAM(IntPtr lp) {
484 return (short) HIWORD(lp);
489 /// Imported native methods
491 private static class NativeMethods {
492 private const string USER = "user32.dll";
493 private const string GDI = "gdi32.dll";
494 public const string DWMAPI = "dwmapi.dll";
496 [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
497 [return: MarshalAs(UnmanagedType.Bool)]
498 public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
499 IntPtr pptDst, ref Size psize, IntPtr hdcSrc, IntPtr pprSrc,
500 int crKey, IntPtr pblend, int dwFlags);
502 [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
503 [return: MarshalAs(UnmanagedType.Bool)]
504 public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
505 IntPtr pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc,
506 int crKey, ref BlendFunction pblend, int dwFlags);
508 [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
509 [return: MarshalAs(UnmanagedType.Bool)]
510 public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
511 IntPtr pptDst, IntPtr psize, IntPtr hdcSrc, IntPtr pprSrc,
512 int crKey, ref BlendFunction pblend, int dwFlags);
514 [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
515 public static extern IntPtr GetDC(IntPtr hWnd);
517 [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
518 public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
520 [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
521 public static extern bool SetWindowPos(IntPtr hWnd,
522 IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
524 [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
525 public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags,
526 int x, int y, IntPtr hWnd, IntPtr tpmParams);
528 [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
529 public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
531 [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
532 public static extern IntPtr CreateDIBSection(IntPtr hdc,
533 [In] ref BitmapInfo pbmi, uint pila, out IntPtr ppvBits,
534 IntPtr hSection, uint dwOffset);
536 [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
537 [return: MarshalAs(UnmanagedType.Bool)]
538 public static extern bool DeleteDC(IntPtr hdc);
540 [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
541 public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
543 [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
544 [return: MarshalAs(UnmanagedType.Bool)]
545 public static extern bool DeleteObject(IntPtr hObject);
547 [DllImport(DWMAPI, CallingConvention = CallingConvention.Winapi)]
548 public static extern int DwmSetWindowAttribute(IntPtr hwnd,
549 WindowAttribute dwAttribute, ref bool pvAttribute, int cbAttribute);
553 public enum HitResult {
569 public delegate void HitTestEventHandler(object sender, HitTestEventArgs e);
571 public class HitTestEventArgs : EventArgs {
572 public HitTestEventArgs(Point location, HitResult hitResult) {
574 HitResult = hitResult;
576 public Point Location { get; private set; }
577 public HitResult HitResult { get; set; }