Closing a view triggered by an InteractionRequest from the same class that owns the InteractionRequest

Topics: Prism v4 - WPF 4
Feb 14, 2013 at 5:23 AM
Edited Feb 14, 2013 at 8:23 PM
I've followed this post by Damian to use custom views and viewmodels in modal dialogs displayed to the user by raising InteractionRequests. Everything is going well and I've converted all of my notifications to the user to now use InteractionRequests. I've built my own "MessageBox" variant with the same code.

Now I want to use an interaction request as a means to display a "busy" dialog. I want to raise the InteractionRequest, do some work, and then interact with the IPopupWindowActionAware object I passed to the InteractionRequest to close the HostWindow. My question is, what is an appropriate way to do this. At first, I wrapped the "work" and the call to close the host window inside of a Task, started it, and then raised the InteractionRequest.
var notificationRequest = new InteractionRequest<MyActionNotification>() // Bound to an action that'll display a modal window

var notification = new MyActionNotification();

Task.Factory.StartNew(() =>
{
    doWork();
    Application.Current.Dispatcher.BeginInvoke((Action)(() => notification.CloseWindow()));
});

notificationRequest.Raise();
But by doing this, I risk the case that "doWork" completes before I raise the "notificationRequest" and then I end up stuck after the Raise because the thread is stuck on a ShowDialog
Developer
Feb 14, 2013 at 8:18 PM
Hi,

I am glad you found my blog post useful.

Regarding the problem you are describing, I don't have much experienced in the use of asynchronous tasks, but as a possible approach I believe you could try raising the notification in the asynchronous task doing something like this:
var notification = new MyActionNotification();

Task.Factory.StartNew(() =>
{
    Application.Current.Dispatcher.BeginInvoke( (Action) ( () => this.notificationRequest.Raise(notification) ) );
    doWork();
    Application.Current.Dispatcher.BeginInvoke( (Action) ( () => notification.CloseWindow() ) );
});
Based on my understanding, by doing this I believe that the notification should be raised before "doing the work".

I hope this helps,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Feb 14, 2013 at 8:31 PM
Thanks so much for the reply. I'll give that a shot. Another question I have, and I'm not sure if it should be another discussion topic, is related to your design of IPopupWindowActionAware. I was thinking that instead of it having a "Window" object, it could have an "IWindow" object (similar to the IWindow in the Stock Trader RI) and then my InteractionRequest context would get this IWindow object. This would allow for easier testing I believe, but I'm not sure if it's the better route to take? What do you think? I was thinking it would look something like this:
/// <summary>
/// Defines the interface for the Dialogs that are shown by <see cref="PopupWindowAction"/>.
/// This allows us to mock a different "window" object when running unit tests with InteractionRequests and the PopupWindowAction
/// </summary>
public interface IWindow
{
        /// <summary>
        /// Ocurrs when the <see cref="IWindow"/> is closed.
        /// </summary>
        event EventHandler Closed;

        /// <summary>
        /// Gets or sets the content for the <see cref="IWindow"/>.
        /// </summary>
        object Content { get; set; }

        /// <summary>
        /// Gets or sets the owner control of the <see cref="IWindow"/>.
        /// </summary>
        object Owner { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="System.Windows.Style"/> to apply to the <see cref="IWindow"/>.
        /// </summary>
        Style Style { get; set; }

        /// <summary>
        /// Gets or sets the data context for the <see cref="IWindow"/>.
        /// </summary>
        object DataContext { get; set; }

        /// <summary>
        /// Gets or sets the height for the <see cref="IWindow"/>.
        /// </summary>
        double Height { get; set; }

        /// <summary>
        /// Gets or sets the width for the <see cref="IWindow"/>.
        /// </summary>
        double Width { get; set; }

        /// <summary>
        /// Gets or sets the title for the <see cref="IWindow"/>.
        /// </summary>
        string Title { get; set; }

        /// <summary>
        /// Opens the <see cref="IWindow"/>.
        /// </summary>
        void Show();

        /// <summary>
        /// Opens the <see cref="IWindow"/> as a modal window.
        /// </summary>
        void ShowDialog();

        /// <summary>
        /// Closes the <see cref="IWindow"/>.
        /// </summary>
        void Close();
}
I assume then when I'm unit testing methods on my notification viewmodels, I could create a test implementation of the IWindow.
Developer
Feb 15, 2013 at 7:00 PM
Hi,

You are right, the current implementation of the IPopupWindowActionAware interface forces your Notification object to have a Window , which is not MVVM friendly as it would make it more difficult to test. In fact, I have already received comments about this in my blog post.

What you are describing about modifying the IPopupWindowActionAware to receive an IWindow seems to be a good approach to solve this. Based on my understanding, you will also need to modify the PopupWindowAction to instantiate a window that implements the IWindow interface and pass it to the corresponding Notification object when needed.

Thanks,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Feb 20, 2013 at 9:44 PM
Hi rms2219

I was wondering if you ever succeeded with your implementation of IWindow and modification of IPopupWindowActionAware? I'm considering abstracting away the Window reference from the ViewModels, but I would like to know how you did this? Can you maybe provide some more details?

It would be a really nice addition to IPopupWindowActionAware which I think is a nice solution.

Best,
Tommy