using System; using System.ComponentModel; using System.Diagnostics; namespace Hardcodet.Wpf.TaskbarNotification.Interop { /// /// Receives messages from the taskbar icon through /// window messages of an underlying helper window. /// public class WindowMessageSink : IDisposable { #region members /// /// The ID of messages that are received from the the /// taskbar icon. /// public const int CallbackMessageId = 0x400; /// /// The ID of the message that is being received if the /// taskbar is (re)started. /// private uint taskbarRestartMessageId; /// /// Used to track whether a mouse-up event is just /// the aftermath of a double-click and therefore needs /// to be suppressed. /// private bool isDoubleClick; /// /// A delegate that processes messages of the hidden /// native window that receives window messages. Storing /// this reference makes sure we don't loose our reference /// to the message window. /// private WindowProcedureHandler messageHandler; /// /// Window class ID. /// internal string WindowId { get; private set; } /// /// Handle for the message window. /// /// The version of the underlying icon. Defines how /// incoming messages are interpreted. /// public NotifyIconVersion Version { get; set; } #endregion #region events /// /// The custom tooltip should be closed or hidden. /// public event Action ChangeToolTipStateRequest; /// /// Fired in case the user clicked or moved within /// the taskbar icon area. /// public event Action MouseEventReceived; /// /// Fired if a balloon ToolTip was either displayed /// or closed (indicated by the boolean flag). /// public event Action BallonToolTipChanged; /// /// Fired if the taskbar was created or restarted. Requires the taskbar /// icon to be reset. /// public event Action TaskbarCreated; #endregion #region construction /// /// Creates a new message sink that receives message from /// a given taskbar icon. /// /// public WindowMessageSink(NotifyIconVersion version) { Version = version; CreateMessageWindow(); } private WindowMessageSink() { } /// /// Creates a dummy instance that provides an empty /// pointer rather than a real window handler.
/// Used at design time. ///
/// internal static WindowMessageSink CreateEmpty() { return new WindowMessageSink { MessageWindowHandle = IntPtr.Zero, Version = NotifyIconVersion.Vista }; } #endregion #region CreateMessageWindow /// /// Creates the helper message window that is used /// to receive messages from the taskbar icon. /// private void CreateMessageWindow() { //generate a unique ID for the window WindowId = "WPFTaskbarIcon_" + DateTime.Now.Ticks; //register window message handler messageHandler = OnWindowMessageReceived; // Create a simple window class which is reference through //the messageHandler delegate WindowClass wc; wc.style = 0; wc.lpfnWndProc = messageHandler; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = IntPtr.Zero; wc.hIcon = IntPtr.Zero; wc.hCursor = IntPtr.Zero; wc.hbrBackground = IntPtr.Zero; wc.lpszMenuName = ""; wc.lpszClassName = WindowId; // Register the window class WinApi.RegisterClass(ref wc); // Get the message used to indicate the taskbar has been restarted // This is used to re-add icons when the taskbar restarts taskbarRestartMessageId = WinApi.RegisterWindowMessage("TaskbarCreated"); // Create the message window MessageWindowHandle = WinApi.CreateWindowEx(0, WindowId, "", 0, 0, 0, 1, 1, 0, 0, 0, 0); if (MessageWindowHandle == IntPtr.Zero) { throw new Win32Exception(); } } #endregion #region Handle Window Messages /// /// Callback method that receives messages from the taskbar area. /// private long OnWindowMessageReceived(IntPtr hwnd, uint messageId, uint wparam, uint lparam) { if (messageId == taskbarRestartMessageId) { //recreate the icon if the taskbar was restarted (e.g. due to Win Explorer shutdown) TaskbarCreated(); } //forward message ProcessWindowMessage(messageId, wparam, lparam); // Pass the message to the default window procedure return WinApi.DefWindowProc(hwnd, messageId, wparam, lparam); } /// /// Processes incoming system messages. /// /// Callback ID. /// If the version is /// or higher, this parameter can be used to resolve mouse coordinates. /// Currently not in use. /// Provides information about the event. private void ProcessWindowMessage(uint msg, uint wParam, uint lParam) { if (msg != CallbackMessageId) return; switch (lParam) { case 0x200: // Debug.WriteLine("MOVE"); MouseEventReceived(MouseEvent.MouseMove); break; case 0x201: Debug.WriteLine("left down 1"); MouseEventReceived(MouseEvent.IconLeftMouseDown); break; case 0x202: Debug.WriteLine("left up"); if (!isDoubleClick) { MouseEventReceived(MouseEvent.IconLeftMouseUp); } isDoubleClick = false; break; case 0x203: Debug.WriteLine("left click 2"); isDoubleClick = true; MouseEventReceived(MouseEvent.IconDoubleClick); break; case 0x204: Debug.WriteLine("right click 1"); MouseEventReceived(MouseEvent.IconRightMouseDown); break; case 0x205: Console.Out.WriteLine("right mouse up"); MouseEventReceived(MouseEvent.IconRightMouseUp); break; case 0x206: //double click with right mouse button - do not trigger event Debug.WriteLine("right click 2"); break; case 0x207: Debug.WriteLine("middle click 1"); MouseEventReceived(MouseEvent.IconMiddleMouseDown); break; case 520: Debug.WriteLine("mouse up middle"); MouseEventReceived(MouseEvent.IconMiddleMouseUp); break; case 0x209: //double click with middle mouse button - do not trigger event Debug.WriteLine("middle click 2"); break; case 0x402: Debug.WriteLine("balloon shown"); BallonToolTipChanged(true); break; case 0x403: case 0x404: Debug.WriteLine("balloon close"); BallonToolTipChanged(false); break; case 0x405: Debug.WriteLine("balloon clicked"); MouseEventReceived(MouseEvent.BalloonToolTipClicked); break; case 0x406: Debug.WriteLine("show custom tooltip"); ChangeToolTipStateRequest(true); break; case 0x407: Debug.WriteLine("close custom tooltip"); ChangeToolTipStateRequest(false); break; default: Debug.WriteLine("Unhandled message ID: " + lParam); break; } } #endregion #region Dispose /// /// Set to true as soon as /// has been invoked. /// public bool IsDisposed { get; private set; } /// /// Disposes the object. /// /// This method is not virtual by design. Derived classes /// should override . /// public void Dispose() { Dispose(true); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SupressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } /// /// This destructor will run only if the /// method does not get called. This gives this base class the /// opportunity to finalize. /// /// Important: Do not provide destructors in types derived from /// this class. /// /// ~WindowMessageSink() { Dispose(false); } /// /// Removes the windows hook that receives window /// messages and closes the underlying helper window. /// private void Dispose(bool disposing) { //don't do anything if the component is already disposed if (IsDisposed || !disposing) return; IsDisposed = true; WinApi.DestroyWindow(MessageWindowHandle); messageHandler = null; } #endregion } }