Showing posts with label Wizard. Show all posts
Showing posts with label Wizard. Show all posts

Wednesday, November 2, 2011

WPF transitions using pixel shaders and Blend SDK

WPF transitions are a very nice way to turn standard, boring application interactions into great user experiences, and show the author pays attention to details. There are basically two ways of implementing transitions in WPF. You can do a manual transition using RenderTransform to modify the visuals’ position, scaling and rotation. Throwing in an opacity animation would improve the effect. This option is talked more in-depth here. However, there is a couple of problems with this approach. The biggest issue is that such transitions are CPU bound, since the transition is calculated on the main processor. The other problem is more prosaic – they are relatively hard to implement. The other way is to utilize pixel shaders to blend between two visuals’ images making the illusion of transition. Transitions done this way are GFX bound, offloading the CPU which can perform other useful tasks. Besides, they look just awesome, as you shall see in a moment :) There is a small problem associated with this approach, though. To write custom pixel shader, some GFX knowledge accompanied by HLSL might be required. Crafting nice effect probably would also require a bit of math :) Thankfully, there are some nice libraries packed with effects ready to use! So no worries, we will not be talking math here!
In this post I will discuss how to build a TransitionControl, which beautifully animates transitions from one visual state into the other. To demonstrate the effect, I updated my wizard demo (discussed here and here) so that transitions between consecutive steps are nicely animated. I will also show what it takes to add transition effect to regular TabControl. The following screens show the slide transition between two wizard steps.
imageimageimage
And the following shows a transition within TabControl.
imageimageimage


Pixel shader libraries
Pixel shaders were introduced in .NET Framework 3.5 SP1. By this time, Microsoft released really cool library called WPF Effects Library. The library is composed of two types of components: effects and transitions. Effects are successors to well known bitmap effects, and provide a way to alter how a visual is rendered on the screen (see this video). Transitions, on the other hand, provide a means to.. transition from one visual state to the other. Easy, huh ? We will leverage them to build robust TransitionControl.
These days the project seems not to be much updated, the latest version is 2.0 beta which targets .NET 3.5, but works with .NET 4.0 and Visual Studio 2010. The good news is that although this project is not longer maintained, most of the effects and transitions made its way to Blend 4.0 SDK! They are available in Microsoft.Expression.Effects.dll assembly.
Okay, so I introduced transitions, just show how to use them and probably we’re done here, right ? it turns out we’re not quite done here. Lets see what is the API for using a transition effect. The base TransitionEffect class, from which all transitions derive, is located in Microsoft.Expression.Interactions.dll assembly, and its public API is defined as follows:
public abstract class TransitionEffect : ShaderEffect
{
    protected TransitionEffect();
    public Brush Input { get; set; }
    public Brush OldImage { get; set; }
    public double Progress { get; set; }
}
The class inherits ShaderEffect, which makes it essentially a regular pixel shader effect. So it can be assigned to the Effect property of any UIElement. There are also two important properties: OldImage and Progress. The OldImage brush defines the look of the old visual, and the Progress, between 0.0 and 1.0, denotes where we are within the transition. So the flow when using any TransitionEffect is as follows:
  1. Choose any transition effect and apply it to any UIElement which is the target visual we want to transition to
  2. Get the VisualBrush which depicts the visual we transition from, and assign it to the OldImage property
  3. Animate Progress property from 0.0 to 1.0 to perform the actual transition
As you can see, this is not very complicated, but far from being convenient. And this is where TransitionControl comes to play (I believe Telerik offers similar control).


Meet the TransitionControl
TransitionControl encapsulates the above three steps and provides a nice API to consume by the client. Let’s think for a moment, what is the most convenient API for the TransitionControl ? Yes, you’re right, it’s a ContentControl! ContentControl provides a Content property which is almost everything we care about. Whenever we change the Content property to something else, we expect to get a nice transition which smoothly goes from one visual state to the other. Having said that, let’s see the API of the TransitionControl.
public class TransitionControl : ContentControl
{    
    public TransitionSelector ContentTransitionSelector { get; set; }
    
    public TimeSpan Duration { get; set; }

    public IEasingFunction EasingFunction { get; set; }

    public bool EnableTransitions { get; set; }       
}
You can see that the TransitionControl is essentially a ContentControl. This is good, because, whenever you use ContentControl, you can swap it for TransitionControl and get the transitions for free! The second important thing is how actually TransitionControl knows about particular transition we want to use ? This is where TransitionSelector comes in. It is an abstract class which behaves similarly to DataTemplateSelector. It defines single method as show below.
public abstract class TransitionSelector
{
    public abstract TransitionEffect GetTransition(object oldContent, object newContent, DependencyObject container);
}
You implement this class and hand over a new instance to TransitionControl. You get both the old and new contents, so it is possible to write dynamic logic which selects transition based on a content. Nice, huh ? There are also two properties which drive the behavior of the transition. Duration determines the length of the transition, and EasingFunction allows to supply easing function :)


How does it work ?
The transition kicks of when the Content property changes. If the previous content was empty, of course no transition occurs. But if both old and new contents are presents, transition is triggered. The “magic” sits in the AnimateContent method, which is responsible for going through the three steps outlined previously.
private void AnimateContent(object oldContent, object newContent)
{
    var oldContentVisual = GetVisualChild();
    var tier = (RenderCapability.Tier >> 16);

    // if we dont have a selector, or the visual content is not a FE, do not animate
    if (EnableTransitions == false || ContentTransitionSelector == null || oldContentVisual == null || tier < 2)
    {
        SetNonVisualChild(newContent);
        return;
    }
    
    // create the transition
    TransitionEffect transitionEffect = ContentTransitionSelector.GetTransition(oldContent, newContent, this);
    if (transitionEffect == null)
    {
        throw new InvalidOperationException("Returned transition effect is null.");
    }

    // create the animation
    DoubleAnimation da = new DoubleAnimation(0.0, 1.0, new Duration(Duration), FillBehavior.HoldEnd);
    da.Completed += delegate
    {
        ApplyEffect(null);
    };
    if (EasingFunction != null)
    {
        da.EasingFunction = EasingFunction;
    }
    else
    {
        da.AccelerationRatio = 0.5;
        da.DecelerationRatio = 0.5;
    }
    transitionEffect.BeginAnimation(TransitionEffect.ProgressProperty, da);

    // create the visual brush which is the source of the "old content" image
    VisualBrush oldVisualBrush = new VisualBrush(oldContentVisual);
    transitionEffect.OldImage = oldVisualBrush;

    SetNonVisualChild(newContent);
    ApplyEffect(transitionEffect);
}


How to make transition aware TabControl
This task is super easy task! Just extract the default template for TabControl, and remove ContentPresenter with name PART_SelectedContentHost and provide the following XAML.
<Controls:TransitionControl 
  x:Name="PART_SelectedContentHost"
  Duration="00:00:01"
  Content="{TemplateBinding SelectedContent}"
  ContentTransitionSelector="{StaticResource TabControlTransitionSelectorKey}"
  Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>

You will have to supply proper TransitionSelector. In the demo application, I used the following:
public class TabControlTransitionSelector : TransitionSelector
{
    private readonly Random _random = new Random();

    private readonly TransitionEffect[] _transitions = new TransitionEffect[]
    {
        new SmoothSwirlGridTransitionEffect(),
        new BlindsTransitionEffect(),
        new CircleRevealTransitionEffect(),
        new CloudRevealTransitionEffect(),
        new FadeTransitionEffect(),
        new PixelateTransitionEffect(),
        new RadialBlurTransitionEffect(),
        new RippleTransitionEffect(),
        new WaveTransitionEffect(),
        new WipeTransitionEffect(),
        new SlideInTransitionEffect { SlideDirection = SlideDirection.TopToBottom }
    };
    public override TransitionEffect GetTransition(object oldContent, object newContent, DependencyObject container)
    {
        var index = _random.NextDouble() * _transitions.Length;
        return _transitions[(int) index];
    }
}


Conclusion
In this post I presented how using pixel shader effects create really cool looking, GFX bound transitions. Since the control responsible for wiring up everything is a ContentControl, you can use it wherever you use regular ContentControl, and you will get the transitions for free.


Download
As always, you can download full sources from my Sky Drive.

Wednesday, February 18, 2009

WPF Wizard Control - Part II

In my previous post about rolling your own WPF wizard control, I've described how one can easily create simple, styleable wizard in WPF. Generally, I blogged that the wizard consists of two things, namely
  • Wizard class - representing the wizard with its buttons (Next, Previous, Finish, etc.) and simple behavior concerned around managing which wizard page should be displayed,
  • WizardPage class - representing a container for a single wizard page.
The first class inherited from the Control class whereas the second inherited from the ContentControl class. Because the Wizard class wasn't derived from any control that can have content, it had to have a bindable collection of wizard's pages. Thus, I brought WizardPagesCollection class into play, which was defined as follows:

    1 
    2     /// 
    3     /// Wizard pages collection.
    4     /// 
    5     public class WizardPagesCollection : ObservableCollection<WizardPage>
    6     {
    7 
    8     }

Besides the collection of pages, the Wizard had two properties, namely
  • ActivePageIndex - the index of the current page in the collection being displayed,
  • ActivePage - the active page itself.
Of course, both properties depends on each other, i.e. if ActivePageIndex changes, ActivePage has to be updated accordingly. This was done using dependency property callbacks. Moreover, I used coerce callbacks to validate the values.
After coding this solution I realized there's a problem with it - the only wizard page that is the part of the wizard's logical tree is the page being displayed! This meant that DataContext property wasn't set correctly, and binding to controls across wizard pages was not possible.

A better Solution

Fortunately, it is very easy to overcome these problems, even without modifying wizard's template! My first assumption about wizard's base class was mistaken, because wizard is indeed a control that should have a content - its own pages :) And because there can be more than one page, the choice is obvious - ItemsControl.

Inheriting from ItemsControl reduced the following code from the Wizard class:

    1 
    2 private WizardPagesCollection m_WizardPages;
    3 
    4 /// 
    5 /// Returns a collection of wizard's pages.
    6 /// 
    7 public WizardPagesCollection WizardPages
    8 {
    9     get { return m_WizardPages; }
   10     set
   11     {
   12         m_WizardPages = value;
   13         m_WizardPages.CollectionChanged += OnWizardPagesChanged;
   14     }
   15 }
   16 
   17 private void OnWizardPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
   18 {
   19     // This code glues all wizard's pages to wizard's DataContext.
   20     // This is done due to the fact that when pages are switched, the
   21     // page that is hidden looses its data context.
   22     foreach (var page in WizardPages)
   23     {
   24         var binding = new Binding("DataContext") { Source = this };
   25         BindingOperations.SetBinding(page, DataContextProperty, binding);
   26     }
   27 }

This was actually the ugly code that attached the value of Wizard's DataContext property to each wizard's page DataContext which eliminated the problem no. 1. And because now wizard contains all the pages within its Items collection (which can be databound via ItemsSource property), all pages appear in the wizard's logical tree. Thus, problems described earlier in this post no more exist :)

Because now I used ItemsControl, I could virtually put anything in the wizard and it will compile. But the wizard expects to contain instances of WizardPage class, so I needed to tell the wizard that it need to wrap any content that is not a WizardPage into an instance of WizardPage. This is done using the following code:

    1 protected override DependencyObject GetContainerForItemOverride()
    2 {
    3     return new WizardPage();
    4 }
    5 
    6 protected override bool IsItemItsOwnContainerOverride(object item)
    7 {
    8     return item is WizardPage;
    9 }

Note, however, that in this solution, both ActivePageIndex and ActivePage properties still play vital role. Also note that there is not a single change in the Wizard's template, which is very simple. The one problem with it is that it defines the "view" for the wizard and for the wizard's page. This will be fixed in the third release :)

I will blog about better approach to implementing custom WPF wizard which fixes the complexity of using the two mentioned properties and uses separate templates for the wizard and its pages in my next post, so stay tuned ;)

The code for this post can be downloaded from here. Note that it actually contains three Wizard's implementations. The one described in this post is contained within WpfWizard2 project. WpfWizard3 will be subject of my next post.

Monday, November 10, 2008

WPF Wizard Control - Part I

Writing a custom wizard control (or simply a control of any kind) in many widely used GUI toolkits is usually a challenging task. But it turns out that doing such a control in WPF is rather easy. In this post, I'm going to explain how to create stylable, simple wizard that looks like this:

I recommend briefly examining attached source code before reading the post because the code is not short enough to be pasted on a blog. Still reading "pure" text without the code is far from being nice :)

As you probably know, WPF defines so called look less controls. This means the look and feel of the user controls is completely separated from its behavior. So firstly, I'll go through the wizard's behavior, and at the end I'll explain wizard's style in a few sentences. For now let's only assume that the wizard has navigation buttons such as Next, Previous, Finish, etc., and three places for content: wizard's header, left (side) header and of course a place for displaying main content. Note that I'm not defining yet where these pieces are going to be displayed, for now I only assume they exist somewhere.

The first decision I had to take was the class I needed to inherit from. The options were UserControl or a Control. Because I wanted to give the wizard some styling capabilities (via ComponentResourceKey) and also I wanted it to look more "professional" I decided to inherit from a Control class. There's yet another factor that actually convinced me no to using UserControl. UserControl directly inherits from a ContentControl, and the wizard itself does not have content of any kind! Wizard's content is provided by means of wizard pages and the wizard should display one page at a time. Precisely, wizard will "know" which page it should display (i.e. which page is the current one) and the wizard's template will contain ContentControl that will be databound to the main content of the current page. Of course the same approach will be used for the headers.

As you may have already guessed, single wizard page is represented by WizardPage class. Because it indeed has a content, it directly inherits from ContentControl. And because single WizardPage may provide optional header and side header, it has corresponding dependency properties of type object. Besides, this class defines some other properties like CanXXX which indicate if XXX navigation button is enabled for the page, and PageClose along with PageShow events that are raised whenever a page is closed or shown. As outlined above, the Wizard class contains a collection of WizardPage class.

The Wizard class contains two important dependency properties - ActivePage and ActivePageIndex. I hope their names are self descriptive. Not surprisingly, these two properties depend on each other, i.e. if I change ActivePageIndex, I expect ActivePage will automatically get updated, and vice versa. Moreover, I don't want to receive an error if I accidentally set inappropriate value for these properties. All this can be achieved using change and coerce callbacks. Here's the code:

  225 
  226         private static void OnActivePageIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  227         {
  228             Wizard wizard = (Wizard)d;
  229             int index = (int)e.NewValue;
  230             int oldIndex = (int)e.OldValue;
  231 
  232             if (index != -1 && index != oldIndex)
  233                 wizard.ActivePage = wizard.WizardPages[index];
  234             else if (index == -1)
  235                 wizard.ActivePage = null;
  236         }
  237 
  238         private static object CoerceActivePageIndex(DependencyObject d, object value)
  239         {
  240             Wizard wizard = (Wizard)d;
  241             int index = (int)value;
  242 
  243             if (index >= wizard.WizardPages.Count)
  244                 return wizard.WizardPages.Count - 1;
  245 
  246             if (index >= 0 && index < wizard.WizardPages.Count)
  247                 return index;
  248 
  249             if (index < 0 && wizard.WizardPages.Count > 0)
  250                 return 0;
  251 
  252             return -1;
  253         }
  254 
  255         private static void OnActivePageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  256         {
  257             Wizard wizard = (Wizard)d;
  258             WizardPage page = (WizardPage)e.NewValue;
  259             WizardPage oldPage = (WizardPage)e.OldValue;
  260 
  261             if (page != null && oldPage != page)
  262             {
  263                 // Raise event
  264                 if (oldPage != null)
  265                     oldPage.OnPageClose();
  266 
  267                 // update the index
  268                 int index = wizard.WizardPages.IndexOf(page);
  269                 wizard.ActivePageIndex = index;
  270 
  271                 // Set boundary values for navigation buttons
  272                 if (index == 0)
  273                 {
  274                     wizard.ActivePage.CanNavigatePrevious = false;
  275                     if (wizard.WizardPages.Count == 1)
  276                         wizard.ActivePage.CanNavigateNext = false;
  277                 }
  278                 else if (index == wizard.WizardPages.Count - 1)
  279                     wizard.ActivePage.CanNavigateNext = false;
  280 
  281                 // After page is up and runnig, rais event
  282                 page.OnPageShow();
  283             }
  284             else if (page == null)
  285             {
  286                 // Raise event
  287                 if (oldPage != null)
  288                     oldPage.OnPageClose();
  289 
  290                 wizard.ActivePageIndex = -1;
  291             }
  292         }
  293 
  294         private static object CoerceActivePage(DependencyObject d, object value)
  295         {
  296             Wizard wizard = (Wizard)d;
  297             WizardPage page = (WizardPage)value;
  298 
  299             int index = wizard.WizardPages.IndexOf(page);
  300 
  301             // Given page does not exist in the internal collection
  302             if (index == -1)
  303             {
  304                 if (wizard.WizardPages.Count > 0)
  305                     return wizard.WizardPages[0];
  306 
  307                 return null;
  308             }
  309 
  310             return page;
  311         }


OnActivePageChanged callback is especially important, because beside updating ActivePageIndex, it performs two other things. Firstly, it raises two events on a WizardPage class - it raises PageClose event on a page that is about to be replaced, then the page gets replaced and PageShow event is raised on a new page. And secondly, it checks if the current page is first or last in the wizard and enables or disables Next/Previous buttons accordingly.

Wizard's look & feel is defined in Themes\Generic.xaml using simple grid layout. The most important part of the control's template is how actually content of wizard's active page gets displayed in the wizard. This is accomplished using content placeholders in form of ContentControl. There is one problem with this design, however. All wizard's pages except the active one are NOT part of the logical tree as they are simply not displayed. And if the active page gets replaced, it is automatically removed from the visual tree. This implies two things. The first one is that the page's DataContext propertyis not propagated to the parent, i.e. if you put an instance of some class in the window's DataContext and you bind controls inside pages to this instance, this won't work (yes, I'm talking about PresentationModel pattern). The second thing is that you cannot bind controls with each other. Hopefully, there is an easy solution to overcome the first problem:

   44 
   45         /// 
   46         /// Returns a collection of wizard's pages.
   47         /// 
   48         public WizardPagesCollection WizardPages
   49         {
   50             get { return m_WizardPages; }
   51             set
   52             {
   53                 m_WizardPages = value;
   54                 m_WizardPages.CollectionChanged += OnWizardPagesChanged;
   55             }
   56         }

  142 
  143         private void OnWizardPagesChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  144         {
  145             // This code glues all wizard's pages to wizard's DataContext. This is done due to the fact that when pages are switched, the
  146             // page that is hidden looses its data context.
  147             foreach (var page in WizardPages)
  148             {
  149                 var binding = new Binding("DataContext") { Source = this };
  150                 BindingOperations.SetBinding(page, DataContextProperty, binding);
  151             }
  152         }



The second one is still unsolved, but because PresentationModel does work with the wizard, it's not a big deal (Update: to see how to solve these problems, see the second post).

You can download full sample from my Code Gallery.

Have fun!