MVVM, WPF

PRISM 4 Custom Transitioning Region

Over the XMAS break I had a chance to look into something I wanted to look at, which is cool.

The thing I wanted to look at was to try and see if the new Composite WPF/SL (PRISM) code, which is now using MEF (Managed Extensibility Framework) would work well with Cinch V2.

Great news is works just fine, and I will be writing up a codeproject article for that real soon, but as part of that codebase, I also wrote a custom PRISM region adaptor that uses a TransitionalElement from the fabolous Microsoft Transitionals project.

For those of you that not heard of Transitionals, it is a set of WPF transitions that you can use to go from one bit of content to another bit of content.

It offers the following transitions:

  • FadeAndGrow
  • Translate
  • FadeAndBlur
  • Rotate
  • CheckerboardTransition
  • DiagonalWipeTransition
  • DiamondsTransition
  • DoorTransition
  • DotsTransition
  • DoubleRotateWipeTransition
  • ExplosionTransition
  • FadeTransition
  • FlipTransition
  • HorizontalBlindsTransition
  • HorizontalWipeTransition
  • MeltTransition
  • PageTransition
  • RollTransition
  • RotateTransition
  • RotateWipeTransition
  • StarTransition
  • TranslateTransition
  • VerticalBlindsTransition
  • VerticalWipeTransition

 

So I took it upon myself to split that custom PRISM region adaptor into a small demo app. This blog has that demo code attached.

The demo app is very simple and has a DockPanel with a ComboBox to select the transition type and 3 images that when clicked on will set a custom  transitonals based PRISM region adaptor to transition to that image. Shown below is an example of one the images 1/2 way through a transition.

image

So how does it all work. It’s really down to the following steps

STEP 1 : Create a custom PRISM region adaptor

This is where we create the custom region adaptor for the type of TransitionElement, as shown below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.ComponentModel.Composition;
using Microsoft.Practices.Prism.Regions;

using Transitionals.Controls;
using System.Collections.Specialized;

namespace PRISMTransitionalRegion.Regions
{
    [Export("PRISMTransitionalRegion.Regions.TransitionElementAdaptor", 
        typeof(TransitionElementAdaptor))]
    public class TransitionElementAdaptor : RegionAdapterBase<TransitionElement>
    {
        [ImportingConstructor]
        public TransitionElementAdaptor(IRegionBehaviorFactory behaviorFactory) :
            base(behaviorFactory)
        {
        }

        protected override void Adapt(IRegion region, TransitionElement regionTarget)
        {
            region.Views.CollectionChanged += (s, e) =>
            {
                //Add
                if (e.Action == NotifyCollectionChangedAction.Add)
                    foreach (FrameworkElement element in e.NewItems)
                        regionTarget.Content = element;

                //Removal
                if (e.Action == NotifyCollectionChangedAction.Remove)
                    foreach (FrameworkElement element in e.OldItems)
                    {
                        regionTarget.Content = null;
                        GC.Collect();
                    }

            };
        }

        protected override IRegion CreateRegion()
        {
            return new AllActiveRegion();
        }

    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

STEP 2 : Create a Region to use this custom region adaptor

Now we need to actually use this custom region adaptor. Which I have decided to do directly in the Shell (standard PRISM starting window), as follows:

<Window x:Class="PRISMTransitionalRegion.Shell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cal="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"  
        xmlns:transitions="clr-namespace:Transitionals.Transitions;assembly=Transitionals"
        xmlns:transitionals="clr-namespace:Transitionals;assembly=Transitionals"
        xmlns:transitionalsControls="clr-namespace:Transitionals.Controls;assembly=Transitionals"
         xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        Title="Shell" 
        WindowState="Normal"
        WindowStyle="ThreeDBorderWindow"
        WindowStartupLocation="CenterScreen"
        Width="800"
        Height="800">


    <DockPanel Grid.Row="1" Grid.Column="1" Margin="10"
                   LastChildFill="True">

        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" 
                    HorizontalAlignment="Left">
            <Label Content="Pick Transition Type" FontSize="10" 
                   Foreground="Black" FontFamily="Verdana"/>
            <ComboBox Margin="5,0,0,0" Width="200"
                          ItemsSource="{Binding TransitionTypes}" 
                          SelectedItem="{Binding SelectedTransitonType}"/>
            
            <StackPanel Orientation="Horizontal" Margin="10,0,0,0">
                <Image Source="/PRISMTransitionalRegion;component/Images/robot1.png" 
                       Width="40" Height="40" Margin="5">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="MouseLeftButtonDown">
                            <i:InvokeCommandAction 
                                Command="{Binding ShowViewCommand}" 
                                CommandParameter="/PRISMTransitionalRegion;component/Images/robot1.png"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Image>

                <Image Source="/PRISMTransitionalRegion;component/Images/robot2.png" 
                       Width="40" Height="40" Margin="5">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="MouseLeftButtonDown">
                            <i:InvokeCommandAction 
                                Command="{Binding ShowViewCommand}" 
                                CommandParameter="/PRISMTransitionalRegion;component/Images/robot2.png"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Image>

                <Image Source="/PRISMTransitionalRegion;component/Images/robot3.png"
                       Width="40" Height="40" Margin="5">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="MouseLeftButtonDown">
                            <i:InvokeCommandAction 
                                Command="{Binding ShowViewCommand}" 
                                CommandParameter="/PRISMTransitionalRegion;component/Images/robot3.png"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Image>

            </StackPanel>
        </StackPanel>

        <transitionalsControls:TransitionElement 
            x:Name="transitionElement"
            Margin="10"
            VerticalAlignment="Center"
            HorizontalAlignment="Center"
            cal:RegionManager.RegionName="imageRegion"
            Transition="{Binding TransitionToUse}">
        </transitionalsControls:TransitionElement>

    </DockPanel>





</Window>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

We also create a simple ViewModel for the Shell in the Shell.xaml.cs code behind, where the ShellViewModel looks like this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel;
using System.Windows;

using Microsoft.Practices.Prism.Regions;

using PRISMTransitionalRegion.Regions;
using PRISMTransitionalRegion.Views;
using PRISMTransitionalRegion.Interfaces;
using PRISMTransitionalRegion.Commands;
using Transitionals;
using Transitionals.Transitions;

namespace PRISMTransitionalRegion.ViewModels
{


    public enum TransitionType
    {
        FadeAndGrow = 1,
        Translate,
        FadeAndBlur,
        Rotate,
        CheckerboardTransition,
        DiagonalWipeTransition,
        DiamondsTransition,
        DoorTransition,
        DotsTransition,
        DoubleRotateWipeTransition,
        ExplosionTransition,
        FadeTransition,
        FlipTransition,
        HorizontalBlindsTransition,
        HorizontalWipeTransition,
        MeltTransition,
        PageTransition,
        RollTransition,
        RotateTransition,
        RotateWipeTransition,
        StarTransition,
        TranslateTransition,
        VerticalBlindsTransition,
        VerticalWipeTransition

    }



    public class ShellViewModel : INPCBase
    {

        private Transition transitionToUse = new FadeAndBlurTransition();
        private Dictionary<TransitionType, Transition> transitionsLookup = 
            new Dictionary<TransitionType, Transition>();
        private DependencyObject view;
        private Lazy<IRegion> region;

        public ShellViewModel(DependencyObject view)
        {
            this.view = view;


            //add transition lookups
            transitionsLookup.Add(TransitionType.FadeAndBlur, 
                new FadeAndBlurTransition());
            transitionsLookup.Add(TransitionType.FadeAndGrow, 
                new FadeAndGrowTransition());
            transitionsLookup.Add(TransitionType.Translate, 
                new TranslateTransition());
            transitionsLookup.Add(TransitionType.Rotate, 
                new RotateTransition());
            transitionsLookup.Add(TransitionType.CheckerboardTransition, 
                new CheckerboardTransition());
            transitionsLookup.Add(TransitionType.DiagonalWipeTransition, 
                new DiagonalWipeTransition());
            transitionsLookup.Add(TransitionType.DiamondsTransition, 
                new DiamondsTransition());
            transitionsLookup.Add(TransitionType.DoorTransition, 
                new DoorTransition());
            transitionsLookup.Add(TransitionType.DotsTransition, 
                new DotsTransition());
            transitionsLookup.Add(TransitionType.DoubleRotateWipeTransition, 
                new DoubleRotateWipeTransition());
            transitionsLookup.Add(TransitionType.ExplosionTransition, 
                new ExplosionTransition());
            transitionsLookup.Add(TransitionType.FadeTransition, 
                new FadeTransition());
            transitionsLookup.Add(TransitionType.FlipTransition, 
                new FlipTransition());
            transitionsLookup.Add(TransitionType.HorizontalBlindsTransition, 
                new HorizontalBlindsTransition());
            transitionsLookup.Add(TransitionType.HorizontalWipeTransition, 
                new HorizontalWipeTransition());
            transitionsLookup.Add(TransitionType.MeltTransition, 
                new MeltTransition());
            transitionsLookup.Add(TransitionType.PageTransition, 
                new PageTransition());
            transitionsLookup.Add(TransitionType.RollTransition, 
                new RollTransition());
            transitionsLookup.Add(TransitionType.RotateTransition, 
                new RotateTransition());
            transitionsLookup.Add(TransitionType.RotateWipeTransition, 
                new RotateWipeTransition());
            transitionsLookup.Add(TransitionType.StarTransition, 
                new StarTransition());
            transitionsLookup.Add(TransitionType.TranslateTransition, 
                new TranslateTransition());
            transitionsLookup.Add(TransitionType.VerticalBlindsTransition, 
                new VerticalBlindsTransition());
            transitionsLookup.Add(TransitionType.VerticalWipeTransition, 
                new VerticalWipeTransition());


            ShowViewCommand = new SimpleCommand<Object, Object>(ExecuteShowViewCommand);

            region = new Lazy<IRegion>(() =>
                {
                    IRegionManager regionManager = RegionManager.GetRegionManager(view);
                    return regionManager.Regions["imageRegion"];
                });

        }



        public SimpleCommand<Object, Object> ShowViewCommand { get; private set; }

        public Array TransitionTypes
        {
            get
            {
                return Enum.GetValues(typeof(TransitionType));
            }
        }


        public TransitionType SelectedTransitonType
        {
            set
            {
                TransitionToUse = transitionsLookup[value];
            }
        }

        public Transition TransitionToUse
        {
            get { return transitionToUse; }
            private set
            {
                transitionToUse = value;
                NotifyPropertyChanged("TransitionToUse");
            }
        }

        private void ExecuteShowViewCommand(Object args)
        {
            if (!String.IsNullOrEmpty(args.ToString()))
                ShowViewInRegion(args.ToString());
        }


        private void ShowViewInRegion(string imageUrl)
        {
            ImageView imageView = 
                (App.Current as App).PrismCompositionContainer.GetExportedValue<ImageView>();
            ((IContextualDataAware<string>)imageView).ContextualData = imageUrl;
            IRegion lazyRegion = region.Value;
            var oldView = lazyRegion.GetView("imageView");
            if (oldView != null)
                lazyRegion.Remove(oldView);
            lazyRegion.Add(imageView, "imageView");
        }
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

STEP 2 : Make sure the custom region adaptor is registered with PRISM

For our custom region adaptor to work with PRISM we need to make sure it registered with PRISM. We can do this in the PRISM 4.0 bootstrapping code, as follows:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Text;
using System.Windows;
using System.Reflection;
using System.Windows.Controls;
using Transitionals.Controls;

using Microsoft.Practices.Prism.MefExtensions;
using Microsoft.Practices.Prism.Regions;

using PRISMTransitionalRegion.Regions;
using PRISMTransitionalRegion.Views;


namespace PRISMTransitionalRegion
{
    public class PRISMTransitionalRegionBootstrapper : MefBootstrapper
    {

        public override void Run(bool runWithDefaultConfiguration)
        {
            base.Run(runWithDefaultConfiguration);
        }

        protected override void ConfigureAggregateCatalog()
        {
            this.AggregateCatalog.Catalogs.Add(
                new AssemblyCatalog(typeof(App).Assembly));
        }

        protected override void InitializeShell()
        {
            base.InitializeShell();

            (App.Current as App).PrismCompositionContainer = this.Container;

            Application.Current.MainWindow = (Shell)this.Shell;
            Application.Current.MainWindow.Show();
        }


        protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
        {
            RegionAdapterMappings mappings = base.ConfigureRegionAdapterMappings();
            mappings.RegisterMapping(typeof(TransitionElement), 
                Container.GetExportedValue<TransitionElementAdaptor>());
            return mappings;
        }


        protected override DependencyObject CreateShell()
        {
            return this.Container.GetExportedValue<Shell>();
        }



    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

 

As always here is a small demo app:

http://dl.dropbox.com/u/2600965/Blogposts/2011/01/PRISMTransitionalRegion.zip

Enjoy….Happy new year all

5 thoughts on “PRISM 4 Custom Transitioning Region

  1. Cinch V2 and prism V4 – nice combination. I also try to use both. I love cinch but need the moduls and regions. I am looking forward to your article.

    1. Won’t be long, to wait. I am not showing you Modules but do use regions, but I will be giving link to a site where you can how things will work with modules and CinchV2

  2. Hey Sacha

    Quite old post, but saved me!!!

    Thanks for it

    Only a little thing (in case you read this).

    In the TransitionElementAdaptor, you return an AllActiveRegion.

    Then when you navigate to a view in that region, it sets the content of the TransitionElement to the view. And it works all right. Then you navigate to another one, and transition kicks in. Ok. You navigate back to the first view. No navigation happens.

    Then I saw two things:
    – First, in the Adapt method, you only listen to region.Views.CollectionChanged.
    – Second, in the CreateRegion, you return an AllActiveRegion. This is fine for ItemControls. However, TransitionElement is a ContentControl

    That’s what I did:
    – Change the CreateRegion, returning a SingleActiveRegion
    – Change the Adapt method, and listen to region.ActiveViews.CollectionChanged

    And then it works like I want. Navigating back and forth. Any time I navigate to a view, since the region is a SingleActiveRegion, it activates the view in the region, so the event kicks in, and the transition works

    Is there anything wrong in my reasoning?

    Thanks anyway, you saved my life… again 🙂

    1. Tio I don’t quite see what you are talking about, but I do agree that it is probably better to listen to region.ActiveViews.CollectionChanged. Did the problem you are talking about show up using my small demo code article?

      Perhaps I just don’t get what you mean.

      Still I have changed it to SingleActiveRegion (my bad), and added in listener for ActiveViews

  3. Hey Sacha!!!

    Sorry for not answering for so long. I didn’t check this post until now.

    Well, I’ll try my best to explain my scenario, and how I reached the problem with your solution, and why I think my way works better.

    I have a prism v4 app. There is a region, named MainRegion. And different views. The region is defined in the Shell, and stretches the whole window. The region was a ContentControl (and thus a SimgleActiveRegion).

    For simplicity sake, say we only have 2 views we want to load in MainRegion. Say ViewA and ViewB.

    ViewA has a button that does IRegionManager.RequestNavigate(MainRegion, ViewB)

    ViewB has a goback button, that does NavigationService.Journal.GoBack()

    Then I run the app, then I click the button to navigate to ViewB. Then I click the button to go back. It works

    I changed the MainRegion from ContentControl to TransitionElement.

    I run the app. When ViewA loads into MainRegion, transition kicks in. It works

    I click the button. requestNavigate loads ViewB, and adds it to the region. The regionadapter handles the CollectionChanged (as we are adding a view), it changes the TransitionElement Content, and transition kicks in. It works

    I click the goback button. ViewA is already loaded into MainRegion, and thus, the CollectionChanged handler doesn’t handle anything. So we keep in ViewB (and we expected to see the transition to ViewA). Fail

    Then I saw that in the regionadapter, we were returning a AllActiveRegion, so when we were navigating back, the handler couldn’t know it.

    I changed to SingleActiveRegion. Then I noticed that we don’t need to handle CollectionChanged from IRegion.Views, but IRegion.ActiveViews. Since it is a SingleActiveRegion, and can have only a single active region at a time, navigating to another view or navigating back will always activate the destination view, regardless of whether it was already in the Views collection or not. And I will be able to handle the activation in the IRegion.ActiveViews.CollectionChanged handler. I don’t need any handler for IRegion.Views

    That’s my train of thought

    Then I repeat my original question: Am I right? Is there anything I’m missing?

    Sorry if I wasn’t very clear or my english isn’t very good (I’m from sunny Spain :P) And thanks a lot for replying

Leave a reply to Avidin Cancel reply