Caliburn.Micro 初学者数据绑定指南
本文提供了如何使用 Caliburn.Micro 实现数据绑定的信息。
引言
数据绑定是 MVVM 的关键特性之一。它在应用程序 UI 和表示逻辑之间建立了连接。它根据业务逻辑中的更改自动更新用户界面。为了更新 UI 中的属性,我们需要从视图模型中通知更改。为此,我们需要实现一个接口,例如 INotifyPropertyChanged
,它通知 UI 模型中的任何更改。但是,接口的实现及其与逻辑的链接本身就是一个有点麻烦的任务。
有一个开源框架专门用于实现 MVVM(Model-View-ViewModel)模式,名为 Caliburn.Micro。它通过减少代码行数和实现 MVVM 的额外工作来简化编码。它的好处之一是简化了数据绑定的过程。Caliburn.Micro 基于约定优于配置的原则工作。我们可能不需要显式实现 INotifyPropertyChanged
。我们只需要遵循一些简单的约定并专注于我们的逻辑。
背景
MVVM 有助于将图形用户界面(无论是标记语言还是 GUI 代码)的开发与业务逻辑或后端逻辑(称为模型,也称为数据模型以区别于视图模型)的开发清晰分离。MVVM 的视图模型是一个值转换器,这意味着视图模型负责以一种易于管理和使用的方式公开模型中的数据对象 [6]。有关 MVVM 是什么以及如何实现的更多详细信息,请点击此链接:
数据绑定
每当我们的业务模型数据发生变化时,都需要反映到用户界面,反之亦然。例如,如果用户编辑 TextBox 元素中的值,则底层数据值会自动更新以反映该更改。
数据绑定可以是单向的(源 -> 目标或目标 -> 源),也可以是双向的(源 <-> 目标)。数据绑定的源可以是普通的 .NET 属性或依赖属性,但绑定的目标属性必须是依赖属性。
现在问题来了,源或目标如何知道已发生更改并且需要更新数据。为此,我们通常实现 INotifyPropertyChanged
接口。在依赖属性上,它通过此接口的 PropertyChanged
事件完成。
数据绑定通常在 XAML 中使用 {Binding}
标记扩展完成。以下示例显示了 TextBox
和 Label
文本之间的一个简单绑定,该绑定反映了输入的值。
<Window x:Class="MyWPFdataBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Label
Content="{Binding ElementName=NameoftextBox, Path=Text}"
HorizontalAlignment="Left"
Name="label1" />
<TextBox
HorizontalAlignment="Left"
Name="textBox1"/>
</Grid>
</Window>
然后,我们需要在 ViewModel
中实现 INotifyPropertyChanged
接口来更新数据。
数据绑定的过程可以通过以下图表更好地解释:

Caliburn.Micro
Caliburn.Micro 的一个非常重要的特性是它遵循一系列约定,即,无需手动编写绑定代码;在 Caliburn.Micro 提供的平台上,约定会处理这些。但就像硬币的两面一样,有些人喜欢约定,有些人不喜欢。这就是 Caliburn.Micro 的设计方式,其约定完全可定制,甚至可以在不需要时完全关闭。但本文的动机只有在您打算使用这些约定并且它们默认开启的情况下才有用,因此了解这些约定是什么以及它们如何工作是很好的。
使用 Caliburn.Micro 时遇到的第一个约定是 View 和 ViewModel 之间的绑定。Caliburn.Micro 执行数据绑定的方式可以通过以下步骤解释:
传递 ViewModel
Caliburn.Micro 中有一个Bootstrapper
类,如果选择在应用程序开发中使用此框架,它就是应用程序的起点。在这里,根 ViewModel
被传递给 ViewModelLocator
类,以确定应用程序的 shell 应该如何渲染。传递 ViewModel
的语法是 Bootstrapper<T>
,其中在 T
的位置,我们传递 ViewModel
的名称。当我们遵循 ViewModel
-First 方法时,会执行此步骤。Caliburn.Micro 支持 ViewModel
-First 和 View
-first 两种方法。我们可以根据我们的需要和用途选择其中任何一种。但我们需要了解这两种方法之间的区别。我们不需要过多地更改代码来实施这些方法中的任何一种。Caliburn.Micro 的 ViewModelLocator
类中有一个细微的更改。ViewModel 优先
它仅仅意味着我们有一个现有的 ViewModel
需要呈现在屏幕上。在这种情况下,ViewModel
负责创建视图并将自身绑定到视图。而且绑定更直接,因为您可以使用基于约定的方法将视图映射到视图模型。Caliburn.Micro 通常更喜欢这种方法。要实现这一点,我们需要在一个名为 AppBootStrapper
的单独类中传递 ViewModel
的名称,该类需要手动添加。ViewLocator.LocateForModelType
看起来像这样:
public static Func<Type, DependencyObject, object, UIElement> LocateForModelType =
(modelType, displayLocation, context) =>
{
var viewTypeName = modelType.FullName.Replace("Model", string.Empty);
if (context != null)
{
viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
viewTypeName = viewTypeName + "." + context;
}
var viewType = (from assmebly in AssemblySource.Instance
from type in assmebly.GetExportedTypes()
where type.FullName == viewTypeName
select type).FirstOrDefault();
return viewType == null ? new TextBlock {Text = string.Format("{0} not found.", viewTypeName) } : GetOrCreateViewType(viewType);
};
View 优先
这是实现 MVVM 应用程序的另一种方法,其中 View
和 ViewModel
s 是松散耦合的。大多数用户通过屏幕操作,大多数开发人员根据屏幕来理解他们的应用程序。因此,这种方法最适合他们。在这种方法中,是 View
驱动 ViewModel
的创建或搜索。视图通常将视图模型绑定为资源,使用定位器模式,或者通过 MEF、Unity 或其他方式注入视图模型。这种方法通常在处理 WP7 时首选。在此实现中,我们需要使用命令 “cal:View.Model
” 在 XAML 中绑定我们的视图模型,如下所示:
<ContentControl cal:View.Model="{Binding ListingViewModel}" />
ViewModelLocator.LocateForViewType
中的代码
public static Func<Type, object> LocateForViewType = viewType =>
{
var typeName = viewType.FullName;
if (!typeName.EndsWith("View"))
typeName += "View";
var viewModelName = typeName.Replace("View", "ViewModel");
var key = viewModelName.Substring(viewModelName.LastIndexOf(".") + 1);
return IoC.GetInstance(null, key);
};
命名约定:在本文中,我们使用 ViewModel
优先方法,为了实现此方法,Caliburn.Micro 使用一个简单的命名约定来查找它应该绑定到 ViewModel
并显示的 UserControl
。我们需要做的是在 ViewModel
s 的名称中添加文本“ViewModel
”,并在命名相应的 View
时从该名称中删除文本“Model
”。换句话说,我们可以说 UI 名称应该以“View
”为后缀。因此,假设我们正在开发一个用于客户客户端界面的应用程序,并将我们的 ViewModel
命名为 CustomerViewModel
,那么我们的 View
的名称将是 CustomerView
。示例:让我们以使用 Caliburn.Micro 实现井字棋游戏为例。我们的 View 的名称是“TTTView
”,相应的 ViewModel
是“TTTViewModel
”。我们需要添加派生自 CM 的 Bootstrapper
类的“AppBootstrapper
”类,并传递我们的 ViewModel
的名称。此类包含以下代码:
namespace Caliburn_TTT
{
class AppBootstrapper : Bootstrapper<TTTViewModel>
{
}
}
绑定如何工作
这是整个过程中最重要的一步。在 View
和 ViewModel
的绑定完成后,无论我们是使用 ViewModel
-First 还是 View
-First 方法,都会调用 ViewModelBinder.Bind
方法。此方法将 View
的 Action.Target
设置为 ViewModel
,即 View
中的控件将根据 XAML 文件中选择的绑定类型,根据 ViewModel
/View
进行更新。ViewModelBinder
还会确定是否需要创建任何常规属性绑定或动作。为此,它会在 View
中搜索绑定/动作的元素候选列表,并将它们与我们在 ViewModel
中定义的属性和方法进行比较。当找到匹配项时,它会将该元素绑定到相应的属性或方法。
搜索匹配:Caliburn.Micro 使用一种适当的方法来搜索元素。它在树中向上和向下搜索,直到找到合适的根节点,例如 Window
或 UserControl
或没有父级的元素。一旦发现这些元素,它就开始第二个任务,即在这些元素中搜索有名称的元素。然后,此函数将找到的元素返回给 ViewModelBinder
以应用约定。
<UserControl x:Class="Caliburn_TTT.TTTView"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:cal="http://www.caliburnproject.org">
<Grid x:Name="grid1" Margin="0,0,0,0" Height="473" VerticalAlignment="Top"
HorizontalAlignment="Left" Width="450">
<Grid.RowDefinitions>
<RowDefinition Height="20*"/>
<RowDefinition Height="150*"/>
</Grid.RowDefinitions>
<Menu Height="21" Grid.ColumnSpan="3" Grid.RowSpan="2"/>
<Button x:Name="Shape00" Background="White" Grid.Row="1">
<TextBlock Width="150" Height="150"
cal:Message.Attach="[Event DoubleClick] = [Action DrawShape(0,0)]"/>
</Button>
<Button x:Name="Shape01" Background="White" Grid.Row="1" Grid.Column="1">
<TextBlock Width="150" Height="150"
cal:Message.Attach="[Event DoubleClick] = [Action DrawShape(0,1)]"/>
</Button>
</Grid>
</UserControl>
Caliburn 使用两种技术将 UserControl
绑定到其对应的方法:
- 如果
UserControl
和方法具有相同的名称。在我们的示例中,Button
可以绑定到与按钮同名的方法(这里是“Shape00
”和“Shape01
”)。但是,当我们使用这种绑定技术时存在一些限制,因为我们不能传递参数,并且该方法绑定到控件的默认事件,就像按钮的情况一样,即“Click
”事件,这意味着当用户点击按钮时将调用该方法。 - 第二种技术是使用命令 “
cal:Message.Attach
”。这种技术克服了前一种技术的局限性。而且,在这种技术中,方法名称不一定与控件名称相同。我们可以给方法任何名称。如上例所示,我们使用名称“DrawShape
”,并且(0,0)
作为参数传递。“[Event DoubleClick]
”表示何时调用该方法。在这种情况下,双击TextBlock
将调用DrawShape
方法。同样,我们可以指定任何事件,当该事件发生时我们希望调用我们的方法,例如点击、按键等。
方法搜索
元素定位后,ViewModelBinder
会在 ViewModel
中搜索相应的约定绑定方法。它首先在 ViewModel
中搜索 public
方法。然后它进行元素名称和方法名称的区分大小写的字符串比较。找到匹配项后,它将元素功能绑定到该方法。以下代码中的方法“Shape00
”和“Shape01
”绑定到上面代码片段中定义的 TextBlock
。方法“DrawShape
”绑定到按钮的点击事件。行号和列号已作为参数传递。
public string Shape00
{ _______;
_______; }
public string Shape01
{_________;
_________;
}
public void DrawShape(int row, int col)
{____________;
____________;
}
将多个视图绑定到单个 ViewModel
这是 Caliburn.Micro 的众多功能之一。它还支持同一个 ViewModel
的多个 View
,即可以根据绑定到同一个 ViewModel
的某个事件的发生,向用户呈现不同的视图。通常,初学者在这种情况下使用 Caliburn.Micro 的约定属性时会遇到问题。然而,我们的示例不需要这个,但它是为了解决一般问题而解释的。
示例:一个 ViewModel
"MainViewModel
" 通过约定绑定到 "MainView
"。但事件发生后,我们希望应用程序显示 "MainView2
" 而不是 "MainView
"。这可以通过以下方式解决:我们需要使用“cal:View.Context
”附加属性,然后命名我们的视图,例如 OurNamespace.Something.ContextView
(从我们的视图模型名称中删除 "ViewModel
",添加一个点,然后是 Context
属性的值)。通过这种方式,我们甚至可以将多个视图绑定到一个视图模型。
<ContentControl x:Name="Toolbar" cal:View.Model="{Binding ActiveItem}" cal:View.Context="Toolbar" />
根据上述代码,您的 ViewModel
的名称应为 Something.Toolbar
。
结论
使用 Caliburn.Micro 将 View
与 ViewModel
绑定变得不那么麻烦。这有助于强制执行 MVVM。大多数情况下,我们可以摆脱 xaml.cs 文件,因为它的存在总是存在视图污染的风险。
从上面我们可以很容易地判断出,使用 Caliburn.Micro 可以让数据绑定变得更容易。但它的用途不仅限于绑定。绑定只是 Caliburn.Micro 简化众多功能中的一个。
参考文献
- http://en.wikipedia.org/wiki/Data_binding
- http://msdn.microsoft.com/en-US/
- https://codeproject.org.cn/script/Articles/Submit.aspx
- http://www.mindscapehq.com/blog/index.php/2012/01/16/caliburn-micro-part-2-data-binding-and-events/
- http://caliburnmicro.codeplex.com/documentation
- http://en.wikipedia.org/wiki/Model_View_ViewModel