Attempt at fixing Issue 253 without breaking Issue 159 once more.
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) 2012 Michael Möller <mmoeller@openhardwaremonitor.org>
12 using System.ComponentModel;
14 using System.Runtime.InteropServices;
15 using System.Reflection;
16 using System.Windows.Forms;
18 namespace OpenHardwareMonitor.GUI {
19 public class NotifyIconAdv : Component {
21 private static int nextId = 0;
23 private object syncObj = new object();
25 private string text = "";
28 private NotifyIconNativeWindow window;
29 private bool doubleClickDown;
31 private MethodInfo commandDispatch;
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;
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; }
58 UpdateNotifyIcon(visible);
71 if (value.Length > 63)
72 throw new ArgumentOutOfRangeException();
74 if (!value.Equals(text)) {
78 UpdateNotifyIcon(visible);
88 if (visible != value) {
90 UpdateNotifyIcon(visible);
95 public NotifyIconAdv() {
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);
104 id = ++NotifyIconAdv.nextId;
105 window = new NotifyIconNativeWindow(this);
106 UpdateNotifyIcon(visible);
109 protected override void Dispose(bool disposing) {
111 if (window != null) {
114 UpdateNotifyIcon(false);
115 window.DestroyHandle();
118 ContextMenuStrip = null;
121 if (window != null && window.Handle != IntPtr.Zero) {
122 NativeMethods.PostMessage(
123 new HandleRef(window, window.Handle), WM_CLOSE, 0, 0);
124 window.ReleaseHandle();
127 base.Dispose(disposing);
130 public void ShowBalloonTip(int timeout) {
131 ShowBalloonTip(timeout, BalloonTipTitle, BalloonTipText, BalloonTipIcon);
134 public void ShowBalloonTip(int timeout, string tipTitle, string tipText,
138 throw new ArgumentOutOfRangeException("timeout");
140 if (string.IsNullOrEmpty(tipText))
141 throw new ArgumentException("tipText");
147 NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData();
148 if (window.Handle == IntPtr.Zero)
149 window.CreateHandle(new CreateParams());
151 data.Window = window.Handle;
153 data.Flags = NativeMethods.NotifyIconDataFlags.Info;
154 data.TimeoutOrVersion = timeout;
155 data.InfoTitle = tipTitle;
157 data.InfoFlags = (int)tipIcon;
159 NativeMethods.Shell_NotifyIcon(
160 NativeMethods.NotifyIconMessage.Modify, data);
164 private void ShowContextMenu() {
165 if (ContextMenu == null && ContextMenuStrip == null)
168 NativeMethods.Point p = new NativeMethods.Point();
169 NativeMethods.GetCursorPos(ref p);
170 NativeMethods.SetForegroundWindow(
171 new HandleRef(window, window.Handle));
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 });
179 NativeMethods.TrackPopupMenuEx(
180 new HandleRef(ContextMenu, ContextMenu.Handle), 72,
181 p.x, p.y, new HandleRef(window, window.Handle),
184 NativeMethods.PostMessage(
185 new HandleRef(window, window.Handle), WM_NULL, 0, 0);
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 });
196 private void UpdateNotifyIcon(bool showNotifyIcon) {
201 window.LockReference(showNotifyIcon);
203 NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData();
204 data.CallbackMessage = WM_TRAYMOUSEMESSAGE;
205 data.Flags = NativeMethods.NotifyIconDataFlags.Message;
207 if (showNotifyIcon && window.Handle == IntPtr.Zero)
208 window.CreateHandle(new CreateParams());
210 data.Window = window.Handle;
214 data.Flags |= NativeMethods.NotifyIconDataFlags.Icon;
215 data.Icon = icon.Handle;
218 data.Flags |= NativeMethods.NotifyIconDataFlags.Tip;
221 if (showNotifyIcon && icon != null) {
225 created = NativeMethods.Shell_NotifyIcon(
226 NativeMethods.NotifyIconMessage.Add, data);
228 System.Threading.Thread.Sleep(200);
231 } while (!created && i < 40);
233 NativeMethods.Shell_NotifyIcon(
234 NativeMethods.NotifyIconMessage.Modify, data);
239 bool deleted = false;
241 deleted = NativeMethods.Shell_NotifyIcon(
242 NativeMethods.NotifyIconMessage.Delete, data);
244 System.Threading.Thread.Sleep(200);
247 } while (!deleted && i < 40);
254 private void ProcessMouseDown(ref Message message, MouseButtons button,
258 if (DoubleClick != null)
259 DoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0));
261 if (MouseDoubleClick != null)
262 MouseDoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0));
264 doubleClickDown = true;
267 if (MouseDown != null)
269 new MouseEventArgs(button, doubleClick ? 2 : 1, 0, 0, 0));
272 private void ProcessMouseUp(ref Message message, MouseButtons button) {
274 MouseUp(this, new MouseEventArgs(button, 0, 0, 0, 0));
276 if (!doubleClickDown) {
278 Click(this, new MouseEventArgs(button, 0, 0, 0, 0));
280 if (MouseClick != null)
281 MouseClick(this, new MouseEventArgs(button, 0, 0, 0, 0));
283 doubleClickDown = false;
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 })) {
294 window.DefWndProc(ref message);
297 private void WndProc(ref Message message) {
298 switch (message.Msg) {
300 UpdateNotifyIcon(false);
303 if (message.LParam != IntPtr.Zero) {
304 window.DefWndProc(ref message);
307 commandDispatch.Invoke(null, new object[] {
308 message.WParam.ToInt32() & 0xFFFF });
310 case WM_INITMENUPOPUP:
311 ProcessInitMenuPopup(ref message);
313 case WM_TRAYMOUSEMESSAGE:
314 switch ((int)message.LParam) {
316 if (MouseMove != null)
318 new MouseEventArgs(Control.MouseButtons, 0, 0, 0, 0));
321 ProcessMouseDown(ref message, MouseButtons.Left, false);
324 ProcessMouseUp(ref message, MouseButtons.Left);
326 case WM_LBUTTONDBLCLK:
327 ProcessMouseDown(ref message, MouseButtons.Left, true);
330 ProcessMouseDown(ref message, MouseButtons.Right, false);
333 if (ContextMenu != null || ContextMenuStrip != null)
335 ProcessMouseUp(ref message, MouseButtons.Right);
337 case WM_RBUTTONDBLCLK:
338 ProcessMouseDown(ref message, MouseButtons.Right, true);
341 ProcessMouseDown(ref message, MouseButtons.Middle, false);
344 ProcessMouseUp(ref message, MouseButtons.Middle);
346 case WM_MBUTTONDBLCLK:
347 ProcessMouseDown(ref message, MouseButtons.Middle, true);
349 case NIN_BALLOONSHOW:
350 if (BalloonTipShown != null)
351 BalloonTipShown(this, EventArgs.Empty);
353 case NIN_BALLOONHIDE:
354 case NIN_BALLOONTIMEOUT:
355 if (BalloonTipClosed != null)
356 BalloonTipClosed(this, EventArgs.Empty);
358 case NIN_BALLOONUSERCLICK:
359 if (BalloonTipClicked != null)
360 BalloonTipClicked(this, EventArgs.Empty);
367 if (message.Msg == NotifyIconAdv.WM_TASKBARCREATED) {
371 UpdateNotifyIcon(visible);
374 window.DefWndProc(ref message);
377 private class NotifyIconNativeWindow : NativeWindow {
378 private NotifyIconAdv reference;
379 private GCHandle referenceHandle;
381 internal NotifyIconNativeWindow(NotifyIconAdv component) {
382 this.reference = component;
385 ~NotifyIconNativeWindow() {
386 if (base.Handle != IntPtr.Zero)
387 NativeMethods.PostMessage(
388 new HandleRef(this, base.Handle), WM_CLOSE, 0, 0);
391 public void LockReference(bool locked) {
393 if (!referenceHandle.IsAllocated) {
394 referenceHandle = GCHandle.Alloc(reference, GCHandleType.Normal);
398 if (referenceHandle.IsAllocated)
399 referenceHandle.Free();
403 protected override void OnThreadException(Exception e) {
404 Application.OnThreadException(e);
407 protected override void WndProc(ref Message m) {
408 reference.WndProc(ref m);
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;
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;
434 private static int WM_TASKBARCREATED =
435 NativeMethods.RegisterWindowMessage("TaskbarCreated");
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);
442 [DllImport("user32.dll", CharSet = CharSet.Auto)]
443 public static extern int RegisterWindowMessage(string msg);
446 public enum NotifyIconDataFlags : int {
454 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
455 public class NotifyIconData {
456 private int Size = Marshal.SizeOf(typeof(NotifyIconData));
457 public IntPtr Window;
459 public NotifyIconDataFlags Flags;
460 public int CallbackMessage;
462 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
465 public int StateMask;
466 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
468 public int TimeoutOrVersion;
469 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
470 public string InfoTitle;
471 public int InfoFlags;
474 public enum NotifyIconMessage : int {
480 [DllImport("shell32.dll", CharSet = CharSet.Auto)]
481 [return: MarshalAs(UnmanagedType.Bool)]
482 public static extern bool Shell_NotifyIcon(NotifyIconMessage message,
483 NotifyIconData pnid);
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);
489 [StructLayout(LayoutKind.Sequential)]
490 public struct Point {
495 [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
496 public static extern bool GetCursorPos(ref Point point);
498 [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
499 public static extern bool SetForegroundWindow(HandleRef hWnd);