Phenomenological Layout in WPF: More on Layout Animations
In a recent post, I stated explaining some of the work done in the AMG-Mercedes demo to handle layout animations. A few people posted some comments looking for more details. I'm back with a code sample that elucidates a concept in WPF called phenomenological layout. Why invoke phenomenology here? Well, I find philosophy helpful in explaining and understanding WPF and computer science in general. A while back, I wrote about WPF's notion of Platonic controls, which I still maintain is a better description than the term "lookless controls," the ability to completely restyle a control will retaining its core purpose. And, I will submit that once you dive into the depths of the Avalon layout engine, you will conclude that there can be a curious relationship between the experience of layout as compared to the intuition of layout, which is a philosophical area phenomenology seeks to explore. This connection/disconnection is especially true if you grok the ghost proxy layout engine in the code sample associated with this post, which allows for some clever layout to layout animations in WPF.
Two main things need to be processed to harness the power of the layout engine in conjuntion with animation. First, one needs to understand the power inherent in the layout engine in WPF. It is powerful and well thought out. A lot of time and energy has gone into it and, if possible, applications should take advantage of it. Second, one needs to understand the ability to do translation transforms as a RenderTransform, which happens after a layout pass, means that the layout engine can be overridden. Just because an element thinks it is in one place doesn't necessarily mean it is there. Phenomenological layout is the marriage of these two ideas: combining the power of the layout engine with translation animations so as to get the best of both.
So, let's look at a concrete example. In the AMG-Mercedes demo, when an element gets a mouse hover event, it scales, while the elements next to it flee to the corner. How to code this? Follow along with the code sample posted.
The first thought would be to use something like a StackPanel to host the elements and place the StackPanel in the center of the page using a Grid. Then, we could use a TranslateTransform to yank the elements out of the stack panel to the top of the page. You can see an implementation of this in the frame called "Using Translation". Here's the problem though? How do you know where to translate the elements to since the translation is relative to the element? How do you get to the upper left corner or lower right corner?
There is a solution to this, which is a very handy method that hangs off all UIElements called TranslatePoint. Basically, it creates a point for you relative to another element. This sort of solves our problem of figuring out where to translate to , which you can see in the frame "Using Translation With TranslatePoint." It doesn't quite work though because the best we can do to get a consistent translate point is to base it of the parent. Watch what happens if you change this line:
pt = hostGrid.TranslatePoint(new Point(0, 0), fe.Parent as UIElement);
pt = hostGrid.TranslatePoint(new Point(0, 0), fe as UIElement);
So, basing off the parent StackPanel is good, but there will be a gap in the elements. I could add some layout logic to handle this I suppose, and the TranslatePoint method could get me pretty far, but not far enough.
Okay, so at this point I could give up with the layout engine and just keep track of the layout myself. You can see this implementation in the one called "Using Canvas". In this case, rather than doing translations, I animate the Canvas.Top and Canvas.Left. This works, but if you look at the code, there is a decent amount of layout logic I am doing to get things to work as expected. It isn't a ton, but if I wanted to do something more complicated, I then start building a layout engine myself. As soon as you find yourself building your own layout engine in WPF, it is a good warning sign that you aren't benefiting from what the underlying platform has to offer. Also, using Canvas means you are absolutely positioned. What happens if the app is resized or is running on a different form factor, like a wide screen monitor? Canvas is very limiting in that regard.
Also, notice in the "Using Canvas" sample, I did throw a viewbox on there: you can see how it maintains the aspect ratio of the application.
So, how to solve? How to get layout goodness with layout animations but at the same time get the goodness of the layout engine? Enter phenomenological layout. Included in the sample is a class called LayoutToLayout animations that provides a solution to our problem. Basically, it combines the usage of a canvas with the layout engine behind the canvas, a proxy for the layout that tells the canvas what to do. The host, which is on the canvas, is bound to a target, which is just a Border. This proxy layout figures out the coordinates for the host and then the host can animate itself between layouts. The host elements are never actually "in" a StackPanel or Grid; but they appear to be because they are getting their translation coordinates from the layout engine. Pretty sneaky! What this means is that you can animate between layouts in WPF and get all the layout goodness like alignment, centering, margins, padding, etc.
You can see this in action in the frames called "Using Phenomenological Layout" and "Using Phenomenological Layout 2". The second example shows where this is really handy. In that case, my ghost stackpanel layout is aligned right and bottom. Even if I resize the app, the layout will always animate aligned to there. How would I possible do this with a canvas that got resized?
So, some more tricks to add to your bag and hopefully some more food for thought on the power and flexibility of this UI platform.
Note: A shout out to Daniel Christensen who prototyped the layout to layout code.