diff --git a/README.md b/README.md index 052d050..c8e2e87 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,9 @@ The sample below shows some of the properties of the control. For a more compreh ``` +## Contributors and Thanks + +Hi, I'm Philipp! This little library was originally written by me, but is currently mostly maintained by [Jan Karger](https://github.com/punker76) and [Robin Krom](https://github.com/Lakritzator). Big, big kudos to the two of you, and everybody else who [contributed](https://github.com/hardcodet/wpf-notifyicon/graphs/contributors) to this library. You rock! + +Make sure to check out Robin's great [Greenshot](https://getgreenshot.org/) tool (that I use on a daily basis), and Jan's [MahApps](https://github.com/MahApps) UI framework. + diff --git a/src/NotifyIconWpf/Interop/KeyboardEvent.cs b/src/NotifyIconWpf/Interop/KeyboardEvent.cs new file mode 100644 index 0000000..52b43ed --- /dev/null +++ b/src/NotifyIconWpf/Interop/KeyboardEvent.cs @@ -0,0 +1,26 @@ +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Event flags for keyboard events. + /// + public enum KeyboardEvent + { + /// + /// The icon was selected with the keyboard + /// and Shift-F10 was pressed. + /// + ContextMenu, + + /// + /// The icon was selected with the keyboard + /// and activated with Spacebar or Enter. + /// + KeySelect, + + /// + /// The icon was selected with the mouse + /// and activated with Enter. + /// + Select, + } +} diff --git a/src/NotifyIconWpf/Interop/WindowMessageSink.cs b/src/NotifyIconWpf/Interop/WindowMessageSink.cs index 2ad3882..fafff14 100644 --- a/src/NotifyIconWpf/Interop/WindowMessageSink.cs +++ b/src/NotifyIconWpf/Interop/WindowMessageSink.cs @@ -94,6 +94,12 @@ public class WindowMessageSink : IDisposable /// public event Action MouseEventReceived; + /// + /// Fired in case the user interacted with the taskbar + /// icon area with keyboard shortcuts. + /// + public event Action KeyboardEventReceived; + /// /// Fired if a balloon ToolTip was either displayed /// or closed (indicated by the boolean flag). @@ -242,8 +248,7 @@ private void ProcessWindowMessage(uint msg, IntPtr wParam, IntPtr lParam) 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"); + KeyboardEventReceived?.Invoke(KeyboardEvent.ContextMenu); break; case WindowsMessages.WM_MOUSEMOVE: @@ -313,13 +318,11 @@ private void ProcessWindowMessage(uint msg, IntPtr wParam, IntPtr lParam) 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"); + KeyboardEventReceived?.Invoke(KeyboardEvent.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"); + KeyboardEventReceived?.Invoke(KeyboardEvent.KeySelect); break; default: @@ -387,4 +390,4 @@ private void Dispose(bool disposing) #endregion } -} \ No newline at end of file +} diff --git a/src/NotifyIconWpf/TaskbarIcon.Declarations.cs b/src/NotifyIconWpf/TaskbarIcon.Declarations.cs index 2e994f3..1388864 100644 --- a/src/NotifyIconWpf/TaskbarIcon.Declarations.cs +++ b/src/NotifyIconWpf/TaskbarIcon.Declarations.cs @@ -1186,6 +1186,126 @@ internal static RoutedEventArgs RaiseTrayMouseMoveEvent(DependencyObject target) #endregion + #region TrayKeyboardContextMenu + + /// + /// TrayKeyboardContextMenu Routed Event + /// + public static readonly RoutedEvent TrayKeyboardContextMenuEvent = EventManager.RegisterRoutedEvent("TrayKeyboardContextMenu", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user moves the mouse over the taskbar icon. + /// + public event RoutedEventHandler TrayKeyboardContextMenu + { + add { AddHandler(TrayKeyboardContextMenuEvent, value); } + remove { RemoveHandler(TrayKeyboardContextMenuEvent, value); } + } + + /// + /// A helper method to raise the TrayKeyboardContextMenu event. + /// + protected RoutedEventArgs RaiseTrayKeyboardContextMenuEvent() + { + return RaiseTrayKeyboardContextMenuEvent(this); + } + + /// + /// A static helper method to raise the TrayKeyboardContextMenu event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayKeyboardContextMenuEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayKeyboardContextMenuEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayKeyboardKeySelect + + /// + /// TrayKeyboardKeySelect Routed Event + /// + public static readonly RoutedEvent TrayKeyboardKeySelectEvent = EventManager.RegisterRoutedEvent("TrayKeyboardKeySelect", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user moves the mouse over the taskbar icon. + /// + public event RoutedEventHandler TrayKeyboardKeySelect + { + add { AddHandler(TrayKeyboardKeySelectEvent, value); } + remove { RemoveHandler(TrayKeyboardKeySelectEvent, value); } + } + + /// + /// A helper method to raise the TrayKeyboardKeySelect event. + /// + protected RoutedEventArgs RaiseTrayKeyboardKeySelectEvent() + { + return RaiseTrayKeyboardKeySelectEvent(this); + } + + /// + /// A static helper method to raise the TrayKeyboardKeySelect event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayKeyboardKeySelectEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayKeyboardKeySelectEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayKeyboardSelect + + /// + /// TrayKeyboardSelect Routed Event + /// + public static readonly RoutedEvent TrayKeyboardSelectEvent = EventManager.RegisterRoutedEvent("TrayKeyboardSelect", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user moves the mouse over the taskbar icon. + /// + public event RoutedEventHandler TrayKeyboardSelect + { + add { AddHandler(TrayKeyboardSelectEvent, value); } + remove { RemoveHandler(TrayKeyboardSelectEvent, value); } + } + + /// + /// A helper method to raise the TrayKeyboardSelect event. + /// + protected RoutedEventArgs RaiseTrayKeyboardSelectEvent() + { + return RaiseTrayKeyboardSelectEvent(this); + } + + /// + /// A static helper method to raise the TrayKeyboardSelect event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayKeyboardSelectEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayKeyboardSelectEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + #region TrayBalloonTipShown /// @@ -1893,4 +2013,4 @@ static TaskbarIcon() ContextMenuProperty.OverrideMetadata(typeof (TaskbarIcon), md); } } -} \ No newline at end of file +} diff --git a/src/NotifyIconWpf/TaskbarIcon.cs b/src/NotifyIconWpf/TaskbarIcon.cs index 33bf8e3..aedf67c 100644 --- a/src/NotifyIconWpf/TaskbarIcon.cs +++ b/src/NotifyIconWpf/TaskbarIcon.cs @@ -132,6 +132,7 @@ public TaskbarIcon() // register event listeners messageSink.MouseEventReceived += OnMouseEvent; + messageSink.KeyboardEventReceived += OnKeyboardEvent; messageSink.TaskbarCreated += OnTaskbarCreated; messageSink.ChangeToolTipStateRequest += OnToolTipChange; messageSink.BalloonToolTipChanged += OnBalloonToolTipChanged; @@ -481,6 +482,37 @@ private void OnMouseEvent(MouseEvent me) #endregion + #region Process Incoming Keyboard Events + + /// + /// Processes keyboard events, which are bubbled + /// through the class' routed events, trigger + /// certain actions (e.g. show a popup), or + /// both. + /// + /// Event flag. + private void OnKeyboardEvent(KeyboardEvent ke) + { + if (IsDisposed) return; + + switch (ke) + { + case KeyboardEvent.ContextMenu: + RaiseTrayKeyboardContextMenuEvent(); + break; + case KeyboardEvent.KeySelect: + RaiseTrayKeyboardKeySelectEvent(); + break; + case KeyboardEvent.Select: + RaiseTrayKeyboardSelectEvent(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(ke), "Missing handler for keyboard event flag: " + ke); + } + } + + #endregion + #region ToolTips /// @@ -762,6 +794,8 @@ private void ShowContextMenu(Point cursorPosition) // fallback, the context menu can't receive keyboard events - should not happen though WinApi.SetForegroundWindow(handle); + ContextMenu.Focus(); + // bubble event RaiseTrayContextMenuOpenEvent(); } @@ -1100,4 +1134,4 @@ private void Dispose(bool disposing) #endregion } -} \ No newline at end of file +}