【问题标题】:Designing a "busy" dialog using InteractionRequests使用 InteractionRequests 设计“忙碌”对话框
【发布时间】:2013-01-30 08:56:04
【问题描述】:

我目前正在使用 Prism 的 InteractionRequest 来显示新窗口。我使用它们进行简单的确认,以及在sample here 之后显示带有自定义视图/视图模型的窗口窗口。无论如何,在所有这些情况下,我都会显示窗口,并且窗口上的某个按钮负责关闭它。我想显示一个窗口并让调用它的对象负责关闭它。

这是我的实现:

动作通知

public abstract class ActionNotification: Notification, INotifyPropertyChanged, IPopupWindowActionAware
{
    public event PropertyChangedEventHandler PropertyChanged;

    // IPopupWindowActionAware 
    public System.Windows.Window HostWindow { get; set; } // Set when the "action" in the view is triggered
    public Notification HostNotification { get; set; } // Set when the "action" in the view is triggered

    public ActionNotification(string content)
    {
        this.Content = content;
    }

    public void CompleteAction()
    {
        if (this.HostWindow != null)
        {
            this.HostWindow.Close();
        }
    }

    // INotifyPropertyChange implementation
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

调用方法

/// <summary>
/// Pushes a unit of work onto a separate thread and notifies the view to display an action notification
/// </summary>
/// <param name="actionNotification">The notification object for the view to display</param>
/// <param name="act">The unit of work to perform on a separate thread</param>
private void DoWorkAndRaiseAction(ActionNotification actionNotification, Action act)
{
    Task.Factory.StartNew(() =>
    {
        try
        {
            act();
        }
        finally
        {
            Application.Current.Dispatcher.Invoke((Action)(() => actionNotification.CompleteAction()));
        }
    });

    ActionInteractionReq.Raise(actionNotification);
}

这一切都很好,但如果在我能够提出InteractionRequest 之前完成“工作”,我会很糟糕。任何人都可以向GUARANTEE提供一些建议,要么在提出请求之前工作尚未完成,否则不要突袭请求?

编辑:我应该补充一点,窗口显示为模式,因此在提出请求后不会执行任何代码,这就是为什么我将工作推到一个单独的任务上

EDIT2:以下是视图与请求交互的方式:

<i:Interaction.Triggers>
    <prism:InteractionRequestTrigger SourceObject="{Binding Path=ActionInteractionReq, Mode=OneWay}">
        <int_req:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" WindowStyle="None" WindowHeight="150" WindowWidth="520">
            <int_req:PopupWindowAction.WindowContent>
                <int_req:ZActionNotificationView/>
            </int_req:PopupWindowAction.WindowContent>
        </int_req:PopupWindowAction>
    </prism:InteractionRequestTrigger>
</i:Interaction.Triggers>

当调用Raise 时,会触发PopupWindowAction 并创建一个新窗口。然后在该窗口上执行ShowDialog

EDIT3:根据 cmets 的建议,我已将PopupWindowAction 包括在内。为简洁起见,我删掉了一些不相关的代码

public class PopupWindowAction : TriggerAction<FrameworkElement>
{
    /*      
       Here is where a few dependency properties live that dictate things like Window size and other stuff, e.g.

        /// <summary>
        /// Determines if the content should be shown in a modal window or not.
        /// </summary>
        public static readonly DependencyProperty IsModalProperty =
            DependencyProperty.Register(
                "IsModal",
                typeof(bool),
                typeof(PopupWindowAction),
                new PropertyMetadata(null));        
    */

    /* 
        Here is where the accessors live for the DPs, e.g.

        /// <summary>
        /// Gets or sets if the window will be modal or not.
        /// </summary>
        public bool IsModal
        {
            get { return (bool)GetValue(IsModalProperty); }
            set { SetValue(IsModalProperty, value); }
        }
    */

    #region PopupWindowAction logic

    /// <summary>
    /// Displays the child window and collects results for <see cref="IInteractionRequest"/>.
    /// </summary>
    /// <param name="parameter">The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference.</param>
    protected override void Invoke(object parameter)
    {
        var args = parameter as InteractionRequestedEventArgs;
        if (args == null)
        {
            return;
        }

        // If the WindowContent shouldn't be part of another visual tree.
        if (this.WindowContent != null && this.WindowContent.Parent != null)
        {
            return;
        }

        var wrapperWindow = this.GetWindow(args.Context); // args.Context here is the Notification object I'm passing to the InteractionRequest

        var callback = args.Callback;
        EventHandler handler = null;
        handler =
            (o, e) =>
            {
                wrapperWindow.Closed -= handler;
                wrapperWindow.Owner = null;
                wrapperWindow.Content = null;
                callback();
            };
        wrapperWindow.Closed += handler;

        if (this.IsModal)
        {
            wrapperWindow.ShowDialog();
        }
        else
        {
            wrapperWindow.Show();
        }
    }

    /// <summary>
    /// Checks if the WindowContent or its DataContext implements IPopupWindowActionAware and IRegionManagerAware.
    /// If so, it sets the corresponding values.
    /// Also, if WindowContent does not have a RegionManager attached, it creates a new scoped RegionManager for it.
    /// </summary>
    /// <param name="notification">The notification to be set as a DataContext in the HostWindow.</param>
    /// <param name="wrapperWindow">The HostWindow</param>
    protected void PrepareContentForWindow(Notification notification, Window wrapperWindow)
    {
        if (this.WindowContent == null)
        {
            return;
        }

        // We set the WindowContent as the content of the window. 
        wrapperWindow.Content = this.WindowContent;

        /* Code removed for brevity */

        // If the WindowContent implements IPopupWindowActionAware, we set the corresponding values.
        IPopupWindowActionAware popupAwareContent = this.WindowContent as IPopupWindowActionAware;
        if (popupAwareContent != null)
        {
            popupAwareContent.HostWindow = wrapperWindow;
            popupAwareContent.HostNotification = notification;
        }

        // If the WindowContent's DataContext implements IPopupWindowActionAware, we set the corresponding values.
        IPopupWindowActionAware popupAwareDataContext = this.WindowContent.DataContext as IPopupWindowActionAware;
        if (popupAwareDataContext != null)
        {
            popupAwareDataContext.HostWindow = wrapperWindow;
            popupAwareDataContext.HostNotification = notification;
        }
    }

    #endregion

    #region Window creation methods

    /// <summary>
    /// Returns the window to display as part of the trigger action.
    /// </summary>
    /// <param name="notification">The notification to be set as a DataContext in the window.</param>
    /// <returns></returns>
    protected Window GetWindow(Notification notification)
    {
        Window wrapperWindow;

        if (this.WindowContent != null)
        {
            wrapperWindow = new Window();
            wrapperWindow.WindowStyle = this.WindowStyle;
            // If the WindowContent does not have its own DataContext, it will inherit this one.
            wrapperWindow.DataContext = notification;
            wrapperWindow.Title = notification.Title ?? string.Empty;               

            this.PrepareContentForWindow(notification, wrapperWindow);
        }
        else
        {
            wrapperWindow = this.CreateDefaultWindow(notification);
            wrapperWindow.DataContext = notification;
        }

        return wrapperWindow;
    }

    private Window CreateDefaultWindow(Notification notification)
    {
        return new DefaultNotificationWindow 
        { 
            NotificationTemplate = this.ContentTemplate, 
            MessageBoxImage = GetImageFromNotification(notification as ZBaseNotification) 
        };
    }        

    #endregion
}

【问题讨论】:

  • 调用.Raise 最终会做什么?谁在打开HostWindowactionNotification 上的值如何更新?
  • @Jon 我已经更新了我的问题,即视图如何与我的视图模型交互(视图模型包含方法DoWorkAndRaiseAction)。我不太清楚你的意思是“它的价值如何在actionNotification 上更新
  • ActionNotification 关闭自己的HostWindow。该属性集的值在哪里?
  • 啊,我明白你的意思了。它设置在PopupWindowAction 内。调用该操作时,我会创建一个新窗口。如果我传入的Notification 对象实现了IPopupWindowActionAware,那么我将HostWindow 的值设置为我刚刚创建的窗口。如果您想了解有关 PopupWindowAction 的更多信息,我最初的问题中链接到的网站包含所有信息。
  • (至少部分)PopupWindowAction 的内部结构是回答这个问题所必需的。最好在问题文本中包含上述评论,或者更好地获取相关代码并将其粘贴。链接的网站总是来来去去,这个问题在几年内该博客不再存在时对任何人都有什么用?如果它将来对任何人都没有用,这难道不是将其关闭为“过于本地化”的主要原因吗?

标签: c# mvvm prism prism-4 interaction


【解决方案1】:

这里的根本问题是启动异步操作的代码和显示窗口的代码只是不合作。基于IPopupWindowActionAware 的设计恕我直言不是很好;在常见情况下推送属性值是可以的,但在这里它开始显示其局限性。

让我们首先考虑一个适用于当前代码的本地化解决方案:

public Window HostWindow { /* call OnPropertyChanged! */ }

public void CompleteAction()
{
    if (this.HostWindow != null)
    {
        this.HostWindow.Close();
    }
    else
    {
        this.PropertyChanged += (o, e) => {
            if (e.PropertyName == "HostWindow" && this.HostWindow != null)
            {
                var hostWindow = this.HostWindow; // prevent closure-related bugs

                // kill it whenever it appears in the future
                hostWindow.Loaded += (o, e) => { hostWindow.Close(); };

                // kill it right now as well if it's been shown already
                // (we cannot assume anything)
                if (hostWindow.IsLoaded)
                {
                    hostWindow.Close();
                }
            }
        };
    }
}

这不是很优雅,但它可以完成工作:如果在窗口已知之前调用CompleteAction,那么当窗口已知时,我们附加一个处理程序,该处理程序在它显示时立即关闭它。双深事件处理程序分配是必要的,因为在我们知道窗口时可能不会显示它。

【讨论】:

  • 感谢您的解决方案。我同意,不是很优雅,但它确实有效。您对更优雅的方法有什么建议吗?我认为您希望看到我完全摆脱 IPopupWindowActionAware,但我想不出另一种方式让我的 ViewModel(即 Notification 对象)与当前作为 DataContext 的 Window 进行交互。
  • @Ryan:IPopupWindowActionAware 的主要问题是它不提供回调。在这种情况下,解决方案是可能的,因为ActionNotification 也“发生”实现了INotifyPropertyChanged。我不建议摆脱界面,但评论可能会很好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-07
  • 2011-06-12
  • 1970-01-01
  • 2010-10-10
  • 2011-05-04
  • 2019-01-20
  • 2012-02-01
相关资源
最近更新 更多