Something strange…
I saw something strange today, so I tried to isolate and reproduce the problem, and I think I have.
This is what I was trying to do:
- I’m writing a gate entry application for a gym. When a member swipes their badge, a popup appears in the bottom-right corner.
- The popup is green (if it’s a valid swipe) or red if not.
- Additionally, the popup may show any number of alerts (e.g. overdue fees, barcode not found, Happy Birthday!, etc…)
So to implement this, I figured I’d create a Window, place it in the bottom-right corner of the screen; create an EntryViewModel (perform rules and logic to work out the state of the ‘Entry’) and finally, use a TemplateSelector to decide how to present the Popup view…
One thing I wanted to do was slide the red ones over to the left side of the screen, and keep them there (the green ones automatically fade away after 2 or 3 seconds)
So I added an animation in the UserControl to grab the parent Window (using a FindAncestor RelativeSource) and decrease the ‘Left’ property down to 0 over 1/2 a second.
What I saw was only *one* of the animations worked… that is, if the window contained a ‘green’ user control, it would fade away, but if it was for a ‘red’ one, it wouldn’t slide off to the left…
Puzzled, I changed the green animation to slide (instead of fade), and still it worked, but still the red one didn’t…
Well this bothered me, so I added a TextBlock into the UserControl, setting the Text to the exact same Binding as the animation (to make sure the parent Window was being correctly derived), and guess what? The animation started working…
Huh? I figured something had gone a bit wrong, and changing the template and re-compiling had fixed it, so I commented out the TextBlock and tried again… bugger me if the animation didn’t stop working again…
I’ve attached a sample app that demonstrates the problem… If anyone can unravel this and let me know what I’ve done wrong, I would be much obliged.
In the meantime, I have a TextBlock in my UserControl with Visibility set to ‘Hidden’ and a bloody big comment next to it saying ‘do not remove this seemingly meaningless TextBlock – It makes the animation work!!’
Update:
WPF Disciple, Microsoft MVP and thoroughly bloody good bloke Paul Stovell enlightened me on this one… What was happening was, the UserControls were attempting to run an animation whose Target objects were the parent Window for the Control. To find the Window, I had ‘reached up and out of the Control’, like this:
<DoubleAnimationUsingKeyFrames
Storyboard.Target="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
Storyboard.TargetProperty="Left">
<LinearDoubleKeyFrame KeyTime="0:0:2" Value="50.0" />
</DoubleAnimationUsingKeyFrames>
but the problem was that this animation was being run (and hence this Binding was being evaluated) before the UserControl had been added to the Visual tree, due the way the Loaded event fires…
The ‘green’ UserControl contained a TextBlock that binds to the name off the EventViewModel, and subsequently the UserControl loaded event is fired just that little bit later (after the Control is added to the Visual tree) – so the ‘green’ ones were working just fine…
As per his suggestion, I have created the animations directly in the parent window (thereby removing the FindAncestor Binding), and fired an event in the UserControl to let the parent know some animation needs to be run…
I’l leave the problematic code here for reference: To see the problem, comment out the TextBlock from any of the UserControls…
Download:
Interesting.
It seems to be a timing issue. If you change the event to UserControl.MouseDown instead of Loaded, it works when you click. The binding in the TextBlock also doesn’t matter – if you have a binding, any binding, it works, but if you remove the binding, it doesn’t work.
This post explains a little about the Loaded event:
http://blogs.msdn.com/b/mikehillberg/archive/2006/09/19/loadedvsinitialized.aspx
My *guess* is that the presence of a binding is enough to change the point at which Loaded is raised (without it, it’s raised before the control is added to the tree, with it, it raises after).
The TextBlock workaround works, but this probably suggests that this might not be the best design – a user control probably shouldn’t “reach up” and manipulate its owning window. An alternative design might be to raise an event, which the parent window handles, and let the parent window do the animation based on the event payload.
Paul
Thanks loads, Paul!
It seemed so much more likely that I’d done something wrong than I’d discovered a bug in the platform – thanks for confirming this!
I like the idea of raising an event from the UserControl and letting the parent Window handle the animation. I’ll fix this up first thing in the morning