WPF的MVVM框架Stylet开发文档 17.设计模式支持

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 ,并使用正确的依赖项实例化它。这是一个非常糟糕的主意的原因有很多:

  1. 我们需要添加一种将 View 名称转换为 ViewModel 名称的方法,这会增加复杂性ViewManager(特别是对于提供自己的 View 的任何人ViewManager)。
  2. 我们需要IViewManager在设计时实例化一个适当的实现。由于用户可以提供他们自己的实现,这将意味着启动整个 IoC 容器。由于这依赖于正确设置引导程序的 Assemblies 属性,因此我们需要启动整个引导程序。这有可能产生严重的副作用(想想启动网络提交、文件系统访问等服务)。
  3. 我们需要提供一个 ViewModel 及其所有依赖项。这意味着实例化服务,这可能会产生严重的副作用。
  4. 无论如何,ViewModel 可能不会包含合适的虚拟数据,所以我们没有得到太多。
  5. 由于我们过于聪明而导致某些服务以错误的方式启动而导致 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

猜你喜欢

转载自blog.csdn.net/qq_39427511/article/details/130456294