Tuesday 31 August 2010

Animated Panel explained

In this post I will explain some key moments of the AnimatedPanel. I won't post any code here - just high level explanation.

So, how does that AnimatedPanel work?
Let's start from the beginning, i.e. from WPF layout model.
WPF layout model involves two steps:
1) Measure pass
2) Arrange pass

In measure-pass all visuals tell the parent container what size they want to be. Parent container analyzes that data and then arranges its content (arrange-pass). In the arrange-pass all visuals should arrange itself according to the size they got from the parent (i.e. what size they should be).

And now let's return to AnimatedPanel.

In MeasureOverride method we measure our children telling them that they should fit itself in ItemSize width/height (note that we actually don't care about what size those visuals want to be). We calculate "resSize" and return this size to the parent. It is very important to return valid size. If we would return an empty size (width and heigth equal to zero) then scroll bars would be disabled! (that's right, with exclamation - I don't want my container to be without scroll bars).

In ArrangeOverride we do all the animation logic. We start with checking if our panel is used as an ItemsHost (i.e. inside ListBox or ListView, etc.). And then you can see some strange code that by some reason calls GetHashCode on child's Content. So why the hell do we need such complexity???? I'll explain it a bit later. After we've got hash code we calculate real position of the element and save it position to the currentMap dictionary (this map contains current positions for all children).

Next step is to determine if this is a new child and to get appropriate transform. First we get TransformGroup from child's RenderTransform (and if child does not have TransformGroup then we create it). Then we look for TranslateTransform inside that TransformGroup. We need TranslateTransform for animating child's movement (why just not to create new TranslateTransform? Because child may have other transforms and we don't want to harm them. That's why we use TransformGroup). If child does not have TranslateTransform then we create it. But, if child does not have TranslateTransform then it may be a completely new child! And this is the place where we need hash code for this child. We simply read previousMap dictionary (it contains positions of all children after previous arrange-pass) and if such hash code exists then we get previous point for this child.

Next step is arrangement of child. We call Arrange method on our child to set child's new location. For our animation we need previous location of the child and for this we have "fromPoint" variable. As soon as we arranged child to its new location we can now animate it. We use "from" animation here. Because "fromPoint" variable contains previous location of the child relative to the child we simply sets the "to" value for animation to zero. We animate child by calling BeginAnimation of our TranslateTransform.

And now I'll explain why we need a hash code.We need hash code for determining previous location of the child. And we actually need hash code of the child's Content because it won't change on sorting or deletion of underlying collection. You may be surprised but when we call Refresh() method on CollectionViewSource's View we will get completely new Children collection in our AnimatedPanel (because View will be rebuilt).

Please note that AnimatedPanel does not expect for objects to be changed. So please take it into account if you want to use this class somewhere.

No comments:

Post a Comment