Blendability 第一部分 – 设计时 ViewModel






4.56/5 (5投票s)
Blendability 第一部分 – 设计时 ViewModel
如果您还没有将“混合性”(Blendability)这个词添加到您的 XAML 术语库中,我相信这篇文章一定会激发您这样做。
“混合性”这个术语描述了数据模型或视图模型在设计时(无论是通过 Expression Blend 还是 Visual Studio Designer)的可视化或设计能力。
构建 Silverlight 或 WPF 应用程序时,大家都喜欢使用 MVVM 模式。这种模式极大地解耦了视图与其逻辑和域模型,从而能够轻松地进行单元测试并提供极大的灵活性。在大多数情况下,除了视图构造函数中的 `InitializeComponent`(对于 `UserControl`)之外,XAML 后面不应该有任何代码。MVVM 模式的这一重要方面应该能够帮助 XAML 设计师使用任何喜欢的工具(例如 Blend)来更改视图,而不会受到视图逻辑的干扰。
让我们来看一个小应用程序,它使用 MVVM 模式来显示一个简单的视图。
public class Camera2dInspectionViewModel : NotificationObject
{
public ObservableCollection<DefectViewModel> Defects { get; private set; }
public double Fps { get; set; }
public double Brightness { get; set; }
public ImageSource CurrentFrame { get; set; }
}
上面的代码片段是 `ViewModel`。它代表了一个二维相机检查单元的视图。为了简单起见,它只向相关视图公开相关的属性。
其中一些属性是直观的,例如 `Fps`,而另一些则代表集合:`Defects`。
让我们来看看视图本身。
<UserControl d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Image Source="{Binding CurrentFrame}" />
<ListBox ItemsSource="{Binding Defects}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="{Binding Zone.X}"
Y="{Binding Zone.Y}" />
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Run Text="Defect #" />
<Run Text="{Binding Id}" />
</TextBlock>
<Rectangle Width="{Binding Zone.Width}"
Height="{Binding Zone.Height}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Text="Brightness" />
<Slider Value="{Binding Brightness}" />
<TextBlock Text="{Binding Fps}" />
</Grid>
</UserControl>
为了清晰起见,我已删除笔刷和布局属性。
现在,应该有一段代码通过将 `View.DataContext` 设置为 `ViewModel` 实例来连接 `View` 和 `ViewModel`。有几种技术可以做到这一点:硬编码、MEF、服务定位器、其他。
无论您喜欢哪种技术,Expression Blend 和 Visual Studio 都热衷于在设计时在 XAML 文件中查找数据上下文对象。如果失败,数据上下文将为 `null`,并且在设计时将不显示任何数据。
如何实例化视图模型,以便视图在设计时呈现它?
嗯,我们可以直接在视图的 XAML 中创建视图模型的实例,但我相信您同意这不是最佳实践,并且在某些情况下(例如视图模型没有默认构造函数、需要使用 DI 容器实例化或需要由业务层自身创建和填充)是不可能的。
有几种方法可以解决这个问题。在这篇文章中,我将介绍我最喜欢的一种方法,即使用仅用于设计时的 `DataContext`。
在使用 Blend 3、4 和 VS2010 时,有一个神奇的设计器属性称为 `d:DataContext`。此属性仅用于设计时 `DataContext`。使用此属性,您可以像往常一样继续将视图的常规 `DataContext` 属性设置为视图模型,然后将视图的、神奇的 `d:DataContext` 设计器属性设置为在 XAML 中创建的不同设计时视图模型实例。使用 Blend 或 Visual Studio 打开设计时的视图时,将应用 `d:DataContext`。运行应用程序时,将使用常规的 `DataContext`。
这个解决方案几乎是完美的。现在我们所要做的就是从 XAML 文件中生成一个仅用于设计时的视图模型。
现在,我们必须承认,没有人(包括 XAML 设计师)喜欢手动生成假数据,而 Blend 在这里大有帮助。Blend 有一个很酷的功能,可以在 XAML 中生成数据。当然有几种选择,但其中一种最适合我们的需求。我们将根据我们的视图模型类生成数据。
要做到这一点,请使用 Blend 打开项目,打开相关的视图,转到右侧的数据选项卡,选择项目,单击右侧第一个小图标,然后选择“从类创建示例数据…”选择相关的视图模型,然后单击确定。
此时,Blend 创建了一个名为 *SampleData* 的新文件夹和一个新的 XAML 文件。
如果您打开创建的 XAML 文件,您会发现以下生成的标记:
<Blendability_Modules_ViewModels:Camera2dInspectionViewModel
xmlns:Blendability_Modules_ViewModels="clr-namespace:Blendability.Modules.ViewModels"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Brightness="638.53"
Fps="990.7">
<Blendability_Modules_ViewModels:Camera2dInspectionViewModel.Defects>
<Blendability_Modules_ViewModels:DefectViewModel Id="63">
<Blendability_Modules_ViewModels:DefectViewModel.Zone>
<Rect Height="315.65" Width="181.51"
X="468.12" Y="991.84">
<Rect.Location>
<Point X="988.41" Y="166.96"/>
</Rect.Location>
<Rect.Size>
<Size Height="422.96"
Width="258.6"/>
</Rect.Size>
</Rect>
</Blendability_Modules_ViewModels:DefectViewModel.Zone>
</Blendability_Modules_ViewModels:DefectViewModel>
</Blendability_Modules_ViewModels:Camera2dInspectionViewModel.Defects>
</Blendability_Modules_ViewModels:Camera2dInspectionViewModel>
令人惊讶的是,Blend 从我们的视图模型生成了设计时实例,并用自动生成的随机数据填充了它,即使视图模型没有默认构造函数,或者在我们的例子中 `Fps` 属性是只读的。它还深入研究了视图模型,生成了所有 `public` 属性,包括集合。为了清晰起见,我不得不删除一些 XAML。
当然,我不得不更改数据并添加缺失的属性,例如 `CurrentFrame`,因为在这个世界上没有什么是不完美的,尤其是软件。
这是我更改数据的最终结果:
<vm:Camera2dInspectionViewModel
xmlns:vm="clr-namespace:Blendability.Modules.ViewModels"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Brightness="0.4"
Fps="29.7">
<vm:Camera2dInspectionViewModel.CurrentFrame>
<BitmapImage UriSource="/SampleData/Penguins.jpg" />
</vm:Camera2dInspectionViewModel.CurrentFrame>
<vm:Camera2dInspectionViewModel.Defects>
<vm:DefectViewModel Id="1">
<vm:DefectViewModel.Zone>
<Rect Height="40" Width="40" X="68" Y="15" />
</vm:DefectViewModel.Zone>
</vm:DefectViewModel>
<vm:DefectViewModel Id="2">
<vm:DefectViewModel.Zone>
<Rect Height="25" Width="25" X="180" Y="50" />
</vm:DefectViewModel.Zone>
</vm:DefectViewModel>
<vm:DefectViewModel Id="3">
<vm:DefectViewModel.Zone>
<Rect Height="50" Width="50" X="50" Y="90" />
</vm:DefectViewModel.Zone>
</vm:DefectViewModel>
</vm:Camera2dInspectionViewModel.Defects>
</vm:Camera2dInspectionViewModel>
现在我们有了仅用于设计时的视图模型,只需打开视图并将 Blend 数据选项卡中的示例视图模型拖放到左侧“对象和时间线”选项卡中视图的根元素上,即可将其与视图的 `d:DataContext` 连接。
太棒了……现在您可以高兴地在设计时查看视图渲染示例视图模型,并且您可以编辑视图控件的样式,编辑 `Defects` 集合的数据模板,并看到视图的全局图景,而无需一次又一次地运行整个应用程序。
请随时从此处下载演示代码。
在下一篇文章中,我将揭示我关于 Prism 模块混合性的小研究。这确实是一个很棒的功能,敬请期待。