wpfprism

WPF Prism CompositeCommand disables button


I have two commands that I want to trigger via a button click. So I've created a CompositeCommand, registered my two commands with it, and bound to my button. My commands are DelegateCommands.

The problem is that the button is disabled and I can't enable it.

I've tried adding trivial CanExecute methods to the DeleteCommands, but that didn't fix it.

I've tried setting monitorCommandActivity to true in the CompositeCommand constructor.

I thought maybe it was disabling because my DelegateCommands have parameters and a CompositeCommand doesn't seem to be able to take any CommandParameters, but I eliminated the parameters in the DelegateCommands and that didn't fix the problem.

View.xaml:

    <TextBlock Text="Theme:" />
    <telerik:RadComboBox x:Name="_themeComboBox"
                         ItemsSource="{Binding ThemeList}"
                         DisplayMemberPath="Name"
                         SelectedItem="{Binding SelectedTheme, Mode=TwoWay}" />
    <telerik:RadButton Content="Apply"
                       Command="{Binding ThemeApplyAndSaveCommand}" />

ViewModel.cs:

public class ViewModel
{
    public ViewModel(ILogger logger, ...)
    {
        ThemeList = _userSettingsService.GetThemeList();

        // Configure the theme Apply button to both select and save the theme
        ThemeApplyAndSaveCommand.RegisterCommand(ThemeApplyCommand);
        ThemeApplyAndSaveCommand.RegisterCommand(ThemeSaveCommand);

        // Activate the user's preferred theme
        SelectedTheme = _userSettingsService.GetThemePreference(securityContext.User);
        ThemeApplyCommand.Execute();

        logger.Debug("    ThemeApplyCommand.CanExecute: {canExecute}", ThemeApplyCommand.CanExecute());
        logger.Debug("    ThemeSaveCommand.CanExecute: {canExecute}", ThemeSaveCommand.CanExecute());
        logger.Debug("    ThemeApplyAndSaveCommand.CanExecute: {canExecute}", ThemeApplyAndSaveCommand.CanExecute(null));
    }


    public CompositeCommand ThemeApplyAndSaveCommand => new CompositeCommand();

    public DelegateCommand ThemeApplyCommand => new DelegateCommand(ExecuteThemeApplyCommand);

    public DelegateCommand ThemeSaveCommand => new DelegateCommand(ExecuteThemeSaveCommand);

    private void ExecuteThemeApplyCommand()
    {
        ...
    }

    private void ExecuteThemeSaveCommand()
    {
        ...
    }
}

Solution

  • You have to register DelegateCommands to a CompositeCommand using the RegisterCommand method. Directly adding them to the RegisteredCommands collection does not work, as it returns a new collection instance each time.

    var compositeCommand = new CompositeCommand();
    compositeCommand.RegisterCommand(MyDelgateCommand);
    compositeCommand.RegisterCommand(MyOtherDelegateCommand);
    

    A CompositeCommand only returns true for CanExecute, if all active commands can execute. You have to make sure to call RaiseCanExecuteChanged when the registered commands should update their activity, so the composite command can reevaluate its activity, too.

    Monitoring the command activity is something completely different. Views or view models in Prism can implement IActiveAware to know when they are activated and deactivated in a region. DelegateCommands implement this interface too, so they can also be activated and deactivated with their corresponding view model. If you do not explicitly enable monitoring command activity, commands will be treated as if they are active.

    Imagine a command to save the active document in a tab control. You could register save commands of the tabs to a common save CompositeCommand, but activate them only if they are on the active tab. In that case, monitoring the activity of the commands will only consider active commands for CanExecute and will execute only them, when the command is invoked.


    Update on your posted code. The corresponding command properties return new commands each time. Initialize the composite command and the other commands in the constructor.

    public ViewModel(ILogger logger, ...)
    {
       ThemeList = _userSettingsService.GetThemeList();
    
       // Configure the theme Apply button to both select and save the theme
       ThemeApplyAndSaveCommand = new CompositeCommand();
       ThemeApplyCommand = new DelegateCommand(ExecuteThemeApplyCommand);
       ThemeSaveCommand = new DelegateCommand(ExecuteThemeSaveCommand);
    
       ThemeApplyAndSaveCommand.RegisterCommand(ThemeApplyCommand);
       ThemeApplyAndSaveCommand.RegisterCommand(ThemeSaveCommand);
    
       // ...the rest of the constructor code.
    }
    
    public CompositeCommand ThemeApplyAndSaveCommand { get; }
    
    public DelegateCommand ThemeApplyCommand { get; }
    
    public DelegateCommand ThemeSaveCommand { get; }