WPF MVVM 双向更新

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13530262/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 06:20:41  来源:igfitidea点击:

WPF MVVM two-way updates

c#wpfmvvmbinding

提问by Kale

I'm trying to setup a working two-way update by using this example.

我正在尝试使用此示例设置有效的双向更新。

These are the relevant code snippets:

这些是相关的代码片段:

XAML:

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>

Main Window:

主窗口:

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();
       }
    }
}

Main view:

主要观点:

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));
      }
   }
}

Student view:

学生观点:

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));
      }
   }
}

THE student:

学生:

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";
   }
}

If you read until here I already thank you :) Still, by calling "clkChangeStudent" I don't see the changes in the textbox. I guess it's because I don't call the set-method of the StudentViewModel. The project I'm working on is a bit complex and a lot of things happen in the class (here Student) itself.

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

How can I get a textbox update by settings values in the Student-class itself?

如何通过学生类本身的设置值获取文本框更新?

回答by Ucodia

Your actual code clearly won't notify changes to the interface. The reason is simple. Your method that changes the student name is in the Student model and that model does not implement the INotifyPropertyChanged.

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

There is 2 solutions to fix this issue depending on one question, does the changeStudent() method has to stick with the object model, that is to say, can your requirements allows you to move the changeStudent() method to the view model?

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

If yes then, first solution, simply remove the changeStudent method from the model and move it to the view model like this:

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

class StudentViewModel : INotifyPropertyChanged
{
    ...

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

In the other case, second solution, you have to raise events whenever a model property changes and then get your view model to suscribe to these changes. You can proceed like this in the model:

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

class Student : INotifyPropertyChanged
{
    ...

    private string studentLastName;

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

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

And for the view model:

对于视图模型:

class StudentViewModel : INotifyPropertyChanged
{
    ...

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

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

Both solution will work. It is really import that you understand that your code explicitelyneeds to notifies the interface whenever a value changes.

两种解决方案都有效。真正重要的是,您了解您的代码在值更改时明确需要通知接口。

回答by Paolo

ChangeStudent doesn't call any of the methods that trigger a property notify event in the view model, it alters the underlying model instead. It's these events that trigger the view to update itself.

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

As an aside you should also look at command binding from the view instead of using click handlers in the code-behind. That way your view doesn't need to know anything about the view model that's attached and can be pure presentation.

顺便说一句,您还应该从视图中查看命令绑定,而不是在代码隐藏中使用单击处理程序。这样你的视图不需要知道关于附加的视图模型的任何信息,并且可以是纯粹的展示。

回答by Andre

First you should use commands instead of events.

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

In your current structure you have to add an

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

OnPropertyChanged("StudentLastName");

call to your ChangedStudent() Method in StudentViewModel. After that you have to set the UpdateSourceTrigger of the Bindings to PropertyChanged

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

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