MVVM:事件路由到子 ViewModel
当有子 ViewModel 的集合时,
引言
目前,MVVM 模式是 WPF 应用程序架构设计中最推荐的模式。
MVVM 原理基于以下组件
通常,我们以一种方式设计我们的应用程序,即一个 View 对应一个 ViewModel。ViewModel 负责为其 View 提供/更新数据和处理事件。从上面的图可以清楚地看到,对于 Button、ContextMenu 等控件,ViewModel 中定义了事件处理程序,例如 OnButtonClickEvent。因此,当发生点击等事件时,注册/指定的事件处理程序将被调用。
注意: View 和 ViewModel 的绑定是通过 DataContext 或 Content [如果 XAML 中使用的元素是 ContentControl] 来完成的。为了开发这个示例程序,我选择了 Caliburn.Micro 框架。从这里下载 Caliburn.micro。
背景/目标
我们可能需要一个 View,它可以附加到 ViewModel 集合中的某个 ViewModel 实例 [相同类型 ViewModel 的多个实例]。
数据绑定和事件路由会按预期工作吗?
这是场景的理想图示。
所以,这就是我们期望应用程序的工作方式,即在选择子视图索引后,子视图将绑定到相应的子 ViewModel。显示的数据和子视图的事件路由将由当前选定的子 ViewModel 处理。
为了验证上述场景,我使用 Caliburn 框架开发了一个 WPF 应用程序,该应用程序代表学生数据。根据所选学生,应用程序将调用注册的电话号码。在完成这个 POC [概念验证] 后,我得到了以下观察结果:
- 选择所需的学生后,学生视图显示正确的信息,如学生姓名和学生号码。
注意:默认情况下,第一个选中的索引是“学生 A”,之后用户可以将其更改为“B”、“C”或“A”。
学生 A:点击“联系”按钮时,会弹出一个消息框,显示“正在联系学生 A:XXXXXXXXX”。
学生 B/C:点击“联系”时,会显示与“学生 A”相同的信息,即“正在联系学生 A:XXXXXXXXX”。对于学生 C,观察结果也相同。
注意:如果代码中将默认学生从 A 更改为 B 或 C,则点击任何学生的“联系”按钮时,消息框都会显示默认学生的信息。
Using the Code
这是承载 StudentInfoView
的 MainWindow 视图。
<Window x:Class="ContactStudent.Views.MainWindowView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro" xmlns:nsVm="clr-namespace:ContactStudent.ViewModels" xmlns:nsVi="clr-namespace:ContactStudent.Views" Title="Student Details" Height="400" Width="488" ResizeMode="NoResize" Background="DarkGray"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="90"/> <RowDefinition Height="302"/> <RowDefinition Height="3*" /> </Grid.RowDefinitions> <Grid Grid.Row="0" Background="DarkGray"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!--<Three Students i.e. A B C>--> <Button Name="Student_A" Grid.Column="0" Content="A" Width="110" Margin="22,22,22,28" ToolTip="Student A Information" cal:Message.Attach="[Event Click]=[Action DisplayStudentDetails('A')]"/> <Button Name="Student_B" Grid.Column="1" Content="B" Width="110" Margin="22,22,22,28" ToolTip="Student B Information" cal:Message.Attach="[Event Click]=[Action DisplayStudentDetails('B')]"/> <Button Name="Student_C" Grid.Column="3" Content="C" Width="110" Margin="22,22,22,28" ToolTip="Student C Information" cal:Message.Attach="[Event Click]=[Action DisplayStudentDetails('C')]"/> </Grid> <nsVi:StudentInfoView Grid.Row="1" DataContext="{Binding Student}" Background="DarkGray" HorizontalAlignment="Left"/> </Grid> </Window>
上面代码片段中突出显示的 [粗体标记] 部分显示了 StudentInfoView
及其 DataContext
绑定。它也可以如下所示:
<ContentControl Grid.Row="1" Content="{Binding Student}" Background="DarkGray" HorizontalAlignment="Left"/>
StudentInfoView
: 按钮 Message.Attach
属性已用粗体格式突出显示。
<UserControl x:Class="ContactStudent.Views.StudentInfoView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro" xmlns:local="clr-namespace:ContactStudent" mc:Ignorable="d" Height="185" Width="302" Background="DarkGray"> <Grid> <TextBlock Text="STUDENT'S DETAIL" TextAlignment="Center" FontWeight="Bold" Margin="64,0,96,165" /> <TextBlock Name="Student_Name" Text="Student Name" FontWeight="Bold" Margin="-1,37,206,128" TextAlignment="Center"/> <TextBlock Name="Student" Text="{Binding StudentName}" FontWeight="Bold" Background="LightGray" Margin="123,35,33,128" TextAlignment="Left"/> <TextBlock Name="Contact_Number" Text="Phone Number" FontWeight="Bold" Margin="0,69,206,97" TextAlignment="Center"/> <TextBlock Name="Number" Text="{Binding PhoneNumber}" FontWeight="Bold" Background="LightGray" Margin="123,69,33,97" /> <Button Name="Contact" Content="Contact" Margin="149,108,90,51" BorderBrush="Black" cal:Message.Attach="[Event Click]=[Action OnContactClick($source, $eventArgs)]"/> </Grid> </UserControl>
MainWindowViewModel
:它描绘了在 DisplayStudentDetails();
下如何进行 StudentViewModel 索引选择,请参考以下代码。
using Caliburn.Micro; public class MainWindowViewModel : PropertyChangedBase { #region Data Member private static int m_nCurrentStudentIndex = 0; //Current selected student index private StudentInfoViewModel[] m_strStudentDetails; //Collection of students #endregion Data Member #region Constructor public MainWindowViewModel() { //allocate the memory for the Student A,B and C m_strStudentDetails = new StudentInfoViewModel[3]; InitializeStudent();//Initialize StudentInfoViewModel instances to hold the information NotifyOfPropertyChange(() => Student); } #endregion Constructor #region Private Methods /// <summary> /// Initialization /// </summary> private void InitializeStudent() { int nIndex = 0; m_strStudentDetails[nIndex] = new StudentInfoViewModel("A", 1122334456); nIndex++; m_strStudentDetails[nIndex] = new StudentInfoViewModel("B", 1111111111); nIndex++; m_strStudentDetails[nIndex] = new StudentInfoViewModel("C", 1111111122); } #endregion Private Methods #region Event Handler /// <summary> /// Click Event Handler for the Button A,B and C /// </summary> /// <param name="strStudent">A/B/C</param> public void DisplayStudentDetails(String strStudent) { switch (strStudent) { case "A": m_nCurrentStudentIndex = 0; break; case "B": m_nCurrentStudentIndex = 1; break; case "C": m_nCurrentStudentIndex = 2; break; } NotifyOfPropertyChange(() => Student); } #endregion Event Handler #region Properties /// <summary> /// Get Current selected Student /// </summary> public StudentInfoViewModel Student { get { return m_strStudentDetails[m_nCurrentStudentIndex]; } } #endregion Properties }
DisplayStudentDetails()
是处理 MainWindowView 中按钮的点击事件的事件处理程序。此处理程序根据单击的按钮来确定 StudentInfoViewModel 的索引。
需要注意的一点是,学生视图显示正确的信息,如姓名和电话号码;这意味着附加的 ViewModel 是根据所选学生设置的。换句话说,DataContext 正在更新。
那么,为什么“联系”按钮的点击事件没有路由到附加的 ViewModel?为什么它总是只路由到第一个附加的 ViewModel?Message.Attach
属性是否在 DataContext
更改时更新?
任何初学者,如果尝试将单个 View 附加到 ViewModel 集合中的任何实例,都可能遇到与将事件路由到正确的 ChildViewModel 相关的问题。
问题
当一个子视图放置在一个主视图内部,并且子视图通过 DataContext 或 Content 绑定到其 ViewModel 时。
主视图可以通过选择索引来更新子视图,这又会在运行时通过 DataContext/Content 更新子 ViewModel 实例;在这种情况下,子视图的 DataContext/Content 属性的更改不会更新 Message.Attach
属性。
换句话说,子视图的动作处理程序的 Target 不会随着主视图内部的选择更新而更新。
解决方案
我建议了几种/多种方法来解决这个问题。每种方法都有其优缺点。
解决方案 1:维护 View 与 ViewModel 的一对一映射
此解决方案涉及在 View 和 ViewModel 之间维护一对一的映射,如下面的图所示。
在附加文档中定义了放置多个 ContentControl
并将它们绑定到它们的 ViewModel 实例。
这种方法对于小型应用程序非常简单易实现。它基本上是关于维护每个 ViewModel 的视图。
根据当前选定的索引,View 将对用户可见,其他视图实例将不可见;与所选视图相关的 ViewModel 也将处于活动状态。
对于我只针对三个学生的应用,我需要更新 MainWindow View 以容纳三个 StudentView 的三个 ContentControl 实例。根据当前选定的索引,相应的 ContentControl 将可见。
MainWindowView
<Grid Grid.Row="0" Background="DarkGray"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!--<Three Students i.e. A B C>--> <Button Name="Student_A" Grid.Column="0" Content="A" Width="110" Margin="22,22,22,28" ToolTip="Student A Information" cal:Message.Attach="[Event Click]=[Action DisplayStudentDetails('A')]"/> <Button Name="Student_B" Grid.Column="1" Content="B" Width="110" Margin="22,22,22,28" ToolTip="Student B Information" cal:Message.Attach="[Event Click]=[Action DisplayStudentDetails('B')]"/> <Button Name="Student_C" Grid.Column="3" Content="C" Width="110" Margin="22,22,22,28" ToolTip="Student C Information" cal:Message.Attach="[Event Click]=[Action DisplayStudentDetails('C')]"/> </Grid> <ContentControl Grid.Row="1" Content="{Binding StudentArr[0]}" Background="DarkGray" Visibility="{Binding IsVisible[0]}"/> <ContentControl Grid.Row="1" Content="{Binding StudentArr[1]}" Background="DarkGray" Visibility="{Binding IsVisible[1]}"/> <ContentControl Grid.Row="1" Content="{Binding StudentArr[2]}" Background="DarkGray" Visibility="{Binding IsVisible[2]}"/>
突出显示的 [粗体标记] 行是 MainWindowView 的修改,用于承载三个 StudentInfoView。每个 ContentControl
都有自己的 Content 和 Visibility 属性。
注意: Content 属性是一个 Object,它包含控件的内容。因为 Content 属性的类型是 Object,所以对可以放入 ContentControl 的内容没有限制。
MainWindowViewModel
ViewModel 也需要修改,以提供每个 ContentControl 的 Content 和 Visibility,以放置相应的 StudentInfoViewModel
。
public class MainWindowViewModel : PropertyChangedBase
{
#region Data Member
private static int m_nCurrentStudentIndex = 0; //Current selected student index
private StudentInfoViewModel[] m_strStudentDetails; //Collection of students
private Visibility[] m_IsSelected; //Collection of Visibility Property for ContentControl element
#endregion Data Member
#region Constructor
public MainWindowViewModel()
{
m_strStudentDetails = new StudentInfoViewModel[3]; // A,B and C
m_IsSelected = new Visibility[3];
InitializeStudent();//Initialize StudentInfoViewModel instances to hold the information
vUpdateVisibility(); //Initialize the visibilty property
}
#endregion Constructor
#region Private Methods
/// <summary>
/// Initialization
/// </summary>
private void InitializeStudent()
{
int nIndex = 0;
m_strStudentDetails[nIndex] = new StudentInfoViewModel("A", 1122334456);
nIndex++;
m_strStudentDetails[nIndex] = new StudentInfoViewModel("B", 1111111111);
nIndex++;
m_strStudentDetails[nIndex] = new StudentInfoViewModel("C", 1111111122);
}
#endregion Private Methods
private void vUpdateVisibility()
{
switch (m_nCurrentStudentIndex)
{
case 0:
m_IsSelected[0] = Visibility.Visible;
m_IsSelected[1] = Visibility.Hidden;
m_IsSelected[2] = Visibility.Hidden;
break;
case 1:
m_IsSelected[1] = Visibility.Visible;
m_IsSelected[0] = Visibility.Hidden;
m_IsSelected[2] = Visibility.Hidden;
break;
case 2:
m_IsSelected[2] = Visibility.Visible;
m_IsSelected[0] = Visibility.Hidden;
m_IsSelected[1] = Visibility.Hidden;
break;
}
NotifyOfPropertyChange(() => IsVisible);
}
#region Event Handler
/// <summary>
/// Click Event Handler for the Button A,B and C
/// </summary>
/// <param name="strStudent">A/B/C</param>
public void DisplayStudentDetails(String strStudent)
{
switch (strStudent)
{
case "A":
m_nCurrentStudentIndex = 0;
break;
case "B":
m_nCurrentStudentIndex = 1;
break;
case "C":
m_nCurrentStudentIndex = 2;
break;
}
vUpdateVisibility();
NotifyOfPropertyChange(() => StudentArr);
}
#endregion Event Handler
/// <summary>
/// Visibility of the ContentControl
/// </summary>
public Visibility[] IsVisible
{
get
{
return m_IsSelected;
}
}
/// <summary>
/// Content property value for ContentControl
/// </summary>
public StudentInfoViewModel[] StudentArr
{
get
{
return m_strStudentDetails;
}
}
}
}
从 ViewModel 的实现可以清楚地理解,我已经维护了 Visibility 和 StudentInfoViewModel
的数组。DisplayStudentDetails()
方法决定了当前选中的 ContentControl。根据 m_nCurrentStudentIndex
vUpdateVisibility ()
将当前选定的设置为可见,其他设置为隐藏。
结论
放置多个 ContentControl 实例并更改它们的可见性,其性能优势在于仅通过强制重建视图来更新控件布局。换句话说,ContentControl 将始终可用在 GUI 中,并且在绑定属性更新时易于引用。
在这种情况下使用一对一映射是可以的,因为我们只处理 3 个学生,试想一下需要数千甚至更多实例的情况。MainWindowView.xaml 的大小会有多大?
其次,我们使用的是 ContentControl 框架元素。当 Content 直到运行时才知道时,ContentControl 是适用的。Content 可以是字符串或任何 Object。但在此场景中,我们知道 View 和 Object,所以我们应该避免使用 ContentControl,因为它可以用其他框架元素替代。
第三,对于包含更多控件(如按钮或更多 UI 元素)的子视图,加载和卸载时间会增加。
它还会影响应用程序的大小。
解决方案 2:在 ViewModel 中访问 UI 元素并更新事件处理程序
此解决方案是关于在 ViewModel 中访问 UI 元素,并根据所选索引分配相关的事件处理程序,例如,我们可以在 ViewModel 中获取按钮句柄并更改事件处理程序。
以下部分解释了如何在 ViewModel 中订阅 UI 元素的事件处理程序。事件处理程序可以根据当前选定的 ViewModel 更新。通过遵循此方法,UI 控件事件可以路由到可用 ViewModel 集合中的目标 ViewModel。
实现步骤
a. 在 ViewModel 中获取 UI 元素
要在 ViewModel 中获取 UI 元素,必须处理 UserControl 的“Loaded
”事件,即 StudentInfoView
。
<UserControl x:Class="ContactStudent.Views.StudentInfoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
xmlns:local="clr-namespace:ContactStudent"
mc:Ignorable="d"
Height="185" Width="302" Background="DarkGray" cal:Message.Attach="[Event Loaded] = [Action vOnLoaded($Source)]" >
注意: 事件处理程序使用 Caliburn 的 Message.Attach
属性定义。另外,请移除在 StudentInfoView.xaml 中为“联系”按钮附加的点击事件。
StudentInfoViewModel
public static Button ContactBtn
{
get;
set;
}
//Handles the Loaded event for the view and assign the handler for the Click Event
public void vOnLoaded(object sender)
{
ContactBtn = ((StudentInfoView)sender).Contact;
ContactBtn.Click += OnContactClick; }
突出显示的部分是控件的 Name 属性的值,在本例中,Button 是控件,Contact 是 Name 属性的值。
b. 更新控件事件(例如 Click)的事件处理程序
以学生为例,“联系”是一个按钮,每次选择学生时都需要更新其 Click 事件,以便 Click 事件可以路由到目标 ViewModel。这种切换是在 MainWindowViewModel
中定义的,因为它充当 StudentInfoViewModel
的控制器。
/// <summary>
/// Click Event Handler for the Button A,B and C
/// <param name="strStudent">A/B/C</param>
public void DisplayStudentDetails(String strStudent)
{
StudentInfoViewModel.ContactBtn.Click -= Student.OnContactClick;
switch (strStudent)
{
case "A":
m_nCurrentStudentIndex = 0;
break;
case "B":
m_nCurrentStudentIndex = 1;
break;
case "C":
m_nCurrentStudentIndex = 2;
break;
}
StudentInfoViewModel.ContactBtn.Click += Student.OnContactClick;
NotifyOfPropertyChange(() => Student);
}
突出显示的部分是 DisplayStudentDetails()
方法的附加内容。正如我之前提到的,这是根据 GUI 上的选择来识别当前选定的 StudentInfoViewModel
的方法。通常,这种路由可以定义在负责处理选择事件更改的事件处理程序中。
结论
此实现涉及在 ViewModel 中访问 UI 元素,但这会强制 View 和 ViewModel 紧密耦合。
对控件的任何更改都会强制在 ViewModel 中实现更改。这种实现对于小型应用程序是没问题的,意味着当 UI 中没有那么多需要以同样方式处理的控件时。
随着控件数量的增加,如果 ViewModel 实例的数量也很多,那么将需要更多地关注处理程序的注册和注销。如果这种情况没有得到妥善处理,可能会导致应用程序崩溃。
您需要非常小心需要内部 ViewModel 访问的控件数量,并且您是否准备好违背 MVVM 原则。
解决方案 3:使用 DependencyProperty
定义的问题也可以使用 DependencyProperty
来解决,即可以通过样式、数据绑定、动画和继承等方法设置的属性。
依赖属性是派生自 DependencyObject
的类的属性,它们很特别,因为它们不是简单地使用后端字段来存储其值,而是使用 DependencyObject
的一些辅助方法。
依赖属性是一个公共的静态只读字段,必须先注册。注册后,此静态属性用于在内部存储系统中获取和设置值。
以下部分解释了使用 DependencyProperty
解决问题的步骤。
实现步骤
a. 在 CustomAttachedProperties 类中定义 DependencyProperty
public static class CustomAttachedProperties
{
public static readonly DependencyProperty AttachExProperty =
DependencyProperty.RegisterAttached(
"AttachEx",
typeof(string),
typeof(CustomAttachedProperties),
new PropertyMetadata(null, OnAttachExChanged)
);
public static void SetAttachEx(DependencyObject d, string attachText)
{
d.SetValue(AttachExProperty, attachText);
}
public static string GetAttachEx(DependencyObject d)
{
return d.GetValue(AttachExProperty) as string;
}
private static void OnAttachExChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as FrameworkElement;
if (control == null)
{
return;
}
control.DataContextChanged += (sender, args) =>
{
//Need to clear AttachProperty value as Caliburn forces the MessageTrigger only when AttachProperty
//has different value then previous one
control.ClearValue(Message.AttachProperty);
control.SetValue(Message.AttachProperty, e.NewValue);
};
}
}
在上面的代码片段中,我们使用了 RegisterAttached
来注册属性,即 AttachEx
,以及 PropertyMetaData
和其 PropertyChangedCallback
,即 OnAttachExChanged
实现的引用。
b. 在 StudentInfoView
中使用依赖属性为其控件定义事件。
<Button Name="Conatct" Content="Contact"local:CustomAttachedProperties.AttachEx="[Event Click]=[Action OnContactClick($source,$eventargs)]" Margin="149,108,90,51" />
突出显示的节显示了如何在 View(即 StudentInfoView)中使用 DependencyProperty。代替使用 Caliburn 的 Message.Attach 属性,使用 DependencyProperty
,即 AttachEx
,来订阅 DataContextChanged 事件。因此,每当 PropertyMetadata
发生变化时,它就会调用 OnAttachExChanged()
。此方法内部处理 DataContextChanged
事件,在该事件中清理控件的 Message.AttachProperty
并设置新值。
结论
DependencyProperty
用于启用声明式代码来修改对象的属性,通过提供更强大的数据变更通知系统来减少数据需求。当应用程序需要某种动画或需要允许在 Style 控件中设置属性时,DependencyProperty
非常有用。
缺点
- DependencyProperties 旨在供 WPF 的绑定系统使用,而 WPF 的绑定系统是将 UI 层与数据层联系起来的。它们应该保留在 UI 层,而不是数据层(ViewModels)中。
- DependencyProperties 主要适用于 VisualElements 层面,所以为我们的每个业务需求创建大量的 DP 可能不是个好主意。此外,DP 的成本比
INotifyPropertyChanged
要高。
解决方案 4:使用 Caliburn 的 View.Model 属性
此解决方案仅与 Caliburn 的属性相关,而我们中的许多人刚开始接触 C# 和 MVVM 实现的各种框架时可能并不知道这一点。
View.Model 属性是 Caliburn 为我提供的发现。在研究了所有方法之后,我才了解到这个属性。
更准确地说,在 WPF 语言中,View.Model 是 Caliburn.Micro 提供的 DependencyProperty
。它为我们提供了一种在 ContentControl 中托管内容、将其绑定到 View.Model 提供的模型以及根据所选模型更新视图的方法。
实现步骤
我首先要提到的是,这在控制 ViewModel 选择的用户控件中是一个非常简单的一行更改。
只需像下面这样更新 MainWindowView,突出显示的属性是解决问题的关键。J
<nsVi:StudentInfoView Grid.Row="1" cal:View.Model="{Binding Student}" Background="DarkGray"/>
或
<ContentControl Grid.Row="1" cal:View.Model="{Binding Student}" Background="DarkGray"/>
View.Model
此属性将视图绑定到指定的模型。在此示例中,视图或 ContentControl 绑定到“Student”,即当前 DataContext。它不仅处理 DataContext 的更改,还考虑了子视图 Message.Attach
属性的更新。
由于 Message.Attach 属性类似于为某些事件定义委托,而 view.model 通过将视图附加到当前 DataContext 或告诉视图这是数据和事件处理的 ViewModel/数据层来处理委托。
因此,我们讨论了四种解决方案及其优缺点,我想总结一下这些方法。
总结/结论
在 StudentInfo 视图的事件处理程序中使用 Caliburn micro 的 Message.Attach
属性定义 [使用 Message.Attach
不会发生绑定]。
所有这些只是为按钮定义的事件(如点击)设置了一个 EventTrigger
,其 Action 类型为 Caliburn.Micro.ActionMessage
。这对于每个创建的视图只发生一次!因此,当 DataTemplate 实例被宿主视图回收时,尽管 DataContext/Content 和所有相关绑定都已更新(StudentInfoView 显示正确的信息,如学生姓名和号码),但与此视图关联的 ActionMessage 从未设置为目标,即新的 ViewModel。
Caliburn.Micro.View.Model
),当模板的 DataContext 更改时,此 DependencyProperty 会更新。这会导致 Caliburn.Micro 实例化一个新视图(View.Model 依赖属性的更改处理程序调用 ViewLocator,如果不存在则实例化一个新视图),其中 ActionMessage 被正确绑定。尽管使用用户定义的 DependencyProperty
、在 ViewModel 中获取 UI 元素等其他方法有助于解决问题,但它们也有其优缺点。所有这些方法都可以用于有限数量的控件,因为随着控件数量的增加,它将影响维护。其次,这些方法将违背 MVVM 原则。
参考文献
http://maonet.wordpress.com/2010/09/24/nested-viewusercontrol-binding-in-caliburn/
http://msdn.microsoft.com/en-us/library/ms752914(v=vs.110).aspx