0

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(); } }
Galactic
  • 400
  • 4
  • 14
  • You lose me a bit toward the end of your question. Could you clarify the last two paragraphs, or include a reporducable example? – Keith Stein Jun 26 '20 at 00:24

2 Answers2

1

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.

Andy
  • 11,864
  • 2
  • 17
  • 20
0

As far as I understand, you have a button whose content and function change depend on some conditions. You also implement 2 commands for each of your button 's functions; however, one of the command still take action when it suppose not to, correct?

In that case, I think you can modify the DelegteCommand (which inherits IComamnd interface) to add a CanExecute() delegate to it. Later on, when you create new instance for for commands, just add a delegate to determine if the command is executable. Something like this:

public class DelegateCommand: ICommand
{
    private Action _execute;
    private Func<bool> _canExecute;

    public DelegateCommand(Action execute, Func<bool> canExecute)
    {
         _execute = execute;
         _canExecute = canExecute;
    }

    public bool CanExecute()
    {
        if (_canExecute != null && _execute)
            return _canExecute.Invoke();
    }

    public void Execute()
    {
        if (CanExecute())
            _execute.Invoke();
    }
}
public ICommand RegisterClicked { get; set; }

RegisterClicked = new DelegateCommand(RegisterInvoke, RegisterCanExecute);

private void RegisterInvoke()
{ 
}
private bool RegisterCanExecute()
{
    // determine when the execution of Register and be perform here
}
Thế Long
  • 516
  • 7
  • 19