MVVM PRISM: 使用泛型交互请求的模态窗口






4.89/5 (17投票s)
泛型如何帮助我们在 WPF PRISM 中实现模态窗口


引言
本文旨在作为我的另一篇文章 MVVM PRISM: 带有异步命令的进度条交互请求 的延续。在这里,我将尝试解释一种通过使用 PRISM 及其交互方式在 WPF 中使用模态窗口的另一种有趣的方式。我不会详细解释每个组件的每个功能,因为在上述相关文章中已经完成了。
我假设您熟悉 WPF、PRISM 和 MVVM 模式。
PRISM 如何尝试获取交互?
如果您阅读过 PRISM 的官方开发者指南,您可能已经看到了文档中简要解释实现模态交互的部分,并且您可能难以理解或获得自定义方式来扩展 IInteractionRequest
以获得比 MessageBox
更多的功能。
好的,让我们专注于这个主题。 基本上,主要思想是通过使用属性绑定从视图模型触发 UI 线程上的 TriggerAction<T>
。 一旦触发器被启动,它将创建 UserControl
(模态视图),它将被插入到包含 TriggerAction<T>
的元素的控件集合中。
对于 UI 线程刚刚创建的视图,它将为按钮设置一个回调,以便退出模态视图并使用任何结果返回主视图。
跟随线路
我项目的最终目标是找到一种使用 MVVM 模式显示模态视图,在类中呈现封装的数据的方法,当然,尽量以最简单的方式来做。然后,让我们尝试泛型。
因此,按照规范,我通过将一个泛型类 (GenericInteractionRequestEventArgs<T>
) 作为参数传递来触发触发器,该类包含一个回调和一个包含要在模态视图中呈现的所有数据的对象。回调接受该泛型类型作为参数。
public class GenericInteractionRequestEventArgs<T> : EventArgs
{
public GenericInteractionRequestEventArgs
(T _entity, Action<T> _callback, Action _cancelCallback)
{
this.CancelCallback = _cancelCallback;
this.Callback = _callback;
this.Entity = _entity;
}
public Action CancelCallback { get; private set; }
public Action<T> Callback { get; private set; }
public T Entity { get; private set; }
}
public class GenericInteractionAction<T> : TriggerAction<Grid>
{
private Dictionary<UIElement, bool> currentState = new Dictionary<UIElement, bool>();
public static readonly DependencyProperty DialogProperty =
DependencyProperty.Register("Dialog", typeof(GenericInteractionDialogBase<T>), typeof(GenericInteractionAction<T>), new PropertyMetadata(null));
public GenericInteractionDialogBase<T> Dialog
{
get { return (GenericInteractionDialogBase<T>)GetValue(DialogProperty); }
set { SetValue(DialogProperty, value); }
}
protected override void Invoke(object parameter)
{
var args = parameter as GenericInteractionRequestEventArgs<T>;
this.SetDialog(args.Entity, args.Callback, args.CancelCallback, null);
}
private void SetDialog(T entity, Action<T> callback, Action cancelCallback, UIElement element)
{
if (this.Dialog is Views.IGenericInteractionView<T>)
{
Views.IGenericInteractionView<T> view = this.Dialog as Views.IGenericInteractionView<T>;
view.SetEntity(entity);
EventHandler handler = null;
handler = (s, e) =>
{
this.Dialog.ConfirmEventHandler -= handler;
this.Dialog.CancelEventHandler -= handler;
this.AssociatedObject.Children.Remove(this.Dialog);
if (e.ToString() == InteractionType.OK.ToString())
callback(view.GetEntity());
else
cancelCallback();
this.RestorePreviousState();
};
this.Dialog.ConfirmEventHandler += handler;
this.Dialog.CancelEventHandler += handler;
this.Dialog.SetValue(Grid.RowSpanProperty, this.AssociatedObject.RowDefinitions.Count == 0 ? 1 : this.AssociatedObject.RowDefinitions.Count);
this.Dialog.SetValue(Grid.ColumnSpanProperty, this.AssociatedObject.ColumnDefinitions.Count == 0 ? 1 : this.AssociatedObject.ColumnDefinitions.Count);
this.AssociatedObject.Children.Add(this.Dialog);
this.SaveCurrentState();
}
}
private void SaveCurrentState()
{
IEnumerator en = this.AssociatedObject.Children.GetEnumerator();
while (en.MoveNext())
{
if (en.Current != this.Dialog)
{
UIElement element = (UIElement)en.Current;
this.currentState.Add(element, element.IsEnabled);
element.IsEnabled = false;
}
}
}
private void RestorePreviousState()
{
foreach (UIElement element in this.currentState.Keys)
element.IsEnabled = this.currentState[element];
this.currentState.Clear();
}
}
一旦触发器被调用,它会创建模态视图,并通过使用它的适配器,它设置视图模型的泛型属性以实现属性绑定。然后,如果我们修改一些数据并按下模态视图的按钮,它会通过调用回调并检索已被修改的对象返回到主视图模型。所有这些都得益于适配器,它负责在 UI 线程和视图模型之间进行交互。为了实现所有这些,我们需要使用泛型 TriggerAction
。 该 TriggerAction
将包含一个泛型 DependencyProperty
,它将是我们想要显示的模态视图。 必须像在泛型 TriggerAction
中定义的那样对该模态视图进行类型化。
public interface IGenericAdapter<T>
{
IGenericViewModel<T> ViewModel { get; }
}
public interface IGenericInteractionView<T>
{
void SetEntity(T entity);
T GetEntity();
}
public interface IGenericViewModel<T> : IGenericInteractionView<T>, INotifyPropertyChanged
{
T Entity { get; set; }
}
另一方面,我们得到了 GenericInteractionDialogBase<T>
,它扩展了 UserControl
,因此,它是每个模态视图的代码隐藏。 它将负责将回调路由到视图模型。
public enum InteractionType
{
OK,
Cancel
}
public class GenericInteractionDialogBase<T> : UserControl
{
class InteractionEventArgs : EventArgs
{
internal InteractionType Type { get; private set; }
internal InteractionEventArgs(InteractionType _type)
{
this.Type = _type;
}
public override string ToString()
{
return this.Type.ToString();
}
}
public GenericInteractionDialogBase() { }
public event EventHandler ConfirmEventHandler;
public event EventHandler CancelEventHandler;
public void Ok()
{
this.OnClose(new InteractionEventArgs(InteractionType.OK));
}
public void Cancel()
{
this.OnClose(new InteractionEventArgs(InteractionType.Cancel));
}
private void OnClose(InteractionEventArgs e)
{
var handler = (e.Type == InteractionType.OK) ?
this.ConfirmEventHandler : this.CancelEventHandler;
if (handler != null)
{
handler(this, e);
}
}
}
Using the Code
从现在开始,如果我们想获得自定义模态视图,我们只需要创建三个类。
以呈现 Animal
类的模态视图为例,这些类是...
首先,我们需要为 Animal
创建一个 TriggerAction
,实现泛型类。 这是必需的,以便用于从主视图触发触发器。
public class AnimalInteractionAction : GenericInteractionAction<Animal>
{
}
其次,我们必须为 Animal
创建一个扩展 GenericInteractionDialogBase
的类。 这将是模态视图,一旦 TriggerAction
被触发,它使用此 UserControl
作为 TriggerAction
中的 DependencyProperty
,因此,我们能够检索模态视图并设置其属性。
public class AnimalInteractionDialog : GenericInteractionDialogBase<animal>
{
}
第三,我们将创建模态视图,设置所有 XAML 并在其代码隐藏中实现方法和属性。
public partial class AnimalInteractionDialogView :
AnimalInteractionDialog, IGenericInteractionView<Animal>, IGenericAdapter<Animal>
{
private readonly IGenericAdapter<Animal> adapter;
public AnimalInteractionDialogView()
{
this.adapter = new GenericAdapter<Animal>();
this.DataContext = this.ViewModel;
InitializeComponent();
}
public void SetEntity(Animal entity)
{
this.ViewModel.SetEntity(entity);
}
public Animal GetEntity()
{
return this.ViewModel.GetEntity();
}
public IGenericViewModel<Animal> ViewModel
{
get { return this.adapter.ViewModel; }
}
}
最后,我们只需要在主视图的 XAML 中设置触发器绑定。 看起来像这样
<Window x:Class="GenericInteractionRequest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity.
InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:interactionRequest="clr-namespace:GenericInteractionRequest.Views"
xmlns:interactions="clr-namespace:GenericInteractionRequest.Interactions"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=
System.Windows.Interactivity"
Title="MainWindow" Height="350" Width="525">
<Grid>
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger
SourceObject="{Binding NotificationToPerson}">
<interactions:PersonInteractionAction>
<interactions:PersonInteractionAction.Dialog>
<interactionRequest:PersonInteractionDialogView />
</interactions:PersonInteractionAction.Dialog>
</interactions:PersonInteractionAction>
</prism:InteractionRequestTrigger>
<prism:InteractionRequestTrigger
SourceObject="{Binding NotificationToAnimal}">
<interactions:AnimalInteractionAction>
<interactions:AnimalInteractionAction.Dialog>
<interactionRequest:AnimalInteractionDialogView />
</interactions:AnimalInteractionAction.Dialog>
</interactions:AnimalInteractionAction>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
<Button Content="Get People" Height="35" HorizontalAlignment="Left"
Margin="12,12,0,0" Name="button1" VerticalAlignment="Top" Width="102"
Command="{Binding Path=SetPeopleCommand}"/>
<Button Content="Get Animal" Height="35" HorizontalAlignment="Left"
Margin="120,12,0,0" Name="button2" VerticalAlignment="Top" Width="107"
Command="{Binding Path=SetAnimalCommand}"/>
<TextBlock Height="229" HorizontalAlignment="Left" Margin="12,70,0,0"
Name="textBlock1" Text="{Binding Path=Output}"
VerticalAlignment="Top" Width="479" />
</Grid>
</Window>
历史
- 2011 年 10 月 16 日 - 第 1 次修订
- 2012 年 2 月 3 日 - 第 2 次修订(包括取消回调支持)
- 2012 年 9 月 19 日 - 第 3 次修订(启用和禁用模态视图后面的控件)