2013-04-06 11 views
9

は、このコードを考えてみましょう:XAMLから要素リソース内のストーリーボードにアクセスするにはどうすればよいですか?

<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> 

上記のコードは問題なく動作します。

エラーになり
<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> 

はこのストーリーボードのタイムラインツリーを凍結することはできませんが、今、私はそうのように、このユーザー・コントロールの(SpecialColor命名)DPへMyStoryのバインドキーフレーム値をしたいですスレッド間で使用する。

これはコードの背後で行うことができます。しかし、どうすればXAMLでそれを行うことができますか?コードビハインド


ソリューション支援:

►ステップ1:brdBaseリソースへMyStoryストーリーボードを置きます。

<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> 

エラー:は 'あるmyStory' という名前のリソースを見つけることができません。リソース名は大文字と小文字を区別します

►ステップ2:IsMouseOver性質上Triggerを排除し、背後にあるコードから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> 

C#のコードビハインド:

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:溶液がすでに行われているが、それは最初の時間では動作しません。幸いにも、この問題の回避策があります。 ControlTemplateStyleに入れるだけで十分です。

は(私はEventTrigger以外のTriggerの種類を必要とControlTemplateUserControl要素をラップする必要があります。)


を更新:

ObjectDataProviderの使用についての考えは失敗しました。

  1. たObjectDataProviderリソースは、ストーリーボードを提供するために使用することはできません!エラーレポート:例外をスローした設定するプロパティ 'System.Windows.Media.Animation.BeginStoryboard.Storyboard':
    • XamlParseException。
    • InnerException: 'System.Windows.Data。ObjectDataProvider 'はプロパティ' Storyboard 'の有効な値ではありません。
  2. AssociatedControl DPは常にnullです。ここで

コードです:

<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> 

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(); 
    } 
} 

答えて

3

何?

<UserControl x:Class="MyApp.MyControl" 
      ... 
      xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
      xmlns:l="clr-namespace:MyApp" 
      DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"> 

    <UserControl.Resources> 
     <Style TargetType="{x:Type l:MyControl}"> 
      <Setter Property="Template"> 
       <Setter.Value> 
        <ControlTemplate TargetType="{x:Type l:MyControl}"> 
         <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 l:MyControl}}, Path=SpecialColor}"/> 
            </ColorAnimationUsingKeyFrames> 
           </Storyboard> 
          </Border.Resources> 

          <i:Interaction.Triggers> 
           <l:InteractiveTrigger Property="IsMouseOver" Value="True"> 
            <l:InteractiveTrigger.CommonActions> 
             <BeginStoryboard Storyboard="{StaticResource MyStory}"/> 
            </l:InteractiveTrigger.CommonActions> 
           </l:InteractiveTrigger> 
          </i:Interaction.Triggers> 
         </Border> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
    </UserControl.Resources> 
</UserControl> 

もしそうなら、私は... IsMouseOver性質上

をトリガを持っている可能性があり、私はそれが作業コード:)私は<Border.Triggers>タグで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 
{ 
    /// <summary> 
    /// InteractiveTrigger is a trigger that can be used as the System.Windows.Trigger but in the System.Windows.Interactivity. 
    /// <para> 
    /// 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. 
    /// </para> 
    /// <para> </para> 
    /// <para> 
    /// 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: 
    /// <para>1- InteractiveTrigger : Trigger</para> 
    /// <para>2- InteractiveMultiTrigger : MultiTrigger</para> 
    /// <para>3- InteractiveDataTrigger : DataTrigger</para> 
    /// <para>4- InteractiveMultiDataTrigger : MultiDataTrigger</para> 
    /// </para> 
    /// </summary> 
    public class InteractiveTrigger : TriggerBase<FrameworkElement> 
    { 
     #region ___________________________________________________________________________________ Properties 

     #region ________________________________________ Value 

     /// <summary> 
     /// [Wrapper property for ValueProperty] 
     /// <para> 
     /// Gets or sets the value to be compared with the property value of the element. The comparison is a reference equality check. 
     /// </para> 
     /// </summary> 
     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 


     /// <summary> 
     /// Gets or sets the name of the object with the property that causes the associated setters to be applied. 
     /// </summary> 
     public string SourceName 
     { 
      get; 
      set; 
     } 

     /// <summary> 
     /// Gets or sets the property that returns the value that is compared with this trigger.Value property. The comparison is a reference equality check. 
     /// </summary> 
     public DependencyProperty Property 
     { 
      get; 
      set; 
     } 

     /// <summary> 
     /// Gets or sets a collection of System.Windows.Setter objects, which describe the property values to apply when the trigger object becomes active. 
     /// </summary> 
     public List<Setter> Setters 
     { 
      get; 
      set; 
     } 

     /// <summary> 
     /// Gets or sets the collection of System.Windows.TriggerAction objects to apply when this trigger object becomes active. 
     /// </summary> 
     public List<System.Windows.TriggerAction> CommonActions 
     { 
      get; 
      set; 
     } 

     /// <summary> 
     /// Gets a value indicating whether this trigger can be active to apply setters and actions. 
     /// </summary> 
     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 

     /// <summary> 
     /// Fires (activates) current trigger by setting setter values and invoking all actions. 
     /// </summary> 
     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<Setter>(); 
      CommonActions = new List<System.Windows.TriggerAction>(); 
     } 

     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 
    } 
} 

私も他のトリガータイプ(すなわちInteractiveMultiTriggerInteractiveDataTriggerInteractiveMultiDataTrigger)だけでなく、条件付きのマルチ条件付きEventTriggersを持つことを可能にするいくつかのより多くのアクションを作成しました。プロの方がこの解決方法を確認したら、私はそれらをすべて公開します。

ご協力いただきありがとうございます。

+0

それはいいです、それは問題なく動作するはずです。私が見ることができる唯一のことは、ストーリーボードのタイムラインをもう凍結しないで、複数のスレッドを使用していないことを意味するため、効率が低下する可能性があるということです。 –

+0

あなたは正しいです。私は 'TriggerAction'の' Invoke'メソッドを呼び出す方法を探しています。このようにして、必要なものはすべて内部的に行う必要があります。何について考えていますか? (私は 'このスニペットを使用できませんでした 'とマークしました) – Mimi

+0

別のビューでは、実際には、Begin()は自動的にフリーズします。なぜXamlは正しく動作しないのですか?とにかく、内部のものにアクセスしようとしているのはなぜですか? –

4

は、ストーリーボードがなければならないので、まあ、あなたは本当に、 "へ" もからにバインドすることはできませんクロススレッドで効率的に作業するために、フリーズしました。

ソリューション1)ハックなし最も簡単な解決策()のコードビハインド含まれます あなたはバインディングを使用しないように、イベントハンドラでマウスオーバーイベントハンドラ&を追加し、必要なアニメーションを見つけ、直接施設「TO」に設定し、 "凍結"を行うことができます。こうしてあなたは何もハードコードしません:)。

解決策2) XAMLのみをサポートするクールなハックがありますが(私にはちょっとしたコンバータの魔法の魔法があります)、私はそれを提案しません。それにもかかわらずクールです:) WPF animation: binding to the "To" attribute of storyboard animationジェイソンの答えを参照してください。

いくつかのことをより多くのあなたが試すことができていることがあります。

Solution3)はは、依存関係プロパティを使用するのではなく、INotifyProperthChangedを実装しないでください。このようにして、 "To"をバインドすることができます。理論的にはこれはうまくいくはずだと思うが、私は試していない。

解決策4)あなたのバインディングにMode = OneTimeを適用します。多分それは動作しますか?

解決策5)正しいスレッドで依存プロパティを評価し、 "To"プロパティを設定する独自の動作を記述してください。私はそれが素晴らしい解決策になると思います。ここで

はあまりにも良い重複です:このコードは本当だった場合WPF Animation "Cannot freeze this Storyboard timeline tree for use across threads"

+0

ありがとう@ Erti-Chris Eelmaa。最初の解決策は意図されていません。 (私は上記の完全なバージョンを与えました!)また、 'Tag'プロパティの使用には、その性能制限のために同意しません。 – Mimi

+0

私は考えがあります。 '' ObjectDataProvider'を使って要素の任意のリソースを返す静的メソッドを呼び出すことはできますか? – Mimi

+0

私はより理論的な解決策を追加しました。 Btw私はObjectDataProviderに精通していません。私はあなたがObjectDataProviderを使って達成したいと思っていることはよくわかりません。私が見ているのは、クラスであり、メソッド名をとり、それを評価し、返されたコレクションにセットして、バインドできるようにすることです。 –

関連する問題