MVVM:View上的listview控件由Model属性更新,但不是由ViewModel中的包装器属性更新

时间:2013-09-17 14:38:17

标签: c# wpf mvvm

我正在尝试创建一个纯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
    }
}

1 个答案:

答案 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接口,因此您不需要 来执行此操作...我只是建议这样做,因为您提到了'包装器属性'在你的问题标题中。

相反,您可以使用FamilyFamilyCollection属性并删除MemberNameMemberLocation属性,除非它们是针对某些特定内容的。使用Family属性,您可以像这样绑定到UI:

<TextBox Text="{Binding Family.Name}" />
<TextBox Text="{Binding Family.Location}" />

我对你的视图模型的主要观点是你 使用包装属性,你直接使用你的数据类型,但你似乎两者都有(除非MemberNameMemberLocation与您的模型无关。

如果您仍然遇到问题,请确保已将视图的DataContext属性正确设置为视图模型的实例。

更新2&gt;&gt;&gt;

好的,我加载了你的代码,发现了你的问题。

为了将来参考,如果您只是查看Visual Studio中Output Window中出现的错误,您可以非常轻松地修复自己的问题......它确实是WPF开发人员最好的朋友。那里有两个错误:

  

BindingExpression路径错误:'object'''FamilyModel'上找不到'MemberLocation'属性   BindingExpression路径错误:'object'''FamilyModel'上找不到'MemberName'属性

在您的XAML中搜索MemberLocationMemberName,我可以看到您的问题。您正尝试从FamilyCollection属性中的项目绑定到这些属性...但这是ObservableCollection类型FamilyModelFamilyModel类不其中包含任何MemberLocationMemberName个属性。这是你的错误。要解决此问题,只需将GridView更改为此:

即可
<GridView>
    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="120"/>
    <GridViewColumn Header="Location" DisplayMemberBinding="{Binding Location}" Width="120"/>
</GridView>

就我个人而言,我认为你应该摆脱你的MemberLocationMemberName属性......它们只会令人困惑。相反,您应该使用这样的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}}中显示所选项目的LocationListView值ES。但当然,这取决于你。