123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- // hardcodet.net NotifyIcon for WPF
- // Copyright (c) 2009 - 2013 Philipp Sumi
- // Contact and Information: http://www.hardcodet.net
- //
- // This library is free software; you can redistribute it and/or
- // modify it under the terms of the Code Project Open License (CPOL);
- // either version 1.0 of the License, or (at your option) any later
- // version.
- //
- // The above copyright notice and this permission notice shall be
- // included in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- // OTHER DEALINGS IN THE SOFTWARE.
- //
- // THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE
- using System;
- using System.ComponentModel;
- using System.Diagnostics;
- namespace Hardcodet.Wpf.TaskbarNotification.Interop
- {
- /// <summary>
- /// Receives messages from the taskbar icon through
- /// window messages of an underlying helper window.
- /// </summary>
- public class WindowMessageSink : IDisposable
- {
- #region members
- /// <summary>
- /// The ID of messages that are received from the the
- /// taskbar icon.
- /// </summary>
- public const int CallbackMessageId = 0x400;
- /// <summary>
- /// The ID of the message that is being received if the
- /// taskbar is (re)started.
- /// </summary>
- private uint taskbarRestartMessageId;
- /// <summary>
- /// Used to track whether a mouse-up event is just
- /// the aftermath of a double-click and therefore needs
- /// to be suppressed.
- /// </summary>
- private bool isDoubleClick;
- /// <summary>
- /// 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.
- /// </summary>
- private WindowProcedureHandler messageHandler;
- /// <summary>
- /// Window class ID.
- /// </summary>
- internal string WindowId { get; private set; }
- /// <summary>
- /// Handle for the message window.
- /// </summary>
- internal IntPtr MessageWindowHandle { get; private set; }
- /// <summary>
- /// The version of the underlying icon. Defines how
- /// incoming messages are interpreted.
- /// </summary>
- public NotifyIconVersion Version { get; set; }
- #endregion
- #region events
- /// <summary>
- /// The custom tooltip should be closed or hidden.
- /// </summary>
- public event Action<bool> ChangeToolTipStateRequest;
- /// <summary>
- /// Fired in case the user clicked or moved within
- /// the taskbar icon area.
- /// </summary>
- public event Action<MouseEvent> MouseEventReceived;
- /// <summary>
- /// Fired if a balloon ToolTip was either displayed
- /// or closed (indicated by the boolean flag).
- /// </summary>
- public event Action<bool> BalloonToolTipChanged;
- /// <summary>
- /// Fired if the taskbar was created or restarted. Requires the taskbar
- /// icon to be reset.
- /// </summary>
- public event Action TaskbarCreated;
- #endregion
- #region construction
- /// <summary>
- /// Creates a new message sink that receives message from
- /// a given taskbar icon.
- /// </summary>
- /// <param name="version"></param>
- public WindowMessageSink(NotifyIconVersion version)
- {
- Version = version;
- CreateMessageWindow();
- }
- private WindowMessageSink()
- {
- }
- /// <summary>
- /// Creates a dummy instance that provides an empty
- /// pointer rather than a real window handler.<br/>
- /// Used at design time.
- /// </summary>
- /// <returns>WindowMessageSink</returns>
- internal static WindowMessageSink CreateEmpty()
- {
- return new WindowMessageSink
- {
- MessageWindowHandle = IntPtr.Zero,
- Version = NotifyIconVersion.Vista
- };
- }
- #endregion
- #region CreateMessageWindow
- /// <summary>
- /// Creates the helper message window that is used
- /// to receive messages from the taskbar icon.
- /// </summary>
- private void CreateMessageWindow()
- {
- //generate a unique ID for the window
- WindowId = "WPFTaskbarIcon_" + Guid.NewGuid();
- //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 = string.Empty;
- 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, IntPtr.Zero, IntPtr.Zero,
- IntPtr.Zero, IntPtr.Zero);
- if (MessageWindowHandle == IntPtr.Zero)
- {
- throw new Win32Exception("Message window handle was not a valid pointer");
- }
- }
- #endregion
- #region Handle Window Messages
- /// <summary>
- /// Callback method that receives messages from the taskbar area.
- /// </summary>
- private IntPtr OnWindowMessageReceived(IntPtr hWnd, uint messageId, IntPtr wParam, IntPtr lParam)
- {
- if (messageId == taskbarRestartMessageId)
- {
- //recreate the icon if the taskbar was restarted (e.g. due to Win Explorer shutdown)
- var listener = TaskbarCreated;
- listener?.Invoke();
- }
- //forward message
- ProcessWindowMessage(messageId, wParam, lParam);
- // Pass the message to the default window procedure
- return WinApi.DefWindowProc(hWnd, messageId, wParam, lParam);
- }
- /// <summary>
- /// Processes incoming system messages.
- /// </summary>
- /// <param name="msg">Callback ID.</param>
- /// <param name="wParam">If the version is <see cref="NotifyIconVersion.Vista"/>
- /// or higher, this parameter can be used to resolve mouse coordinates.
- /// Currently not in use.</param>
- /// <param name="lParam">Provides information about the event.</param>
- private void ProcessWindowMessage(uint msg, IntPtr wParam, IntPtr lParam)
- {
- if (msg != CallbackMessageId) return;
- var message = (WindowsMessages) lParam.ToInt32();
- //Debug.WriteLine("Got message " + message);
- switch (message)
- {
- case WindowsMessages.WM_CONTEXTMENU:
- // TODO: Handle WM_CONTEXTMENU, see https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw
- //Debug.WriteLine("Unhandled WM_CONTEXTMENU");
- break;
- case WindowsMessages.WM_MOUSEMOVE:
- MouseEventReceived?.Invoke(MouseEvent.MouseMove);
- break;
- case WindowsMessages.WM_LBUTTONDOWN:
- MouseEventReceived?.Invoke(MouseEvent.IconLeftMouseDown);
- break;
- case WindowsMessages.WM_LBUTTONUP:
- if (!isDoubleClick)
- {
- MouseEventReceived?.Invoke(MouseEvent.IconLeftMouseUp);
- }
- isDoubleClick = false;
- break;
- case WindowsMessages.WM_LBUTTONDBLCLK:
- isDoubleClick = true;
- MouseEventReceived?.Invoke(MouseEvent.IconDoubleClick);
- break;
- case WindowsMessages.WM_RBUTTONDOWN:
- MouseEventReceived?.Invoke(MouseEvent.IconRightMouseDown);
- break;
- case WindowsMessages.WM_RBUTTONUP:
- MouseEventReceived?.Invoke(MouseEvent.IconRightMouseUp);
- break;
- case WindowsMessages.WM_RBUTTONDBLCLK:
- //double click with right mouse button - do not trigger event
- break;
- case WindowsMessages.WM_MBUTTONDOWN:
- MouseEventReceived?.Invoke(MouseEvent.IconMiddleMouseDown);
- break;
- case WindowsMessages.WM_MBUTTONUP:
- MouseEventReceived?.Invoke(MouseEvent.IconMiddleMouseUp);
- break;
- case WindowsMessages.WM_MBUTTONDBLCLK:
- //double click with middle mouse button - do not trigger event
- break;
- case WindowsMessages.NIN_BALLOONSHOW:
- BalloonToolTipChanged?.Invoke(true);
- break;
- case WindowsMessages.NIN_BALLOONHIDE:
- case WindowsMessages.NIN_BALLOONTIMEOUT:
- BalloonToolTipChanged?.Invoke(false);
- break;
- case WindowsMessages.NIN_BALLOONUSERCLICK:
- MouseEventReceived?.Invoke(MouseEvent.BalloonToolTipClicked);
- break;
- case WindowsMessages.NIN_POPUPOPEN:
- ChangeToolTipStateRequest?.Invoke(true);
- break;
- case WindowsMessages.NIN_POPUPCLOSE:
- ChangeToolTipStateRequest?.Invoke(false);
- break;
- case WindowsMessages.NIN_SELECT:
- // TODO: Handle NIN_SELECT see https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw
- //Debug.WriteLine("Unhandled NIN_SELECT");
- break;
- case WindowsMessages.NIN_KEYSELECT:
- // TODO: Handle NIN_KEYSELECT see https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw
- //Debug.WriteLine("Unhandled NIN_KEYSELECT");
- break;
- default:
- //Debug.WriteLine("Unhandled NotifyIcon message ID: " + lParam);
- break;
- }
- }
- #endregion
- #region Dispose
- /// <summary>
- /// Set to true as soon as <c>Dispose</c> has been invoked.
- /// </summary>
- public bool IsDisposed { get; private set; }
- /// <summary>
- /// Disposes the object.
- /// </summary>
- /// <remarks>This method is not virtual by design. Derived classes
- /// should override <see cref="Dispose(bool)"/>.
- /// </remarks>
- public void Dispose()
- {
- Dispose(true);
- // This object will be cleaned up by the Dispose method.
- // Therefore, you should call GC.SuppressFinalize to
- // take this object off the finalization queue
- // and prevent finalization code for this object
- // from executing a second time.
- GC.SuppressFinalize(this);
- }
- /// <summary>
- /// This destructor will run only if the <see cref="Dispose()"/>
- /// method does not get called. This gives this base class the
- /// opportunity to finalize.
- /// <para>
- /// Important: Do not provide destructor in types derived from
- /// this class.
- /// </para>
- /// </summary>
- ~WindowMessageSink()
- {
- Dispose(false);
- }
- /// <summary>
- /// Removes the windows hook that receives window
- /// messages and closes the underlying helper window.
- /// </summary>
- private void Dispose(bool disposing)
- {
- //don't do anything if the component is already disposed
- if (IsDisposed) return;
- IsDisposed = true;
- //always destroy the unmanaged handle (even if called from the GC)
- WinApi.DestroyWindow(MessageWindowHandle);
- messageHandler = null;
- }
- #endregion
- }
- }
|