如何在MVVM之后为WPF构建通用/可重用的模式对话框

And*_*uus 11 wpf modal-dialog datatemplate mvvm

我想构建一个通用/可重用的模式对话框,我可以在我们的WPF(MVVM) - WCF LOB应用程序中使用它.

我有一个视图和相关的ViewModel,我想使用对话框显示.Views和ViewModel之间的绑定是使用以类型为目标的DataTemplates完成的.

以下是我能够起草的一些要求:

  • 我更喜欢这个基于Window而不是使用Adorners和控件,它们就像一个模态对话框.
  • 它应该从内容中获得最小尺寸.
  • 它应该以所有者窗口为中心.
  • 窗口不得显示"最小化"和"最大化"按钮.
  • 它应该从内容中获得它的标题.

做这个的最好方式是什么?

Mar*_*ann 11

我通常通过将此接口注入适当的ViewModel来解决这个问题:

public interface IWindow
{
    void Close();

    IWindow CreateChild(object viewModel);

    void Show();

    bool? ShowDialog();
}
Run Code Online (Sandbox Code Playgroud)

这允许ViewModels生成子窗口并以无模式方式显示它们.

IWindow的可重用实现是这样的:

public class WindowAdapter : IWindow
{
    private readonly Window wpfWindow;

    public WindowAdapter(Window wpfWindow)
    {
        if (wpfWindow == null)
        {
            throw new ArgumentNullException("window");
        }

        this.wpfWindow = wpfWindow;
    }

    #region IWindow Members

    public virtual void Close()
    {
        this.wpfWindow.Close();
    }

    public virtual IWindow CreateChild(object viewModel)
    {
        var cw = new ContentWindow();
        cw.Owner = this.wpfWindow;
        cw.DataContext = viewModel;
        WindowAdapter.ConfigureBehavior(cw);

        return new WindowAdapter(cw);
    }

    public virtual void Show()
    {
        this.wpfWindow.Show();
    }

    public virtual bool? ShowDialog()
    {
        return this.wpfWindow.ShowDialog();
    }

    #endregion

    protected Window WpfWindow
    {
        get { return this.wpfWindow; }
    }

    private static void ConfigureBehavior(ContentWindow cw)
    {
        cw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
        cw.CommandBindings.Add(new CommandBinding(PresentationCommands.Accept, (sender, e) => cw.DialogResult = true));
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以将此窗口用作可重复使用的主机窗口.没有代码隐藏:

<Window x:Class="Ploeh.Samples.ProductManagement.WpfClient.ContentWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:self="clr-namespace:Ploeh.Samples.ProductManagement.WpfClient"
        xmlns:pm="clr-namespace:Ploeh.Samples.ProductManagement.PresentationLogic.Wpf;assembly=Ploeh.Samples.ProductManagement.PresentationLogic.Wpf"
        Title="{Binding Path=Title}"
        Height="300"
        Width="300"
        MinHeight="300"
        MinWidth="300" >
    <Window.Resources>
        <DataTemplate DataType="{x:Type pm:ProductEditorViewModel}">
            <self:ProductEditorControl />
        </DataTemplate>
    </Window.Resources>
    <ContentControl Content="{Binding}" />
</Window>
Run Code Online (Sandbox Code Playgroud)

您可以在我的书中阅读更多相关信息(以及下载完整的代码示例).


And*_*uus 7

我正在回答我自己的问题,以帮助其他人找到我在一个地方努力寻找的所有答案.上面的内容似乎是一个直截了当的问题,实际上存在多个问题,我希望在下面充分回答.

开始.

用作通用对话框的WPF窗口可能如下所示:

<Window x:Class="Example.ModalDialogView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ex="clr-namespace:Example"
        Title="{Binding Path=mDialogWindowTitle}" 
        ShowInTaskbar="False" 
        WindowStartupLocation="CenterOwner"
        WindowStyle="SingleBorderWindow"
        SizeToContent="WidthAndHeight"
        ex:WindowCustomizer.CanMaximize="False"
        ex:WindowCustomizer.CanMinimize="False"
        >
    <DockPanel Margin="3">
        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" FlowDirection="RightToLeft">
            <Button Content="Cancel" IsCancel="True" Margin="3"/>
            <Button Content="OK" IsDefault="True" Margin="3" Click="Button_Click" />
        </StackPanel>
        <ContentPresenter Name="WindowContent" Content="{Binding}"/>
    </DockPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

在MVVM之后,显示对话框的正确方法是通过中介.要使用中介,通常还需要一些服务定位器.有关调解员的具体细节,请查看此处.

我解决的解决方案涉及实现IDialogService接口,该接口通过简单的静态ServiceLocator解析.这篇优秀的代码项目文章详细介绍了这一点.在文章论坛中记下这条消息.此解决方案还解决了通过ViewModel实例发现所有者窗口的问题.

使用此接口,您可以调用IDialogService.ShowDialog(ownerViewModel,dialogViewModel).现在,我是从所有者ViewModel调用它,这意味着我的ViewModel之间有很多引用.如果您使用聚合事件,您可能会从指挥中调用此事件.

在最终将在对话框中显示的视图上设置最小大小不会自动设置对话框的最小大小.此外,由于对话框中的逻辑树包含ViewModel,因此您不能只绑定到WindowContent元素的属性.这个问题与我的解决方案有关.

我上面提到的答案还包括将窗口置于所有者中心的代码.

最后,禁用最小化和最大化按钮是WPF本身无法做到的.最优雅的解决方案恕我直言正在使用.