C# WPF-为什么SetProperty()在XAML中设置{Binding BindableObject.Text}时不触发CanExecute?
我使用以下新的BindableViewModel类(我也在本主题底部发布了一个旧类)使对象可观察(来自MS示例): 然后,在我的页面视图模型中,我使用TextBoxModel定义字段:C# WPF-为什么SetProperty()在XAML中设置{Binding BindableObject.Text}时不触发CanExecute?,c#,wpf,xaml,mvvm,command,C#,Wpf,Xaml,Mvvm,Command,我使用以下新的BindableViewModel类(我也在本主题底部发布了一个旧类)使对象可观察(来自MS示例): 然后,在我的页面视图模型中,我使用TextBoxModel定义字段: public class Page1ViewModel : BindableBase { private TextBoxModel _firstName = null; public TextBoxModel FirstName { get { return _firstName; } set {
public class Page1ViewModel : BindableBase
{
private TextBoxModel _firstName = null;
public TextBoxModel FirstName { get { return _firstName; } set {
if (SetProperty(ref _firstName, value))
{
SubmitCommand.RaiseCanExecuteChanged();
} } }
private TextBoxModel _surname = null;
public TextBoxModel Surname { get { return _surname; } set {
if (SetProperty(ref _surname, value))
{
SubmitCommand.RaiseCanExecuteChanged();
} } }
private DelegateCommand _submitCommand = null;
public DelegateCommand SubmitCommand { get { return _submitCommand??(_submitCommand=new DelegateCommand(SubmitExecute, SubmitCanExecute)); } }
private void SubmitExecute()
{
MessageBox.Show($"{FirstName.Text} {Surname.Text}");
}
private bool SubmitCanExecute()
{
if(string.IsNullOrEmpty(FirstName.Text))
return false;
else if(string.IsNullOrEmpty(Surname.Text))
return false;
else
return true;
}
}
在我的XAML视图中,我像往常一样设置文本框绑定:
<TextBox Text="{Binding FirstName.Text, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding FirstName.IsEnabled}"/>
<TextBox Text="{Binding Surname.Text, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding Surname.IsEnabled}"/>
<Button Content="Submit" Command{Binding SubmitCommand} />
当我运行此程序时,我发现文本更改不起作用。它没有在setter中触发if(SetProperty(ref _firstName,value))或if(SetProperty(ref _lasname,value))。如果我不在TextBoxModel中组合属性,那么一切都可以正常工作。如果我使用旧的ViewModelBase,TextBoxModel也可以正常工作
所以我想我在使用新的BindableBase类时一定错过了什么?期待您的帮助,谢谢
旧的ViewModelBase:
[Obsolete("Please use BindableBase。")]
public abstract class ObservableModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public static string GetPropertyName<T>(System.Linq.Expressions.Expression<Func<T>> e)
{
var member = (System.Linq.Expressions.MemberExpression)e.Body;
return member.Member.Name;
}
protected virtual void RaisePropertyChanged<T>
(System.Linq.Expressions.Expression<Func<T>> propertyExpression)
{
RaisePropertyChanged(GetPropertyName(propertyExpression));
}
protected void RaisePropertyChanged(String propertyName)
{
System.ComponentModel.PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
{
temp(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
[过时(“请使用BindableBase”)]
公共抽象类ObservableModel:INotifyPropertyChanged
{
公共事件属性更改事件处理程序属性更改;
公共静态字符串GetPropertyName(System.Linq.Expressions.Expression e)
{
var member=(System.Linq.Expressions.MemberExpression)e.Body;
返回member.member.Name;
}
受保护的虚拟空RaisePropertyChanged
(System.Linq.Expressions.Expression propertyExpression)
{
RaisePropertyChanged(GetPropertyName(propertyExpression));
}
受保护的void RaisePropertyChanged(字符串propertyName)
{
System.ComponentModel.PropertyChangedEventHandler温度=PropertyChanged;
如果(温度!=null)
{
temp(这是新的System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
您的绑定将永远不会设置FirstName
或LastName
,并且您显示的其他内容也不会设置。它们都将是null
。绑定到Text
只会设置该值,不会创建新的TextBoxModel
并设置该值
如果您希望以这种方式组织工作,您需要执行以下操作:
public class Page1ViewModel : BindableBase
{
private readonly TextBoxModel _firstName = new TextBoxModel();
public Page1ViewModel()
{
_firstName.PropertyChanged +=
(sender, args) => SubmitCommand.RaiseCanExecuteChanged();
}
public TextBoxModel FirstName
{
get { return _firstName; }
}
}
public class PersonViewModel : PropertyChangedBase
{
private PersonModel _Person;
public string FirstName
{
get { return _Person.FirstName; }
set
{
_Person.FirstName = value;
NotifyOfPropertyChange();
}
}
public string LastName
{
get { return _Person.LastName; }
set
{
_Person.LastName = value;
NotifyOfPropertyChange();
}
}
//TODO: Your command goes here
public PersonViewModel()
{
//TODO: Get your model from somewhere.
_Person = new PersonModel();
}
}
您的绑定将永远不会设置
FirstName
或LastName
,您显示的其他内容也不会设置。它们都将是null
。绑定到Text
只会设置该值,不会创建新的TextBoxModel
并设置该值
如果您希望以这种方式组织工作,您需要执行以下操作:
public class Page1ViewModel : BindableBase
{
private readonly TextBoxModel _firstName = new TextBoxModel();
public Page1ViewModel()
{
_firstName.PropertyChanged +=
(sender, args) => SubmitCommand.RaiseCanExecuteChanged();
}
public TextBoxModel FirstName
{
get { return _firstName; }
}
}
public class PersonViewModel : PropertyChangedBase
{
private PersonModel _Person;
public string FirstName
{
get { return _Person.FirstName; }
set
{
_Person.FirstName = value;
NotifyOfPropertyChange();
}
}
public string LastName
{
get { return _Person.LastName; }
set
{
_Person.LastName = value;
NotifyOfPropertyChange();
}
}
//TODO: Your command goes here
public PersonViewModel()
{
//TODO: Get your model from somewhere.
_Person = new PersonModel();
}
}
你想做什么是有道理的,但你如何做却不必要地复杂。你有一个
TextBoxModel
,它试图模仿一个真正的TextBox
,你根本不需要这样做,我认为你的方法是完全错误的
首先,模型表示的是数据,而不是UI。与其创建表示文本框的TextBoxModel
,不如创建表示数据结构的模型,例如PersonModel
或UserModel
。以下是我的意思的一个例子:
public class PersonModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
注意:您可以将INotifyPropertyChanged
内容放入模型中,但这并不是真正的UI问题,它只是数据。属性更改实现的最佳位置是视图模型,我将在下面进一步解释
好的,现在数据层已排序,您只需通过视图模型将该模型暴露给视图即可。首先,这里是一个简单的属性更改基础,我将在本例中使用它:
public abstract class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyOfPropertyChange([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
如下所示公开模型属性:
public class Page1ViewModel : BindableBase
{
private readonly TextBoxModel _firstName = new TextBoxModel();
public Page1ViewModel()
{
_firstName.PropertyChanged +=
(sender, args) => SubmitCommand.RaiseCanExecuteChanged();
}
public TextBoxModel FirstName
{
get { return _firstName; }
}
}
public class PersonViewModel : PropertyChangedBase
{
private PersonModel _Person;
public string FirstName
{
get { return _Person.FirstName; }
set
{
_Person.FirstName = value;
NotifyOfPropertyChange();
}
}
public string LastName
{
get { return _Person.LastName; }
set
{
_Person.LastName = value;
NotifyOfPropertyChange();
}
}
//TODO: Your command goes here
public PersonViewModel()
{
//TODO: Get your model from somewhere.
_Person = new PersonModel();
}
}
现在,您只需将文本框
绑定到FirstName
或LastName
查看模型属性:
<TextBox Text="{Binding FirstName}" ... />
其实就这么简单。您不需要在数据层中重新创建文本框
,事实上,所有UI关注点都应该与模型层完全分离
使用此方法,您也不必担心引发CanExecuteChanged
方法,因为它已经由INotifyPropertyChanged
处理
请记住,用户界面就是用户界面,数据就是数据您试图做的事情是有意义的,但您如何做却毫无必要地复杂。你有一个TextBoxModel
,它试图模仿一个真正的TextBox
,你根本不需要这样做,我认为你的方法是完全错误的
首先,模型表示的是数据,而不是UI。与其创建表示文本框的TextBoxModel
,不如创建表示数据结构的模型,例如PersonModel
或UserModel
。以下是我的意思的一个例子:
public class PersonModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
注意:您可以将INotifyPropertyChanged
内容放入模型中,但这并不是真正的UI问题,它只是数据。属性更改实现的最佳位置是视图模型,我将在下面进一步解释
好的,现在数据层已排序,您只需通过视图模型将该模型暴露给视图即可。首先,这里是一个简单的属性更改基础,我将在本例中使用它:
public abstract class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyOfPropertyChange([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
如下所示公开模型属性:
public class Page1ViewModel : BindableBase
{
private readonly TextBoxModel _firstName = new TextBoxModel();
public Page1ViewModel()
{
_firstName.PropertyChanged +=
(sender, args) => SubmitCommand.RaiseCanExecuteChanged();
}
public TextBoxModel FirstName
{
get { return _firstName; }
}
}
public class PersonViewModel : PropertyChangedBase
{
private PersonModel _Person;
public string FirstName
{
get { return _Person.FirstName; }
set
{
_Person.FirstName = value;
NotifyOfPropertyChange();
}
}
public string LastName
{
get { return _Person.LastName; }
set
{
_Person.LastName = value;
NotifyOfPropertyChange();
}
}
//TODO: Your command goes here
public PersonViewModel()
{
//TODO: Get your model from somewhere.
_Person = new PersonModel();
}
}
现在,您只需将文本框
绑定到FirstName
或LastName
查看模型属性:
<TextBox Text="{Binding FirstName}" ... />
其实就这么简单。您不需要在数据层中重新创建文本框
,事实上,所有UI关注点都应该与模型层完全分离
使用此方法,您也不必担心引发CanExecuteChanged
方法,因为它已经由INotifyPropertyChanged
处理
记住,用户界面就是用户界面,数据就是数据我不明白这是怎么回事。当文本发生更改时(或者实际上根本没有更改!),您没有设置FirstName
或姓氏,因此不会调用SetProperty
。你