WPF: CanExecute is Always Disabled

2019-07-17 00:53发布

问题:

I've got a TextBox for the user to enter a string, and an "Add" button to perform some tasks with the value. I added a CanExecute method to the DelegateCommand so that the button would only work when there was text in the box.

However, it's disabled all the time - even when the condition is true. Typing in the box does not enable the button.

<TextBox Text="{Binding BuildEntryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding AddBuildCommand}" Content="Add" />

this.AddBuildCommand = new DelegateCommand(() => this.AddBuild(), () => this.CanAddBuild());

private bool CanAddBuild()
{
    if (this.BuildEntryText != null)
    {
        return true;
    }
    else
    {
        return false;
    }
}

I've had this happen before (not always with the same types of controls), and ordinarily I would just remove the CanExecute and change it to an "if" check inside the action method. This time, though, I actually have to get it working and grey the button out the "proper" way.

Any insight would be greatly appreciated. Thanks in advance.

UPDATE:

I've added RaiseCanExecuteChanged to the set portion of the BuildEntryText property. Now the button starts disabled and becomes enabled when text is first typed. However, it remains enabled even after the text is deleted or the box is cleared by the add function.

public string BuildEntryText
{
    get
    {
        return this.buildEntryText;
    }

    set
    {
        this.SetProperty<string>(ref this.buildEntryText, value);
        this.AddBuildCommand.RaiseCanExecuteChanged();
    }
}

回答1:

Your delegate command need's to requery to check if the command can execute .The delegate command defines event called CanExecuteChanged which is raised on the UI thread to cause every invoking control to requery to check if the command can execute

This is how Delegate Command looks like(yours could be similar too)

public class DelegateCommand<T> : System.Windows.Input.ICommand
{
   private readonly Predicate<T> _canExecute;
   private readonly Action<T> _execute;

     public DelegateCommand(Action<T> execute)
    : this(execute, null)
     {
     }

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

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null)
            return true;

        return _canExecute((parameter == null) ? default(T) :(T)Convert.ChangeType(parameter, typeof(T)));
    }

    public void Execute(object parameter)
    {
        _execute((parameter == null) ? default(T) : (T)Convert.ChangeType(parameter, typeof(T)));
    }

    public event EventHandler CanExecuteChanged;
    public void RaiseCanExecuteChanged()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, EventArgs.Empty);
    }
}

You are binding it to the text command of the TextBox

Here is a sample ViewModel.cs

public class ViewModel
{
    private readonly DelegateCommand<string> MyButtonCommand;

    public ViewModel()
    {
        MyButtonCommand= new DelegateCommand<string>(
            (s) => { MessageBox.Show("Command Executed")}, //Execute
            (s) => { return !string.IsNullOrEmpty(_input); } //CanExecute
            );
    }

    public DelegateCommand<string> AddBuildCommand
    {
        get { return MyButtonCommand; }
    }

    private string _input;
    public string BuildEntryText
    {
        get { return _input; }
        set
        {
            _input = value;
            MyButtonCommand.RaiseCanExecuteChanged();
        }
    }
}  

and In your XAML

 <TextBox Text="{Binding BuildEntryText,   UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding AddBuildCommand}" Content="Add" />


回答2:

I figured out this was a two-part problem, and it wasn't actually a problem with my DelegateCommand (at least, not entirely).

1) I needed to call RaiseCanExecuteChanged, as Rachel suggested, or the UI wasn't going to refresh the control's IsEnabled property.

2) I needed to add an additional condition to check the length of the string, instead of only checking if it's null. Apparently, an empty TextBox starts as null, but if you add characters and then delete them, it doesn't go back to being null again. It's a string of length 0.

<TextBox Text="{Binding BuildEntryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding AddBuildCommand}" Content="Add" />

public DelegateCommand AddBuildCommand { get; private set; }

this.AddBuildCommand = new DelegateCommand(() => this.AddBuild(), () => this.CanAddBuild());

public string BuildEntryText
{
    get
    {
        return this.buildEntryText;
    }

    set
    {
        this.SetProperty<string>(ref this.buildEntryText, value);

        // PART 1:
        this.AddBuildCommand.RaiseCanExecuteChanged();
    }
}

private bool CanAddBuild()
{
    // PART 2:
    if (this.BuildEntryText != null && this.BuildEntryText.Length > 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}