C# MVVM对话服务替代方案
在学习如何使用MVVM模式编程时,我遇到了一个常见问题—显示ViewModels中的各种对话框 起初对我来说很简单。我创建了一个IWindowService接口,并在WindowsService类中实现了它。我使用这个类来启动新的视图窗口 但是我需要MessageBox风格的对话框。所以我创建了一个IDialogService和一个DialogService类。我对打开/保存文件对话框也做了同样的操作 我注意到创建ViewModel实例变得非常复杂:C# MVVM对话服务替代方案,c#,wpf,mvvm,C#,Wpf,Mvvm,在学习如何使用MVVM模式编程时,我遇到了一个常见问题—显示ViewModels中的各种对话框 起初对我来说很简单。我创建了一个IWindowService接口,并在WindowsService类中实现了它。我使用这个类来启动新的视图窗口 但是我需要MessageBox风格的对话框。所以我创建了一个IDialogService和一个DialogService类。我对打开/保存文件对话框也做了同样的操作 我注意到创建ViewModel实例变得非常复杂: ViewModel VM = new Vie
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不关心服务做什么。它只调用服务的方法并期望结果。不管服务是否显示对话框,你都在隐藏它。仅仅因为视图模型没有显式地显示,它不会删除依赖项。该服务由视图模型使用。该图如下所示:视图模型->服务->控制。由于服务在视图的域中运行,因此视图模型依赖于视图。您刚刚从视图模型中提取了对话框逻辑,并将其放入视图模型仍然必须知道的另一个类中。它应该是:控制->服务->视图模型。对话框逻辑(何时、为什么、显示哪个和如何显示)