7

I have a form with a textbox and a button.

When that textbox has it's value changed, the button command doesn't call the CanExecute method of it's command.

The command parameter is set but doesn't seem to change. After load the window, the button remains disabled.

<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Save" Command="{Binding SaveChangesCommand}" CommandParameter="{Binding Name}" />

I know the binding is working because I created a behavior that receives a target binding and raise the CanExecute when the the binding changes.

With this behavior, the CanExecute is called normally.

<Button Content="Save" Command="{Binding SaveChangesCommand}">
    <i:Interaction.Behaviors>
        <behaviors:CallCommandCanExecuteWhenBindingChange Target="{Binding Name}" />
    </i:Interaction.Behaviors>
</Button>

ViewModel:

public class EditViewModel : INotifyPropertyChanged
{
    private string _name;

    public EditViewModel()
    {
        SaveChangesCommand = new DelegateCommand(p => SaveChanges(), p => CanSaveChanges());
    }

    public string Name
    {
        get { return _name; }
        set
        {
            if (value == _name) return;
            _name = value;
            OnPropertyChanged();
        }
    }

    public DelegateCommand SaveChangesCommand { get; private set; }

    private void SaveChanges()
    {
    }
    private bool CanSaveChanges()
    {
        return !string.IsNullOrWhiteSpace(Name);
    }
}

DelegateCommand:

public interface IBaseCommand : ICommand
{
    void OnCanExecuteChanged();
}

public class DelegateCommand : IBaseCommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

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

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return _canExecute(parameter);
    }
    public void Execute(object parameter)
    {
        _execute(parameter);
        OnCanExecuteChanged();
    }

    public void OnCanExecuteChanged()
    {
        var handler = CanExecuteChanged;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }
}

CallCommandCanExecuteWhenBindingChange:

public class CallCommandCanExecuteWhenBindingChange : Behavior<FrameworkElement>
{
    public static readonly DependencyProperty<CallCommandCanExecuteWhenBindingChange, object> TargetProperty;
    private ICommandBase _command;

    static CallCommandCanExecuteWhenBindingChange()
    {
        var dependency = new DependencyRegistry<CallCommandCanExecuteWhenBindingChange>();

        TargetProperty = dependency.Register(b => b.Target, s => s.OnTargetChange());
    }

    public object Target
    {
        get { return TargetProperty.Get(this); }
        set { TargetProperty.Set(this, value); }
    }

    private void OnTargetChange()
    {
        if (_command == null && AssociatedObject != null)
        {
            var field = AssociatedObject.GetType().GetProperty("Command");
            _command = (IBaseCommand)field.GetValue(AssociatedObject);
        }

        if (_command != null)
            _command.OnCanExecuteChanged();
    }
}

Does anyone know why the button doesn't call the CanExecute?

Thiago Romam
  • 439
  • 4
  • 14
  • How Button Command will fire when you change Textbox text? – Vimal CK Feb 05 '15 at 12:34
  • Since the textbox is binding to a property with notification and the button's CommandParameter is binding to the same property, it will be notified and call the Command's CanExecute method – Thiago Romam Feb 05 '15 at 12:42

1 Answers1

16

In your DelegateCommand implementation CanExecuteChanged should add/remove to CommandManager.RequerySuggested event

Occurs when the CommandManager detects conditions that might change the ability of a command to execute.

change it to

public event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}
dkozl
  • 32,814
  • 8
  • 87
  • 89
  • I need a public method for raise the CanExecuteChanged. Can I add/remove to a private _canExecuteChanged too? Or there is a better way? – Thiago Romam Feb 05 '15 at 12:31
  • 1
    Technically you can but if you want to revalidate command you can call [`CommandManager.InvalidateRequerySuggested`](https://msdn.microsoft.com/en-us/library/system.windows.input.commandmanager.invalidaterequerysuggested(v=vs.110).aspx) method. It _forces the CommandManager to raise the RequerySuggested event_. – dkozl Feb 05 '15 at 12:52
  • Does CommandManager.InvalidateRequerySuggested refresh all commands of the application? – Thiago Romam Feb 05 '15 at 13:17
  • 1
    It raises `CommandManager.RequerySuggested` event so it will call everything that has subscribed to this event – dkozl Feb 05 '15 at 13:18