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 替代方案 的相关文章

  • 在 C++ 中使用 matlab 结构(matlab 函数调用的返回值)(由 matlab 编译器生成的库)

    你好 我有一个相当简单的 matlab 函数 例如 function MYSTRUCT myfunc MYSTRUCT prop1 test MYSTRUCT prop2 foo MYSTRUCT prop3 42 end 我用 matla
  • 如何在c++中读取pcap文件来获取数据包信息?

    我想用 C 编写一个程序来读取 pcap 文件并获取数据包的信息 例如 len sourc ip flags 等 现在我找到了如下代码 我认为它会帮助我获取信息 但是我有一些疑问 首先我想知道应该将哪个库添加到我的程序中 然后什么是 pca
  • 确保 StreamReader 不会挂起等待数据

    下面的代码读取从 tcp 客户端流读取的所有内容 并且在下一次迭代中它将仅位于 Read 上 我假设正在等待数据 我如何确保它不会在没有任何内容可供读取时返回 我是否必须设置低超时 并在失败时响应异常 或者有更好的办法吗 TcpClient
  • 错误:表达式不产生值

    我尝试将以下 C 代码转换为 VB NET 但在编译代码时出现 表达式不产生值 错误 C Code return Fluently Configure Mappings m gt m FluentMappings AddFromAssemb
  • 当我们想要返回对象的引用时,为什么我们在赋值运算符中返回 *this 而通常(而不是 this)?

    我正在学习 C 和指针 我以为我理解了指针 直到我看到这个 一方面 asterix 运算符是解引用的 这意味着它返回值所指向的地址中的值 而与号 运算符则相反 它返回值存储的地址记忆 现在阅读有关赋值重载的内 容 它说 我们返回 this因
  • 使用 Newtonsoft 和 C# 反序列化嵌套 JSON

    我正在尝试解析来自 Rest API 的 Json 响应 我可以获得很好的响应并创建了一些类模型 我正在使用 Newtonsoft 的 Json Net 我的响应中不断收到空值 并且不确定我的模型设置是否正确或缺少某些内容 例如 我想要获取
  • 在 C 中初始化变量

    我知道有时如果你不初始化int 如果打印整数 您将得到一个随机数 但将所有内容初始化为零似乎有点愚蠢 我问这个问题是因为我正在评论我的 C 项目 而且我对缩进非常直接 并且它可以完全编译 90 90 谢谢 Stackoverflow 但我想
  • 为什么调用非 const 成员函数而不是 const 成员函数?

    为了我的目的 我尝试包装一些类似于 Qt 共享数据指针的东西 经过测试 我发现当应该调用 const 函数时 会选择它的非 const 版本 我正在使用 C 0x 选项进行编译 这是一个最小的代码 struct Data int x con
  • 从 Linux 内核模块中调用用户空间函数

    我正在编写一个简单的 Linux 字符设备驱动程序 以通过 I O 端口将数据输出到硬件 我有一个执行浮点运算的函数来计算硬件的正确输出 不幸的是 这意味着我需要将此函数保留在用户空间中 因为 Linux 内核不能很好地处理浮点运算 这是设
  • 具有交替类型的可变参数模板参数包

    我想知道是否可以使用参数包捕获交替参数模式 例如 template
  • Qt - ubuntu中的串口名称

    我在 Ubuntu 上查找串行端口名称时遇到问题 如您所知 为了在 Windows 上读取串口 我们可以使用以下代码 serial gt setPortName com3 但是当我在 Ubuntu 上编译这段代码时 我无法使用这段代码 se
  • WPF - 关闭 App.g.cs 中 Main 的自动生成

    我正在学习WPF 我想在 App xaml cs 中提供我自己的 Main 方法 而不是在 App g cs 中为我生成一个方法 然而 我不断遇到冲突 因为我还没有找到如何阻止生成额外的 Main 我的项目文件或其他地方是否有控制此设置的设
  • 使用自定义堆的类似 malloc 的函数

    如果我希望使用自定义预分配堆构造类似 malloc 的功能 那么 C 中最好的方法是什么 我的具体问题是 我有一个可映射 类似内存 的设备 已将其放入我的地址空间中 但我需要获得一种更灵活的方式来使用该内存来存储将随着时间的推移分配和释放的
  • Azure 辅助角色“请求输入之一超出范围”的内部异常。

    我在辅助角色中调用 CloudTableClient CreateTableIfNotExist 方法 但收到一个异常 其中包含 请求输入之一超出范围 的内部异常 我做了一些研究 发现这是由于将表命名为非法表名引起的 但是 我尝试为我的表命
  • 为什么 std::strstream 被弃用?

    我最近发现std strstream已被弃用 取而代之的是std stringstream 我已经有一段时间没有使用它了 但它做了我当时需要做的事情 所以很惊讶听到它的弃用 我的问题是为什么做出这个决定 有什么好处std stringstr
  • “接口”类似于 boost::bind 的语义

    我希望能够将 Java 的接口语义与 C 结合起来 起初 我用过boost signal为给定事件回调显式注册的成员函数 这非常有效 但后来我发现一些函数回调池是相关的 因此将它们抽象出来并立即注册所有实例的相关回调是有意义的 但我了解到的
  • C++ 函数重载类似转换

    我收到一个错误 指出两个重载具有相似的转换 我尝试了太多的事情 但没有任何帮助 这是那段代码 CString GetInput int numberOfInput BOOL clearBuffer FALSE UINT timeout IN
  • 调用堆栈中的“外部代码”是什么意思?

    我在 Visual Studio 中调用一个方法 并尝试通过检查调用堆栈来调试它 其中一些行标记为 外部代码 这到底是什么意思 方法来自 dll已被处决 外部代码 意味着该dll没有可用的调试信息 你能做的就是在Call Stack窗口中单
  • WebSocket安全连接自签名证书

    目标是一个与用户电脑上安装的 C 应用程序交换信息的 Web 应用程序 客户端应用程序是 websocket 服务器 浏览器是 websocket 客户端 最后 用户浏览器中的 websocket 客户端通过 Angular 持久创建 并且
  • 如何从 ODBC 连接获取可用表的列表?

    在 Excel 中 我可以转到 数据 gt 导入外部数据 gt 导入数据 然后选择要使用的数据源 然后在提供登录信息后 它会给我一个表格列表 我想知道如何使用 C 以编程方式获取该列表 您正在查询什么类型的数据源 SQL 服务器 使用权 看

随机推荐

  • 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 类中实现它 我使用此类