Notification Content (Text) not displaying on InteractionRequest Popup

Topics: Prism v4 - WPF 4
Oct 3, 2012 at 2:57 PM
Edited Oct 3, 2012 at 3:35 PM

I am using InteractionRequests with a PopupAction a couple different ways. First, the traditional way of simply notifying the user with an OK button. Second, with an OK/Cancel using Confirmation. Finally, I wanted to simply notify the user of the current state. This notification would display with text, and when the operation finished, the "Complete" method would be called on it, which in turn would close itself.

The problem I am having is that for the third style described above, the window will display, but the text inside does not display. If I specify that I want the window to be Modal, the text will display fine. I suspect there's some threading issue where the GUI thread is blocking and the text isn't being drawn.

Can anyone help me figure out why my text doesn't display on my notification?

Here is my notification class:

public class ZActionNotificationViewModel : Notification, IPopupWindowActionAware
    {
        public Window HostWindow { get; set; }

        public Notification HostNotification { get; set; }

        public ZActionNotificationViewModel()
        {
            Title = string.Empty;
        }

        public ZActionNotificationViewModel(string content)
            : base()
        {
            this.Content = content;
        }

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

The PopupWindowAction (a TriggerAction) checks if my notification is IPopupWindowActionAware which just assigns the Window it creates to the property on the notification so that it has control of itself.

Here is how I declare my popup in my XAML:

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

Now my ZActionNotificationView:

<UserControl x:Class="Zeller.Gp.ZActionNotificationView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" >
    <Grid>
        <TextBlock Text="{Binding Path=Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</UserControl>

And finally, how I use the notification:

ZActionNotificationViewModel actionNotification = new ZActionNotificationViewModel() { Content = "Saving contract..." };
ActionNotification.Raise(actionNotification);

state.Contract = contractName;
state.ContractRevLevel = revisionLevel;

ThreadPool.QueueUserWorkItem(DoContractSave, state);
state.Event.WaitOne();

actionNotification.CompleteAction();
Oct 3, 2012 at 7:07 PM

The more I research this, the more I'm feeling like it is an issue with the WPF "rendering thread". Basically, the view itself isn't being rendered inside of the window. The window displays, but the rendering thread blocks because I'm doing other work on the GUI thread.

Developer
Oct 3, 2012 at 8:09 PM

Hi,

It seems you are using a modified version of the PopupWindowAction described in this blog post: PopupWindowAction: using custom views instead of windows in WPF and Prism.

Using the aforementioned PopupWindowAction we were able to reproduce the behavior you are mentioning and it seems to be related to a race condition between the composition of the Window and the call to the WaitOne method (which I assume is the one blocking the UI thread.)

This race condition can be seen by removing the call to the CompleteAction method at the end of the code-snippet you provided. Like this, when the WaitOne method return, you should see the "Saving contact..." message appearing in the Window.

The reason why this doesn't happen when setting the IsModel property to false, is that the Window is created using the ShowDialog method. Therefore, the logic following the invocation of the Raise method will be executed only after the Window is closed.

How to solve this race condition will depend mostly of the requirements of your scenario and your personal preferences.

A possible approach to avoid this could be to change the implementation used to wait for the background thread to finish. For example, instead of blocking the UI thread, you could provide a callback to the state object that could be invoked after the background thread finishes.

Another approach could be to wait for the Window to be composed before executing the rest of the code. For example, before raising the interaction request, your view model could subscribe to a ViewLoaded event in the Event Aggregator. Then in the Loaded event of the ZActionNotificationView, you could publish this event (through the Event Aggregator) to inform the view model that the Window was rendered correctly.

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini