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

时间:2010-06-02 08:22:51

标签: wpf mvvm datatemplate modal-dialog

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

我想要使用对话框显示视图和关联的ViewModel。视图和ViewModel之间的绑定是使用以类型为目标的DataTemplates完成的。

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

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

这样做的最佳方式是什么?

2 个答案:

答案 0 :(得分:11)

我通常通过将此接口注入适当的ViewModel来处理此问题:

public interface IWindow
{
    void Close();

    IWindow CreateChild(object viewModel);

    void Show();

    bool? ShowDialog();
}

这允许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));
    }
}

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

<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>

您可以在my book中阅读有关此内容的更多信息(以及下载完整的代码示例)。

答案 1 :(得分: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>

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

我解决的解决方案涉及实现通过简单的静态ServiceLocator解析的IDialogService接口。 This优秀的代码项目文章详细介绍了该文章。请注意文章论坛中的this消息。此解决方案还解决了通过ViewModel实例发现所有者窗口的问题。

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

在视图中设置最终将在对话框中显示的最小大小不会自动设置对话框的最小大小。此外,由于对话框中的逻辑树包含ViewModel,因此您不能只绑定到WindowContent元素的属性。 This问题可以解答我的问题。

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

最后,禁用最小化和最大化按钮是WPF本身无法做到的。最优雅的解决方案是IMHO使用this