WPF Region in a Dialog.

Topics: Prism v4 - WPF 4
Jan 28, 2011 at 6:52 PM
Edited Jan 28, 2011 at 7:03 PM

I have a region in a Dialog window

 

<Window x:Class="CDS.Services.Options.OptionsView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://www.codeplex.com/CompositeWPF" 
        Title="Options" 
        SizeToContent="WidthAndHeight">
  <Grid Height="300" Width="550">
      <ContentControl prism:RegionManager.RegionName="OptionsPageRegion" Margin="10" />
  </Grid>
</Window>

 

I've tried:

 

      OptionsView ov = new OptionsView();
      RegionManager.SetRegionContext(ov, _RegionManager);
      RegionManager.UpdateRegions();
      bool b = _RegionManager.Regions.ContainsRegionWithName("OptionsPageRegion");

But the region is not found.

 

The other thing I found, was the composite contrib but that seems like it's more complex than a simple action like this should be.

What is the correct way to do this in V4, MEF & WPF?

Jan 28, 2011 at 9:03 PM

well reference the StockTrader RI and look for the SecondaryRegion tag in the XAML.  The way you're attempting it will likely get an error related to there can only be one Window as the root.  Also the code for the SecondaryRegion is located in the Infrastructure in the Behavior folder, works just fine for me at this point.  I am able to get model validation to bubble thru no problem.

Jan 29, 2011 at 4:02 AM
Edited Jan 29, 2011 at 5:29 AM

Wow, that looks like a lot of work to just have a region in another window!

In looking through the code, it looks like what they do is create a new region, assign it a custom behavior to it and then add that new region to the Region Manager. (see RegionPopupBehaviors.RegisterNewPopupRegion)

The custom behavior (DialogActivationBehavior) listens for it's Region.ActiveViews.CollectionChanged event to fire.  When it sees one, it the take e.NewItems[0] which is the constructed view and creates a window, and sets the Window.Contents to that view via a wrapper around the window.

OK... I think I see how I could use that for this.  What has me wondering if there isn't an easier way, is the comments at the start of RegionPopupBehaviors:

/// Although the fastest way is to create a RegionAdapter for a Window and register it with the RegionAdapterMappings,
/// this would be conceptually incorrect because we want to create a new popup window everytime a view is added 
/// (instead of having a Window as a host control and replacing its contents everytime Views are added, as 
/// other adapters do).  This is why we have a different class for this behavior, instead of reusing the 
/// RegionManager.RegionNameProperty attached property.

I don't want a new window every time, I want to replace the contents of my region every time.  So I guess I'm off to look at RegionAdapters and how to map them next.  Anyone have a good example for me?

Edit: oh... wait, that takes me back to composite contrib and the WindowRegionAdapter.  So I guess I need to finish converting that to 4.0 and see what I get.

 

Jan 29, 2011 at 5:26 AM

OK... Stocktrader RI creates a single window and changes it's contents each time RequestNavigate is called. 

The WindowRegionAdapter  project creates a new window every time you click the button. 

Both methods attach themselves to the MainWindow of the application.  Why that's needed... I haven't delved that deep.

Both examples inject the views that are added to their region into the Content property of the window. 

My problem, is my region is buried deep within the Window, and I don't want this behavior to be the behavior for all Windows. 

So... I think the Stocktrader RI method is the way for me to go on this.  I'm thinking of a new attached property that somehow allows me to register my dialog window region with my custom behavior.  Sound right?  Anyone got an easier solution?

Jan 29, 2011 at 8:17 PM

Here's what I came up with, this is for a modal dialog only:

First I had to create a custom region manager:

 

using System;
using System.Windows;
using CDS.Prism.Behaviors;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.ServiceLocation;

namespace CDS.Prism
  {
  public static class ExternalRegionManager
    {

    #region Region Property
    public static string GetRegion(DependencyObject obj)
      {
      return (string)obj.GetValue(RegionProperty);
      }

    public static void SetRegion(DependencyObject obj, string value)
      {
      obj.SetValue(RegionProperty, value);
      }

    public static readonly DependencyProperty RegionProperty =
        DependencyProperty.RegisterAttached("Region", typeof(string), typeof(ExternalRegionManager),
        new PropertyMetadata(string.Empty, RegionChanged));

    private static void RegionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
      {
      ActiveRegion = new WeakReference(sender);
      ActiveRegionName = (string)e.NewValue;
      }

    public static string        ActiveRegionName  { get; private set; }
    public static WeakReference ActiveRegion      { get; private set; }
    #endregion Region Property

    public static void RegisterNewExternalRegion(string regionName)
      {
      IRegionManager regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
      if (regionManager != null)
        {
        IRegion region = new SingleActiveRegion();
        ExternalRegionActivationBehavior behavior = new ExternalRegionActivationBehavior();
        //behavior.HostControl = owner;
        region.Behaviors.Add(ExternalRegionActivationBehavior.BehaviorKey, behavior);
        regionManager.Regions.Add(regionName, region);
        }
      }

    public static void RemoveExternalRegion(string regionName)
      {
      IRegionManager regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
      if (regionManager != null)
        {
        regionManager.Regions.Remove(regionName);
        }
      }

    }
  }

The attached Region property, is the one I use in the dialog box, there can be only one with this code.  But I will likely extend it so that I can use this for more than just my one modal dialog.

Then the custom behavior:

using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.Prism.Regions;

namespace CDS.Prism.Behaviors
  {
  
  public class ExternalRegionActivationBehavior : RegionBehavior
    {
    /// <summary>
    /// The key of this behavior
    /// </summary>
    public const string BehaviorKey = "ExternalRegionActivation";

    /// <summary>
    /// Performs the logic after the behavior has been attached.
    /// </summary>
    protected override void OnAttach()
      {
      this.Region.ActiveViews.CollectionChanged += this.ActiveViews_CollectionChanged;
      }

    private void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
      {
      if (e.Action == NotifyCollectionChangedAction.Add)
        {
        this.InjectView(e.NewItems[0]);
        }
      }

    private void InjectView(object view) 
      {
      DependencyObject eregion = ExternalRegionManager.ActiveRegion.Target as DependencyObject;
      if (eregion != null)
        {
        if (eregion is ContentControl)
          {
          ((ContentControl)eregion).Content = view;
          }
        }
      }

    }
  }

Now my service opens the dialog like this:

      OptionsView ov = new OptionsView();
      ExternalRegionManager.RegisterNewExternalRegion("OptionsPageRegion");
      ov.DataContext = ovm;
      ov.Owner = Application.Current.MainWindow;
      ov.ShowDialog();
      ExternalRegionManager.RemoveExternalRegion("OptionsPageRegion");

I can then use standard navigation for it:

_RegionManager.RequestNavigate("OptionsPageRegion", page);

page being the URI being navigated to.

I would love some feed back on this.  I frankly have no clue if this is the best way to do it.