2013-04-06 61 views
9

Hãy xem xét mã này:Cách truy cập bảng phân cảnh trong tài nguyên phần tử từ 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> 

Mã trên không có vấn đề gì. Bây giờ, tôi muốn giá trị key-frame ràng buộc của MyStory đến một DP (tên SpecialColor) của thành viên này kiểm soát như vậy:

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

mà làm cho một lỗi:

có thể không đóng băng cây này timeline Storyboard cho sử dụng trên các chủ đề.

Có thể thực hiện việc này bằng mã phía sau. Nhưng làm thế nào tôi có thể làm điều đó trong XAML?


Code-Behind Aided Giải pháp:

Bước 1: Đưa MyStory storyboard vào brdBase nguồn lực.

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

Lỗi:Không thể tìm thấy tài nguyên có tên là 'mystory'. Tên tài nguyên phân biệt chữ hoa chữ thường.

Bước 2: Loại bỏTrigger trên IsMouseOver tài sản và bắt đầu quá trình MyStory từ mã phía sau.

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

Bước 3: Các giải pháp đã được thực hiện, nhưng nó không hoạt động ở lần đầu tiên. May mắn thay, có một giải pháp cho vấn đề này. Đủ để đặt ControlTemplate trong một Style.

(Tôi cần Trigger loại khác hơn EventTrigger và phải quấn UserControl yếu tố với ControlTemplate.)


Cập nhật:

Ý tưởng về việc sử dụng ObjectDataProvider thất bại.

  1. An ObjectDataProvider không thể sử dụng tài nguyên để cung cấp bảng phân cảnh !!! Báo cáo lỗi là:
    • XamlParseException: Thiết lập thuộc tính 'System.Windows.Media.Animation.BeginStoryboard.Storyboard' ném một ngoại lệ.
    • InnerException: 'System.Windows.Data.ObjectDataProvider 'không phải là một giá trị hợp lệ cho thuộc tính' Storyboard '.
  2. Các AssociatedControl DP luôn là null.

Đây là mã:

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

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

Trả lời

3

Điều gì xảy ra nếu mã này đúng?

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

Nếu vậy, tôi có thể có một Trigger trên IsMouseOver tài sản ...

Tôi rất vui khi nói đó là một mã làm việc :) Tôi chỉ có thể sử dụng EventTrigger trong <Border.Triggers> thẻ. Đó là giới hạn. Vì vậy, tôi bắt đầu suy nghĩ về ý tưởng này: Nếu tôi có thể có một kích hoạt tùy chỉnh mà có thể làm việc trong phạm vi FrameworkElement.Triggers? Đây là mã:

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

Tôi cũng đã tạo loại cò khác (ví dụ: InteractiveMultiTrigger, InteractiveDataTrigger, InteractiveMultiDataTrigger) cũng như một số tác vụ khác mà làm cho nó có thể để có một EventTriggers có điều kiện và nhiều điều kiện. Tôi sẽ xuất bản tất cả nếu các bạn chuyên nghiệp xác nhận giải pháp này.

Cảm ơn sự quan tâm của bạn!

+0

thật tuyệt, nó sẽ hoạt động không có vấn đề gì. điều duy nhất tôi có thể thấy là nó có thể kém hiệu quả hơn vì bạn không đóng băng dòng thời gian của bảng phân cảnh nữa và điều đó có nghĩa là nó không sử dụng nhiều luồng. –

+0

Bạn nói đúng. Tôi đang tìm cách gọi phương thức 'Invoke' của' TriggerAction'. Thông qua cách này, mọi thứ cần thiết phải được thực hiện trong nội bộ. Bạn có ý tưởng gì không? (Tôi đã đánh dấu nó là 'Không thể sử dụng đoạn mã này'.) – Mimi

+0

Trên một chế độ xem khác, trên thực tế, từ tài liệu, Begin() tự động đóng băng. Tôi quan tâm nhiều hơn về lý do tại sao Xaml không hoạt động chính xác? Tại sao bạn đang cố gắng truy cập nội dung anyways? Nó không bao giờ là ý tưởng hay, trừ khi hoàn toàn không có cách nào khác –

4

Vâng, bạn có thể không thực sự liên kết với "To" cũng không Từ, bởi vì kịch bản đã được đông lạnh, để hoạt động hiệu quả với luồng chéo.

Solution1) giải pháp đơn giản nhất mà không cần hack (liên quan đến code-behind): Thêm MouseOver xử lý sự kiện & trong xử lý sự kiện, xác định vị trí hình ảnh động cần thiết, thiết lập "Để" sở hữu trực tiếp, vì vậy bạn sẽ không sử dụng ràng buộc và "đóng băng" có thể được thực hiện. Bằng cách này bạn sẽ không hardcode bất cứ điều gì :).

Solution2) Có một bản hack tuyệt vời chỉ hỗ trợ XAML (một chút phép thuật chuyển đổi), nhưng tôi không đề xuất. Tuy nhiên, nó vẫn tuyệt vời :) WPF animation: binding to the "To" attribute of storyboard animation Xem câu trả lời của Jason.

Có vài thứ nữa mà bạn có thể thử:

solution3) Không sử dụng các thuộc tính phụ thuộc, nhưng thay vì thực hiện INotifyProperthChanged. Bằng cách này bạn vẫn có thể BIND "To". Lưu ý rằng tôi nghĩ rằng điều này lý thuyết nên hoạt động, nhưng tôi đã không cố gắng.

Solution4) Áp dụng chế độ = OneTime cho ràng buộc của bạn. Có lẽ nó hoạt động?

Solution5) Viết hành vi được đính kèm của riêng bạn sẽ đánh giá thuộc tính phụ thuộc vào chuỗi chính xác và đặt thuộc tính "Tới". Tôi nghĩ rằng đó sẽ là giải pháp tốt đẹp.

Đây cũng là bản sao tốt quá: WPF Animation "Cannot freeze this Storyboard timeline tree for use across threads"

+0

Cảm ơn @ Erti-Chris Eelmaa. Giải pháp đầu tiên không được dự định. (Tôi đã cung cấp phiên bản hoàn chỉnh ở trên!) Ngoài ra, tôi không đồng ý với việc sử dụng thuộc tính 'Tag' vì giới hạn hiệu suất của nó. – Mimi

+0

Tôi có một ý tưởng. ☼ Chúng ta có thể sử dụng 'ObjectDataProvider' để gọi một phương thức tĩnh trả về bất kỳ tài nguyên tùy ý nào của một phần tử không? – Mimi

+0

Tôi đã thêm nhiều giải pháp lý thuyết. Btw Tôi không quen thuộc với ObjectDataProvider. Tôi không chắc chắn những gì bạn muốn đạt được với ObjectDataProvider mặc dù. Tất cả những gì tôi thấy, là nó là một lớp, có một tên phương thức, đánh giá nó, và thiết lập nó thành bộ sưu tập được trả về để bạn có thể liên kết với nó. –

Các vấn đề liên quan