GUI/NotifyIconAdv.cs
author moel.mich
Tue, 17 Jul 2012 16:10:59 +0000
changeset 364 25ef2c489ce8
child 371 c1a0d321e646
permissions -rw-r--r--
Attempt at fixing Issue 253 without breaking Issue 159 once more.
     1 /*
     2  
     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/.
     6  
     7   Copyright (C) 2012 Michael Möller <mmoeller@openhardwaremonitor.org>
     8 	
     9 */
    10 
    11 using System;
    12 using System.ComponentModel;
    13 using System.Drawing;
    14 using System.Runtime.InteropServices;
    15 using System.Reflection;
    16 using System.Windows.Forms;
    17 
    18 namespace OpenHardwareMonitor.GUI {
    19   public class NotifyIconAdv : Component {
    20 
    21     private static int nextId = 0;
    22 
    23     private object syncObj = new object();
    24     private Icon icon;
    25     private string text = "";
    26     private int id;
    27     private bool created;
    28     private NotifyIconNativeWindow window;
    29     private bool doubleClickDown;
    30     private bool visible;
    31     private MethodInfo commandDispatch;
    32 
    33     public event EventHandler BalloonTipClicked;
    34     public event EventHandler BalloonTipClosed;
    35     public event EventHandler BalloonTipShown;
    36     public event EventHandler Click;
    37     public event EventHandler DoubleClick;
    38     public event MouseEventHandler MouseClick;
    39     public event MouseEventHandler MouseDoubleClick;
    40     public event MouseEventHandler MouseDown;
    41     public event MouseEventHandler MouseMove;
    42     public event MouseEventHandler MouseUp;
    43 
    44     public string BalloonTipText { get; set; }
    45     public ToolTipIcon BalloonTipIcon { get; set; }
    46     public string BalloonTipTitle { get; set; }
    47     public ContextMenu ContextMenu { get; set; }
    48     public ContextMenuStrip ContextMenuStrip { get; set; }
    49     public object Tag { get; set; }
    50 
    51     public Icon Icon {
    52       get {
    53         return icon;
    54       }
    55       set {
    56         if (icon != value) {
    57           icon = value;
    58           UpdateNotifyIcon(visible);
    59         }
    60       }
    61     }
    62 
    63     public string Text {
    64       get {
    65         return text;
    66       }
    67       set {
    68         if (value == null)
    69           value = "";
    70 
    71         if (value.Length > 63)
    72           throw new ArgumentOutOfRangeException();
    73 
    74         if (!value.Equals(text)) {
    75           text = value;
    76 
    77           if (visible) 
    78             UpdateNotifyIcon(visible);          
    79         }
    80       }
    81     }
    82 
    83     public bool Visible {
    84       get {
    85         return visible;
    86       }
    87       set {
    88         if (visible != value) {
    89           visible = value;
    90           UpdateNotifyIcon(visible);          
    91         }
    92       }
    93     }
    94     
    95     public NotifyIconAdv() {
    96       BalloonTipText = "";
    97       BalloonTipTitle = "";
    98 
    99       commandDispatch = typeof(Form).Assembly.
   100         GetType("System.Windows.Forms.Command").GetMethod("DispatchID",
   101         BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
   102         null, new Type[] { typeof(int) }, null);
   103 
   104       id = ++NotifyIconAdv.nextId;
   105       window = new NotifyIconNativeWindow(this);
   106       UpdateNotifyIcon(visible);
   107     }
   108 
   109     protected override void Dispose(bool disposing) {
   110       if (disposing) {
   111         if (window != null) {
   112           icon = null;
   113           text = "";
   114           UpdateNotifyIcon(false);
   115           window.DestroyHandle();
   116           window = null;
   117           ContextMenu = null;
   118           ContextMenuStrip = null;
   119         }
   120       } else {
   121         if (window != null && window.Handle != IntPtr.Zero) {
   122           NativeMethods.PostMessage(
   123             new HandleRef(window, window.Handle), WM_CLOSE, 0, 0);
   124           window.ReleaseHandle();
   125         }
   126       }
   127       base.Dispose(disposing);
   128     }
   129 
   130     public void ShowBalloonTip(int timeout) {
   131       ShowBalloonTip(timeout, BalloonTipTitle, BalloonTipText, BalloonTipIcon);
   132     }
   133 
   134     public void ShowBalloonTip(int timeout, string tipTitle, string tipText,
   135       ToolTipIcon tipIcon) 
   136     {
   137       if (timeout < 0)
   138         throw new ArgumentOutOfRangeException("timeout");
   139 
   140       if (string.IsNullOrEmpty(tipText))
   141         throw new ArgumentException("tipText");
   142 
   143       if (DesignMode)
   144         return;
   145 
   146       if (created) {
   147         NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData();
   148         if (window.Handle == IntPtr.Zero)
   149           window.CreateHandle(new CreateParams());
   150 
   151         data.Window = window.Handle;
   152         data.ID = id;
   153         data.Flags = NativeMethods.NotifyIconDataFlags.Info;
   154         data.TimeoutOrVersion = timeout;
   155         data.InfoTitle = tipTitle;
   156         data.Info = tipText;
   157         data.InfoFlags = (int)tipIcon;
   158 
   159         NativeMethods.Shell_NotifyIcon(
   160           NativeMethods.NotifyIconMessage.Modify, data);
   161       }
   162     }
   163 
   164     private void ShowContextMenu() {
   165       if (ContextMenu == null && ContextMenuStrip == null)
   166         return;
   167 
   168       NativeMethods.Point p = new NativeMethods.Point();
   169       NativeMethods.GetCursorPos(ref p);
   170       NativeMethods.SetForegroundWindow(
   171         new HandleRef(window, window.Handle));
   172 
   173       if (ContextMenu != null) {
   174         ContextMenu.GetType().InvokeMember("OnPopup",
   175           BindingFlags.NonPublic | BindingFlags.InvokeMethod |
   176           BindingFlags.Instance, null, ContextMenu,
   177           new Object[] { System.EventArgs.Empty });
   178 
   179         NativeMethods.TrackPopupMenuEx(
   180           new HandleRef(ContextMenu, ContextMenu.Handle), 72,
   181           p.x, p.y, new HandleRef(window, window.Handle),
   182           IntPtr.Zero);
   183 
   184         NativeMethods.PostMessage(
   185           new HandleRef(window, window.Handle), WM_NULL, 0, 0);
   186         return;
   187       }
   188 
   189       if (ContextMenuStrip != null)
   190         ContextMenuStrip.GetType().InvokeMember("ShowInTaskbar",
   191           BindingFlags.NonPublic | BindingFlags.InvokeMethod |
   192           BindingFlags.Instance, null, ContextMenuStrip,
   193           new Object[] { p.x, p.y });
   194     }
   195 
   196     private void UpdateNotifyIcon(bool showNotifyIcon) {
   197       if (DesignMode)
   198         return;
   199 
   200       lock (syncObj) {
   201         window.LockReference(showNotifyIcon);
   202 
   203         NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData();
   204         data.CallbackMessage = WM_TRAYMOUSEMESSAGE;
   205         data.Flags = NativeMethods.NotifyIconDataFlags.Message;
   206 
   207         if (showNotifyIcon && window.Handle == IntPtr.Zero)
   208           window.CreateHandle(new CreateParams());
   209 
   210         data.Window = window.Handle;
   211         data.ID = id;
   212 
   213         if (icon != null) {
   214           data.Flags |= NativeMethods.NotifyIconDataFlags.Icon;
   215           data.Icon = icon.Handle;
   216         }
   217 
   218         data.Flags |= NativeMethods.NotifyIconDataFlags.Tip;
   219         data.Tip = text;
   220 
   221         if (showNotifyIcon && icon != null) {
   222           if (!created) {
   223             int i = 0;
   224             do {
   225               created = NativeMethods.Shell_NotifyIcon(
   226                 NativeMethods.NotifyIconMessage.Add, data);
   227               if (!created) {
   228                 System.Threading.Thread.Sleep(200);
   229                 i++;
   230               }
   231             } while (!created && i < 40);
   232           } else {
   233             NativeMethods.Shell_NotifyIcon(
   234               NativeMethods.NotifyIconMessage.Modify, data);
   235           }
   236         } else {
   237           if (created) {
   238             int i = 0;
   239             bool deleted = false;
   240             do {
   241               deleted = NativeMethods.Shell_NotifyIcon(
   242                 NativeMethods.NotifyIconMessage.Delete, data);
   243               if (!deleted) {
   244                 System.Threading.Thread.Sleep(200);
   245                 i++;
   246               }
   247             } while (!deleted && i < 40);
   248             created = false;
   249           }
   250         }
   251       }
   252     }
   253 
   254     private void ProcessMouseDown(ref Message message, MouseButtons button, 
   255       bool doubleClick) 
   256     {
   257       if (doubleClick) {
   258         if (DoubleClick != null)
   259           DoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0));
   260 
   261         if (MouseDoubleClick != null)
   262           MouseDoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0));
   263 
   264         doubleClickDown = true;
   265       }
   266 
   267       if (MouseDown != null)
   268         MouseDown(this, 
   269           new MouseEventArgs(button, doubleClick ? 2 : 1, 0, 0, 0));      
   270     }
   271 
   272     private void ProcessMouseUp(ref Message message, MouseButtons button) {
   273       if (MouseUp != null)
   274         MouseUp(this, new MouseEventArgs(button, 0, 0, 0, 0));
   275 
   276       if (!doubleClickDown) {
   277         if (Click != null)
   278           Click(this, new MouseEventArgs(button, 0, 0, 0, 0));
   279 
   280         if (MouseClick != null)
   281           MouseClick(this, new MouseEventArgs(button, 0, 0, 0, 0));
   282       }
   283       doubleClickDown = false;
   284     }
   285 
   286     private void ProcessInitMenuPopup(ref Message message) {
   287       if (ContextMenu != null &&
   288         (bool)ContextMenu.GetType().InvokeMember("ProcessInitMenuPopup",
   289           BindingFlags.NonPublic | BindingFlags.InvokeMethod |
   290           BindingFlags.Instance, null, ContextMenu,
   291           new Object[] { message.WParam })) {
   292         return;
   293       }
   294       window.DefWndProc(ref message);
   295     }
   296 
   297     private void WndProc(ref Message message) {
   298       switch (message.Msg) {
   299         case WM_DESTROY:
   300           UpdateNotifyIcon(false);
   301           return;
   302         case WM_COMMAND:
   303           if (message.LParam != IntPtr.Zero) {
   304             window.DefWndProc(ref message);
   305             return;
   306           }
   307           commandDispatch.Invoke(null, new object[] { 
   308             message.WParam.ToInt32() & 0xFFFF });
   309           return;
   310         case WM_INITMENUPOPUP:
   311           ProcessInitMenuPopup(ref message);
   312           return;
   313         case WM_TRAYMOUSEMESSAGE:
   314           switch ((int)message.LParam) {
   315             case WM_MOUSEMOVE:
   316               if (MouseMove != null)
   317                 MouseMove(this, 
   318                   new MouseEventArgs(Control.MouseButtons, 0, 0, 0, 0));              
   319               return;
   320             case WM_LBUTTONDOWN:
   321               ProcessMouseDown(ref message, MouseButtons.Left, false);
   322               return;
   323             case WM_LBUTTONUP:
   324               ProcessMouseUp(ref message, MouseButtons.Left);
   325               return;
   326             case WM_LBUTTONDBLCLK:
   327               ProcessMouseDown(ref message, MouseButtons.Left, true);
   328               return;
   329             case WM_RBUTTONDOWN:
   330               ProcessMouseDown(ref message, MouseButtons.Right, false);
   331               return;
   332             case WM_RBUTTONUP:
   333               if (ContextMenu != null || ContextMenuStrip != null)
   334                 ShowContextMenu();
   335               ProcessMouseUp(ref message, MouseButtons.Right);
   336               return;
   337             case WM_RBUTTONDBLCLK:
   338               ProcessMouseDown(ref message, MouseButtons.Right, true);
   339               return;
   340             case WM_MBUTTONDOWN:
   341               ProcessMouseDown(ref message, MouseButtons.Middle, false);
   342               return;
   343             case WM_MBUTTONUP:
   344               ProcessMouseUp(ref message, MouseButtons.Middle);
   345               return;
   346             case WM_MBUTTONDBLCLK:
   347               ProcessMouseDown(ref message, MouseButtons.Middle, true);
   348               return;
   349             case NIN_BALLOONSHOW:
   350               if (BalloonTipShown != null)
   351                 BalloonTipShown(this, EventArgs.Empty);
   352               return;
   353             case NIN_BALLOONHIDE:
   354             case NIN_BALLOONTIMEOUT:
   355               if (BalloonTipClosed != null)
   356                 BalloonTipClosed(this, EventArgs.Empty);
   357               return;
   358             case NIN_BALLOONUSERCLICK:
   359               if (BalloonTipClicked != null)
   360                 BalloonTipClicked(this, EventArgs.Empty);
   361               return;
   362             default:
   363               return;
   364           }
   365       }
   366 
   367       if (message.Msg == NotifyIconAdv.WM_TASKBARCREATED) {
   368         lock (syncObj) {
   369           created = false;
   370         }
   371         UpdateNotifyIcon(visible);
   372       }
   373 
   374       window.DefWndProc(ref message);
   375     }
   376 
   377     private class NotifyIconNativeWindow : NativeWindow {
   378       private NotifyIconAdv reference;
   379       private GCHandle referenceHandle;
   380 
   381       internal NotifyIconNativeWindow(NotifyIconAdv component) {
   382         this.reference = component;
   383       }
   384 
   385       ~NotifyIconNativeWindow() {
   386         if (base.Handle != IntPtr.Zero)
   387           NativeMethods.PostMessage(
   388             new HandleRef(this, base.Handle), WM_CLOSE, 0, 0);
   389       }
   390 
   391       public void LockReference(bool locked) {
   392         if (locked) {
   393           if (!referenceHandle.IsAllocated) {
   394             referenceHandle = GCHandle.Alloc(reference, GCHandleType.Normal);
   395             return;
   396           }
   397         } else {
   398           if (referenceHandle.IsAllocated)
   399             referenceHandle.Free();
   400         }
   401       }
   402 
   403       protected override void OnThreadException(Exception e) {
   404         Application.OnThreadException(e);
   405       }
   406 
   407       protected override void WndProc(ref Message m) {
   408         reference.WndProc(ref m);
   409       }
   410     }    
   411 
   412     private const int WM_NULL = 0x00;
   413     private const int WM_DESTROY = 0x02;
   414     private const int WM_CLOSE = 0x10;
   415     private const int WM_COMMAND = 0x111;
   416     private const int WM_INITMENUPOPUP = 0x117;
   417     private const int WM_MOUSEMOVE = 0x200;
   418     private const int WM_LBUTTONDOWN = 0x201;
   419     private const int WM_LBUTTONUP = 0x202;
   420     private const int WM_LBUTTONDBLCLK = 0x203;
   421     private const int WM_RBUTTONDOWN = 0x204;
   422     private const int WM_RBUTTONUP = 0x205;
   423     private const int WM_RBUTTONDBLCLK = 0x206;
   424     private const int WM_MBUTTONDOWN = 0x207;
   425     private const int WM_MBUTTONUP = 0x208;
   426     private const int WM_MBUTTONDBLCLK = 0x209;
   427     private const int WM_TRAYMOUSEMESSAGE = 0x800;
   428 
   429     private const int NIN_BALLOONSHOW = 0x402;
   430     private const int NIN_BALLOONHIDE = 0x403;
   431     private const int NIN_BALLOONTIMEOUT = 0x404;
   432     private const int NIN_BALLOONUSERCLICK = 0x405;
   433 
   434     private static int WM_TASKBARCREATED =
   435       NativeMethods.RegisterWindowMessage("TaskbarCreated");
   436 
   437     private static class NativeMethods {
   438       [DllImport("user32.dll", CharSet = CharSet.Auto)]
   439       public static extern IntPtr PostMessage(HandleRef hwnd, int msg,
   440         int wparam, int lparam);
   441 
   442       [DllImport("user32.dll", CharSet = CharSet.Auto)]
   443       public static extern int RegisterWindowMessage(string msg);
   444 
   445       [Flags]
   446       public enum NotifyIconDataFlags : int {
   447         Message = 0x1,
   448         Icon = 0x2,
   449         Tip = 0x4,
   450         State = 0x8,
   451         Info = 0x10
   452       }
   453 
   454       [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
   455       public class NotifyIconData {
   456         private int Size = Marshal.SizeOf(typeof(NotifyIconData));
   457         public IntPtr Window;
   458         public int ID;
   459         public NotifyIconDataFlags Flags;
   460         public int CallbackMessage;
   461         public IntPtr Icon;
   462         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
   463         public string Tip;
   464         public int State;
   465         public int StateMask;
   466         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
   467         public string Info;
   468         public int TimeoutOrVersion;
   469         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
   470         public string InfoTitle;
   471         public int InfoFlags;
   472       }
   473 
   474       public enum NotifyIconMessage : int {
   475         Add = 0x0,
   476         Modify = 0x1,
   477         Delete = 0x2
   478       }
   479 
   480       [DllImport("shell32.dll", CharSet = CharSet.Auto)]
   481       [return: MarshalAs(UnmanagedType.Bool)]
   482       public static extern bool Shell_NotifyIcon(NotifyIconMessage message,
   483         NotifyIconData pnid);
   484 
   485       [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
   486       public static extern bool TrackPopupMenuEx(HandleRef hmenu, int fuFlags,
   487         int x, int y, HandleRef hwnd, IntPtr tpm);
   488 
   489       [StructLayout(LayoutKind.Sequential)]
   490       public struct Point {
   491         public int x;
   492         public int y;
   493       }
   494 
   495       [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
   496       public static extern bool GetCursorPos(ref Point point);
   497 
   498       [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
   499       public static extern bool SetForegroundWindow(HandleRef hWnd);
   500     }
   501   }
   502 }