Как создать общий/повторно используемый модальный диалог для WPF после MVVM

Я хотел бы создать универсальный/повторно используемый модальный диалог, который я могу использовать в нашем приложении WPF (MVVM) - WCF LOB.

У меня есть Views и связанные ViewModels, которые я хотел бы отображать с помощью диалоговых окон. Связывание между представлениями и ViewModels выполняется с использованием целевых DataTemplates с типом.

Вот некоторые требования, которые я смог создать:

  • Я предпочитаю, чтобы это было основано на Window вместо использования Adorners и элементов управления, которые действуют как модальный диалог.
  • Он должен получить минимальный размер контента.
  • Он должен располагаться в окне владельца.
  • В окне не должно отображаться кнопки "Минимизировать" и "Максимизировать".
  • Он должен получить свое название из контента.

Каков наилучший способ сделать это?

Ответ 1

Я отвечаю на свой вопрос, чтобы помочь другим найти все ответы, которые я изо всех сил пытался найти в одном месте. То, что выше похоже на прямую проблему, на самом деле представляет несколько проблем, на которые я надеюсь ответить ниже.

Здесь.

Ваше окно 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 правильный способ показать диалог - через посредника. Чтобы использовать медиатор, вам обычно требуется некоторый локатор обслуживания. Для получения подробной информации о посреднике посмотрите здесь.

Решение, на котором я остановился, включал реализацию интерфейса IDialogService, который разрешен простым статическим ServiceLocator. В этой превосходной статье кодекса есть подробности об этом. Обратите внимание на это сообщение в форуме статей. Это решение также решает проблему открытия окна владельца через экземпляр ViewModel.

Используя этот интерфейс, вы можете вызвать IDialogService.ShowDialog(ownerViewModel, dialogViewModel). На данный момент я называю это от владельца ViewModel, то есть у меня есть жесткие ссылки между моими ViewModels. Если вы используете агрегированные события, вы, вероятно, вызовете это из проводника.

Установка минимального размера в представлении, который в конечном итоге будет отображаться в диалоговом окне, автоматически не устанавливает минимальный размер диалогового окна. Кроме того, поскольку логическое дерево в диалоговом окне содержит ViewModel, вы не можете просто привязываться к свойствам элемента WindowContent. Этот вопрос имеет ответ с моим решением.

Ответ, о котором я упоминал выше, также включает код, который центрирует окно владельца.

Наконец, отключение кнопок минимизации и максимизации - это то, что WPF не может сделать изначально. Самое элегантное решение IMHO использует this.

Ответ 2

Я обычно общаюсь с этим, вводя этот интерфейс в соответствующие ViewModels:

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>

Вы можете узнать больше об этом (а также загрузить полный образец кода) в моей книге.