如何从XAML访问元素资源中的故事板?
考虑以下代码:
...
上面的代码没有问题。 现在,我想将MyStory
关键帧值绑定到此用户控件的DP(名为SpecialColor
),如下所示:
这会出错:
无法冻结此Storyboard时间轴树以跨线程使用。
使用后面的代码可以做到这一点。 但是我怎么能只在XAML中做呢?
代码隐藏辅助解决方案:
► 步骤1:将MyStory
故事板放入brdBase
资源中。
...
错误: 找不到名为“MyStory”的资源。 资源名称区分大小写。
► 步骤2:消除IsMouseOver
属性上的IsMouseOver
并从后面的代码开始MyStory
。
C#Code-Behind:
private void brdBase_MouseEnter(object sender, MouseEventArgs e) { Border grdRoot = (Border)this.Template.FindName("brdBase", this); Storyboard story = grdRoot.Resources["MyStory"] as Storyboard; story.Begin(this, this.Template); }
► 步骤3:解决方案已经完成,但第一次不起作用。 幸运的是,这个问题有一个解决方法。 将ControlTemplate
放入Style
足够了。
(我需要其他Trigger
类型而不是EventTrigger
并且必须使用ControlTemplate
包装UserControl
元素。)
更新:
关于使用ObjectDataProvider
的想法失败了。
- ObjectDataProvider资源不能用于提供故事板! 错误报告是:
- XamlParseException:设置属性’System.Windows.Media.Animation.BeginStoryboard.Storyboard’抛出exception。
- InnerException: ‘System.Windows.Data.ObjectDataProvider’不是属性’Storyboard’的有效值。
- AssociatedControl DP始终为空。
这是代码:
MyStory ...
StoryboardFinder类:
public class StoryboardFinder : DependencyObject { #region ________________________________________ AssociatedControl public Control AssociatedControl { get { return (Control)GetValue(AssociatedControlProperty); } set { SetValue(AssociatedControlProperty, value); } } public static readonly DependencyProperty AssociatedControlProperty = DependencyProperty.Register("AssociatedControl", typeof(Control), typeof(StoryboardFinder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None)); #endregion public Storyboard Finder(string resourceName) { // // Associated control is always null :( // return new Storyboard(); } }
好吧,你不能真正绑定到“To”和From,因为故事板必须被冻结,以便有效地使用跨线程。
解决方案1)最简单的解决方案,无需黑客攻击(涉及代码隐藏):在事件处理程序中添加MouseOver事件处理程序,找到必要的动画,直接设置“To”属性,这样就不会使用绑定,可以完成“冻结” 。 这样你就不会硬编码:)。
解决方案2)有一个很酷的黑客只支持XAML(一点转换魔术),但我不建议。 尽管如此,它很酷:) WPF动画:绑定到故事板动画的“To”属性请参阅Jason的回答。
你可以尝试更多的东西:
解决方案3)不要使用依赖属性,而是实现INotifyProperthChanged。 这样你仍然可以BIND“To”。 请注意,我认为这应该在理论上有效,但我没有尝试过。
解决方案4)将Mode = OneTime应用于绑定。 也许它有效?
解决方案5)编写您自己的附加行为,该行为将评估正确线程上的依赖项属性并设置“To”属性。 我认为这将是一个很好的解决方案。
这里也是很好的重复: WPF动画“无法冻结此Storyboard时间轴树以跨线程使用”
如果这段代码是真的怎么办?
如果是这样,我可以在IsMouseOver
属性上有一个Trigger …
我很高兴地说这是一个有效的代码:)我只能在
标签中使用EventTrigger
。 这是限制。 所以我开始考虑这个想法:如果我有一个可以在FrameworkElement.Triggers
范围内工作的自定义触发器怎么办? 这是代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Windows; using System.Windows.Interactivity; using System.Windows.Media.Animation; namespace TriggerTest { /// /// InteractiveTrigger is a trigger that can be used as the System.Windows.Trigger but in the System.Windows.Interactivity. /// /// Note: There is neither `EnterActions` nor `ExitActions` in this class. The `CommonActions` can be used instead of `EnterActions`. /// Also, the `Actions` property which is of type System.Windows.Interactivity.TriggerAction can be used. /// /// /// /// There is only one kind of triggers (ie EventTrigger) in the System.Windows.Interactivity. So you can use the following triggers in this namespace: /// 1- InteractiveTrigger : Trigger /// 2- InteractiveMultiTrigger : MultiTrigger /// 3- InteractiveDataTrigger : DataTrigger /// 4- InteractiveMultiDataTrigger : MultiDataTrigger /// /// public class InteractiveTrigger : TriggerBase { #region ___________________________________________________________________________________ Properties #region ________________________________________ Value /// /// [Wrapper property for ValueProperty] /// /// Gets or sets the value to be compared with the property value of the element. The comparison is a reference equality check. /// /// public object Value { get { return (object)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(InteractiveTrigger), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, OnValuePropertyChanged)); private static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { InteractiveTrigger instance = sender as InteractiveTrigger; if (instance != null) { if (instance.CanFire) instance.Fire(); } } #endregion /// /// Gets or sets the name of the object with the property that causes the associated setters to be applied. /// public string SourceName { get; set; } /// /// Gets or sets the property that returns the value that is compared with this trigger.Value property. The comparison is a reference equality check. /// public DependencyProperty Property { get; set; } /// /// Gets or sets a collection of System.Windows.Setter objects, which describe the property values to apply when the trigger object becomes active. /// public List Setters { get; set; } /// /// Gets or sets the collection of System.Windows.TriggerAction objects to apply when this trigger object becomes active. /// public List CommonActions { get; set; } /// /// Gets a value indicating whether this trigger can be active to apply setters and actions. /// private bool CanFire { get { if (this.AssociatedObject == null) { return false; } else { object associatedValue; if (string.IsNullOrEmpty(SourceName)) associatedValue = this.AssociatedObject.GetValue(Property); else associatedValue = (this.AssociatedObject.FindName(SourceName) as DependencyObject).GetValue(Property); TypeConverter typeConverter = TypeDescriptor.GetConverter(Property.PropertyType); object realValue = typeConverter.ConvertFromString(Value.ToString()); return associatedValue.Equals(realValue); } } } #endregion #region ___________________________________________________________________________________ Methods /// /// Fires (activates) current trigger by setting setter values and invoking all actions. /// private void Fire() { // // Setting setters values to their associated properties.. // foreach (Setter setter in Setters) { if (string.IsNullOrEmpty(setter.TargetName)) this.AssociatedObject.SetValue(setter.Property, setter.Value); else (this.AssociatedObject.FindName(setter.TargetName) as DependencyObject).SetValue(setter.Property, setter.Value); } // // Firing actions.. // foreach (System.Windows.TriggerAction action in CommonActions) { Type actionType = action.GetType(); if (actionType == typeof(BeginStoryboard)) { (action as BeginStoryboard).Storyboard.Begin(); } else throw new NotImplementedException(); } this.InvokeActions(null); } #endregion #region ___________________________________________________________________________________ Events public InteractiveTrigger() { Setters = new List (); CommonActions = new List(); } protected override void OnAttached() { base.OnAttached(); if (Property != null) { object propertyAssociatedObject; if (string.IsNullOrEmpty(SourceName)) propertyAssociatedObject = this.AssociatedObject; else propertyAssociatedObject = this.AssociatedObject.FindName(SourceName); // // Adding a property changed listener to the property associated-object.. // DependencyPropertyDescriptor dpDescriptor = DependencyPropertyDescriptor.FromProperty(Property, propertyAssociatedObject.GetType()); dpDescriptor.AddValueChanged(propertyAssociatedObject, PropertyListener_ValueChanged); } } protected override void OnDetaching() { base.OnDetaching(); if (Property != null) { object propertyAssociatedObject; if (string.IsNullOrEmpty(SourceName)) propertyAssociatedObject = this.AssociatedObject; else propertyAssociatedObject = this.AssociatedObject.FindName(SourceName); // // Removing previously added property changed listener from the associated-object.. // DependencyPropertyDescriptor dpDescriptor = DependencyPropertyDescriptor.FromProperty(Property, propertyAssociatedObject.GetType()); dpDescriptor.RemoveValueChanged(propertyAssociatedObject, PropertyListener_ValueChanged); } } private void PropertyListener_ValueChanged(object sender, EventArgs e) { if (CanFire) Fire(); } #endregion } }
我还创建了其他触发器类型(即InteractiveMultiTrigger
, InteractiveDataTrigger
, InteractiveMultiDataTrigger
)以及一些可以使条件和多条件EventTriggers成为可能的动作。 如果专业人士确认这个解决方案,我会发布所有内容。
感谢您的关注!