| 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 FILEusing 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    }}
 |