Ben's profileSpacecatazBlogLists Tools Help
    April 29

    Animated Transitions in MVVM with WPF (my attempt)

    I've been filling my brain with all kinds of data and discussions surrounding the MVVM model lately. I really like the pattern, but there is a particular piece of the puzzle that's been bugging me. I spend a lot of time developing demos in WPF that use a lot of advanced graphics techniques. Every single change to the UI needs to happen smoothly, and that means a lot of animation and transitions. When I look at my code and try to think about how I would move more logic out of the view and either into XAML or into the ViewModel, this is an area that is most often the sticking point.

    Well, after being inspired by Josh Smith's post about that very subject, I took my own crack at this. My primary motivation was finding something that would work for transitioning items in and out of itemscontrols as well as single child content controls. I also wanted to avoid adding code to the view. Josh was sticking with current "production-ready" code, but since my apps are often just demos anyway, I decided to go ahead and use the Blend3 Behavior goodness.

    The idea is this: If you apply one of my Behaviors to a content control, and the content is of type ITranstionItem, then the behavior will apply a transition when the value changes. If you apply the behavior to a datatemplate to be used in an itemscontrol, those items will transition in and out when they are added/removed. The behaviors are meant to be stackable too. I built two behaviors (or actually I guess they're actions), one just fades items in and out, the other applies storyboards defined in the view and assigned at design time.

    So even though this is very rough, I decided to show some details here. I don’t really like what I named things, and for some reason I did everything with properties and monitoring for PropertyChanged events instead of custom events (I did a lot of iterating on the fly) but I decided to stop here and step back. There's probably memory leaks and obvious improvement to be made.

    Here's the interface. The viewmodels I want to support transitioning will implement this (or extend the abstract class below).
    public interface ITransitionItem : INotifyPropertyChanged
    {
        bool TransitionEnabled { get; set; }
        bool IsNew { get; set; }
        bool IsOld { get; set; }
        bool IsReadyToRemove { get; set; }
    }

    I also created an abstract class implementation for the properties and one reusable method called DelaySet. This is where I should probably change things to use more custom events rather than properties.
    protected bool DelaySet(object currentValue, Action applyValue)
    {
        var tItem = currentValue as ITransitionItem;
        if (tItem != null &&
            !tItem.IsNew &&
            !tItem.IsReadyToRemove &&
            !tItem.IsOld)
        {
            tItem.IsOld = true;
            PropertyChangedEventHandler handler = null;
            handler = new PropertyChangedEventHandler(delegate(object s, PropertyChangedEventArgs ev)
            {
                if (tItem != null && tItem.IsReadyToRemove)
                {
                    tItem.PropertyChanged -= handler;
                    applyValue.Invoke();
                }
            });
            tItem.PropertyChanged += handler;
            return true;
        }
        else
            return false;
    }

    Then here is a view and viewmodel using the fade action:
    <ContentPresenter Content="{Binding Path=Content, NotifyOnTargetUpdated=true}"
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="TargetUpdated" >
                <PersonalMVVMSampleApp_Behaviors:Fade_InAndOut_TransitionAction />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ContentPresenter>

    public class ApplicationViewModel : TransitionItem
    {...
      public ITransitionItem Content
      {
        get { return _content; }
        set 
        {
            if (!DelaySet(_content, () => Content = value))
            {
                _content = value;
                OnPropertyChanged("Content");
            }
        }
      }

    The code for items controls is even simpler because no special code is needed in the viewmodel. It's handled by a custom version of ObservableCollection:
    public class TransitioningObservableCollection<T> : ObservableCollection<T> where T : ITransitionItem
    {
        protected override void RemoveItem(int index)
        {
            var toRemove = this[index];
            if (toRemove.IsReadyToRemove)
                base.RemoveItem(index);
            else if (!toRemove.TransitionEnabled)
                base.RemoveItem(index);
            else
            {
                toRemove.IsOld = true;
                toRemove.PropertyChanged += new PropertyChangedEventHandler(delegate(object s, PropertyChangedEventArgs e)
                {
                    if (toRemove.IsReadyToRemove) Remove(toRemove);
                });
            }
        }
    }

    I won't post the details of the behaviors here, but basically they attach and look for a transiton item to listen to, then apply the transtions when appropriate. (i'll make the source code available soon)

    This is meant to get me on the road to something reusable, but I welcome suggestions for how to improve this, or you can just tell me that this is a crazy way to handle this issue and you'll probably be right.