17.设计模式支持
介绍
“设计模式”或“设计时”是指当您的项目加载到 Visual Studio XAML 设计器或 Expression Blend 中时,您将看到 XAML 的渲染版本。大多数时候,设计者不会尝试评估您的任何绑定或为它们提供任何 IntelliSense。然而,通过一些配置,您可以获得可爱的 IntelliSense,并在您的视图中显示来自您的 ViewModel 的一些虚拟值。
Stylet 对设计模式有一些基本支持。本文对其进行了记录,并提供了有关如何使用它和利用现有 XAML 功能来增强设计时体验的说明。
此处显示的所有示例都可以在DesignMode 示例项目中“准备运行” 。
仅限 IntelliSense,无绑定
这是最基本的技术,您需要做的额外工作很少。您将获得绑定的 IntelliSense(至少在 Visual Studio 2013 及更高版本中),但您不会在视图中看到来自 ViewModel 的任何虚拟数据。
首先,您需要在视图的根目录中进行以下声明。如果您已在 Visual Studio 2013 中创建了 UserControl,则默认情况下会添加这些控件。
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
您还需要为 ViewModel 添加命名空间:
xmlns:vms="clr-namespace:DesignMode.ViewModels"
一旦你掌握了这个,你就需要一条额外的魔法线。这会告诉 XAML 设计器DataContext
此视图的 是您的SampleViewModel
,并且绑定 IntelliSense 应该使用此属性:
d:DataContext="{d:DesignInstance vms:SampleViewModel}"
把它们放在一起,你最终会得到这样的东西:
<UserControl x:Class="DesignMode.Views.SampleView"
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"
mc:Ignorable="d"
xmlns:vms="clr-namespace:DesignMode.ViewModels"
d:DataContext="{d:DesignInstance vms:SampleViewModel}">
...
</UserControl>
Intellisense 和 Dummy Data,让 Designer 实例化 ViewModel
此技术与前一个技术非常相似,只是我们让 XAML 设计器为我们实例化 ViewModel。设计人员将使用此 ViewModel 实例从中获取绑定的虚拟数据。
为了让设计者能够做到这一点,ViewModel 必须有一个无参数的构造函数。这既是福也是祸。好的一面是,它为您提供了一个将一些虚拟数据注入 ViewModel 的属性以供设计人员使用的好地方。不利的一面是您的 ViewModel 现在包含仅供设计人员使用的代码…
请注意,在设计时无法访问 IoC 容器(并且任何请求访问的人都将被带回并…处理)。因此,如果您的 ViewModel 有任何依赖项,它们将在设计时不可用。通常这不是问题:只访问属性(不调用任何方法),因此您实际上不应该做任何需要访问您的依赖项之一的事情。请记住它。
以这种方式编写以支持设计模式的示例 ViewModel 可能如下所示:
public class SampleViewModel
{
private readonly IUserService userService;
public string CurrentUserName {
get; private set; }
public SampleViewModel()
{
this.CurrentUserName = "Dummy Username";
}
public SampleViewModel(IUserService userService)
{
this.userService = userService;
this.CurrentUserName = this.userService.CurrentUser.UserName;
}
}
请注意,StyletIoC 将始终选择其可以解析的参数最多的构造函数,因此它还将调用接受IUserService
. 另一方面,设计者将始终调用无参数构造函数。
如果您的 ViewModel 通常只有一个无参数构造函数,您可以使用Execute.InDesignMode,如下所示:
public class SampleViewModel
{
public string SomeText {
get; set; }
public SampleViewModel()
{
if (Execute.InDesignMode)
this.SomeText = "Dummy Text";
else
this.SomeText = "Actual Text";
}
}
无论哪种方式,一旦您获得了设计人员可以使用的带有无参数构造函数的 ViewModel,您就可以告诉设计人员使用以下方法实例化它:
d:DataContext="{d:DesignInstance vms:SampleViewModel, IsDesignTimeCreatable=True}"
或者,完整的:
<UserControl x:Class="DesignMode.Views.SampleView"
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"
mc:Ignorable="d"
xmlns:vms="clr-namespace:DesignMode.ViewModels"
d:DataContext="{d:DesignInstance vms:SampleViewModel, IsDesignTimeCreatable=True}">
...
</UserControl>
Intellisense 和虚拟数据,使用 ViewModelLocator
以前的方法有一个很大的缺点:它要求您的 ViewModel 了解设计模式,并且包含仅在设计时调用的代码。一些人认为这是一种巨大的代码味道。
另一种方法是使用 ViewModelLocator - 一个仅在设计时使用的类,它可以实例化和配置您的 ViewModel。这意味着任何仅限设计时的本地都可以进入 ViewModelLocator,而不会进入各个视图。
如果这听起来很复杂,请耐心等待。这一切都应该在一分钟内变得有意义。
首先,让我们获取一个示例 ViewModel:
public class SampleViewModel
{
private readonly IUserService userService;
public string CurrentUserName {
get;set; }
public SampleViewModel(IUserService userService)
{
this.userService = userService;
this.CurrentUserName = this.userService.CurrentUser.UserName;
}
}
接下来,我们需要一个 ViewModelLocator。这是一个简单的类,每个 ViewModel 包含一个我们可能希望在设计时访问的属性:
public class ViewModelLocator
{
public SampleViewModel SampleViewModel
{
get
{
var vm = new SampleViewModel();
vm.CurrentUserName = "Dummy Username";
return vm;
}
}
}
请注意 ViewModel 是如何仅在需要时实例化的?这是因为 ViewModelLocator 本身会在运行时被实例化,但它的属性只会在设计时被访问。
接下来,让我们将它添加到我们应用程序的资源中,以便我们的视图可以使用它:
<Application x:Class="DesignMode.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:local="clr-namespace:DesignMode">
<Application.Resources>
<s:ApplicationLoader>
<s:ApplicationLoader.Bootstrapper>
<local:Bootstrapper/>
</s:ApplicationLoader.Bootstrapper>
<local:ViewModelLocator x:Key="ViewModelLocator"/>
</s:ApplicationLoader>
</Application.Resources>
</Application>
…然后在我们的视图中使用它:
<UserControl x:Class="DesignMode.Views.SampleView"
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"
mc:Ignorable="d"
xmlns:vms="clr-namespace:DesignMode.ViewModels"
d:DataContext="{Binding Source={StaticResource ViewModelLocator}, Path=SampleViewModel}">
...
</UserControl>
在设计时启用/禁用按钮
在上面的所有示例中,我们只绑定了 View DataContext
:我们没有对其View.ActionTarget做任何事情。这意味着按钮的启用将不会反映守卫属性的值,如果它存在——它将始终启用。
注意:View.ActionTarget
默认情况下,Stylet在实例化 View 时会绑定到对应的 ViewModel。但是,在设计时,Stylet 不负责实例化视图,因此View.ActionTarget
未绑定。
如果你想让按钮的 enabledless 反映它的 guard 属性,你需要添加s:View.ActionTarget="{Binding}"
到你的视图,例如
<UserControl x:Class="DesignMode.Views.SampleView"
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:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
xmlns:vms="clr-namespace:DesignMode.ViewModels"
d:DataContext="{d:DesignInstance vms:SampleViewModel, IsDesignTimeCreatable=True}"
s:View.ActionTarget="{Binding}">
...
</UserControl>
使用替代 ViewModel
_另一个解决“我的 ViewModel 知道设计时间,而它不应该知道”_的问题是让你的 View 实现一个接口(一个真实的接口,或者你头脑中的假接口),然后你编写另一个设计时间-only View 实现相同的接口,并包含虚拟数据。然后在设计时以相同的方式绑定到它d:DataContext="{d:DesignInstance vms:DummyViewModel, IsDesignTimeCreatable=True}"
:不过,对于大多数开发人员来说,这开销太大了。
WPF 也有设计时数据的概念,例如参见。
为什么 Stylet 不能自动找到我的 ViewModel?
由于 Stylet 能够获取 ViewModel,并找到并实例化它的 View,您可能会想问为什么它不能以相反的方式执行此操作:也就是说,在设计时,自动为给定的 View 找到正确的 ViewModel ,并使用正确的依赖项实例化它。这是一个非常糟糕的主意的原因有很多:
- 我们需要添加一种将 View 名称转换为 ViewModel 名称的方法,这会增加复杂性
ViewManager
(特别是对于提供自己的 View 的任何人ViewManager
)。 - 我们需要
IViewManager
在设计时实例化一个适当的实现。由于用户可以提供他们自己的实现,这将意味着启动整个 IoC 容器。由于这依赖于正确设置引导程序的 Assemblies 属性,因此我们需要启动整个引导程序。这有可能产生严重的副作用(想想启动网络提交、文件系统访问等服务)。 - 我们需要提供一个 ViewModel 及其所有依赖项。这意味着实例化服务,这可能会产生严重的副作用。
- 无论如何,ViewModel 可能不会包含合适的虚拟数据,所以我们没有得到太多。
- 由于我们过于聪明而导致某些服务以错误的方式启动而导致 Designer 错误(或更糟,具有网络/文件系统副作用)调试起来很痛苦,我们不应该将其强加于人。
s:View.Model 和嵌入式视图
如果您的视图中有一个嵌入式视图(例如<ContentControl s:View.Model="{Binding SomeChildViewModel}"/>
),那么 Stylet 将只显示一条消息,类似于“View for ViewModelType.SomeChildViewModel”,而不是尝试定位正确的视图。这与我们不自动定位 ViewModel 的原因非常相似:这意味着启动 IoC 容器(我们需要找到用户的IViewManager
实现),这意味着运行引导程序,这让我们可以做非常危险的事情。最好避免。
项目原地址:https://github.com/canton7/Stylet
当前文档原地址:https://github.com/canton7/Stylet/wiki/Design-Mode-Support
上一篇:WPF的MVVM框架Stylet开发文档 16.监听INotifyPropertyChanged接口
下一篇:WPF的MVVM框架Stylet开发文档 18.记录 Logging