C# MVVM对话服务替代方案

C# MVVM对话服务替代方案,c#,wpf,mvvm,C#,Wpf,Mvvm,在学习如何使用MVVM模式编程时,我遇到了一个常见问题—显示ViewModels中的各种对话框 起初对我来说很简单。我创建了一个IWindowService接口,并在WindowsService类中实现了它。我使用这个类来启动新的视图窗口 但是我需要MessageBox风格的对话框。所以我创建了一个IDialogService和一个DialogService类。我对打开/保存文件对话框也做了同样的操作 我注意到创建ViewModel实例变得非常复杂: ViewModel VM = new Vie

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

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

但是我需要MessageBox风格的对话框。所以我创建了一个IDialogService和一个DialogService类。我对打开/保存文件对话框也做了同样的操作

我注意到创建ViewModel实例变得非常复杂:

ViewModel VM = new ViewModel(Data, AnotherData, MoreData, WindowService, DialogService, FileDialogService, AnotherService);
我试图将所有服务合并到一个FrontendService类中,但这使得维护变得非常困难,IfFrontEndService接口变得非常“臃肿”


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

对话框或
窗口通常与视图相关。如果您想实现MVVM,那么必须将视图与视图模型分开。MVVM将帮助您实现这一点


MVVM依赖关系图和职责概述

依赖关系图显示视图依赖于视图模型。为了解耦,这种依赖关系是单向的。这是可能的,因为绑定机制

由于对话框是视图的一部分,视图模型调用的
Window.Show()
将添加一个箭头,该箭头指向视图模型之间的视图。这意味着视图模型现在取决于视图。
现在,您已经在视图上创建了显式依赖关系,您将遇到新的问题:如果您决定显示不同的窗口或用弹出窗口替换对话框(或者在任何时候修改视图),则必须修改视图模型。这正是MVVM设计要避免的。在为视图模型编写单元测试时,必须使
ShowDialog()
成员未经测试

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

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

如果您需要一个专门的服务来处理此问题,请让它完全在视图中运行。
由于视图模型是数据相关的(数据表示),因此它只能标记视图可以触发的数据相关状态(例如数据验证错误,尽管建议的方法是在视图模型上实现)

因此,我的建议是让您的视图模型不承担与视图相关的责任,并让其业务只关注模型数据表示。
这样,您就不会违反MVVM模式,并且还可以保持构造函数较小或为空(在您的情况下)。更小的构造函数意味着更少的依赖关系

最简单的方法是从代码隐藏或路由事件处理程序显示对话框。然后使用
ICommand
或更新绑定,将收集的数据(如果这是一个输入对话框)传递给视图模型(如果需要)

或者设计自己的对话框

这是一个模式对话框的示例,只能使用XAML显示和隐藏该对话框。不涉及C#。它使用事件触发器设置对话框网格的
可见性
的动画(或者设置
不透明度
的动画)。该事件由
按钮触发。对于不同的场景,
按钮
可以简单地替换为
数据触发器
或使用
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>

这是一个对话框:

您可以封装对话框并将实现移动到专用的
控件中,例如
DialogControl
,该控件更易于在整个应用程序中使用(无重复代码,改进了处理)

编辑以显示错误的 以下是由视图模型操作的对话框服务引入的依赖项:


如您所见,我们现在有一个箭头(依赖项),它从视图模型指向视图。现在,视图中的更改将反映到视图模型并影响实现。这是因为视图模型现在包含在视图逻辑中,在这种特殊情况下还包括用户交互逻辑。

使用DependencyInjection容器来构造视图模型。不要从VM显示对话框-这是一种常见的误用。相反,您应该从视图触发它,因为总是显示该对话框是对用户与视图交互的响应。相关:,我看不出我是如何违反MVVM原则的。我为ViewModel提供了一个服务,使其不受用户交互的影响,这正是我所做的。当我决定对ViewModel进行单元测试时,我可以只提供一个不做任何事情的虚拟服务。ViewModel不关心服务做什么。它只调用服务的方法并期望结果。不管服务是否显示对话框,你都在隐藏它。仅仅因为视图模型没有显式地显示,它不会删除依赖项。该服务由视图模型使用。该图如下所示:视图模型->服务->控制。由于服务在视图的域中运行,因此视图模型依赖于视图。您刚刚从视图模型中提取了对话框逻辑,并将其放入视图模型仍然必须知道的另一个类中。它应该是:控制->服务->视图模型。对话框逻辑(何时、为什么、显示哪个和如何显示)