Skip to content

Update NativeMenuItem enabled state when CommandParameter changes - #22368

Merged
MrJul merged 4 commits into
AvaloniaUI:mainfrom
NathanDrake2406:fix/21914-native-menu-item-command-parameter
Oct 5, 2026
Merged

MrJul merged 4 commits into
AvaloniaUI:mainfrom
NathanDrake2406:fix/21914-native-menu-item-command-parameter

Conversation

@NathanDrake2406

Copy link
Copy Markdown
Contributor

What does the pull request do?

Makes NativeMenuItem query CanExecute again when its CommandParameter changes, so IsEnabled matches the current parameter.

What is the current behavior?

NativeMenuItem queries Command.CanExecute(CommandParameter) only when Command changes. If CommandParameter is assigned afterwards, IsEnabled keeps the result for the previous parameter.

This is the cause of #21914. When the application DataContext is set before AvaloniaXamlLoader.Load, a Command binding resolves at the moment it is applied. A CommandParameter written as a property element is assigned after that:

<NativeMenuItem Header="Int Parameter" Command="{Binding IntParameterCommand}">
  <NativeMenuItem.CommandParameter>
    <x:Int32>123</x:Int32>
  </NativeMenuItem.CommandParameter>
</NativeMenuItem>

The item calls CanExecute(null). A command such as RelayCommand<int> returns false for a null parameter, and the item stays disabled. When the DataContext is set after the XAML is loaded, the parameter is already present when the command arrives, so the item is enabled.

A string parameter is not affected, because RelayCommand<string> accepts null.

What is the updated/expected behavior with this PR?

Scenario Before After
Command set, then CommandParameter set to a value the command accepts Item stays disabled Item is enabled
CommandParameter changed to a value the command rejects Item stays enabled Item is disabled
CommandParameter changed on an item without a Command IsEnabled unchanged IsEnabled unchanged

In the sample from the issue, the Int Parameter and Bool Parameter items are now enabled for all three DataContext locations. The Timespan Parameter item stays disabled, because its command uses CanNeverExecute.

How was the solution implemented (if it's not obvious)?

NativeMenuItem.OnPropertyChanged now calls the existing CanExecuteChanged() method when CommandParameterProperty changes. Button and MenuItem already query CanExecute again in their CommandParameterProperty branches, so this follows the same contract.

The new branch does nothing when Command is null. CanExecuteChanged() writes IsEnabled directly and uses true when there is no command. Without the guard, a parameter change would enable an item that has IsEnabled="False" and no command.

Scope:

  • TrayIcon also has Command and CommandParameter, but it queries CanExecute only at click time and stores no result. It is not affected.
  • NativeMenuBarPresenter forwards the parameter to a managed MenuItem, which already handles the change.
  • The Command branch still overwrites an explicit IsEnabled value. That behaviour is unchanged here.

Validation:

  • Added NativeMenuItemTests with a command that accepts only an int parameter. The two tests that set and clear CommandParameter after Command fail without the fix and pass with it. The third test covers the item without a command.
  • Loaded the XAML shape from the issue (TrayIcon.Icons on an Application, Command binding, x:Int32 property element) with the runtime XAML loader. With the DataContext set before the load, the item is disabled without the fix and enabled with it. With the DataContext set after the load, the item is enabled in both cases. This check is not part of the PR.
  • Ran the NativeMenuItemTests and TrayIconTests classes in Avalonia.Controls.UnitTests. All pass.
  • Not verified on a physical tray menu on Windows, macOS, or Linux.

Checklist

Breaking changes

None. An item whose command result depends on the parameter can now change IsEnabled when the parameter changes.

Obsoletions / Deprecations

None.

Fixed issues

Fixes #21914

A NativeMenuItem keeps the CanExecute result from the moment its Command
is set. When CommandParameter is set or cleared afterwards, IsEnabled
does not change.

Add tests that set and clear CommandParameter after Command. Both fail
at present. A third test records that a parameter change does not enable
an item that has no command.
A NativeMenuItem queries CanExecute only when its Command changes. If
CommandParameter is assigned afterwards, IsEnabled keeps the result for
the previous parameter.

This occurs in XAML when the data context is set before the XAML is
loaded. The Command binding resolves when it is applied, which is before
a CommandParameter property element is assigned. A command that rejects
a null parameter, such as RelayCommand<int>, then leaves the item
disabled permanently.

Query CanExecute again when CommandParameter changes, as Button and
MenuItem do. Skip the query when the item has no command, because there
is no result to refresh and the query would reset IsEnabled to true.

Fixes AvaloniaUI#21914
@NathanDrake2406
NathanDrake2406 marked this pull request as ready for review October 2, 2026 02:06
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

NuGet packages

The packages for this pull request have been published (workflow run):

@MrJul MrJul added bug backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Oct 2, 2026
@MrJul
MrJul enabled auto-merge October 5, 2026 16:42
@MrJul
MrJul added this pull request to the merge queue Oct 5, 2026
Merged via the queue into AvaloniaUI:main with commit 0924260 Oct 5, 2026
11 checks passed
grokys pushed a commit to Evan260/Avalonia that referenced this pull request Oct 7, 2026
…aloniaUI#22368)

* test(menu): cover NativeMenuItem command parameter changes

A NativeMenuItem keeps the CanExecute result from the moment its Command
is set. When CommandParameter is set or cleared afterwards, IsEnabled
does not change.

Add tests that set and clear CommandParameter after Command. Both fail
at present. A third test records that a parameter change does not enable
an item that has no command.

* fix(menu): update NativeMenuItem state when CommandParameter changes

A NativeMenuItem queries CanExecute only when its Command changes. If
CommandParameter is assigned afterwards, IsEnabled keeps the result for
the previous parameter.

This occurs in XAML when the data context is set before the XAML is
loaded. The Command binding resolves when it is applied, which is before
a CommandParameter property element is assigned. A command that rejects
a null parameter, such as RelayCommand<int>, then leaves the item
disabled permanently.

Query CanExecute again when CommandParameter changes, as Button and
MenuItem do. Skip the query when the item has no command, because there
is no result to refresh and the query would reset IsEnabled to true.

Fixes AvaloniaUI#21914

* chore: trim code comments

---------

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
MrJul added a commit that referenced this pull request Oct 9, 2026
…2368)

* test(menu): cover NativeMenuItem command parameter changes

A NativeMenuItem keeps the CanExecute result from the moment its Command
is set. When CommandParameter is set or cleared afterwards, IsEnabled
does not change.

Add tests that set and clear CommandParameter after Command. Both fail
at present. A third test records that a parameter change does not enable
an item that has no command.

* fix(menu): update NativeMenuItem state when CommandParameter changes

A NativeMenuItem queries CanExecute only when its Command changes. If
CommandParameter is assigned afterwards, IsEnabled keeps the result for
the previous parameter.

This occurs in XAML when the data context is set before the XAML is
loaded. The Command binding resolves when it is applied, which is before
a CommandParameter property element is assigned. A command that rejects
a null parameter, such as RelayCommand<int>, then leaves the item
disabled permanently.

Query CanExecute again when CommandParameter changes, as Button and
MenuItem do. Skip the query when the item has no command, because there
is no result to refresh and the query would reset IsEnabled to true.

Fixes #21914

* chore: trim code comments

---------

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
@MrJul MrJul added backported-12.1.x and removed backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TrayIcon command parameters fail when DataContext is assigned before XAML loading

3 participants