-->

How to make the CanExecute trigger properly using

2019-06-08 03:19发布

问题:

I have a Model

public class Irritant : BindableBase
{
    private short _id;
    private string _name;
    private string _description;

    public short Id
    {
        get { return _id; }
        set { SetProperty(ref _id, value); }
    }

    public string Name
    {
        get { return _name; }
        set { SetProperty(ref _name, value); }
    }

    public string Description
    {
        get { return _description; }
        set { SetProperty(ref _description, value); }
    }


    public Irritant()
    {
        Id = 0;
        Name = "";
        Description = "";       
    }
}

Then my ViewModel with two versions

public class IrritantViewModel : BindableBase
{
    private IrritantDb db = new IrritantDb();


    //Version 1 - The Model's property is coded in IrritantViewModel
    //private short _id;
    //private string _name = "Alen";
    //private string _description;

    //public short Id
    //{
    //    get { return _id; }
    //    set { SetProperty(ref _id, value); }
    //}

    //public string Name
    //{
    //    get { return _name; }
    //    set { SetProperty(ref _name, value); }
    //}

    //public string Description
    //{
    //    get { return _description; }
    //    set { SetProperty(ref _description, value); }
    //}


    //Version2 - I use the Irritant Model as property of IrritantViewModel
    private DateTime? _lastUpdated;
    private Irritant _entity;

    public Irritant Entity
    {
        get { return _entity; }
        set { SetProperty(ref _entity, value); }
    }

    public DateTime? LastUpdated
    {
        get { return _lastUpdated; }
        set { SetProperty(ref _lastUpdated, value); }
    }

    public DelegateCommand UpdateCommand { get; set; }

    public IrritantViewModel()
    {
        Entity = new Irritant();

        //Version1
        //UpdateCommand = new DelegateCommand(EditCommand, CanExecute).ObservesProperty(() => Name);

        //Version2
        UpdateCommand = new DelegateCommand(EditCommand, CanExecute).ObservesProperty(() => Entity.Name);
    }

    private bool CanExecute()
    {
        //Version1
        //switch (Name)
        //{
        //    case null:
        //        return false;
        //    case "":
        //        return false;
        //}


        //Version2
        switch (Entity.Name)
        {
            case null:
                return false;
            case "":
                return false;
        }

        return true;
    }

    private void EditCommand()
    {
        LastUpdated = DateTime.UtcNow;
    }
}

And this is my View

public partial class IrritantView : UserControl
{
    public IrritantView()
    {
        InitializeComponent();
        DataContext = new IrritantViewModel();
    }
}

<Grid >
    <ScrollViewer>
        <StackPanel MinWidth="200">

        <TextBlock Text="Irritant"  />



        <!--Version 1-->
                <!--<TextBlock Text="Name" />
                <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
                <TextBlock Text="Description" />
                <TextBox Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}" />
                -->


        <!--Version 2-->

            <TextBlock Text="Name" />
            <TextBox Text="{Binding Entity.Name, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="Description" />
            <TextBox Text="{Binding Entity.Description, UpdateSourceTrigger=PropertyChanged}" />



            <TextBlock Text="Last Updated" />
            <Label Content="{Binding LastUpdated, UpdateSourceTrigger=PropertyChanged}" />
            <Button Content="Save" 
                    Command="{Binding UpdateCommand}"
                    />
        </StackPanel>
    </ScrollViewer>
</Grid>

The Version1 works fine, the Save Button disables when the TextBox that is bound to Name (TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}") is null or empty.

But with version 2, the Save Button doesn't disable. It only calls the method CanExecute during initialization, removing the text in the TextBox doesn't disable the Button. What did I do wrong?

回答1:

DelegateCommand doesn't raise CanExecuteChanged event automatically, you have to raise that event manually by calling RaiseCanExecuteChanged when appropriate. Apart from using DelegateCommand, you can use RelayCommand which relays on CommandManager.RequerySuggested event which do the similar thing for you.

  1. Change your Command definition returning ICommand:

    public ICommand UpdateCommand { get; set; }
    
  2. Initialize the command using below:

    UpdateCommand = new AutoCanExecuteCommand(new DelegateCommand(EditCommand, CanExecute));
    
  3. Use the following class as a wrapper:

    public class AutoCanExecuteCommand : ICommand
    {
        public ICommand WrappedCommand { get; private set; }
    
        public AutoCanExecuteCommand(ICommand wrappedCommand)
        {
            if (wrappedCommand == null) 
            {
                throw new ArgumentNullException("wrappedCommand");
            }
    
            WrappedCommand = wrappedCommand;
        }
    
        public void Execute(object parameter)
        {
            WrappedCommand.Execute(parameter);
        }
    
        public bool CanExecute(object parameter)
        {
            return WrappedCommand.CanExecute(parameter);
        }
    
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }
    


回答2:

I wouldn't recommend hooking into the CommandManager for a number of reasons. Besides memory leaks, it can introduce performance problems in your apps since you have no control over when, or how many times, the CommandManager will invoke the CanExecute (which happens on the UI thread). Instead, I would recommend using the INPC of your model object instead as shown in this answer:

ObservesProperty method isn't observing model's properties at Prism 6