我正在尝试创建一个纯MVVM示例应用程序。 我的问题是,如果我将Model属性绑定到UI上的ListView项,它运行良好,但是当我尝试绑定Model属性[在ViewModel中创建]的包装时,它不起作用。
在我的示例应用程序中,如果我使用Name&在FamilyView.xaml \ ListView控件中的位置[在Model中公开的属性]属性,它显示项目但是如果我使用MemberName和MemberLocation [在ViewModel中公开的属性]它不会更新列表。
我对MVVM中各层之间关系的理解是,ViewModel将View&模型。如果是这样,那么我们应该使用ViewModel属性绑定到View而不是Model属性。请建议如何通过将其绑定到ViewModel属性来更新我的列表。
我的代码如下:
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
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
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();
}
}
}
其他帮手类:
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
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
}
}
答案 0 :(得分:0)
您谈论视图模型包装器属性,但您没有包装任何内容。您的视图模型只是将模型中的属性加倍。你有两个选择;或者直接在模型类上实现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"); }
}
更新&gt;&gt;&gt;
抱歉,我以为你可以独自完成剩下的工作。这里FamilyModel
的实例是私有字段。这是在UI中绑定的 never 数据。而是绑定'wrapped'属性:
<TextBox Text="{Binding Name}" />
<TextBox Text="{Binding Location}" />
您可以在构造函数中设置私有data
字段,也可以在特定Button.Click
等上设置:
data = GetDataFromDatabaseOrWherever();
但是,由于您已直接在模型类上实现了INotifyPropertyChanged
接口,因此您不需要 来执行此操作...我只是建议这样做,因为您提到了'包装器属性'在你的问题标题中。
相反,您可以使用Family
和FamilyCollection
属性并删除MemberName
和MemberLocation
属性,除非它们是针对某些特定内容的。使用Family
属性,您可以像这样绑定到UI:
<TextBox Text="{Binding Family.Name}" />
<TextBox Text="{Binding Family.Location}" />
我对你的视图模型的主要观点是你 使用包装属性,或你直接使用你的数据类型,但你似乎两者都有(除非MemberName
和MemberLocation
与您的模型无关。
如果您仍然遇到问题,请确保已将视图的DataContext
属性正确设置为视图模型的实例。
更新2&gt;&gt;&gt;
好的,我加载了你的代码,发现了你的问题。
为了将来参考,如果您只是查看Visual Studio中Output Window
中出现的错误,您可以非常轻松地修复自己的问题......它确实是WPF开发人员最好的朋友。那里有两个错误:
BindingExpression路径错误:'object'''FamilyModel'上找不到'MemberLocation'属性 BindingExpression路径错误:'object'''FamilyModel'上找不到'MemberName'属性
在您的XAML中搜索MemberLocation
和MemberName
,我可以看到您的问题。您正尝试从FamilyCollection
属性中的项目绑定到这些属性...但这是ObservableCollection
类型FamilyModel
而FamilyModel
类不其中包含任何MemberLocation
或MemberName
个属性。这是你的错误。要解决此问题,只需将GridView
更改为此:
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="120"/>
<GridViewColumn Header="Location" DisplayMemberBinding="{Binding Location}" Width="120"/>
</GridView>
就我个人而言,我认为你应该摆脱你的MemberLocation
和MemberName
属性......它们只会令人困惑。相反,您应该使用这样的Family
属性:
<TextBox Text="{Binding Family.Name, FallbackValue=BindingFailed}" ... />
<TextBox Text="{Binding Family.Location, FallbackValue=BindingFailed}" ... />
然后你可以像这样简化你的添加方法:
public void AddFamilyDetails()
{
FamilyCollection.Add(Family);
Family = new FamilyModel();
}
这也可以让你这样做:
<ListView ItemsSource="{Binding FamilyCollection}" SelectedItem="{Binding Family}" ... />
...如果您尚未解决问题,则会在Name
的{{1}}中显示所选项目的Location
和ListView
值ES。但当然,这取决于你。