OriginalSource is wrong when command is invoked on a ListBoxItem by key gesture
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
* .NET Core Version: 3.1
* Windows version: Windows 10 version 2004
* Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes
Problem description
===================
When a ``ContextMenu`` is attached to a ``ListBoxItem``, commands can be invoked through the context menu itself or by using a key gesture. Depending on how the command is invoked, the ``Executed`` event handler gets different values for ``ExecutedRoutedEventArgs.OriginalSource``:
**Actual behavior:**
* When the command is invoked through the context menu, ``OriginalSource`` is the ``ListBoxItem``.
* When the command is invoked by the key gesture, ``OriginalSource`` is the ``ListBox`` instead of the ``ListBoxItem``. Since the ``Source`` and the ``sender`` are also the ``ListBox``, the event handler never gets the ``ListBoxItem`` that the command was invoked on.
**Expected behavior:**
* No matter how the command is invoked, ``OriginalSource`` should always be the ``ListBoxItem``.
Minimal repro
=============
XAML:
```XAML
<Setter Property="ContextMenu" Value="{StaticResource CommandsContextMenu}"/>
```
Code-behind:
```C#
public MainWindow()
{
InitializeComponent();
var items = new ObservableCollection { "Item 1", "Item 2", "Item 3" };
CommandsListBox.DataContext = items;
}
private void Properties_Executed(object sender, ExecutedRoutedEventArgs e)
{
Debug.WriteLine($"sender = {sender.GetType().Name}, e.Source = {e.Source.GetType().Name}, e.OriginalSource = {e.OriginalSource.GetType().Name}");
}
```
Try both invocation methods to see the different values of ``e.OriginalSource``:
1. Right-click a list box item, and select Properties from the context menu.
2. Select a list box item, and press the F4 key.
The ``Properties_Executed`` event handler will write the types to the debug output:
```
sender = ListBox, e.Source = ListBox, e.OriginalSource = ListBoxItem
sender = ListBox, e.Source = ListBox, e.OriginalSource = ListBox
```
Cause of bug
============
When an accelerator key is pressed, the ``KeyEventArgs.OriginalSource`` actually starts out with the correct value of the ``ListBoxItem``. However, this is lost because ``CommandManager.ExecuteCommand`` throws away ``inputEventArgs`` when it calls ``RoutedCommand.ExecuteCore``.
When ``RoutedCommand.ExecuteImpl`` creates the ``ExecutedRoutedEventArgs``, it uses ``target`` as the source of the event. But the ``target`` parameter comes from ``CommandManager.TranslateInput``, which sets it to the ``ListBox``.
CommandManager.cs:
```C#
459 // We currently do not support declaring the element with focus as the target
460 // element by setting target == null. Instead, we interpret a null target to indicate
461 // the element that we are routing the event through, e.g. the targetElement parameter.
462 if (target == null)
463 {
464 target = targetElement;
465 }
```
The comment implies that this is a TODO that was never done. It also says that the correct behavior is to set the focused element (``ListBoxItem``) as the target. This is what the context menu is already doing.
Context menu codepath
=====================
When the user selects the context menu item, ``RoutedCommand.ExecuteImpl`` is called for the first time with the ``MenuItem`` as the ``target``. But the developer can't do anything with a ``MenuItem``, so WPF takes additional steps to find a more semantically useful element. ``CommandManager.OnExecuted`` transfers the event to the parent scope's focused element, which is the ``ListBoxItem``.
CommandManager.cs:
```C#
556 // This element is a focus scope.
557 // Try to transfer the event to its parent focus scope's focused element.
558 IInputElement focusedElement = GetParentScopeFocusedElement(d);
559 if (focusedElement != null)
560 {
561 TransferEvent(focusedElement, e);
562 }
```
Contributor guide
Assessment
This issue has not been assessed yet.