Popup region implementation for WPF class library

Topics: Prism v4 - WPF 4
Aug 10, 2011 at 7:30 PM
Edited Aug 10, 2011 at 8:35 PM

Hi,

My Prism WPF app is a class library hosted in another win32 app. So Shell.xaml is a UserControl. I'm trying to implement a popup region following the NewsReader in StockTraderRI. Idealy, this popup can interact with the control in the view on Shell (like drag and drop).

I tried to use WindowDialogActivationBehavior (like the Desktop version of StockTraderRI), but I can only open this popup once and it is not staying on top of the Shell, even the view was correctly loaded in the popup. I don't think I should use this method since my shell is not a window but a UserControl.

I tried to use PopupDialogActivationBehavior (Silerlight version), the popup shows up and closes when click the "x", but the view in it doesn't get loaded (contentcontrol is empty). The View and ViewModel for the popup does get called when I debug.

My question is can I achieve what I need using one of the solution? If yes, how to fix the problems?

 

Thanks!

 

Aug 11, 2011 at 1:44 PM
Edited Aug 11, 2011 at 1:47 PM

Some code for easier diagnose.

Popup region defined in Shell.xaml (Shell is a UserControl)

infBehaviors:RegionPopupBehaviors.CreatePopupRegionWithName="PopupRegion"
infBehaviors:RegionPopupBehaviors.ContainerWindowStyle="{StaticResource PopupStyle}"

Files for Popup region behavior copied from StockTraderRI: RegionPopupBehaviors.cs, DialogActiveationBehavior.cs, IWindow.cs, PopupDialogActivationBehavior.Silverlight.cs, PopupWrapper.Silverlight.cs, TreeHelper.cs

In RegionPopupBehaviors.cs,

behavior = new PopupDialogActivationBehavior();
In TreeHelper.cs: 
//#if SILVERLIGHT
            FrameworkElement frameworkElement = dependencyObject as FrameworkElement;
            if (frameworkElement != null)
            {
                parent = frameworkElement.Parent ?? System.Windows.Media.VisualTreeHelper.GetParent(frameworkElement);
            } 
 The view that need to show up in popup:

  [PartCreationPolicy(CreationPolicy.NonShared)]            //tried NonShare and Shared
    [ViewExport("VesselTypeListView")]
    public partial class VesselTypeListView : UserControl

 In the ViewModel that need to bring up the popup by button click:

private const string VesselTypeTest = "VesselTypeListView";
[ImportingConstructor] public ThroughputResultViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
 {     ...
 this._showVesselTypeListCommand = new DelegateCommand(this.ShowVesselTypeList);
       ...
}
private void ShowVesselTypeList()         {     
       this._regionManager.RequestNavigate("PopupRegion"new Uri(VesselTypeTest, UriKind.Relative)); 
}
Currently, the popup shows up fine but it's empty. the VesselTypeListView and model gets excuted but not loaded in popup.
Thanks.
Aug 11, 2011 at 3:52 PM

Just found out the problem, in DialogActivationBehavior.cs

 private void PrepareContentDialog(object view)
        {
            this.contentDialog = this.CreateWindow();
            this.contentDialog.Content = view;
            this.contentDialog.Owner = this.HostControl;
            this.contentDialog.Closed += this.ContentDialogClosed;
            //this.contentDialog.Style = this.GetStyleForView();
            this.contentDialog.Show();
        }

I have to comment out the line this.contentDialog.Style = this.GetStyleForView(); My guess is that it couldn't resolve the style. Because my app is WPF class library, so I can't use App.xaml to have an application level style for

RegionPopupBehaviors.ContainerWindowStyle="{StaticResource PopupStyle}"
I created a MyResourceDictionary.xaml loaded in code in the host like this:
if (System.Windows.Application.Current == null)
            {
                new MyApp();
                MyApp.Current.Resources.MergedDictionaries.Add(
                    MyApp.LoadComponent(new Uri("NYRCPTAGXAddin;component/MyResourceDictionary.xaml", UriKind.Relative)) as System.Windows.ResourceDictionary);
            }
Apparently, this works if I use the WindowWrapper.cs (the view shows up in popup), but not with PopupWrapper. How can I get around this? Is there a way that I can define the style when I create the content for the popup?
Thanks!
Developer
Aug 11, 2011 at 8:03 PM

Hi Julie,

I'm glad you've found a solution to your original problem; thank you for sharing it with the rest of the community.

Regarding the PopupWrapper issue, have you tried using the PopupDialogActivationBehavior in WPF outside your win32 application, using managed-only code?

If you are able to reproduce this undesired behavior in isolation, it would be helpful if you could provide us with a repro sample application, so that we can help you find the underlying cause behind it.

Thanks,

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

Aug 12, 2011 at 12:34 AM

Hi,

Thanks for your response!

I was able to reproduce this in a stand along WPF app and loaded to SkyDrive (https://skydrive.live.com/redir.aspx?cid=4f0ce6c357477187&resid=4F0CE6C357477187!121). The name of the zip file is TestWPFAppMEF. It has a moduleA displays in Shell and click button

in ModuleAView bring up popup that contains ModuleB. Currently the line in DialogActivationBehavior.cs

//this.contentDialog.Style = this.GetStyleForView();
is commentted out, so moduleB shows up in the popup. If uncomment this line, only the popup with content border but no ModuleB.
Thanks a lot for your help!
Developer
Aug 12, 2011 at 4:03 PM

Hi Julie,

The problem you're experiencing might be caused by the differences between Silverlight and WPF; since you're trying to use a component that was intended to be used in Silverlight, but in a WPF application, such differences might show up and cause unexpected behaviors like this.

As for the specific error you're experiencing, we've examined your sample and found that, probably due to the aforementioned differences between Silverlight and WPF, the PopupStyle you're defining in the App.xaml file isn't showing the ContentControl's content through the ContentPresenter. We therefore modified the style to define the Content property in the content presenter explicitly, to something like this:

<Style x:Key="PopupStyle" TargetType="ContentControl">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Border x:Name="overlay" Background="#A9000000" Padding="50,50,50,50" Opacity="0" >
                            <Border.Triggers>
                                <EventTrigger RoutedEvent="Border.Loaded">
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="overlay" Storyboard.TargetProperty="(UIElement.Opacity)">
                                                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                                                <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
                                            </DoubleAnimationUsingKeyFrames>
                                            <PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="box" Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)">
                                                <SplinePointKeyFrame KeyTime="00:00:00" Value="0.5,0.5"/>
                                                <SplinePointKeyFrame KeyTime="00:00:00.3000000" Value="0.5,0.5"/>
                                                <SplinePointKeyFrame KeyTime="00:00:00.9000000" Value="0.5,0.5"/>
                                            </PointAnimationUsingKeyFrames>
                                            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="box" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                                                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                                                <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
                                                <SplineDoubleKeyFrame KeyTime="00:00:00.9000000" Value="1" KeySpline="0.5,0,0.5,1"/>
                                            </DoubleAnimationUsingKeyFrames>
                                            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="box" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                                                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                                                <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
                                                <SplineDoubleKeyFrame KeyTime="00:00:00.9000000" Value="1" KeySpline="0.5,0,0.5,1"/>
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </EventTrigger>
                            </Border.Triggers>
                            <Border x:Name="box" HorizontalAlignment="Center" VerticalAlignment="Center" Background="#FFFFFFFF" CornerRadius="12,12,12,12" MinHeight="200" MinWidth="200" RenderTransformOrigin="0.5,0.5">
                                <Border.RenderTransform>
                                    <TransformGroup>
                                        <ScaleTransform/>
                                        <SkewTransform/>
                                        <RotateTransform/>
                                        <TranslateTransform/>
                                    </TransformGroup>
                                </Border.RenderTransform>
                                <Grid x:Name="grid">
                                    <ContentPresenter Content="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="15,15,15,15" />
                                    <Button Cursor="Hand"  HorizontalAlignment="Right" Width="Auto" Behaviors:ButtonBehaviors.CloseAncestorPopup="true" VerticalAlignment="Top" BorderBrush="{x:Null}" Background="{x:Null}" Foreground="{x:Null}" BorderThickness="0,0,0,0" Template="{StaticResource CloseButtonStyle}" Margin="15,15,15,15"/>
                                </Grid>
                            </Border>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

And after that, we verified that the code worked and the popup content was being shown appropriately.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Aug 12, 2011 at 4:29 PM

Hi Guido,

Yes!! It worked!

I noticed that the style returned from this.GetStyleForView() have different TargetType between my app and StockTraderRI. One from PresentationFramework, one from System.Windows.Conrols assembly

which seems like that they are coming from WPF and Silverlight. But totally clueless how to make it work.

Thank you so much for yor quick resonse!

My guess is that down the road in the implementation, the difference between WPF and Silverlight may create other issues. But there should be ways to work them out. What do you think?

 

 

Developer
Aug 12, 2011 at 5:11 PM

Julie,

In my opinion you are right, you should be able to work around most of the difficulties that may arise from this. You might find the following chapter useful, as it goes deeper into the differences between WPF and Silverlight and provides possible solutions to overcome them in a scenario in which it is intended to share code between Silverlight and WPF:

Chapter 10: Sharing Code Between Silverlight and WPF

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Aug 12, 2011 at 8:17 PM

Hi,

Thanks for the reference. That Prism4 help file has been an excellent source.

I have a follow up question regarding the popup. If I need to pass the location of the button that brings up the popup so I can position my popup accordingly.

should I do this through the owner of the popup which is the Shell? How to trace down to the control in one of the region on the Shell?

 

Thanks!

Developer
Aug 15, 2011 at 6:56 PM
Edited Aug 15, 2011 at 6:57 PM

Hi Julie,

Based on my understanding of your scenario, you need to be able to determine in which coordinates to show your popup when adding a view to the region. You might be able to manipulate that through the VerticalOffset and HorizontalOffset properties of the popup that lays inside the PopupWrapper generated by the PopupDialogActivationBehavior behavior. In order to achieve that, you could modify the implementation of the Show method in the PopupWrapper class so that it modifies the aforementioned properties depending on certain properties of the object contained in the object used as its Content (i.e. in its Content property).

For example, the current implementation of the PopupWrapper.Show method has the following code:

 

public void Show()
        {
            this.popUp.IsOpen = true;
        }

 

But as a workaround, you could modify it so that for example, in case you have set a Tuple containing two values in the content's tag property, those values are used as the VerticalOffset and HorizontalOffset for the popup. The code might look like this:

 

public void Show()
        {
            if (this.Content as FrameworkElement != null)
            {
                if ((this.Content as FrameworkElement).Tag as Tuple<int,int> != null)
                {
                    var tuple = (this.Content as FrameworkElement).Tag as Tuple<int, int>;
                    this.popUp.HorizontalOffset = tuple.Item1;
                    this.popUp.VerticalOffset = tuple.Item2;
                }
            }

            this.popUp.IsOpen = true;
        }

 

You can also define your own class containing the metadata instead of using the tag property of the view you're adding to that region, or even define an alternative approach, such as using a declarative attribute on the view you're going to add to the region, to fulfill this requirement.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Aug 15, 2011 at 8:44 PM

Hi Guido,

I tried the code, but my goal is to set the popup to be positioned at the user clicked location on the Shell (Owner of the popup). I was trying to set the HorizontalOffset and VerticalOffset when the Owner of the pop

up was set (where the size of the popup set). But don't kown how to find the location that the user clicked through the owner (right now button click brings the popup). Can this be done?

Another better alternative is to be able to drag around the popup. I'm thinking to add an element (rectangle or button) on the Content of the popup (like the close button) and handle MouseMove on this element to

change the HorizontalOffset and VerticalOffset of the popup. Is this possible?

Thanks a lot!

 

 

Aug 17, 2011 at 1:59 PM

Hi,

I've been thinking about the solution of the "dragable popup". Maybe a ChildWindow is more suitable in my case? If true, I'm NOT going to need the popup region, just do something like State-Based Navigation sample?

From the research, I found this: Extended WPF Toolkit. Do you think this would work better in my case?

 

Thanks for your inputs!

Developer
Aug 17, 2011 at 2:24 PM

Julie,

As you're mentioning, it's likely that the ChildWindow will be more suitable for your scenario, since it's going to be simpler to achieve your requirements using it, rather than implementing its functionality by yourself.

You could therefore implement a new behavior that inherits from the DialogActivationBehavior, or just modify one of the existing ones to use the Extended WPF Toolkit's child window instead of a popup.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Aug 17, 2011 at 3:32 PM

Hi Guido,

I just tried to bring up the ChildWindow in Shell.xaml. It doesn't work because the child window is restricted inside the shell window. Our design team determined that this "popup" has to be extended outside the shell.

If by using DialogActivationBehavior, will that still utilize the "popup region" to host the child window and get around the issue of childwindow stay inside of shell?

 

Thanks!

Aug 17, 2011 at 9:25 PM

Hi Guido,

I've been trying the ChildWindow by using a ChildWindowDialogActivationBehavior which inherits from the DialogActivationBehavior, but with no luck.

The set statement in ChildWindowWrapper keeps getting into infinite loop:

public object Owner
        {
            get { return this.Owner; }
            set { this.Owner = value as FrameworkElement; }
        }

I uploaded the project here https://skydrive.live.com/redir.aspx?cid=4f0ce6c357477187&resid=4F0CE6C357477187!121.

Could you please help?

 

 

Developer
Aug 18, 2011 at 1:16 PM

Julie,

We've downloaded your sample. We'll examine it and provide you with our feedback soon.

Thanks,

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Aug 18, 2011 at 1:36 PM

I appreciated for your time.

Now I'm thinking maybe the "Set" statement I mentioned above has something to do with the error. Because ChildWindow doesn't have Owner property, I changed from

this.window.Owner = value as Window;

to

ths.Owner = value as FrameworkElement;

For my TestWPFApp statand along, it need to be "value as Window". But for my WPF hosted app, the Shell is a UserControl, so it need to be FrameworkElement, right?

 

Developer
Aug 18, 2011 at 7:07 PM
Edited Aug 18, 2011 at 7:08 PM

Julie,

We've examined your sample and found that the infinite loop you're experiencing is caused by the fact that you're trying to modify a property's value in the property's setter, so when you assign a value to this.Owner, the setter is called again, which modifies the value again, calling the setter and so forth.

You could try having a private field (for example called owner, in lowercase) and use that field in the getter and setter of your Owner property.

As for your last post, what you're saying sounds reasonable for your scenario.

Please let us know if you are still unable to make your ChildWindowDialogActivationBehavior work, so that we provide further guidance about it.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Aug 23, 2011 at 3:58 AM

Hi Guido,

Thanks for your suggestion. Since the popup is the preferred UI for our application, I went back to visit the implementation of the draggable popup and finally got it to work. I added a label in the popup along side of the close button and

implement the event for MouseLeftButtonDown and capture mouse move for the label. This is the code I added in ButtonBehaviors.Silverlight.cs for anyone finding it is useful.

public static readonly DependencyProperty DragAncestorPopupProperty = DependencyProperty.RegisterAttached(
               "DragAncestorPopup", typeof(bool), typeof(ButtonBehaviors), new PropertyMetadata(OnDragAncestorPopupChanged));

        public static bool GetDragAncestorPopup(DependencyObject dependencyObject)
        {
            return (bool)(dependencyObject.GetValue(DragAncestorPopupProperty) ?? false);
        }

        public static void SetDragAncestorPopup(DependencyObject dependencyObject, bool value)
        {
            dependencyObject.SetValue(DragAncestorPopupProperty, value);
        }

        private static void OnDragAncestorPopupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UIElement label = d as UIElement;
            if (label != null)
            {
                if ((bool)e.NewValue)
                {
                    label.MouseLeftButtonDown += DragBarMouseDown;
                }
                else
                {
                    label.MouseLeftButtonDown -= DragBarMouseDown;
                }
            }
        }

        private static void DragBarMouseDown(object sender, MouseButtonEventArgs e)
        {
            UIElement label = sender as UIElement;

            lastPoint = e.GetPosition(null);
            label.CaptureMouse();
            label.MouseMove += RootVisual_MouseMove;
            label.MouseLeftButtonUp += RootVisual_MouseLeftButtonUp;
            label.LostMouseCapture += topbar_LostMouseCapture;
            e.Handled = true;

        }
        private static void RootVisual_MouseMove(object sender, MouseEventArgs e)
        {
            //isDragging = true;
            //ChangeVisualState(true);    
            UIElement label = sender as UIElement;
            if (label != null && GetDragAncestorPopup(label))
            {
                var popup = TreeHelper.FindAncestor(label, d => d is Popup) as Popup;
                if (popup != null)
                {
                    Point p2 = e.GetPosition(null);
                    double dX = p2.X - lastPoint.X;
                    double dY = p2.Y - lastPoint.Y;
                    popup.HorizontalOffset += dX;
                    popup.VerticalOffset += dY;
                    lastPoint = p2;
                }
            }
        }

        private static void RootVisual_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            StopDrag(sender);
        }
        private static void topbar_LostMouseCapture(object sender, MouseEventArgs e)
        {
            StopDrag(sender);
        }

        private static void StopDrag(object sender)
        {
            UIElement label = sender as UIElement;
            label.ReleaseMouseCapture();
            label.MouseMove -= RootVisual_MouseMove;
            label.MouseLeftButtonUp -= RootVisual_MouseLeftButtonUp;
            label.LostMouseCapture -= topbar_LostMouseCapture;

        }

In the xaml that define the "PopupStyle", add a label inside the grid named "grid":

<Label x:Name="DragBar" Cursor="Hand" HorizontalAlignment="Center" Width="30" infBehaviors:ButtonBehaviors.DragAncestorPopup="true" VerticalAlignment="Top" BorderBrush="{x:Null}" Background="White" Foreground="{x:Null}" />

This is not working perfectly because the popup doesn't move to the exaact location of the mouse, but it serves the purpose for now.

I would like still try the ChildWindow solution when I get a chance.

Very appreciated all your help!

Developer
Aug 23, 2011 at 1:50 PM

Julie,

Thank you for sharing your implementation with the rest of the community, as it might be helpful for other users trying to achieve similar scenarios.

Thanks,

Guido Leandro Maliandi 
http://blogs.southworks.net/gmaliandi

Sep 6, 2011 at 1:17 PM

No problem.

One further question regarding this popup window. Rigth now the close of the popup is using the "ButtonBehavior"  through button on the ContentControl in popup.

Now if I need to use a button on one of the views loaded inside popup region to close the popup, how do I do that?

Thanks!

Developer
Sep 6, 2011 at 1:59 PM

Hi,

You could try removing the view from the popup region you have created through the region manager. That would cause the popup to be closed.

You might find this thread useful:

I hope you find this helpful.

Agustin Adami
http://blogs.southworks.net/aadami