Как создать общий/повторно используемый модальный диалог для 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>
Вы можете узнать больше об этом (а также загрузить полный образец кода) в моей книге.