moel@363: /* moel@363: moel@363: This Source Code Form is subject to the terms of the Mozilla Public moel@363: License, v. 2.0. If a copy of the MPL was not distributed with this moel@363: file, You can obtain one at http://mozilla.org/MPL/2.0/. moel@363: moel@363: Copyright (C) 2012 Michael Möller moel@363: moel@363: */ moel@363: moel@363: using System; moel@363: using System.ComponentModel; moel@363: using System.Drawing; moel@363: using System.Runtime.InteropServices; moel@363: using System.Reflection; moel@363: using System.Windows.Forms; moel@363: moel@363: namespace OpenHardwareMonitor.GUI { moel@363: public class NotifyIconAdv : Component { moel@363: moel@363: private static int nextId = 0; moel@363: moel@363: private object syncObj = new object(); moel@363: private Icon icon; moel@363: private string text = ""; moel@363: private int id; moel@363: private bool created; moel@363: private NotifyIconNativeWindow window; moel@363: private bool doubleClickDown; moel@363: private bool visible; moel@363: private MethodInfo commandDispatch; moel@363: moel@363: public event EventHandler BalloonTipClicked; moel@363: public event EventHandler BalloonTipClosed; moel@363: public event EventHandler BalloonTipShown; moel@363: public event EventHandler Click; moel@363: public event EventHandler DoubleClick; moel@363: public event MouseEventHandler MouseClick; moel@363: public event MouseEventHandler MouseDoubleClick; moel@363: public event MouseEventHandler MouseDown; moel@363: public event MouseEventHandler MouseMove; moel@363: public event MouseEventHandler MouseUp; moel@363: moel@363: public string BalloonTipText { get; set; } moel@363: public ToolTipIcon BalloonTipIcon { get; set; } moel@363: public string BalloonTipTitle { get; set; } moel@363: public ContextMenu ContextMenu { get; set; } moel@363: public ContextMenuStrip ContextMenuStrip { get; set; } moel@363: public object Tag { get; set; } moel@363: moel@363: public Icon Icon { moel@363: get { moel@363: return icon; moel@363: } moel@363: set { moel@363: if (icon != value) { moel@363: icon = value; moel@363: UpdateNotifyIcon(visible); moel@363: } moel@363: } moel@363: } moel@363: moel@363: public string Text { moel@363: get { moel@363: return text; moel@363: } moel@363: set { moel@363: if (value == null) moel@363: value = ""; moel@363: moel@363: if (value.Length > 63) moel@363: throw new ArgumentOutOfRangeException(); moel@363: moel@363: if (!value.Equals(text)) { moel@363: text = value; moel@363: moel@363: if (visible) moel@363: UpdateNotifyIcon(visible); moel@363: } moel@363: } moel@363: } moel@363: moel@363: public bool Visible { moel@363: get { moel@363: return visible; moel@363: } moel@363: set { moel@363: if (visible != value) { moel@363: visible = value; moel@363: UpdateNotifyIcon(visible); moel@363: } moel@363: } moel@363: } moel@363: moel@363: public NotifyIconAdv() { moel@363: BalloonTipText = ""; moel@363: BalloonTipTitle = ""; moel@363: moel@363: commandDispatch = typeof(Form).Assembly. moel@363: GetType("System.Windows.Forms.Command").GetMethod("DispatchID", moel@363: BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, moel@363: null, new Type[] { typeof(int) }, null); moel@363: moel@363: id = ++NotifyIconAdv.nextId; moel@363: window = new NotifyIconNativeWindow(this); moel@363: UpdateNotifyIcon(visible); moel@363: } moel@363: moel@363: protected override void Dispose(bool disposing) { moel@363: if (disposing) { moel@363: if (window != null) { moel@363: icon = null; moel@363: text = ""; moel@363: UpdateNotifyIcon(false); moel@363: window.DestroyHandle(); moel@363: window = null; moel@363: ContextMenu = null; moel@363: ContextMenuStrip = null; moel@363: } moel@363: } else { moel@363: if (window != null && window.Handle != IntPtr.Zero) { moel@363: NativeMethods.PostMessage( moel@363: new HandleRef(window, window.Handle), WM_CLOSE, 0, 0); moel@363: window.ReleaseHandle(); moel@363: } moel@363: } moel@363: base.Dispose(disposing); moel@363: } moel@363: moel@363: public void ShowBalloonTip(int timeout) { moel@363: ShowBalloonTip(timeout, BalloonTipTitle, BalloonTipText, BalloonTipIcon); moel@363: } moel@363: moel@363: public void ShowBalloonTip(int timeout, string tipTitle, string tipText, moel@363: ToolTipIcon tipIcon) moel@363: { moel@363: if (timeout < 0) moel@363: throw new ArgumentOutOfRangeException("timeout"); moel@363: moel@363: if (string.IsNullOrEmpty(tipText)) moel@363: throw new ArgumentException("tipText"); moel@363: moel@363: if (DesignMode) moel@363: return; moel@363: moel@363: if (created) { moel@363: NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData(); moel@363: if (window.Handle == IntPtr.Zero) moel@363: window.CreateHandle(new CreateParams()); moel@363: moel@363: data.Window = window.Handle; moel@363: data.ID = id; moel@363: data.Flags = NativeMethods.NotifyIconDataFlags.Info; moel@363: data.TimeoutOrVersion = timeout; moel@363: data.InfoTitle = tipTitle; moel@363: data.Info = tipText; moel@363: data.InfoFlags = (int)tipIcon; moel@363: moel@363: NativeMethods.Shell_NotifyIcon( moel@363: NativeMethods.NotifyIconMessage.Modify, data); moel@363: } moel@363: } moel@363: moel@363: private void ShowContextMenu() { moel@363: if (ContextMenu == null && ContextMenuStrip == null) moel@363: return; moel@363: moel@363: NativeMethods.Point p = new NativeMethods.Point(); moel@363: NativeMethods.GetCursorPos(ref p); moel@363: NativeMethods.SetForegroundWindow( moel@363: new HandleRef(window, window.Handle)); moel@363: moel@363: if (ContextMenu != null) { moel@363: ContextMenu.GetType().InvokeMember("OnPopup", moel@363: BindingFlags.NonPublic | BindingFlags.InvokeMethod | moel@363: BindingFlags.Instance, null, ContextMenu, moel@363: new Object[] { System.EventArgs.Empty }); moel@363: moel@363: NativeMethods.TrackPopupMenuEx( moel@363: new HandleRef(ContextMenu, ContextMenu.Handle), 72, moel@363: p.x, p.y, new HandleRef(window, window.Handle), moel@363: IntPtr.Zero); moel@363: moel@363: NativeMethods.PostMessage( moel@363: new HandleRef(window, window.Handle), WM_NULL, 0, 0); moel@363: return; moel@363: } moel@363: moel@363: if (ContextMenuStrip != null) moel@363: ContextMenuStrip.GetType().InvokeMember("ShowInTaskbar", moel@363: BindingFlags.NonPublic | BindingFlags.InvokeMethod | moel@363: BindingFlags.Instance, null, ContextMenuStrip, moel@363: new Object[] { p.x, p.y }); moel@363: } moel@363: moel@363: private void UpdateNotifyIcon(bool showNotifyIcon) { moel@363: if (DesignMode) moel@363: return; moel@363: moel@363: lock (syncObj) { moel@363: window.LockReference(showNotifyIcon); moel@363: moel@363: NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData(); moel@363: data.CallbackMessage = WM_TRAYMOUSEMESSAGE; moel@363: data.Flags = NativeMethods.NotifyIconDataFlags.Message; moel@363: moel@363: if (showNotifyIcon && window.Handle == IntPtr.Zero) moel@363: window.CreateHandle(new CreateParams()); moel@363: moel@363: data.Window = window.Handle; moel@363: data.ID = id; moel@363: moel@363: if (icon != null) { moel@363: data.Flags |= NativeMethods.NotifyIconDataFlags.Icon; moel@363: data.Icon = icon.Handle; moel@363: } moel@363: moel@363: data.Flags |= NativeMethods.NotifyIconDataFlags.Tip; moel@363: data.Tip = text; moel@363: moel@363: if (showNotifyIcon && icon != null) { moel@363: if (!created) { moel@363: int i = 0; moel@363: do { moel@363: created = NativeMethods.Shell_NotifyIcon( moel@363: NativeMethods.NotifyIconMessage.Add, data); moel@363: if (!created) { moel@363: System.Threading.Thread.Sleep(200); moel@363: i++; moel@363: } moel@363: } while (!created && i < 40); moel@363: } else { moel@363: NativeMethods.Shell_NotifyIcon( moel@363: NativeMethods.NotifyIconMessage.Modify, data); moel@363: } moel@363: } else { moel@363: if (created) { moel@363: int i = 0; moel@363: bool deleted = false; moel@363: do { moel@363: deleted = NativeMethods.Shell_NotifyIcon( moel@363: NativeMethods.NotifyIconMessage.Delete, data); moel@363: if (!deleted) { moel@363: System.Threading.Thread.Sleep(200); moel@363: i++; moel@363: } moel@363: } while (!deleted && i < 40); moel@363: created = false; moel@363: } moel@363: } moel@363: } moel@363: } moel@363: moel@363: private void ProcessMouseDown(ref Message message, MouseButtons button, moel@363: bool doubleClick) moel@363: { moel@363: if (doubleClick) { moel@363: if (DoubleClick != null) moel@363: DoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0)); moel@363: moel@363: if (MouseDoubleClick != null) moel@363: MouseDoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0)); moel@363: moel@363: doubleClickDown = true; moel@363: } moel@363: moel@363: if (MouseDown != null) moel@363: MouseDown(this, moel@363: new MouseEventArgs(button, doubleClick ? 2 : 1, 0, 0, 0)); moel@363: } moel@363: moel@363: private void ProcessMouseUp(ref Message message, MouseButtons button) { moel@363: if (MouseUp != null) moel@363: MouseUp(this, new MouseEventArgs(button, 0, 0, 0, 0)); moel@363: moel@363: if (!doubleClickDown) { moel@363: if (Click != null) moel@363: Click(this, new MouseEventArgs(button, 0, 0, 0, 0)); moel@363: moel@363: if (MouseClick != null) moel@363: MouseClick(this, new MouseEventArgs(button, 0, 0, 0, 0)); moel@363: } moel@363: doubleClickDown = false; moel@363: } moel@363: moel@363: private void ProcessInitMenuPopup(ref Message message) { moel@363: if (ContextMenu != null && moel@363: (bool)ContextMenu.GetType().InvokeMember("ProcessInitMenuPopup", moel@363: BindingFlags.NonPublic | BindingFlags.InvokeMethod | moel@363: BindingFlags.Instance, null, ContextMenu, moel@363: new Object[] { message.WParam })) { moel@363: return; moel@363: } moel@363: window.DefWndProc(ref message); moel@363: } moel@363: moel@363: private void WndProc(ref Message message) { moel@363: switch (message.Msg) { moel@363: case WM_DESTROY: moel@363: UpdateNotifyIcon(false); moel@363: return; moel@363: case WM_COMMAND: moel@363: if (message.LParam != IntPtr.Zero) { moel@363: window.DefWndProc(ref message); moel@363: return; moel@363: } moel@363: commandDispatch.Invoke(null, new object[] { moel@363: message.WParam.ToInt32() & 0xFFFF }); moel@363: return; moel@363: case WM_INITMENUPOPUP: moel@363: ProcessInitMenuPopup(ref message); moel@363: return; moel@363: case WM_TRAYMOUSEMESSAGE: moel@363: switch ((int)message.LParam) { moel@363: case WM_MOUSEMOVE: moel@363: if (MouseMove != null) moel@363: MouseMove(this, moel@363: new MouseEventArgs(Control.MouseButtons, 0, 0, 0, 0)); moel@363: return; moel@363: case WM_LBUTTONDOWN: moel@363: ProcessMouseDown(ref message, MouseButtons.Left, false); moel@363: return; moel@363: case WM_LBUTTONUP: moel@363: ProcessMouseUp(ref message, MouseButtons.Left); moel@363: return; moel@363: case WM_LBUTTONDBLCLK: moel@363: ProcessMouseDown(ref message, MouseButtons.Left, true); moel@363: return; moel@363: case WM_RBUTTONDOWN: moel@363: ProcessMouseDown(ref message, MouseButtons.Right, false); moel@363: return; moel@363: case WM_RBUTTONUP: moel@363: if (ContextMenu != null || ContextMenuStrip != null) moel@363: ShowContextMenu(); moel@363: ProcessMouseUp(ref message, MouseButtons.Right); moel@363: return; moel@363: case WM_RBUTTONDBLCLK: moel@363: ProcessMouseDown(ref message, MouseButtons.Right, true); moel@363: return; moel@363: case WM_MBUTTONDOWN: moel@363: ProcessMouseDown(ref message, MouseButtons.Middle, false); moel@363: return; moel@363: case WM_MBUTTONUP: moel@363: ProcessMouseUp(ref message, MouseButtons.Middle); moel@363: return; moel@363: case WM_MBUTTONDBLCLK: moel@363: ProcessMouseDown(ref message, MouseButtons.Middle, true); moel@363: return; moel@363: case NIN_BALLOONSHOW: moel@363: if (BalloonTipShown != null) moel@363: BalloonTipShown(this, EventArgs.Empty); moel@363: return; moel@363: case NIN_BALLOONHIDE: moel@363: case NIN_BALLOONTIMEOUT: moel@363: if (BalloonTipClosed != null) moel@363: BalloonTipClosed(this, EventArgs.Empty); moel@363: return; moel@363: case NIN_BALLOONUSERCLICK: moel@363: if (BalloonTipClicked != null) moel@363: BalloonTipClicked(this, EventArgs.Empty); moel@363: return; moel@363: default: moel@363: return; moel@363: } moel@363: } moel@363: moel@363: if (message.Msg == NotifyIconAdv.WM_TASKBARCREATED) { moel@363: lock (syncObj) { moel@363: created = false; moel@363: } moel@363: UpdateNotifyIcon(visible); moel@363: } moel@363: moel@363: window.DefWndProc(ref message); moel@363: } moel@363: moel@363: private class NotifyIconNativeWindow : NativeWindow { moel@363: private NotifyIconAdv reference; moel@363: private GCHandle referenceHandle; moel@363: moel@363: internal NotifyIconNativeWindow(NotifyIconAdv component) { moel@363: this.reference = component; moel@363: } moel@363: moel@363: ~NotifyIconNativeWindow() { moel@363: if (base.Handle != IntPtr.Zero) moel@363: NativeMethods.PostMessage( moel@363: new HandleRef(this, base.Handle), WM_CLOSE, 0, 0); moel@363: } moel@363: moel@363: public void LockReference(bool locked) { moel@363: if (locked) { moel@363: if (!referenceHandle.IsAllocated) { moel@363: referenceHandle = GCHandle.Alloc(reference, GCHandleType.Normal); moel@363: return; moel@363: } moel@363: } else { moel@363: if (referenceHandle.IsAllocated) moel@363: referenceHandle.Free(); moel@363: } moel@363: } moel@363: moel@363: protected override void OnThreadException(Exception e) { moel@363: Application.OnThreadException(e); moel@363: } moel@363: moel@363: protected override void WndProc(ref Message m) { moel@363: reference.WndProc(ref m); moel@363: } moel@363: } moel@363: moel@363: private const int WM_NULL = 0x00; moel@363: private const int WM_DESTROY = 0x02; moel@363: private const int WM_CLOSE = 0x10; moel@363: private const int WM_COMMAND = 0x111; moel@363: private const int WM_INITMENUPOPUP = 0x117; moel@363: private const int WM_MOUSEMOVE = 0x200; moel@363: private const int WM_LBUTTONDOWN = 0x201; moel@363: private const int WM_LBUTTONUP = 0x202; moel@363: private const int WM_LBUTTONDBLCLK = 0x203; moel@363: private const int WM_RBUTTONDOWN = 0x204; moel@363: private const int WM_RBUTTONUP = 0x205; moel@363: private const int WM_RBUTTONDBLCLK = 0x206; moel@363: private const int WM_MBUTTONDOWN = 0x207; moel@363: private const int WM_MBUTTONUP = 0x208; moel@363: private const int WM_MBUTTONDBLCLK = 0x209; moel@363: private const int WM_TRAYMOUSEMESSAGE = 0x800; moel@363: moel@363: private const int NIN_BALLOONSHOW = 0x402; moel@363: private const int NIN_BALLOONHIDE = 0x403; moel@363: private const int NIN_BALLOONTIMEOUT = 0x404; moel@363: private const int NIN_BALLOONUSERCLICK = 0x405; moel@363: moel@363: private static int WM_TASKBARCREATED = moel@363: NativeMethods.RegisterWindowMessage("TaskbarCreated"); moel@363: moel@363: private static class NativeMethods { moel@363: [DllImport("user32.dll", CharSet = CharSet.Auto)] moel@363: public static extern IntPtr PostMessage(HandleRef hwnd, int msg, moel@363: int wparam, int lparam); moel@363: moel@363: [DllImport("user32.dll", CharSet = CharSet.Auto)] moel@363: public static extern int RegisterWindowMessage(string msg); moel@363: moel@363: [Flags] moel@363: public enum NotifyIconDataFlags : int { moel@363: Message = 0x1, moel@363: Icon = 0x2, moel@363: Tip = 0x4, moel@363: State = 0x8, moel@363: Info = 0x10 moel@363: } moel@363: moel@363: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] moel@363: public class NotifyIconData { moel@363: private int Size = Marshal.SizeOf(typeof(NotifyIconData)); moel@363: public IntPtr Window; moel@363: public int ID; moel@363: public NotifyIconDataFlags Flags; moel@363: public int CallbackMessage; moel@363: public IntPtr Icon; moel@363: [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] moel@363: public string Tip; moel@363: public int State; moel@363: public int StateMask; moel@363: [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] moel@363: public string Info; moel@363: public int TimeoutOrVersion; moel@363: [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] moel@363: public string InfoTitle; moel@363: public int InfoFlags; moel@363: } moel@363: moel@363: public enum NotifyIconMessage : int { moel@363: Add = 0x0, moel@363: Modify = 0x1, moel@363: Delete = 0x2 moel@363: } moel@363: moel@363: [DllImport("shell32.dll", CharSet = CharSet.Auto)] moel@363: [return: MarshalAs(UnmanagedType.Bool)] moel@363: public static extern bool Shell_NotifyIcon(NotifyIconMessage message, moel@363: NotifyIconData pnid); moel@363: moel@363: [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] moel@363: public static extern bool TrackPopupMenuEx(HandleRef hmenu, int fuFlags, moel@363: int x, int y, HandleRef hwnd, IntPtr tpm); moel@363: moel@363: [StructLayout(LayoutKind.Sequential)] moel@363: public struct Point { moel@363: public int x; moel@363: public int y; moel@363: } moel@363: moel@363: [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] moel@363: public static extern bool GetCursorPos(ref Point point); moel@363: moel@363: [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] moel@363: public static extern bool SetForegroundWindow(HandleRef hWnd); moel@363: } moel@363: } moel@363: }