dotnet / dotnet/wpf

OriginalSource is wrong when command is invoked on a ListBoxItem by key gesture

Open
#3,149 0 comments 1 reaction 0 assignees View on GitHub
Bug
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.