WPF 如何更新 CanExecute

Jun*_*222 8 c# wpf xaml mvvm

我有以下问题。

我有以下简单的 xaml:

<TextBox Name="NameBox" Text ="{Binding Name}" />
<Button Content="Save" Command="{Binding SaveCommand}" CommandParameter="{Binding Entity}"  />
Run Code Online (Sandbox Code Playgroud)

我将此窗口的 DataContext 绑定到以下视图模型

public class MyViewModel
{
    public SimpleModel Entity { get; set; }

    private ICommand _saveCommand;

    public ICommand SaveCommand { get { return _saveCommand ?? (_saveCommand = new MyCommand(OnSaveItem, parameter => CanSaveItem())); } }

    public void OnSaveItem(object parameter) 
    {
        // some code
    }

    public virtual bool CanSaveItem()
    {
        return !String.IsNullOrWhiteSpace(Entity.Name);
    }

}      
Run Code Online (Sandbox Code Playgroud)

简单模型是

public class SimpleModel
{        
    public int Id { get; set; }

    public string Name { get; set; }       
}
Run Code Online (Sandbox Code Playgroud)

这段代码大部分是正确的,但我无法使 CanSaveItem 方法正常工作。我不知道如何告诉 SaveCommand ViewModel 的属性已更改。我知道我必须使用 CanExecuteChanged 或 CommandManager.InvalidateRequerySuggested 并且我尝试使用它们一些时间但我不知道如何正确使用它并且它没有生效。你能帮我解决这个问题吗?

更新。

public class MyCommand : ICommand
{        
    public MyCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _canExecute = canExecute;
        _execute = execute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {                       
        _execute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    private readonly Predicate<object> _canExecute;

    private readonly Action<object> _execute;
}
Run Code Online (Sandbox Code Playgroud)

DRa*_*app 9

看起来您处于早期学习曲线上,这可能会令人困惑……有时对我来说仍然如此。

无论如何,我对您拥有的内容进行了一些细微的更改,并解释了我对他们所做的事情。

public class MyViewModel
{
    public SimpleModel Entity { get; set; }

    private MyCommand _saveCommand;

    public MyCommand SaveCommand { get { return _saveCommand ?? (_saveCommand = new MyCommand(OnSaveItem, parameter => CanSaveItem())); } }

    public MyViewModel()
    {
        //------ You need to create an instance of your entity to bind to
        Entity = new SimpleModel();

        //-- I added an event handler as your "Entity" object doesn't know 
        //-- about the button on the view model.  So when it has something
        //-- change, have it call anybody listening to its exposed event.
        Entity.SomethingChanged += MyMVVM_SomethingChanged;
    }

    void MyMVVM_SomethingChanged(object sender, EventArgs e)
    {
        // Tell our mvvm command object to re-check its CanExecute
        SaveCommand.RaiseCanExecuteChanged();
    }

    public void OnSaveItem(object parameter)
    {
        // some code
    }

    public virtual bool CanSaveItem()
    {
        //-- Checking directly to your Entity object
        return !String.IsNullOrWhiteSpace(Entity.Name);
    }
}


public class SimpleModel
{
    //-- Simple constructor to default some values so when you run
    //-- your form, you SHOULD see the values immediately to KNOW
    //-- the bindings are correctly talking to this entity. 
    public SimpleModel()
    {
        _name = "test1";
        _Id = 123;
    }

    //-- changed to public and private... and notice in the setter
    //-- to call this class's "somethingChanged" method
    private int _Id;
    public int Id
    {
        get { return _Id; }
        set
        {
            _Id = value;
            somethingChanged("Id");
        }
    }

    private string _name;
    public string Name 
    { get { return _name; }
        set { _name = value;
                somethingChanged( "Name" );
        }
    }

    //-- Expose publicly for anything else to listen to (i.e. your view model)
    public event EventHandler SomethingChanged;

    //-- So, when any property above changes, it calls this method with whatever
    //-- its property is just as a reference.  Then checks.  Is there anything
    //-- listening to our exposed event handler?  If so, pass the information on
    private void somethingChanged( string whatProperty)
    {
        // if something is listening
        if (SomethingChanged != null)
            SomethingChanged(whatProperty, null);
    }


}


public class MyCommand : ICommand
{
    public MyCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _canExecute = canExecute;
        _execute = execute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    private readonly Predicate<object> _canExecute;

    private readonly Action<object> _execute;

    //-- Change to the event handler definition, just expose it
    public event EventHandler CanExecuteChanged;

    //-- Now expose this method so your mvvm can call it and it rechecks 
    //-- it's own CanExecute reference
    public void RaiseCanExecuteChanged()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, new EventArgs());
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,表单中的绑定。我不知道您是如何将视图的“DataContext”设置为视图模型的,但假设一切正确且没有问题,请将文本框和命令按钮调整为类似

<TextBox Name="NameBox" Text ="{Binding Entity.Name, 
    NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}"  />

<Button Content="Save" Command="{Binding SaveCommand}" CommandParameter="{Binding Entity}"  />
Run Code Online (Sandbox Code Playgroud)

请注意,文本绑定是到 MVVM 上的“实体”对象,然后是实体对象的“.Name”属性。这里重要的是 UpdateSourceTrigger。这会强制为每次字符更改更新回您的数据绑定,因此一旦您删除最后一个字符或开始键入第一个字符,“保存”按钮将分别刷新。