【问题标题】:Working with DelegateCommand's CanExecute action使用 DelegateCommand 的 CanExecute 操作
【发布时间】:2012-03-28 01:46:05
【问题描述】:

我在 Prism / WPF 项目中有一个这样的 ViewModel 类。

public class ContentViewModel : ViewModelBase, IContentViewModel
{
    public ContentViewModel(IPersonService personService)
    {
        Person = personService.GetPerson();
        SaveCommand = new DelegateCommand(Save, CanSave);
    }

    public Person Person { get; set; }

    public DelegateCommand SaveCommand { get; set; }

    private void Save()
    {
        // Save actions here...
    }

    private bool CanSave()
    {
        return Person.Error == null;
    }
}

上述ViewModel中使用的person类型定义如下:

public class Person : INotifyPropertyChanged, IDataErrorInfo
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged("FirstName");
        }
    }

    // other properties are implemented in the same way as above...

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private string _error;
    public string Error
    {
        get
        {
            return _error;
        }
    }

    public string this[string columnName]
    {
        get
        {
            _error = null;
            switch (columnName)
            {
                // logic here to validate columns...
            }
            return _error;
        }
    }
}

ContentViewModel 的一个实例被设置为 View 的 DataContext。在视图中,我使用绑定到 Person 如下:

<TextBox Text="{Binding Person.FirstName, ValidatesOnDataErrors=True}" />
<Button Content="Save" Command="{Binding SaveCommand}" />

当我对绑定到人的属性(如 FirstName)的 TextBox 进行更改并单击保存时,我可以在 ViewModel 命令处理程序中看到更改。但是,如果这些属性中的任何一个在验证中失败,则永远不会执行 CanSave 并且永远不会禁用按钮。

如何在上述场景中禁用基于 DelegateCommand 的 CanExecute 操作处理程序的按钮?

【问题讨论】:

    标签: wpf mvvm prism delegatecommand


    【解决方案1】:

    在 ContentViewModel 的构造函数中添加这一行

    public ContentViewModel(IPersonService personService)
    {
        //GetPerson
        Person.PropertyChanged +=person_PropertyChanged;
    }
    

    并编写一个方法来处理您调用 CommandManager.InvalidateRequerySuggested() 或 SaveCommand.RaiseCanExecuteChanged() 的事件

    private void person_PropertyChanged(object sender, EventArgs args)
    {
       CommandManager.InvalidateRequerySuggested();
       //SaveCommand.RaiseCanExecuteChanged()
    }
    

    希望这可行。 :-)

    【讨论】:

    • 问题是当您更新 TextBox 绑定到 Person.FirstName 时,Person 永远不会被视为已更改,并且不会触发其 PropertyChanged 事件。
    • 在 FirstName 的设置器中,您正在引发一个 PropertyChanged 事件,其中 'FirstName' 作为参数对吗?因此,当 FirstName 更改时,就会引发此问题。在 person_PropertyChanged 中,您可以检查参数字符串是否为“FirstName”并相应地执行操作
    • 我在 person 类中有逻辑来验证它的列 - 请参阅 Person 类中实现 IDataErrorInfo 的索引器。在不复制 ViewModel 逻辑的情况下实现验证的最佳方法是什么?当我准备在 ViewModel 中实施某种验证时,您的建议有效 - 在这种情况下 IDataErrorInfo 是无用的。
    • 看看这是否解决了你的问题:arrangeactassert.com/…
    【解决方案2】:

    用所有可以改变错误的属性试试这个:

     public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged("FirstName");
    
            OnPropertyChanged("Error");
        }
    }
    

    或者

            switch (columnName)
            {
                // logic here to validate columns...
    
                OnPropertyChanged("Error");
            }
    

    您遇到的问题是错误更改时未调用 OnPropertyChanged。

    下一步是在创建人员的 propertychanged 事件时订阅它,并创建一个处理程序来检查 propertychanged,然后更改命令使用的布尔变量。

     public ContentViewModel(IPersonService personService)
    {
        Person = personService.GetPerson();
        Person.PropertyChanged+= PersonPropertyChangedHandler;
        SaveCommand = new DelegateCommand(Save, personHasError);
    }
    
    bool personHasError = false;
    void PersonPropertyChangedHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Error")
        {
            if(Person.Error == null)
               personHasError = true;
            else
               personHasError = false;
        } 
    }
    

    希望这可行。我是手工制作的,没有检查它,所以如果它有问题或者其他什么问题,请告诉我并纠正它

    【讨论】:

    • 您遇到的问题是错误更改时未调用 OnPropertyChanged。 +1
    【解决方案3】:

    简而言之 - 当您认为您的 CanExecute() 返回值可以更改时,您应该调用 yourDelegateCommand.RaiseCanExecuteChanged()

    在您的示例中,您应该通过INotifyPropertyChanged 接口通知您的Person.Error 属性已更改,订阅您的ContentViewModel 类中的Person.PropertyChanged 事件并在您的Person.Error 每次更改时调用SaveCommand.RaiseCanExecuteChanged() .请注意 - 在您的场景中,Person.Error 不会在 Person.FirstName 更改时自动重新计算 - 您应该手动执行此操作。

    更新:

    public class ContentViewModel : ViewModelBase, IContentViewModel
    {
        public ContentViewModel(IPersonService personService)
        {
            Person = personService.GetPerson();
            Person.PropertyChanged += Person_PropertyChanged;
            SaveCommand = new DelegateCommand(Save, CanSave);
        }
    
        private void PersonPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
        {
            SaveCommand.RaiseCanExecuteChanged();
        }
    
        private void Save()
        {
            // Save actions here...
        }
    
        private bool CanSave()
        {
            return IsErrorPresented(Person);
        }
    
        private bool IsErrorPresented(object o)
        {
            if (!(o is IDataErrorInfo))
                return false;
    
            var propNames = o.GetType()
                .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Select(p => p.Name);
    
            var o2 = (o as IDataErrorInfo);
    
            var errors = propNames.Select(p => o2[p])
                .Where(p => !String.IsNullOrEmpty(p))
                .ToList();
    
            ValidationSummary.ErrorMessages = errors;
    
            return errors.Count > 0;
        }
    }
    
    <TextBox Text="{Binding Person.FirstName, 
                            UpdateSourceTrigger=PropertyChanged, 
                            ValidatesOnDataErrors=True, 
                            ValidatesOnExceptions=True, 
                            NotifyOnValidationError=True}" />
    <Button Content="Save" Command="{Binding SaveCommand}" />
    

    如果您还将PropertyChanged 指定为UpdateSourceTrigger,您的保存按钮将在您输入时更新..

    【讨论】:

    • 你的意思是这样的私人 Person _person;公共人员人员 { 得到 { 返回 _person; } 设置 { _person = 价值; if (_person.Error != null) SaveCommand.RaiseCanExecuteChanged(); OnPropertyChanged("人");当孩子发生变化时,我在 People 中没有收到任何通知。我认为这就是问题所在。
    猜你喜欢
    • 2019-02-15
    • 2011-11-13
    • 1970-01-01
    • 2017-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-20
    • 2011-01-19
    相关资源
    最近更新 更多