【问题标题】:WPF MVVM two-way updatesWPF MVVM 双向更新
【发布时间】:2012-11-23 13:38:47
【问题描述】:

我正在尝试使用this example 设置有效的双向更新。

这些是相关代码sn-ps:

XAML:

<Button Click="clkInit">Initialize</Button>
<Button Click="clkStudent">Add student</Button>
<Button Click="clkChangeStudent">Change students</Button>
(...)
<TabControl Name="tabControl1" ItemsSource="{Binding StudentViewModels}" >
   <TabControl.ItemTemplate>
      <DataTemplate>
         <TextBlock Text="{Binding Path=StudentFirstName}" />
      </DataTemplate>
   </TabControl.ItemTemplate>
   <TabControl.ContentTemplate>                
      <DataTemplate>
         <Grid>
            <Label Content="First Name" Name="label1" />
            <TextBox Name="textBoxFirstName" Text="{Binding Path=StudentFirstName}" />
            <Label Content="Last Name" Name="label2" />
            <TextBox Name="textBoxLastName" Text ="{Binding Path=StudentLastName}" />
         </Grid>                    
      </DataTemplate>
   </TabControl.ContentTemplate>
</TabControl>

主窗口:

public partial class MainWindow : Window
{
    internal MainWindowViewModel myMWVM;
    public MainWindow()
    {
       InitializeComponent();
    }

    private void clkInit(object sender, RoutedEventArgs e)
    {
       myMWVM= new MainWindowViewModel();
       DataContext = myMWVM;
    }
    private void clkStudent(object sender, RoutedEventArgs e)
    {
       myMWVM.StudentViewModels.Add(new StudentViewModel());
    }
    // For testing - call a function out of the student class to make changes there
    private void clkChangeStudent(object sender, RoutedEventArgs e)
    {
       for (Int32 i = 0; i < test.StudentViewModels.Count; i++)
       {
           myMWVM.StudentViewModels.ElementAt((int)i).changeStudent();
       }
    }
}

主视图:

class MainWindowViewModel : INotifyPropertyChanged
{
   ObservableCollection<StudentViewModel> _studentViewModels = 
        new ObservableCollection<StudentViewModel>();

   // Collection for WPF.
   public ObservableCollection<StudentViewModel> StudentViewModels
   {
      get { return _studentViewModels; }
   }

   // Constructor. Add two stude
   public MainWindowViewModel()
   {
      _studentViewModels.Add(new StudentViewModel());
      _studentViewModels.Add(new StudentViewModel());
   }

   // Property change.
   public event PropertyChangedEventHandler PropertyChanged;
   private void OnPropertyChanged(string propertyName)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}

学生观:

class StudentViewModel : INotifyPropertyChanged
{
   Lazy<Student> _model;

   string _studentFirstName;
   public string StudentFirstName
   {
      get { return _studentFirstName; }
      set
      {
         if (_studentFirstName != value)
         {
            _studentFirstName = value;
            _model.Value.StudentFirstName = value;
            OnPropertyChanged("StudentFirstName");
         }
      }
   }

   string _studentLastName;
   public string StudentLastName
   {
      get { return _studentLastName; }
      set
      {
         if (_studentLastName != value)
         {
            _studentLastName = value;
            _model.Value.StudentLastName = value;
            OnPropertyChanged("StudentLastName");
         }
      }
   }

   public void changeStudent()
   {
      _model.Value.changeStudent();
   }


   public StudentViewModel()
   {
      _studentFirstName = "Default";
      _model = new Lazy<Student>(() => new Student());
   }


   public event PropertyChangedEventHandler PropertyChanged;
   private void OnPropertyChanged(string propertyName)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}

学生:

class Student
{
   public string StudentFirstName { get; set; }
   public string StudentLastName { get; set; }

   public Student()
   {
      MessageBox.Show("Student constructor called");
   }
   public Student(string nm)
   {
      StudentLastName = nm;
   }
   public void changeStudent()
   {
      StudentLastName = "McDonald";
   }
}

如果你读到这里,我已经感谢你了 :) 不过,通过调用“clkChangeStudent”,我看不到文本框中的变化。我猜是因为我没有调用 StudentViewModel 的 set-method。我正在做的项目有点复杂,很多事情发生在班级(这里是学生)本身。

如何通过 Student-class 本身的设置值更新文本框?

【问题讨论】:

  • 您正在代码隐藏中将项目添加到集合中,而不是使用命令。这不是 MVVM。
  • 这只是为了模拟类本身而不是视图模型中正在发生的事情。这就是我在我的项目中所拥有的,“学生”保存了有关设备的信息,这些信息在没有任何用户交互的情况下发生变化,我的目标是创建一个绑定以在用户界面上“实时”显示这些信息。
  • @Robal 在这种情况下,您需要有一种方法来通知视图模型底层模型已更改。一种选择是在模型上实现 INotifyPropertyChanged 并以这种方式将其冒泡到视图
  • 我现在通过向学生本身添加通知并将其“冒泡”到视图来关注 Ucodia 的回答。这不是我所期待的解决方案,但它可能会奏效。

标签: c# wpf mvvm binding


【解决方案1】:

您的实际代码显然不会通知界面更改。原因很简单。您更改学生姓名的方法位于 Student 模型中,并且该模型未实现 INotifyPropertyChanged。

根据一个问题有两种解决方案来解决这个问题,changeStudent() 方法是否必须坚持对象模型,也就是说,您的要求是否允许您将changeStudent() 方法移动到视图型号?

如果是,那么第一个解决方案,只需从模型中删除 changeStudent 方法并将其移动到视图模型中,如下所示:

class StudentViewModel : INotifyPropertyChanged
{
    ...

    public void changeStudent()
    {
        this.StudentLastName = "McDonald";
    }
}

在另一种情况下,第二种解决方案,您必须在模型属性更改时引发事件,然后让您的视图模型接受这些更改。您可以在模型中这样进行:

class Student : INotifyPropertyChanged
{
    ...

    private string studentLastName;

    public string StudentLastName
    {
        get
        {
            return this.studentLastName;
        }

        set
        {
            if(this.studentLastname != value)
            {
                this.studentLastName = value;
                this.OnPropertyChanged("StudentLastName");
            }
        }
    }
}

对于视图模型:

class StudentViewModel : INotifyPropertyChanged
{
    ...

    public StudentViewModel(Student model)
    {
        this._model = model;

        this._model.PropertyChanged += (sender, e) =>
        {
            if(e.PropertyName == "StudentLastName")
            {
                this.OnPropertyChanged("StudentLastName");
            }
        };
    }
}

两种解决方案都可以。了解您的代码明确需要在值更改时通知界面,这真的很重要。

【讨论】:

  • 我现在正在尝试实现解决方案 2 的通知(正如我所用的那样),您能否简要说明“StudentViewModel”中的新构造函数将要做什么?我在这里遇到错误,因为 '_model' 是 'Lazy' 而 'model' 是 'Student'。
  • 是的,我没有意识到您的 Student 模型是延迟加载的。实际上,您应该将this._model = model; 替换为this._model = new Lazy&lt;Student&gt;(() =&gt; model);,并且它应该可以在不删除延迟加载的情况下工作。您可以在此处获取有关延迟加载对象的一些有用信息:msdn.microsoft.com/en-us/library/vstudio/dd642331.aspx
【解决方案2】:

ChangeStudent 不会调用在视图模型中触发属性通知事件的任何方法,而是更改底层模型。正是这些事件触发了视图进行自我更新。

顺便说一句,您还应该从视图中查看命令绑定,而不是在代码隐藏中使用点击处理程序。这样一来,您的视图就不需要了解附加的视图模型的任何信息,并且可以是纯粹的呈现。

【讨论】:

    【解决方案3】:

    首先你应该使用命令而不是事件。

    在您当前的结构中,您必须添加一个

    OnPropertyChanged("StudentLastName");
    

    在 StudentViewModel 中调用 ChangedStudent() 方法。 之后,您必须将 Bindings 的 UpdateSourceTrigger 设置为 PropertyChanged

    Text="{Binding Path=StudentFirstName, UpdateSourceTrigger=PropertyChanged}"
    

    【讨论】:

    • 您不需要明确将属性更改为触发器,这会自动发生。问题是这个触发器根本没有发生!
    • 不需要将 UpdateSourceTrigger 添加到绑定中,它应该像这样开箱即用:Text="{Binding StudentFirstName}"
    猜你喜欢
    • 2011-06-24
    • 1970-01-01
    • 2021-11-25
    • 2012-09-03
    • 2020-08-09
    • 2016-01-29
    • 1970-01-01
    • 1970-01-01
    • 2020-12-04
    相关资源
    最近更新 更多