WPF 如何更新 CanExecute

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25289753/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 12:21:03  来源:igfitidea点击:

WPF how to update CanExecute

c#wpfxamlmvvm

提问by Junior222

I have the following question.

我有以下问题。

I have the following simple xaml:

我有以下简单的 xaml:

<TextBox Name="NameBox" Text ="{Binding Name}" />
<Button Content="Save" Command="{Binding SaveCommand}" CommandParameter="{Binding Entity}"  />

And i bind DataContext of this Window to following View Model

我将此窗口的 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);
    }

}      

SimpleModel is

简单模型是

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

    public string Name { get; set; }       
}

This code works mostly correct but i can not make method CanSaveItem to work properly. I don't know how to tell to SaveCommand that properties of ViewModel was changed. I know that i have to use CanExecuteChanged or CommandManager.InvalidateRequerySuggested and i tried to use their some times but i don't know how to do it properly and it didn't take an effect. Could you help me with this problem?

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

UPD.

更新。

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;
}

采纳答案by DRapp

It appears you are on an early learning curve, and this can be confusing... and sometimes still is to me too.

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

Anyhow, I've made some slight changes to what you had and explain what I did to them.

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

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());
    }
}

Finally, the bindings in the form. I don't know how you have set the "DataContext" of your view to your view model, but assuming that is all correct and no issue, adjust the textbox and command button to something like

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

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

<Button Content="Save" Command="{Binding SaveCommand}" CommandParameter="{Binding Entity}"  />

Notice the text binding is to the "Entity" object on your MVVM and then the ".Name" property of your Entity object. The important thing here is the UpdateSourceTrigger. This forces an update back to your data binding for every character change, so as soon as you remove the last character, or start typing the first character, the "Save" button will then be refreshed respectively.

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