MVVM DialogService 替代方案

2024-01-01

在学习如何使用 MVVM 模式进行编程时,我遇到了一个常见问题 - 显示 ViewModel 中的各种对话框。

起初它对我来说看起来很简单。我创建了一个 IWindowService 接口,并在 WindowService 类中实现它。我使用此类来启动新的视图窗口。

但后来我需要 MessageBox 风格的对话框。所以我创建了一个 IDialogService 和一个 DialogService 类。我对“打开/保存文件”对话框做了同样的事情。

毕竟,我注意到创建 ViewModel 实例变得相当复杂:

ViewModel VM = new ViewModel(Data, AnotherData, MoreData, WindowService, DialogService, FileDialogService, AnotherService);

我尝试将所有服务合并到一个 FrontendService 类中,但这使得维护变得非常困难,并且 IFrontendService 接口变得非常“臃肿”。

现在我正在寻找替代方法。对我来说最好的情况是不需要将实例传递给 ViewModel 构造函数。


对话框或Window一般来说与视图相关。如果你想实现 MVVM,那么你必须将视图与视图模型分开。 MVVM 将帮助您完成此任务。

MVVM dependency graph
MVVM dependency graph and responsibilities overview

依赖关系图显示视图依赖于视图模型。这种依赖是单向的,目的是解耦。这仅是由于数据绑定机制才可能实现的。

需要强调的是,MVVM 是一种应用程序架构设计模式。它从以下位置查看应用程序成分观点而不是阶级观点。显然,将数据绑定的源类命名为“ViewModel”的广泛做法是相当具有误导性的。事实上,由于视图是由许多类(例如控件)组成的,因此视图模型也是如此。它是一个组件。

由于对话框是视图的一部分,因此Window.Show()由视图模型调用会添加一个非法箭头,从视图模型指向视图。这意味着视图模型现在依赖于视图。
现在,您已经创建了对视图的显式依赖关系,您将遇到新问题(MVVM 最初试图解决的问题):如果您决定显示不同的窗口类型或用弹出窗口替换对话框(或者换句话说,随时)你修改了视图),那么你就必须修改视图模型。这正是 MVVM 设计时要避免的。

解决方案是让 View 自行处理。当视图需要显示对话框时,它必须自行完成。

对话框是提供用户交互的一种方式。用户交互不是 视图模型的业务。

如果您需要专用服务来处理显示 GUI,那么它必须完全在视图中操作 - 因此它不能被视图模型引用。
由于视图模型与数据相关(数据的表示),因此它只能标记视图可以触发的数据相关状态(例如数据验证错误,其中推荐的方法是实现INotifyDataErrorInfo https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifydataerrorinfo?view=netframework-4.8在视图模型上)。

我的建议是让你的 View Model 摆脱 View 相关的责任,并使其业务仅专注于模型数据呈现 - 或者放弃 MVVM 并返回到最初的解耦问题。

解决方案1

最简单的方法是从代码隐藏或路由事件处理程序显示对话框。它可以由视图模型抛出或引发的异常或事件触发。例如,如果写入文件失败,视图模型可以引发 FileError 事件,视图会侦听该事件并做出反应,例如,通过向用户显示对话框。
然后使用以下方法将收集到的数据(如果这是输入对话框)传递到视图模型(如果需要)ICommand或通过更新数据绑定。

代码隐藏并不违反 MVVM,因为 MVVM 是基于组件的,而代码隐藏是一种 C# 语言,对于 MVVM 的概念来说是未知的。设计模式的一个要求是它必须与语言无关。在 MVVM 的定义中,代码隐藏不起任何作用——它没有被提及。

解决方案2

或者通过实现 ContentControl(或 UserControl)来设计您自己的对话框。这样的控件完美地融入了 WPF 框架,并允许编写 MVVM 兼容的代码。您现在可以利用数据绑定和数据触发器来显示和隐藏控件/对话框。

本机 Windows 对话框不能很好地集成到 WPF 框架中。 AWindow无法使用触发器显示。我们必须调用Show() or DialogShow()方法。这就是原来的问题所在“如何以 MVVM 兼容的方式显示对话框”发源于。

这是模式对话框的示例,只能使用 XAML 显示和隐藏该对话框。不涉及 C#。它使用事件触发器来动画Visibility对话框网格的(或者动画化Opacity)。该事件由一个Button。针对不同场景Button可以简单地替换为DataTrigger或使用绑定BooloeanToVisibilityConverter:

<Window>
  <Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click" SourceName="OpenDialogButton">
      <BeginStoryboard>
        <Storyboard>
          <ObjectAnimationUsingKeyFrames 
                          Storyboard.TargetName="ExampleDialog"
                          Storyboard.TargetProperty="Visibility"
                          Duration="0">
            <DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}"/>
          </ObjectAnimationUsingKeyFrames>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Window.Triggers>

  <Grid SnapsToDevicePixels="True" x:Name="Root">

    <!-- The example dialog -->
    <Grid x:Name="ExampleDialog" Visibility="Hidden"  Panel.ZIndex="100" VerticalAlignment="Top">

      <!-- The Rectangle stretches over the whole window area --> 
      <!-- and covers all window child elements except the dialog -->
      <!-- This prevents user interaction with the covered elements -->
      <!-- and adds modal behavior to the dialog -->
      <Rectangle
        Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=ActualWidth}"
        Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=ActualHeight}"
        Fill="Gray" Opacity="0.7" />
      <Grid Width="400" Height="200" >
        <Grid.RowDefinitions>
          <RowDefinition Height="*"/>
          <RowDefinition Height="100"/>
        </Grid.RowDefinitions>
        <Border Grid.RowSpan="2" Background="LightGray" BorderBrush="Black" BorderThickness="1">
          <Border.Effect>
            <DropShadowEffect BlurRadius="5" Color="Black" Opacity="0.6" />
          </Border.Effect>
        </Border>
        <TextBlock Grid.Row="0" TextWrapping="Wrap"
                   Margin="30"
                   Text="I am a modal dialog and my Visibility or Opacity property can be easily modified by a trigger or a nice animation" />
        <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Right" Height="50" >
          <Button x:Name="OkButton"
                  Content="Ok" Width="80" />
          <Button x:Name="CancelButton" Margin="30,0,30,0"
                  Content="Cancel" Width="80" />
        </StackPanel>
      </Grid>

      <Grid.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
          <BeginStoryboard>
            <Storyboard>
              <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExampleDialog"
                                             Storyboard.TargetProperty="Visibility"
                                             Duration="0">
                <DiscreteObjectKeyFrame Value="{x:Static Visibility.Hidden}" />
              </ObjectAnimationUsingKeyFrames>
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger>
      </Grid.Triggers>
    </Grid>

    <! The actual control or page content -->
    <StackPanel>
      <TextBlock Text="This is some page content" />

      <!-- The button to open the dialog. This can be replaced by a DataTrigger -->
      <Button x:Name="OpenDialogButton" Content="ShowDialog" Width="100" Height="50" />
    </StackPanel>
  </Grid>
</Window>

这是对话框:

您可以封装对话框并将实现移至专用的Control e.g. DialogControl,这在整个应用程序中更易于使用(没有重复的代码,改进的处理)。您可以将常见的窗口镶边添加到对话框中,例如标题栏、图标和镶边按钮来控制对话框的状态。

编辑以显示错误以及“对话服务”违反/消除 MVVM 的原因

视图模型组件引用的所有内容要么也是视图模型的一部分,要么是模型的一部分。上面的 MVVM 依赖关系图很好地表明 View 组件对于 View Model 组件是完全未知的。现在,视图模型已知的对话框服务如何像对话框一样显示视图的模块,not违反了 MVVM 模式?显然,要么视图模型组件了解视图组件的模块,要么视图模型包含非法责任。不管怎样,对话服务显然不能解决问题。从名为的类中移动代码...ViewModel到一个名为...Service,其中原始类仍然引用新类,在架构方面没有做任何事情。代码仍然位于同一组件中,由视图模型类引用。除了显示对话框的类的名称之外,没有任何更改。给一个类命名并不会改变它的性质。例如,将我的数据绑定源命名为 MainView 而不是 MainViewModel 并不会使 MainView 成为视图的一部分。
一般来说,类命名或命名约定与架构、MVVM 完全无关。责任和依赖性是利益问题。

以下是对话服务引入的依赖项is 经营者 the 查看模型:

正如您所看到的,我们现在有一个从视图模型指向视图的箭头(依赖项)。现在视图中的更改将反映到视图模型并影响实现。这是因为视图模型现在涉及视图逻辑 - 在这种特殊情况下是用户交互逻辑。用户交互逻辑是GUI,是View。从视图模型控制这个逻辑会让人尖叫“MVVM 违规”......
如果你能接受这种违规行为就好了。但这违反了 MVVM 设计模式,不能作为“显示对话框的 MVVM 方式”出售。至少我们不应该买它。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MVVM DialogService 替代方案 的相关文章

随机推荐

  • firebase 库实现出现 Appcompat 错误

    您好 我有一个关于添加 firebase 依赖项的小问题 当我添加这一行时 implementation com google firebase firebase core 17 0 0 在我的 build gradle 中 我看到一条错误
  • 如何在 Windows Phone 8.1 中添加 AppBar

    在Windows Phone 8中 添加应用程序栏并管理它非常容易 但现在我测试新的Windows Phone 8 1 SDK来构建具有新地理围栏功能的项目 但我不知道如何在应用程序中添加应用程序栏 在Windows Phone 8 1中
  • 如何使用另一个测试结果中的标签来标记 ggplot 中的条形图?

    我想用测试的标签输出来标记我的图 例如 使用 agricolae 库中的 LSD test 进行 LSD 测试输出 a b ab 等 这是运行的示例 library ggplot2 library agricolae wt lt gl 3
  • 根据用户选择将 MYSQL 表中的数据输出为 HTML 表单

    我有一个 MYSQL 表 roomcost 用于保存租用房间的成本 costID Room Cost 1 room1 15 2 room2 30 3 room3 50 rsRoomCost SQL 是 SELECT FROM roomcos
  • 通过反射和使用 Class.cast() 进行投射[重复]

    这个问题在这里已经有答案了 可能的重复 Java Class cast 与强制转换运算符 https stackoverflow com questions 1555326 java class cast vs cast operator
  • 删除 jgit 未按预期工作的分支

    我正在尝试使用 jgit 删除我的存储库中的一个分支 DeleteBranchCommand command git branchDelete command setBranchNames myBranch command setForce
  • 如何获取网页上任意指定像素的颜色? [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我最初的搜索表明 出于安全原因 我可能无法执行此操作 但无论如何我都会询问 我怎样才能得到的颜色any网页上的指定像素 更具体地说 这是我自
  • 使用访问令牌下载 bitBucket 私有存储库的 powershell 脚本

    我试图通过首先从 bitbucket 获取代码来自动化构建过程 如下所示 output E FileName xyz url https bitbucket org WhatEver WhatEverBranchName get maste
  • 如何在 HTML 中创建小空间?

    有长破折号和短破折号 是否有一个 en 相当于 有没有一个en相当于纯Ascii 32 我想要一个更好的方式来写这个 123 span class spanen nbsp span 456 span class spanen nbsp sp
  • 从 CGI C 模块返回 http 错误代码

    我有一个用 C 编写的 CGI 模块 在某些情况下我想从该模块返回 HTTP 错误 400 问题是 我不知道如何从模块返回 HTTP 错误 看起来像我的模块中的 return 1 返回 500 内部服务器错误 我尝试过退回 400 等 但还
  • 使用 Android lrucache 的示例

    我需要帮助了解 androids LruCache 我想用来将图像加载到我的网格视图中 以便更好地加载 滚动 有人可以发布使用 LruCache 的示例代码吗 提前致谢 下面是我为使用 LruCache 制作的一个类 这是基于演示的事半功倍
  • 如果通过引用捕获异常,可以修改它并重新抛出吗?

    该标准是否对通过引用捕获的异常以及尝试修改它会发生什么有任何规定 考虑以下代码 class my exception public std logic error public std vector
  • php header excel 和 utf-8

    ob start echo D s ui header Content Type application vnd ms excel charset utf 8 header Content type application x msexce
  • 我可以使用 twiml 向传入的 twilio 呼叫发送数字吗?

    我有一个 twilio 号码 可以使用 twiml 处理来电 这些来电希望接听者在呼叫接通后按一些数字 如果我要拨打电话 我可以使用sendDigits的属性
  • 对象与字典:如何组织数据树?

    我正在编程某种模拟 其数据组织在树中 主要对象是World其中包含一堆方法和一个列表City对象 每个City对象又具有一堆方法和一个列表Population对象 Population对象没有自己的方法 它们只是拥有属性 我的问题是关于后者
  • Pymongo - ValueError:使用 insert_many 时 NaTType 不支持 utcoffset

    我正在尝试将文档从一个数据库增量复制到另一个数据库 某些字段包含以下格式的日期时间值 2016 09 22 00 00 00 而其他的则采用这种格式 2016 09 27 09 03 08 988 我像这样提取并插入文档 pd DataFr
  • 无法识别“nuget”,但其他 nuget 命令可以工作

    我正在尝试使用创建一个 nuget 包http docs nuget org docs creating packages creating and publishing a package From a convention based
  • tkinter 粘性不适用于某些框架

    我正在使用 tkinter 编写纸牌游戏 但我在网格布局管理器 粘性 配置方面遇到了问题 我希望帮助修复我的代码以使框架显示在所需的位置 在我的代码和下面的插图中 有一个框架 b2 其中包含另外两个框架 一个绿色 b2a 一个红色 b2b
  • Java 到 XSD 或 XSD 到 Java

    我知道 使用 JAXB 您可以从 XSD 生成 Java 文件 http www javaworld com javaworld jw 06 2006 jw 0626 jaxb html你也可以从带注释的 POJO 生成 XSD https
  • MVVM DialogService 替代方案

    在学习如何使用 MVVM 模式进行编程时 我遇到了一个常见问题 显示 ViewModel 中的各种对话框 起初它对我来说看起来很简单 我创建了一个 IWindowService 接口 并在 WindowService 类中实现它 我使用此类