简体   繁体   English

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

[英]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. 相反,你可以用你的FamilyFamilyCollection性能和摆脱的MemberNameMemberLocation性质,除非他们是一些具体的事情。 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. 关于视图模型的主要要点是要么使用包装的属性, 要么直接使用数据类型,但是似乎两者都有(除非MemberNameMemberLocation与模型无关)。

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中搜索MemberLocationMemberName ,我可以看到您的问题。 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类型的FamilyModelFamilyModel类没有任何MemberLocationMemberName中它的性能。 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. 就个人而言,我认为您应该摆脱MemberLocationMemberName属性……它们只是令人困惑的事情。 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. ...,如果尚未解决,它将在TextBoxListView中显示所选项目的NameLocation值。 But of course, it's up to you. 但是,当然,这取决于您。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM