1.2 Released, Rotate Fixed, Lesson Learned

Coordinator
Jul 29, 2008 at 10:44 PM
1.2 just went live. I fixed the RotateTransition bug and learned a few things in the process. I thought I'd share them with you (these are also included in the issue resolution notes).

Previously whenever a clone (VisualBrush) was made for a Visual the original Visual was hidden. This makes sense in a lot of cases like RotateTransition where the original visuals need to be hidden while the cube is rotating (the cube shows two rectangles that use VisualBrush to represent the respective  original visuals). But you may want to create a transition where the original visual actually stays around (think reflection or water effects). So I changed the code to not to hide the original automatically and updated all of the transitions that used VisualBrush to hide their original visual when it made sense.

 

"So what went wrong?" Well, I left a little detail out in my explanation above. When I was making changes and bug fixes to deal with the previous VisualBrush problem I noticed that the visuals were being placed into an adorner. I didn't understand the reason for the adorner at the time and Adorner isn't available in Silverlight (we eventually want to port if we can). So I removed the Adorner and started dealing with the content directly. And when I said above that the original Visual was "hidden", that's not exactly the case. The Visual itself can't be set to Visbility.Hidden or the VisualBrush won't render it. Removing the Visual from the Visual Tree won't work either because of the framework bug that will cause the VisualBrush to crash. So instead of hiding the visual or removing it from the visual tree, I instead move the visual to a Grid that's in the visual tree but is hidden.

 

This works in theory, but I had set the grid to Visibility.Collapsed instead of Visibility.Hidden. "Why does that matter?" It matters because Visibility.Collapsed means that layout will never occur for the items inside the grid. "And why does that matter?" It matters because if a Visual is never added to a container that performs layout, a VisualBrush pointing at that visual won't have anything to render.

 

"So why did it effect only RotateTransition?" Well, it turns out that RotateTransition is the only transition currently that uses VisualBrush to represent both the old content and the new content. The old content renders just fine because it's already been visible on the screen. But the new content has never had it's layout performed and we're just shuffling it off to the hidden grid (which also doesn't do layout). So the new content doesn't get rendered until after the transition completes and it gets put into the regular Child collection.

 

"Okay, that explains why it didn't work from A to B, but why did it work from B to C"? That's because I left out one final detail. TransitionElement internally uses ContentPresenters to host the old content and the new content. This is so we can support data templates. In truth, it's actually the content presenters that needed to have their layout run. After we'd completed at least one transition, both presenters had their layout run and from that point onward everything works as expected.

 

"Alright, but how'd you fix it?" Glad you asked. As usual, for such a complicated problem the fix was pretty simple: I changed the hidden grid from Visibility.Collapsed to Visibility.Hidden. That way anything in the hidden container will still participate in layout and this approach has two benefits: The first is that our VisualBrush will have content to render. The second is a side effect but it's a great one. Since the hidden grid expands to fill TransitionElement, whenever TransitionElement resizes the hidden content and their associated Visual Brushes will update accordingly. I don't know if you noticed the "squishing" or "stretching" that used to happen with RotateTransition when the content wasn't in the visual tree, but that doesn't happen anymore.

Jul 30, 2008 at 6:58 PM
Good catch!!

And yes I did notice the squishing, thanks for killing that one too.