我是C#和XAML的新手,我正在尝试学习如何在MVVM设计模式下使用数据上下文和绑定。我写了一个非常简单的程序。该程序允许用户创建一个新人员,并在MainWindow中显示该人员的详细信息。要创建一个新的人,用户从MainWindow的菜单中选择“添加新人”。会弹出一个窗口(PersonView),用户可以在其中输入此人的姓名和年龄。当用户在PersonView窗口上单击OK时,将触发一个事件,该事件将Person的数据复制到MainWindow中的属性中。一旦通过PersonView窗口复制了数据,就会关闭。
问题在于此人的详细信息未在主窗口中更新。使用调试器,我可以看到我的PropertyChanged事件触发了,并且MainWindow中的事件处理程序正在运行。我已经将MainWindow的DataContext(在c#中)设置为等于MainWindow中包含要显示的数据的属性。我已经在MainWindow中为XAML中的Name和Age TextBlocks设置了绑定。
我已经读过this和this,但仍然无法正常工作。我想念什么?
代码:
Person.cs:
{
public class Person
{
#region Constructors
public Person()
{
Name = "";
Age = 0;
}
public Person(string name, int age)
{
Name = name;
Age = age;
}
#endregion
#region Properties
public string Name { get; set; }
public int Age { get; set; }
#endregion
#region Methods
#endregion
}
}
PersonViewModel.cs
using System.ComponentModel;
namespace MVVM_Basics.ViewModel
{
public class PersonViewModel : INotifyPropertyChanged
{
#region Constructors
public PersonViewModel()
{
_person.Name = "";
_person.Age = 0;
}
public PersonViewModel(string name, int age)
{
_person.Name = name;
_person.Age = age;
}
#endregion
#region Properties
Person _person = new Person();
public Person Person
{ get { return _person; }
set
{
_person = value;
OnPropertyChanged(new PropertyChangedEventArgs("Person"));
}
}
#region Events and EventHandlers
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged; //Associate event handler with event
handler?.Invoke(this, e); //invoke the delegate
}
#endregion
#endregion
#region Methods
#endregion
}
}
PersonView.xaml.cs
using System;
using System.Windows;
namespace MVVM_Basics.View
{
/// <summary>
/// Interaction logic for PersonView.xaml
/// </summary>
public partial class PersonView : Window
{
#region Constructors
public PersonView()
{
InitializeComponent();
}
#endregion
#region Properties
public PersonViewModel pvm { get; set; }
#endregion
#region Events and EventHandlers
public event EventHandler NewPersonCreated;
protected void OnNewPersonCreated(EventArgs e)
{
EventHandler handler = NewPersonCreated; //Link handler to event
handler?.Invoke(this, e); //Invoke handler
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
pvm = new PersonViewModel(this.NameTextBox.Text, Convert.ToInt32(this.AgeTextBox.Text));
OnNewPersonCreated(new EventArgs()); //Trigger the event
this.Close();
}
private void CancelEventHandler(object sender, RoutedEventArgs e)
{
//When the cancel button is clicked...
this.Close();
}
#endregion
#region Methods
#endregion
}
}
PersonView.xaml
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MVVM_Basics.View"
mc:Ignorable="d"
Title="PersonView" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name" Margin="30, 30"/>
<TextBox x:Name="NameTextBox" Grid.Row="0" Grid.Column="1" Margin="30, 30"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Age" Margin="30, 30"/>
<TextBox x:Name="AgeTextBox" Grid.Row="1" Grid.Column="1" Margin="30, 30"/>
<Button x:Name="OkButton" Content="Ok" Grid.Row="2" Grid.Column="0" Click="OkButton_Click" Margin="30, 30"/>
<Button x:Name="CancelButton" Content="Cancel" Grid.Row="2" Grid.Column="1" Click="CancelEventHandler" Margin="30, 30"/>
</Grid>
</Window>
MainWindow.xaml.cs
using MVVM_Basics.ViewModel;
using System;
using System.Windows;
namespace MVVM_Basics
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Constructors
public MainWindow()
{
InitializeComponent();
pvm.Person.Name = "Name from constructor";
this.DataContext = pvm; //Set Window's data context
}
#endregion
#region Properties
public PersonViewModel pvm { get; set; } = new PersonViewModel();
#endregion
#region Events and EventHandlers
private void AddNewPerson(object sender, RoutedEventArgs e)
{
//When user clicks on File>New person...
PersonView pv = new PersonView();
pv.NewPersonCreated += Pv_NewPersonCreated; //Subscribe event handler to event
pv.Show();
}
private void Pv_NewPersonCreated(object sender, EventArgs e)
{
//This delegate is called when PersonView creates a new PersonViewModel object
PersonView pv = (PersonView)sender;
pvm = pv.pvm; //Copy the PersonViewModel object from PersonView to MainWindow
//NameTextBlock.Text = pvm.Person.Name; //This works, but I want to use bindings, not this hack
pv.NewPersonCreated -= Pv_NewPersonCreated; //Unsubscribe event handler from event, going to close PersonView shortly
}
#endregion
#region Methods
#endregion
}
}
MainWindow.xaml
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:MVVM_Basics" mc:Ignorable="d" Title="MainWindow" Height="450" Width="400" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="25"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Menu Grid.Row="0"> <MenuItem Header="_File"> <MenuItem Header="Add _new person" Click="AddNewPerson"/> </MenuItem> </Menu> <Grid Grid.Row="1" Grid.Column="1" > <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!--Subgrid Row 0--> <TextBlock Text="Name:" Grid.Row="0" Grid.Column="0"/> <TextBlock x:Name="NameTextBlock" Text="{Binding Person.Name}" Grid.Row="0" Grid.Column="1" MinWidth="50" /> <!--Subgrid Row 1--> <TextBlock Text="Age:" Grid.Row="1" Grid.Column="0"/> <TextBlock x:Name="AgeTextBlock" Text="{Binding Person.Age}" Grid.Row="1" Grid.Column="1"/> </Grid> </Grid> </Window>