65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (13投票s)

2011年9月25日

CPOL

3分钟阅读

viewsIcon

50089

downloadIcon

2250

如何使用自定义交互请求和进度条来报告异步命令的进度。

Interaction.png

引言

我确信,如果您一直在使用 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 捕获了进度通知,它将通过使用 InteractionRequestView 发出一个 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
© . All rights reserved.