Mim*_*imi 9 c# wpf xaml binding storyboard
考虑以下代码:
<UserControl x:Class="MyApp.MyControl"
...
xmlns:local="clr-namespace:MyApp"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<UserControl.Template>
<ControlTemplate>
<ControlTemplate.Resources>
<Storyboard x:Key="MyStory">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase">
<SplineColorKeyFrame KeyTime="0:0:1" Value="Red"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Border x:Name="brdBase" BorderThickness="1" BorderBrush="Cyan" Background="Black">
...
</Border>
<ControlTemplate.Triggers>
<Trigger SourceName="brdBase" Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource MyStory}"/>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Template>
</UserControl>
Run Code Online (Sandbox Code Playgroud)
上面的代码没有问题.现在,我想将关键帧值绑定到此用户控件MyStory
的DP(命名SpecialColor
),如下所示:
<Storyboard x:Key="MyStory">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase">
<SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}, Path=SpecialColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
Run Code Online (Sandbox Code Playgroud)
这会出错:
无法冻结此Storyboard时间轴树以跨线程使用.
使用后面的代码可以做到这一点.但是我怎么才能在XAML中做到这一点?
代码隐藏辅助解决方案:
► 步骤1:将MyStory
故事板放入brdBase
资源中.
<UserControl.Template>
<ControlTemplate>
<Border x:Name="brdBase" BorderThickness="1" BorderBrush="Cyan" Background="Black">
<Border.Resources>
<Storyboard x:Key="MyStory">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase">
<SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}, Path=SpecialColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Border.Resources>
...
</Border>
<ControlTemplate.Triggers>
<Trigger SourceName="brdBase" Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource MyStory}"/>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Template>
Run Code Online (Sandbox Code Playgroud)
错误: 找不到名为"MyStory"的资源.资源名称区分大小写.
► 第2步:消除Trigger
对IsMouseOver
财产,并开始MyStory
从后面的代码.
<UserControl.Template>
<ControlTemplate>
<Border x:Name="brdBase" BorderThickness="1" BorderBrush="Cyan" Background="Black" MouseEnter="brdBase_MouseEnter">
<Border.Resources>
<Storyboard x:Key="MyStory">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase">
<SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}, Path=SpecialColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Border.Resources>
</Border>
</ControlTemplate>
</UserControl.Template>
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
► 步骤3:解决方案已经完成,但第一次不起作用.幸运的是,这个问题有一个解决方法.这足以让它成为ControlTemplate
一个Style
.
(我需要其他Trigger
类型EventTrigger
而且必须UserControl
用ControlTemplate
.包装元素.)
更新:
使用ObjectDataProvider
失败的想法.
这是代码:
<UserControl.Template>
<ControlTemplate>
<ControlTemplate.Resources>
<local:StoryboardFinder x:Key="StoryboardFinder1" AssociatedControl="{Binding ElementName=brdBase}"/>
<ObjectDataProvider x:Key="dataProvider" ObjectInstance="{StaticResource StoryboardFinder1}" MethodName="Finder">
<ObjectDataProvider.MethodParameters>
<sys:String>MyStory</sys:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ControlTemplate.Resources>
<Border x:Name="brdBase" BorderThickness="1" BorderBrush="Cyan" Background="Black">
<Border.Resources>
<Storyboard x:Key="MyStory">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase">
<SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}, Path=SpecialColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Border.Resources>
...
</Border>
<ControlTemplate.Triggers>
<Trigger SourceName="brdBase" Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource dataProvider}"/>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Template>
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
如果这段代码是真的怎么办?
\n\n<UserControl x:Class="MyApp.MyControl"\n ...\n xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"\n xmlns:l="clr-namespace:MyApp"\n DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">\n\n <UserControl.Resources>\n <Style TargetType="{x:Type l:MyControl}">\n <Setter Property="Template">\n <Setter.Value>\n <ControlTemplate TargetType="{x:Type l:MyControl}">\n <Border x:Name="brdBase" BorderThickness="1" BorderBrush="Cyan" Background="Black">\n <Border.Resources>\n <Storyboard x:Key="MyStory">\n <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase">\n <SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type l:MyControl}}, Path=SpecialColor}"/>\n </ColorAnimationUsingKeyFrames>\n </Storyboard>\n </Border.Resources>\n\n <i:Interaction.Triggers>\n <l:InteractiveTrigger Property="IsMouseOver" Value="True">\n <l:InteractiveTrigger.CommonActions>\n <BeginStoryboard Storyboard="{StaticResource MyStory}"/>\n </l:InteractiveTrigger.CommonActions>\n </l:InteractiveTrigger>\n </i:Interaction.Triggers>\n </Border>\n </ControlTemplate>\n </Setter.Value>\n </Setter>\n </Style>\n </UserControl.Resources>\n</UserControl>\n
Run Code Online (Sandbox Code Playgroud)\n\n如果是这样,我可以在IsMouseOver
财产上有一个触发器......
我很高兴地说这是一个工作代码:)我只能EventTrigger
在<Border.Triggers>
标签中使用。这是限制。所以我开始思考这个想法:如果我可以有一个可以在FrameworkElement.Triggers
范围内工作的自定义触发器怎么办?这是代码:
using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Windows;\nusing System.Windows.Interactivity;\nusing System.Windows.Media.Animation;\n\nnamespace TriggerTest\n{\n /// <summary>\n /// InteractiveTrigger is a trigger that can be used as the System.Windows.Trigger but in the System.Windows.Interactivity.\n /// <para>\n /// Note: There is neither `EnterActions` nor `ExitActions` in this class. The `CommonActions` can be used instead of `EnterActions`.\n /// Also, the `Actions` property which is of type System.Windows.Interactivity.TriggerAction can be used.\n /// </para>\n /// <para>\xc2\xa0</para>\n /// <para>\n /// There is only one kind of triggers (i.e. EventTrigger) in the System.Windows.Interactivity. So you can use the following triggers in this namespace:\n /// <para>1- InteractiveTrigger : Trigger</para>\n /// <para>2- InteractiveMultiTrigger : MultiTrigger</para>\n /// <para>3- InteractiveDataTrigger : DataTrigger</para>\n /// <para>4- InteractiveMultiDataTrigger : MultiDataTrigger</para>\n /// </para>\n /// </summary>\n public class InteractiveTrigger : TriggerBase<FrameworkElement>\n {\n #region ___________________________________________________________________________________ Properties\n\n #region ________________________________________ Value\n\n /// <summary>\n /// [Wrapper property for ValueProperty]\n /// <para>\n /// Gets or sets the value to be compared with the property value of the element. The comparison is a reference equality check.\n /// </para>\n /// </summary>\n public object Value\n {\n get { return (object)GetValue(ValueProperty); }\n set { SetValue(ValueProperty, value); }\n }\n\n public static readonly DependencyProperty ValueProperty =\n DependencyProperty.Register("Value",\n typeof(object),\n typeof(InteractiveTrigger),\n new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, OnValuePropertyChanged));\n\n private static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)\n {\n InteractiveTrigger instance = sender as InteractiveTrigger;\n\n if (instance != null)\n {\n if (instance.CanFire)\n instance.Fire();\n }\n }\n\n #endregion\n\n\n /// <summary>\n /// Gets or sets the name of the object with the property that causes the associated setters to be applied.\n /// </summary>\n public string SourceName\n {\n get;\n set;\n }\n\n /// <summary>\n /// Gets or sets the property that returns the value that is compared with this trigger.Value property. The comparison is a reference equality check.\n /// </summary>\n public DependencyProperty Property\n {\n get;\n set;\n }\n\n /// <summary>\n /// Gets or sets a collection of System.Windows.Setter objects, which describe the property values to apply when the trigger object becomes active.\n /// </summary>\n public List<Setter> Setters\n {\n get;\n set;\n }\n\n /// <summary>\n /// Gets or sets the collection of System.Windows.TriggerAction objects to apply when this trigger object becomes active.\n /// </summary>\n public List<System.Windows.TriggerAction> CommonActions\n {\n get;\n set;\n }\n\n /// <summary>\n /// Gets a value indicating whether this trigger can be active to apply setters and actions.\n /// </summary>\n private bool CanFire\n {\n get\n {\n if (this.AssociatedObject == null)\n {\n return false;\n }\n else\n {\n object associatedValue;\n\n if (string.IsNullOrEmpty(SourceName))\n associatedValue = this.AssociatedObject.GetValue(Property);\n else\n associatedValue = (this.AssociatedObject.FindName(SourceName) as DependencyObject).GetValue(Property);\n\n TypeConverter typeConverter = TypeDescriptor.GetConverter(Property.PropertyType);\n object realValue = typeConverter.ConvertFromString(Value.ToString());\n\n return associatedValue.Equals(realValue);\n }\n }\n }\n\n #endregion\n\n\n #region ___________________________________________________________________________________ Methods\n\n /// <summary>\n /// Fires (activates) current trigger by setting setter values and invoking all actions.\n /// </summary>\n private void Fire()\n {\n //\n // Setting setters values to their associated properties..\n //\n foreach (Setter setter in Setters)\n {\n if (string.IsNullOrEmpty(setter.TargetName))\n this.AssociatedObject.SetValue(setter.Property, setter.Value);\n else\n (this.AssociatedObject.FindName(setter.TargetName) as DependencyObject).SetValue(setter.Property, setter.Value);\n }\n\n //\n // Firing actions.. \n //\n foreach (System.Windows.TriggerAction action in CommonActions)\n {\n Type actionType = action.GetType();\n\n if (actionType == typeof(BeginStoryboard))\n {\n (action as BeginStoryboard).Storyboard.Begin();\n }\n else\n throw new NotImplementedException();\n }\n\n this.InvokeActions(null);\n }\n\n #endregion\n\n\n #region ___________________________________________________________________________________ Events\n\n public InteractiveTrigger()\n {\n Setters = new List<Setter>();\n CommonActions = new List<System.Windows.TriggerAction>();\n }\n\n protected override void OnAttached()\n {\n base.OnAttached();\n\n if (Property != null)\n {\n object propertyAssociatedObject;\n\n if (string.IsNullOrEmpty(SourceName))\n propertyAssociatedObject = this.AssociatedObject;\n else\n propertyAssociatedObject = this.AssociatedObject.FindName(SourceName);\n\n //\n // Adding a property changed listener to the property associated-object..\n //\n DependencyPropertyDescriptor dpDescriptor = DependencyPropertyDescriptor.FromProperty(Property, propertyAssociatedObject.GetType());\n dpDescriptor.AddValueChanged(propertyAssociatedObject, PropertyListener_ValueChanged);\n }\n }\n\n protected override void OnDetaching()\n {\n base.OnDetaching();\n\n if (Property != null)\n {\n object propertyAssociatedObject;\n\n if (string.IsNullOrEmpty(SourceName))\n propertyAssociatedObject = this.AssociatedObject;\n else\n propertyAssociatedObject = this.AssociatedObject.FindName(SourceName);\n\n //\n // Removing previously added property changed listener from the associated-object..\n //\n DependencyPropertyDescriptor dpDescriptor = DependencyPropertyDescriptor.FromProperty(Property, propertyAssociatedObject.GetType());\n dpDescriptor.RemoveValueChanged(propertyAssociatedObject, PropertyListener_ValueChanged);\n }\n }\n\n private void PropertyListener_ValueChanged(object sender, EventArgs e)\n {\n if (CanFire)\n Fire();\n }\n\n #endregion\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n我还创建了其他触发器类型(即InteractiveMultiTrigger
,,InteractiveDataTrigger
InteractiveMultiDataTrigger
)以及更多操作,这使得有条件和多条件 EventTriggers 成为可能。如果你们专业人士确认了这个解决方案,我将全部发布。
感谢您的关注!
\n 归档时间: |
|
查看次数: |
7016 次 |
最近记录: |