Show Multiple non-Modal dialogs at once

Topics: Prism v4 - WPF 4
Apr 30, 2013 at 9:17 PM
Edited Apr 30, 2013 at 9:28 PM
My application requires "popup" windows on two distinct occassions:
  1. User clicks on an item in a vew to display more details.
  2. An event is caught and displays a details view immediately to the user.
I have used this article (PopupModalWindowAction) to be able to display a single popup in either case. However, in the second case (event displays view), I need the ability to display multiple non-modal dialogs, as these events often come in several at a time.

I'm using the same View and ViewModel for both popups, but one is modal (when you click on an item) and the other is non-modal. The ViewModel that raises the request passes in an object as part of the request to be displayed by the popup. I previously set this in the context so the ViewModel had a parameterless constructor, but once I added the second type of popup, I realized that this would probably cause an issue, as the requests are raised in different views/ViewModels depending on their initiator.

First off, is this the correct approach for this type of popup or should I use the popup behavior?

In the article, it is mentioned that this could be achieved:
Currently this could be achieved by creating a new custom window in the PopupModalWindowAction each time the InteractionRequest is raised, this is why the default windows implementation can be re open without problems.
However, I'm uncertain exactly where to implement this. How should the PopupWindowAction class change to implement this?

Thanks
-g
Developer
May 2, 2013 at 8:23 PM
Hi,

Based on my understanding, the author of the article was referring to a limitation of the popup action when defining a custom Window in it, where the Window could be used only once during the lifetime of the application. The default implementations of the popup action for Notifications and Confirmations do not have this problem, as they can be reused multiple times (but not at the same time.)
However, as long as you only need to show a message in the from of a Notification, I believe you could find the following blog post interesting, where an improved version of this behavior is described:
In the sample included in the aforementioned blog post, you should see that when clicking the Raise Default Notification button you are able to show several non-modal notifications as popups at the same time. Also, this popup includes the feature of defining if the popup should be modal or not through a XAML property.

I hope this helps,

Damian Cherubini
http://blogs.southworks.net/dcherubini
May 13, 2013 at 6:40 PM
Edited May 13, 2013 at 8:54 PM
Thanks for your feedback, DCherubini. I had previously followed the link in the original article to the article you mentioned. I am using the implementation described in that article, but still unable to display multiple instances of the popup. To provide more context, I have the following:

Parent class that displays the modal popup when a listbox item is clicked

<UserControl>
      <i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding DetailsRequest, Mode=OneWay}">
            <modal:PopupWindowAction IsModal="False" CenterOverAssociatedObject="False" InitialHeight="300" InitialWidth="500">
                <modal:PopupWindowAction.WindowContent>
                    <views:ResponseDetailsView />
                </modal:PopupWindowAction.WindowContent>
            </modal:PopupWindowAction>
        </prism:InteractionRequestTrigger>
    </i:Interaction.Triggers>
    
    <Grid><ListBox x:Name="responses" SelectedItem="{Binding Selected}" ItemsSource="{Binding Responses}" ItemContainerStyle="{StaticResource ResponseListBoxItemStyle}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
                  <ItemsControl prism:RegionManager.RegionName="{x:Static inf:RegionNames.ResponseDetailRegion}"
                      prism:RegionManager.RegionContext="{Binding Selected}" />
    </Grid>
</UserControl>

Corresponding ViewModel for the above View

public class ResponsesViewModel : ViewModelBase, IActiveAware
    {
        public ResponsesViewModel() { // Init commands and InteractionRequests }
        public ObservableCollection<ResponseItem> Responses { get; private set; }

        public ResponseItem Selected { // getter and setter with RaisePropertyChange on setter }
        public bool IsActive { ... }
        public InteractionRequest<ResponseDetailViewModel> DetailsRequest { get; private set; }
        public ICommand DisplayDetailsCommand { get; private set; }
        private void DisplayDetails(ResponseItem item)
        {
            // Here is where I raise the Notification Request. The second one does not show up
            this.DetailsRequest.Raise(new ResponseDetailViewModel(item)
            { 
                Title = string.Format("{0}: {1}", item.ResponseType.GetDescription(), item.TCN)
            });

            this.DetailsRequest.Raise(new ResponseDetailViewModel(item)
            {
                Title = string.Format("{0}: {1}", "DUPE", item.TCN)
            });
        }

        private void Activate() { // Do first time setup for UI }
        private void LoadResponsesFromRepository() { ... }
        private void Initialize() { .... }
        private void OnResponseReceived(ResponseEventArgs e) { ... }
        public event EventHandler IsActiveChanged;
        private bool _isActive = false;
        private ResponseItem _selected;
    }

Custom View for popup

<UserControl> 
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
        <Grid>
                     <Image Source="{Binding Image, Converter={c:PathToBitmapImageThumbnailConverter}}" />
            </Grid>
<!-- Other data display -->
</UserControl>

Notification ViewModel

    public class ResponseDetailViewModel : Notification, IPopupWindowActionAware, INotifyPropertyChanged
    {
        public ResponseDetailViewModel(ResponseItem response) { // Init Commands }
        public ResponseItem Response { get; set; } // NotificationPropertyChanged event
        public string OriImage { get; set; } // NotificationPropertyChanged event
        public ICommand OnLoadedCommand { get; private set; }
        public ObservableCollection<TransactionInfo> ResponseInfo { get; set; }
        public System.Windows.Window HostWindow { get; set; }
        public Notification HostNotification { get; set; }
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnLoaded() { // Load Data }
        private void RaisePropertyChanged(string property) { // General property change raising code }
}
In the above scenario, I only have the double event InteractionRequest in there for testing. However, I have another view that handles the other scenario where multiple requests would need to be raised. This is what it looks like:

Parent UserControl that needs to display multiple instances of popup

<UserControl>
    <i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding NotificationRequest, Mode=OneWay}">
            <modal:PopupWindowAction IsModal="False" CenterOverAssociatedObject="False" InitialHeight="300" InitialWidth="500">
                <modal:PopupWindowAction.WindowContent>
                    <views:ResponseDetailsView />
                </modal:PopupWindowAction.WindowContent>
            </modal:PopupWindowAction>
        </prism:InteractionRequestTrigger>
    </i:Interaction.Triggers>
    <Grid> <!-- Controls to show other stuff -->    </Grid>
</UserControl>

ViewModel for above View

    public class TmRootViewModel : NotificationObject    {
        public TmRootViewModel() {
        {
           // This event is caught here every time a new Response is received.
           // This requires multiple popups to display each new response object
            CompositeEventHelper.Subscribe<ResponseReceivedEvent, ResponseEventArgs>(OnResponseReceived);

            NotificationRequest = new InteractionRequest<ResponseDetailViewModel>();
        }

        // This is the method that gets called each time the above event is caught
       // I put the code below to test for multiple popups
        private void OnResponseReceived(ResponseEventArgs e)
        {            
            Application.Current.Dispatch(() =>
                {
                    this.NotificationRequest.Raise(new ResponseDetailViewModel(response)
                    {
                        Title = string.Format("{0}: {1}", e.ResponseType.GetDescription(), response.Data)
                    });

                    this.NotificationRequest.Raise(new ResponseDetailViewModel(response)
                    {
                        Title = string.Format("{0}: {1}", "DUPE", response.Data)
                    });
                });
        }

        public InteractionRequest<ResponseDetailViewModel> NotificationRequest { get; private set; }
    }
}
So...as you can see from the above code and comments, I need to be able to display multiple non-modal dialogs in once scenario. Is there a way to do this using InteractionRequests, or would I need to look at a different way of doing this altogether? I understand from your comments on your article, that the popup view are created before the interaction request is raised, and that is why only a single instance can be displayed utilizing this code. I'm not well versed in WPF, so I'm unsure of a fix for this. Can you possible recommend a work around, or a different approach?

<Edit>
After looking at the StockTrader RI again, I don't think the popup behavior it uses would be able to meet my requirements either. It appears that it would only be able to display one view at a time as well.
</Edit>
Thanks
-G
May 14, 2013 at 7:10 PM
I did some more coding and poking around this morning. I had previously thought that it was an issue with a new Window not being created, but I found that is not the case. I found that the WindowContent.Parent changes during the second call to the PopupWindowAction.Invoke, so I guess that the parent change simply moves that object into the new parent and removes it from the old parent.

I found a hack to make it do what I need it to do, for now. However, it breaks the MVVM pattern.

In my ViewModel that needs to display multiple controls at once, I changed the code that raises the request to expliciently create new content.
 this.NotificationRequest.Raise(new ResponseDetailViewModel(response)
                    {
                        Title = string.Format("{0}: {1}", e.ResponseType.GetDescription(), response.Data),
                        Content = new CustomView()
                    });
I also had to change the XAML for the parent control to just use a Notification.
<i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding NotificationRequest, Mode=OneWay}">
            <modal:PopupWindowAction IsModal="False" CenterOverAssociatedObject="True" InitialHeight="300" InitialWidth="500" />
        </prism:InteractionRequestTrigger>
    </i:Interaction.Triggers>
As you can see from the above code, I also added initial height and width dependency properties so that the window would display at a reasonable size, even with content that might stretch the UI beyond the screen.

To take care of loading the info in the control when it is loaded, I already had an interaction trigger for the Loaded event that calls a command on the ViewModel. I had added that on the DefaultNotificationWindow in my test code, but since I already had that in my code, I was able to remove it from the DefaultNotificationWindow and rely soley on the Loaded command of the CustomView.
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
I'm sure there is a better and cleaner way to do this, but for not this will have to do, as deadlines approach. :)

-g
Developer
May 14, 2013 at 7:14 PM
Hi,

That is right, when defining a view to be used in the PopupWindowAction, you are passing an instance of that view in XAML, and that instance is reused each time the InteractionRequest is raised. Hence, as you cannot show the same instance of a view more than once at the same time, you can show only one popup at a time. This is not the case when using the default window for Notification as the PopupWindowAction creates a new popup for each notification it receives.

Also, as you said, it doesn't seem the StockTrader's popup region will work for this scenario either, as all of its views will be showed in a single window.

As a possible approach, you could modify the PopupWindowAction to change how it uses the views so that it could be able to show more than one popup. For example, if the ResponseDetailsView is a common view that you will use in several parts of your application, you could modify the PopupWindowAction to instantiate create it manually (as it does with the default Notification window) according to the view model. Other approach could be to modify the PopupWindowAction to receive the view type (using the x:Type Markup Extension) instead of the view instance. Then, each time the InteractionRequest is raised, the action could re-create the view using the ServiceLocator, obtaining a different instance for each interaction.

Regards,

Damian Cherubini
http://blogs.southworks.net/dcherubini
May 14, 2013 at 7:54 PM
Thanks for the response. I had thought about a way to use service location to instantiate the view, but I was unsure where to implement it.

I will have to look into the Markup Extension a little more. I've used it only to make ValueConverters a little easier to deal with in XAML.

I'll post back when I've had time to play around.

Thanks
-g