[英]MVVM: listview control on View is updated by Model properties but not by their wrapper properties in the ViewModel
I am trying to create a sample pure MVVM application. 我正在尝试创建一个示例纯MVVM应用程序。 My problem is that if I bind Model property to ListView item on UI, it works well but when I try to bind wrapper of Model property [created in ViewModel] it does not work. 我的问题是,如果我将Model属性绑定到UI上的ListView项,则可以很好地工作,但是当我尝试绑定Model属性的包装器(在ViewModel中创建)时,它将不起作用。
In my sample application, If I use Name & Location [Properties exposed in Model] properties in FamilyView.xaml\\ListView control, it displays the items but if I use MemberName and MemberLocation [Properties exposed in ViewModel] it does not update the list. 在我的示例应用程序中,如果我使用FamilyView.xaml \\ ListView控件中的“名称和位置[在模型中公开的属性]”属性,它将显示项目,但如果我使用MemberName和MemberLocation [在ViewModel中公开的属性],则不会更新列表。
What I understand about relation among layers in MVVM is that ViewModel separates View & Model. 我对MVVM中各层之间的关系了解到的是ViewModel将View&Model分开。 If so then we should use ViewModel properties to bind to View not the Model properties. 如果是这样,那么我们应该使用ViewModel属性而不是Model属性绑定到View。 Please suggest how to update my list by binding it to ViewModel properties. 请建议如何通过将其绑定到ViewModel属性来更新我的列表。
My codes are as below: 我的代码如下:
FamilyView.xaml FamilyView.xaml
<Window x:Class="MVVM_15thSep13.View.FamilyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVM_15thSep13.ViewModel"
Title="FamilyView" Height="283" Width="367">
<Window.DataContext>
<local:FamilyViewModel/>
</Window.DataContext>
<Grid>
<TextBox Text="{Binding Family.Name, FallbackValue=BindingFailed}" Height="23" HorizontalAlignment="Left" Margin="12,16,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
<TextBox Text="{Binding Family.Location, FallbackValue=BindingFailed}" Height="23" HorizontalAlignment="Left" Margin="12,57,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" />
<Button Command="{Binding AddDetailsCommand}" Content="Button" Height="23" HorizontalAlignment="Left" Margin="240,31,0,0" Name="button1" VerticalAlignment="Top" Width="93" />
<ListView ItemsSource="{Binding FamilyCollection}" SelectedItem="{Binding Family}" Height="126" HorizontalAlignment="Left" Margin="14,110,0,0" Name="listView1" VerticalAlignment="Top" Width="319" UseLayoutRounding="True">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="120"/>
<GridViewColumn Header="Location" DisplayMemberBinding="{Binding Location}" Width="120"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
FamilyModel.cs FamilyModel.cs
namespace MVVM_15thSep13.Model
{
public class FamilyModel:ObservableObject
{
private string m_Name;
private string m_Location;
public string Name
{
get { return m_Name; }
set
{
m_Name = value;
if (m_Name != value)
OnPropertyChanged("Name");
}
}
public string Location
{
get { return m_Location; }
set
{
m_Location = value;
if (m_Location != value)
OnPropertyChanged("Location");
}
}
public FamilyModel()
{
m_Name = "Default Name";
m_Location = "Default Location";
}
public FamilyModel(string name, string location)
{
m_Name = name;
m_Location = location;
}
}
}
FamilyViewModel.cs FamilyViewModel.cs
namespace MVVM_15thSep13.ViewModel
{
public class FamilyViewModel:ObservableObject
{
private FamilyModel m_Family;
private ObservableCollection<FamilyModel> m_FamilyCollection;
private ICommand m_AddDetailsCommand;
public FamilyViewModel()
{
m_Family = new FamilyModel();
m_FamilyCollection = new ObservableCollection<FamilyModel>();
}
public FamilyModel Family
{
get { return m_Family; }
set
{
if (m_Family != value)
{
m_Family = value;
OnPropertyChanged("Family");
}
}
}
public ObservableCollection<FamilyModel> FamilyCollection
{
get { return m_FamilyCollection; }
set { m_FamilyCollection = value; }
}
public ICommand AddDetailsCommand
{
get
{
if (m_AddDetailsCommand == null)
m_AddDetailsCommand = new RelayCommand(param => AddFamilyDetails(), null);
return m_AddDetailsCommand;
}
}
public void AddFamilyDetails()
{
FamilyCollection.Add(Family);
Family = new FamilyModel();
}
}
}
Other Helper Classes: 其他助手类:
RelayCommand.cs RelayCommand.cs
namespace MVVM_15thSep13.HelperClasses
{
public class RelayCommand:ICommand
{
private readonly Action<object> m_Execute;
private readonly Predicate<object> m_CanExecute;
public RelayCommand(Action<object> exec) : this(exec, null) { }
public RelayCommand(Action<object> exec, Predicate<object> canExec)
{
if (exec == null)
throw new ArgumentNullException("exec");
m_Execute = exec;
m_CanExecute = canExec;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (parameter == null)
return true;
else
return m_CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (m_CanExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (m_CanExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
m_Execute(parameter);
}
#endregion
}
}
ObservableObject.cs ObservableObject.cs
namespace MVVM_15thSep13.HelperClasses
{
public abstract class ObservableObject:INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
#endregion
}
}
You speak about view model wrapper properties, but you're not wrapping anything. 您谈论的是视图模型包装器属性,但没有包装任何东西。 Your view model is just doubling up the properties from the model. 您的视图模型只是将模型的属性加倍。 You have two choices; 您有两种选择; either implement the INotifyPropertyChanged
interface directly on your model classes and use instances of them in your view model (like you are now), or actually 'wrap' your model's properties in the view model: 您可以直接在模型类上实现INotifyPropertyChanged
接口,然后在视图模型中使用它们的实例(就像现在一样),或者在视图模型中实际“包装”模型的属性:
private FamilyModel data;
public string Name
{
get { return data.Name; }
set { data.Name = value; OnPropertyChanged("Name"); }
}
public string Location
{
get { return data.Location; }
set { data.Location = value; OnPropertyChanged("Location"); }
}
UPDATE >>> 更新>>>
Sorry, I assumed that you could work the rest out by yourself. 抱歉,我认为您可以自己解决其余的问题。 The instance of FamilyModel
here is a private field. 这里的FamilyModel
实例是一个私有字段。 This is never data bound in the UI. 这绝不是UI中的数据绑定。 Instead, you bind the 'wrapped' properties: 而是绑定“包装”属性:
<TextBox Text="{Binding Name}" />
<TextBox Text="{Binding Location}" />
You would set the private data
field in a constructor, or upon a particular Button.Click
, etc.: 您可以在构造函数中或在特定的Button.Click
等上设置私有data
字段:
data = GetDataFromDatabaseOrWherever();
However, as you have implemented the INotifyPropertyChanged
interface directly on your model classes, you don't need to do this... I just suggested this because you mentioned 'wrapper properties' in your question title. 但是,由于您直接在模型类上实现了INotifyPropertyChanged
接口,因此您不需要这样做...我只是建议这样做,因为您在问题标题中提到了“包装器属性”。
Instead, you can just use your Family
and FamilyCollection
properties and get rid of the MemberName
and MemberLocation
properties unless they're for something specific. 相反,你可以用你的Family
和FamilyCollection
性能和摆脱的MemberName
和MemberLocation
性质,除非他们是一些具体的事情。 Using the Family
property, you can bind to the UI like this: 使用Family
属性,您可以像这样绑定到UI:
<TextBox Text="{Binding Family.Name}" />
<TextBox Text="{Binding Family.Location}" />
My main point about your view model was that you either use wrapped properties, or you use your data types directly, but you seem to have both (unless MemberName
and MemberLocation
have nothing to do with your model. 关于视图模型的主要要点是要么使用包装的属性, 要么直接使用数据类型,但是似乎两者都有(除非MemberName
和MemberLocation
与模型无关)。
If you are still having problems, then make sure that you've set the DataContext
property of your view to an instance of your view model correctly. 如果仍然有问题,请确保已将视图的DataContext
属性正确设置为视图模型的实例。
UPDATE 2 >>> 更新2 >>>
Ok, I loaded up your code and found your problem. 好的,我加载了您的代码并找到了问题。
For future reference, you can fix your own problems like this quite easily if you just look at the errors that you get in the Output Window
in Visual Studio...it really is a WPF developer's best friend. 供以后参考,如果您仅查看在Visual Studio的“ Output Window
中遇到的错误,就可以很轻松地解决此类问题,它确实是WPF开发人员的最好朋友。 There were two errors there: 那里有两个错误:
BindingExpression path error: 'MemberLocation' property not found on 'object' ''FamilyModel' BindingExpression path error: 'MemberName' property not found on 'object' ''FamilyModel' BindingExpression路径错误:在“对象”“ FamilyModel”上找不到“ MemberLocation”属性BindingExpression路径错误:在“对象”“ FamilyModel”上找不到“ MemberName”属性
Searching your XAML for MemberLocation
and MemberName
, I could see your problem. 在XAML中搜索MemberLocation
和MemberName
,我可以看到您的问题。 You're trying to bind to these properties from the items in your FamilyCollection
property... but this is an ObservableCollection
of type FamilyModel
and the FamilyModel
class doesn't have any MemberLocation
or MemberName
properties in it. 你试图绑定到从您的项目这些属性FamilyCollection
财产......但这是一个ObservableCollection
类型的FamilyModel
和FamilyModel
类没有任何MemberLocation
或MemberName
中它的性能。 There's your error. 有你的错误。 To fix it, simply change your GridView
to this: 要修复它,只需将GridView
更改为此:
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="120"/>
<GridViewColumn Header="Location" DisplayMemberBinding="{Binding Location}" Width="120"/>
</GridView>
Personally, I think that you should just get rid of your MemberLocation
and MemberName
properties... they are just confusing matters. 就个人而言,我认为您应该摆脱MemberLocation
和MemberName
属性……它们只是令人困惑的事情。 Instead, you should just use your Family
property like this: 相反,您应该只使用Family
属性,如下所示:
<TextBox Text="{Binding Family.Name, FallbackValue=BindingFailed}" ... />
<TextBox Text="{Binding Family.Location, FallbackValue=BindingFailed}" ... />
Then you could simplify your add method like this: 然后,您可以像这样简化add方法:
public void AddFamilyDetails()
{
FamilyCollection.Add(Family);
Family = new FamilyModel();
}
That would also enable you to do this: 这也将使您能够执行此操作:
<ListView ItemsSource="{Binding FamilyCollection}" SelectedItem="{Binding Family}" ... />
... which, if you haven't worked it out, will show the Name
and Location
values of the selected item in the ListView
in the TextBox
es. ...,如果尚未解决,它将在TextBox
的ListView
中显示所选项目的Name
和Location
值。 But of course, it's up to you. 但是,当然,这取决于您。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.