It's a shame there isn’t a better error message. The inability to assign a target object without a name is also disappointing.
Another issue is the duration property for these animations. You can set it without receiving an error, but the duration won’t change. This is fine as you should be seeking consistency with other apps. It's just sad that this is a silent failure. It is noted on MSDN though.
This custom behaviour adds both the animation and a command binding. If specified the bound command will execute after the animation completes. If the command reports as not executable, neither the animation nor the command executes.
1: public class BehaviourBase<T> : DependencyObject, IBehavior where T : DependencyObject
2: {
3:
4: private T _AssociatedObject;
5:
6: public DependencyObject AssociatedObject
7: {
8: get
9: {
10: return _AssociatedObject;
11: }
12: }
13:
14: public T TypedAssociatedObject
15: {
16: get { return _AssociatedObject; }
17: }
18:
19: public void Attach(DependencyObject associatedObject)
20: {
21: if (associatedObject == null) throw new ArgumentNullException("associatedObject");
22:
23: _AssociatedObject = (T)associatedObject;
24: Attached(_AssociatedObject);
25: }
26:
27: public virtual void Attached(T associatedObject)
28: {
29: }
30:
31: public void Detach()
32: {
33: if (_AssociatedObject != null)
34: {
35: Detatching();
36: _AssociatedObject = null;
37: }
38: }
39:
40: public virtual void Detatching()
41: {
42: }
43:
44: }
45:
46: public class VisualTapBehavior : BehaviourBase<UIElement>
47: {
48:
49: public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(VisualTapBehavior), new PropertyMetadata(null));
50: public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(VisualTapBehavior), new PropertyMetadata(null));
51:
52: private Storyboard _PointerDownStoryboard;
53: private Storyboard _PointerUpStoryboard;
54:
55: public Storyboard Storyboard { get; internal set; }
56:
57: public ICommand Command
58: {
59: get { return (ICommand)this.GetValue(CommandProperty); }
60: set
61: {
62: this.SetValue(CommandProperty, value);
63: }
64: }
65:
66: public object CommandParameter
67: {
68: get { return this.GetValue(CommandParameterProperty); }
69: set
70: {
71: this.SetValue(CommandParameterProperty, value);
72: }
73: }
74:
75: public override void Attached(UIElement associatedObject)
76: {
77: base.Attached(associatedObject);
78:
79: //Stupidly, these theme animations require the object to have a name :(
80: var fe = ((FrameworkElement)associatedObject);
81: var name = fe.Name;
82: if (String.IsNullOrEmpty(name))
83: {
84: name = Guid.NewGuid().ToString();
85: fe.Name = name;
86: }
87:
88: if (fe.Resources.ContainsKey("VisualTapDownAnimation"))
89: _PointerDownStoryboard = (Storyboard)fe.Resources["VisualTapDownAnimation"];
90: else
91: {
92: var pointerDownStoryboard = new Storyboard();
93: var downAnimation = new PointerDownThemeAnimation();
94: Storyboard.SetTargetName(downAnimation, name);
95: pointerDownStoryboard.Children.Add(downAnimation);
96: _PointerDownStoryboard = pointerDownStoryboard;
97: fe.Resources.Add(new KeyValuePair<object, object>("VisualTapDownAnimation", pointerDownStoryboard));
98: }
99:
100: if (fe.Resources.ContainsKey("VisualTapUpAnimation"))
101: _PointerUpStoryboard = (Storyboard)fe.Resources["VisualTapUpAnimation"];
102: else
103: {
104: var pointerUpStoryboard = new Storyboard();
105: var upAnimation = new PointerUpThemeAnimation();
106: Storyboard.SetTargetName(upAnimation, name);
107: pointerUpStoryboard.Children.Add(upAnimation);
108: pointerUpStoryboard.Completed += PointerUpStoryboard_Completed;
109: _PointerUpStoryboard = pointerUpStoryboard;
110: ((FrameworkElement)associatedObject).Resources.Add(new KeyValuePair<object, object>("VisualTapUpAnimation", pointerUpStoryboard));
111: }
112:
113: associatedObject.PointerPressed += AssociatedObject_PointerPressed;
114: associatedObject.PointerReleased += AssociatedObject_PointerReleased;
115: }
116:
117: private async void PointerUpStoryboard_Completed(object sender, object e)
118: {
119: await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
120: () =>
121: {
122: var command = this.Command;
123: var commandParameter = this.CommandParameter;
124: if (command != null && command.CanExecute(commandParameter))
125: command.Execute(commandParameter);
126: });
127: }
128:
129: public override void Detatching()
130: {
131: base.Detatching();
132:
133: TypedAssociatedObject.PointerPressed -= this.AssociatedObject_PointerPressed;
134: TypedAssociatedObject.PointerReleased -= this.AssociatedObject_PointerReleased;
135:
136: _PointerDownStoryboard = null;
137: }
138:
139: private void AssociatedObject_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
140: {
141: ((FrameworkElement)sender).CapturePointer(e.Pointer);
142:
143: var command = this.Command;
144: var commandParameter = this.CommandParameter;
145: if (command == null || command.CanExecute(CommandParameter))
146: RunStoryboardIfNotNull(_PointerDownStoryboard);
147: }
148:
149: private void AssociatedObject_PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
150: {
151: ((FrameworkElement)sender).ReleasePointerCapture(e.Pointer);
152: RunStoryboardIfNotNull(_PointerUpStoryboard);
153: }
154:
155: private void RunStoryboardIfNotNull(Storyboard storyboard)
156: {
157: if (storyboard != null)
158: {
159: storyboard.Stop();
160: storyboard.Begin();
161: }
162: }
163: }