wpfeventhandlericommanddelegatecommand

WPF ICommand equivalent of Click -= Button_Click


For WPF ICommand, what is the equivalent of the event handler -=?

We have a user control button that manages the function of submitting a purchase request but requires a user to register on the workstation first.

The WinForms usage is:

this.Click -= this.btnRegister_Click;
this.Click -= this.btnSubmit_Click;

A modernized version in WPF has a single ICommand property and the typical declaration.

public ICommand ClickCommand { get; set; }
....
ClickCommand = new DelegateCommand(RegisterClicked);

Once the user registers, the user control will reinitialize to the submit process. A simple trigger exists that checks the status of the button. Visually, the triggers are working just fine which indicates that the program is processing the initialization code blocks.

<Label.Style>
    <Style TargetType="Label" BasedOn="{StaticResource ButtonLabel}">
        <Setter Property="Content" Value="Approvals"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=ButtonState}" Value="{x:Static locdata:WorkflowButtonState.Register}">
                <Setter Property="Content" Value="Register"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=ButtonState}" Value="{x:Static locdata:WorkflowButtonState.Submit}">
                <Setter Property="Content" Value="Submit"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Label.Style>

Initialization code blocks

private void InitRegister()
{
    ButtonState = WorkflowButtonState.Register;
    ClickCommand = new DelegateCommand(RegisterClicked);
}
private void InitSubmit()
{
    ButtonState = WorkflowButtonState.Submit;
    ClickCommand = new DelegateCommand(SubmitClicked);
}

private void Reset()
{
    ClickCommand = null;
    ButtonState = WorkflowButtonState.Default;
}

Debug mode clearly shows that the ClickCommand execute method is the SubmitClicked, however the RegisterClicked event still fires. This only happens when the Register process occurs first because the user has not previously logged on the workstation.

What is needed to replicate the EventHandler -= behavior since setting ClickCommand = null did not help?

UPDATE

The ICommand usage in my applications to this point has been a single use. As Andy's answer stated, it should be treated like a property and to raise the property changed event.

The updated ICommand declaration was, as I have done numerous times for properties, all that needed changed.

private ICommand _clickCommand;
public ICommand ClickCommand { get => _clickCommand; set { _clickCommand = value; OnPropertyChanged(); } }

Solution

  • Two things to think about here:

    1. A command on a button is not an event, it's a dependency property. https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.buttonbase.command?view=netcore-3.1

    2. How do you tell a dependency property in the view that it's value should be read again from the viewmodel?

    You notify property changed.

    Some sample markup:

        <Window.DataContext>
            <local:MainWindowViewModel/>
        </Window.DataContext>
        <StackPanel>
            <Button Command="{Binding TestCommand}" Content="Test"/>
            <Button Command="{Binding ChangeCommand}" Content="Change Test Command"/>
        </StackPanel>
    </Window>
    

    There's a test command which will start off as one command and then when you click the change test command button it will change to a different command.

    My viewmodel:

        public class MainWindowViewModel : BindableBase
        {
            private DelegateCommand testCommand;
            public DelegateCommand TestCommand 
            {
                get => testCommand;
                set { testCommand = value; RaisePropertyChanged(); }
            }
            public DelegateCommand ChangeCommand { get; set; }
            public MainWindowViewModel()
            {
                TestCommand = new DelegateCommand(() => { MessageBox.Show("Original Command"); });
                ChangeCommand = new DelegateCommand(() => 
                {
                    TestCommand = new DelegateCommand(() => { MessageBox.Show("Replacement is used"); });
                });
            }
        }
    

    When changecommand is invoked, the delegatecommand for testcommand is replaced with a new command.

    And.

    Property changed is invoked.

    Which tells the UI to go get that new command.

    If I then remove that raise property changed from TestCommand:

            public DelegateCommand TestCommand 
            {
                get => testCommand;
                set { testCommand = value;  }
            }
    

    Click the change button and then click my test button, I get the original command runs. Which is the problem you are seeing. Because your command looks like:

    public ICommand ClickCommand { get; set; }
    

    With no raise property changed.

    Note:

    If my changecommand is:

                ChangeCommand = new DelegateCommand(() => 
                {
                    TestCommand = null;
                });
    

    Then click that change button, click the test button and nothing happens. It's command is now null.