Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial hooks for keyboard-related events. #57

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ The sample below shows some of the properties of the control. For a more compreh
</Window>
```

## 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.

26 changes: 26 additions & 0 deletions src/NotifyIconWpf/Interop/KeyboardEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Hardcodet.Wpf.TaskbarNotification.Interop
{
/// <summary>
/// Event flags for keyboard events.
/// </summary>
public enum KeyboardEvent
{
/// <summary>
/// The icon was selected with the keyboard
/// and Shift-F10 was pressed.
/// </summary>
ContextMenu,

/// <summary>
/// The icon was selected with the keyboard
/// and activated with Spacebar or Enter.
/// </summary>
KeySelect,

/// <summary>
/// The icon was selected with the mouse
/// and activated with Enter.
/// </summary>
Select,
}
}
17 changes: 10 additions & 7 deletions src/NotifyIconWpf/Interop/WindowMessageSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ public class WindowMessageSink : IDisposable
/// </summary>
public event Action<MouseEvent> MouseEventReceived;

/// <summary>
/// Fired in case the user interacted with the taskbar
/// icon area with keyboard shortcuts.
/// </summary>
public event Action<KeyboardEvent> KeyboardEventReceived;

/// <summary>
/// Fired if a balloon ToolTip was either displayed
/// or closed (indicated by the boolean flag).
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -387,4 +390,4 @@ private void Dispose(bool disposing)

#endregion
}
}
}
122 changes: 121 additions & 1 deletion src/NotifyIconWpf/TaskbarIcon.Declarations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,126 @@ internal static RoutedEventArgs RaiseTrayMouseMoveEvent(DependencyObject target)

#endregion

#region TrayKeyboardContextMenu

/// <summary>
/// TrayKeyboardContextMenu Routed Event
/// </summary>
public static readonly RoutedEvent TrayKeyboardContextMenuEvent = EventManager.RegisterRoutedEvent("TrayKeyboardContextMenu",
RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon));

/// <summary>
/// Occurs when the user moves the mouse over the taskbar icon.
/// </summary>
public event RoutedEventHandler TrayKeyboardContextMenu
{
add { AddHandler(TrayKeyboardContextMenuEvent, value); }
remove { RemoveHandler(TrayKeyboardContextMenuEvent, value); }
}

/// <summary>
/// A helper method to raise the TrayKeyboardContextMenu event.
/// </summary>
protected RoutedEventArgs RaiseTrayKeyboardContextMenuEvent()
{
return RaiseTrayKeyboardContextMenuEvent(this);
}

/// <summary>
/// A static helper method to raise the TrayKeyboardContextMenu event on a target element.
/// </summary>
/// <param name="target">UIElement or ContentElement on which to raise the event</param>
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

/// <summary>
/// TrayKeyboardKeySelect Routed Event
/// </summary>
public static readonly RoutedEvent TrayKeyboardKeySelectEvent = EventManager.RegisterRoutedEvent("TrayKeyboardKeySelect",
RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon));

/// <summary>
/// Occurs when the user moves the mouse over the taskbar icon.
/// </summary>
public event RoutedEventHandler TrayKeyboardKeySelect
{
add { AddHandler(TrayKeyboardKeySelectEvent, value); }
remove { RemoveHandler(TrayKeyboardKeySelectEvent, value); }
}

/// <summary>
/// A helper method to raise the TrayKeyboardKeySelect event.
/// </summary>
protected RoutedEventArgs RaiseTrayKeyboardKeySelectEvent()
{
return RaiseTrayKeyboardKeySelectEvent(this);
}

/// <summary>
/// A static helper method to raise the TrayKeyboardKeySelect event on a target element.
/// </summary>
/// <param name="target">UIElement or ContentElement on which to raise the event</param>
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

/// <summary>
/// TrayKeyboardSelect Routed Event
/// </summary>
public static readonly RoutedEvent TrayKeyboardSelectEvent = EventManager.RegisterRoutedEvent("TrayKeyboardSelect",
RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon));

/// <summary>
/// Occurs when the user moves the mouse over the taskbar icon.
/// </summary>
public event RoutedEventHandler TrayKeyboardSelect
{
add { AddHandler(TrayKeyboardSelectEvent, value); }
remove { RemoveHandler(TrayKeyboardSelectEvent, value); }
}

/// <summary>
/// A helper method to raise the TrayKeyboardSelect event.
/// </summary>
protected RoutedEventArgs RaiseTrayKeyboardSelectEvent()
{
return RaiseTrayKeyboardSelectEvent(this);
}

/// <summary>
/// A static helper method to raise the TrayKeyboardSelect event on a target element.
/// </summary>
/// <param name="target">UIElement or ContentElement on which to raise the event</param>
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

/// <summary>
Expand Down Expand Up @@ -1893,4 +2013,4 @@ static TaskbarIcon()
ContextMenuProperty.OverrideMetadata(typeof (TaskbarIcon), md);
}
}
}
}
36 changes: 35 additions & 1 deletion src/NotifyIconWpf/TaskbarIcon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public TaskbarIcon()

// register event listeners
messageSink.MouseEventReceived += OnMouseEvent;
messageSink.KeyboardEventReceived += OnKeyboardEvent;
messageSink.TaskbarCreated += OnTaskbarCreated;
messageSink.ChangeToolTipStateRequest += OnToolTipChange;
messageSink.BalloonToolTipChanged += OnBalloonToolTipChanged;
Expand Down Expand Up @@ -481,6 +482,37 @@ private void OnMouseEvent(MouseEvent me)

#endregion

#region Process Incoming Keyboard Events

/// <summary>
/// Processes keyboard events, which are bubbled
/// through the class' routed events, trigger
/// certain actions (e.g. show a popup), or
/// both.
/// </summary>
/// <param name="ke">Event flag.</param>
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

/// <summary>
Expand Down Expand Up @@ -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();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to understand why you have added a focus, can you add some comment here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still would like to know why you think this is needed, was there some kind of issue without it?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this line, the context menu doesn't have keyboard focus after being displayed (at least on Windows 11), so navigation with the arrow keys doesn't work, and you can't even close the context menu with Escape. This line should definitely be merged into the main branch, even without the rest of the patch.


// bubble event
RaiseTrayContextMenuOpenEvent();
}
Expand Down Expand Up @@ -1100,4 +1134,4 @@ private void Dispose(bool disposing)

#endregion
}
}
}