MVVM PRISM:使用异步命令的进度条交互请求






4.81/5 (13投票s)
如何使用自定义交互请求和进度条来报告异步命令的进度。

引言
我确信,如果您一直在使用 PRISM 和 MVVM 模式进行项目开发,那么在某个时候您必须处理交互请求,无论是为了呈现一些数据,还是为了模拟类似 WinForms 中使用 MessageBox
所做的交互。
如果您是第一次遇到交互请求,可能会感到有些困惑。 此外,关于它的信息有限,即使对于 WPF 桌面版本也是如此。 这就是我决定发表这篇文章的原因。 我希望它至少能有所帮助,并展示交互请求在您的应用程序中可以成为一个非常强大的资源。
本文旨在展示如何使用自定义交互请求对话框,该对话框包含一个进度条,该进度条绑定到在后台执行某些操作的异步命令。 对话框报告进度并允许用户取消正在执行作业的 BackgroundWorker
。 一旦线程完成,对话框会自动关闭。
在本文中,我假设您熟悉 MVVM 模式和 WPF PRISM。
理解各部分
PRISM 允许您通过使用 InteractionRequest<T>
与来自 View Model 的 GUI 交互。 对于本文,我使用了 InteractionRequest<Notification>
,因为我只想显示一些数据并取消一个操作。 从我们的 View Model,我们可以向 GUI 的线程发出一个 Notification
,以便由触发器捕获。 一旦触发器被启动,它会调用一个 TriggerAction<T>
,它是用于创建和操作对话框的处理程序。 在这种情况下,它是一个 TriggerAction<Grid>
,因为触发器的定义由一个 Grid
拥有。
以下代码示例显示了 MainWindow
的 XAML 看起来是什么样的
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Content="Sync Command" Command="{Binding Path=CommonCommand}" Grid.Row="0" />
<Button Content="Async Command" Command="{Binding Path=AsyncCommand}" Grid.Row="1" />
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding NotificationToProgress}">
<interactions:InteractionProgressDialog>
<interactions:InteractionProgressDialog.Dialog>
<interactionRequest:ProgressbarInteractionDialog />
</interactions:InteractionProgressDialog.Dialog>
</interactions:InteractionProgressDialog>
</prism:InteractionRequestTrigger>
<prism:InteractionRequestTrigger SourceObject="{Binding NotificationToClose}">
<interactions:InteractionCloseDialog>
</interactions:InteractionCloseDialog>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
</Grid>
交互对话框是使用 MVVM 模式设计的,但添加了一个新功能,以便从 View 与其 View Model 交互,并通过使用属性绑定实现数据通信。 如果您熟悉 MVVM,您可能听说过适配器。 适配器只是 View
和 ViewModel
之间的另一层,具有清晰的接口,可以从一个地方到另一个地方进行通信以实现绑定。
以下代码示例显示了适配器和 View Model 的接口。
public interface IProgressbarView
{
void SetProggessStep(int step);
void SetProgressMessage(string message);
void SetTitle(string title);
}
public interface IProgressbarViewModel : Views.IProgressbarView, INotifyPropertyChanged
{
int Step { get; set; }
string Message { get; }
string Title { get; }
}
public interface IProgressbarAdapter : Views.IProgressbarView
{
IProgressbarViewModel ViewModel { get; }
}
那么我们得到了什么…
首先,我们有一个带有 BackgroundWorker
的 Command,它完成了所有脏活。 该命令实现了 INotifyPropertyChanged
,通过使用它,它将向主 View Model 报告进度。
一旦主 View Model 从 AsynCmd
捕获了进度通知,它将通过使用 InteractionRequest
向 View
发出一个 Notification
。
void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case IS_BUSY_PROPERTY:
AsynCmd cmd = this.AsyncCommand as AsynCmd;
if (!cmd.IsCancelPending)
{
this.PropertyChanged(this,
new PropertyChangedEventArgs(IS_EXECUTING_PROPERTY));
if (cmd.IsBusy)
App.Current.Dispatcher.Invoke
(new InteractionDelegate(this.ShowInteraction));
else
App.Current.Dispatcher.Invoke
(new InteractionDelegate(this.CloseInteraction));
}
break;
case PROGRESS_PROPERTY:
App.Current.Dispatcher.Invoke(new InteractionDelegate(this.SetProgressStep));
break;
}
}
private void ShowInteraction()
{
(this.NotificationToProgress as ProgressRequest)
.ShowDialog(TITLE_DIALOG, INITIAL_MESSAGE_DIALOG, this.CancelProcess);
}
private void CloseInteraction()
{
(this.NotificationToClose as CloseRequest).Close();
}
private void SetProgressStep()
{
(this.NotificationToProgress as ProgressRequest)
.SetProgressStep(this.Progress,
string.Format(PROGRESS_MESSAGE_DIALOG, this.Progress));
}
private void CancelProcess(Notification notification)
{
(this.AsyncCommand as AsynCmd).CancelProcess();
}
然后,View 的触发器将调用 InteractionProgressDialog
,它在第一次调用时将创建对话框的 View,并将该 View 作为控件添加到其容器的控件集合中。
对于下一个通知,InteractionProgressDialog
将使用适配器设置对话框的 View Model 的属性。
protected override void Invoke(object parameter)
{
var args = parameter as InteractionRequestedEventArgs;
if (args != null)
{
Notification notification = args.Context;
UIElement element = InteractionDialogBase.FindDialog(this.AssociatedObject);
this.SetDialog(notification, args.Callback, element);
}
}
private void SetDialog
(Notification notification, Action callback, UIElement element)
{
ProgressRequest.ProgressMessage msg =
notification.Content as ProgressRequest.ProgressMessage;
if (this.Dialog is IProgressbarView)
{
IProgressbarView view = (IProgressbarView)this.Dialog;
view.SetProggessStep(msg.Step);
view.SetProgressMessage(msg.Message);
if (msg.Title != null)
view.SetTitle(msg.Title);
}
if (element == null)
{
EventHandler handler = null;
handler = (s, e) =>
{
this.Dialog.Closed -= handler;
this.AssociatedObject.Children.Remove(this.Dialog);
callback();
};
if (msg.Initialize)
{
this.Dialog.Closed += 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);
}
}
}
一旦 AsynCmd
完成其工作,主 View Model 将使用 NotificationToClose
报告 View,一切都已完成。 该通知将被另一个 View 的触发器捕获,该触发器使用另一个处理程序 (InteractionCloseDialog
) 来关闭对话框。
一些思考
在本文中,我讨论了一种在 MVVM 中实现模态视图的有趣方法,但是,我并不是说这是正确的方法。 我只是发表这篇文章,因为我知道 InteractionRequest
是 PRISM 中一个稍微令人困惑的部分,因为它确实让我疯狂了一段时间。
实现相同结果的另一种方法是,在主 View 中使用隐藏的对话框,并将所有绑定集中到主 View Model,就像在 HTML 中使用 JavaScript 和 div
所做的那样。
我很乐意收到关于它的反馈,所以请随意写评论。
历史
- 2011 年 9 月 25 日 - 第 1st 版