Animazione WPF: associazione all’attributo “A” dell’animazione storyboard

Sto cercando di creare un pulsante che si comporta in modo simile al pulsante “slide” su iPhone. Ho un’animazione che regola la posizione e la larghezza del pulsante, ma voglio che questi valori siano basati sul testo utilizzato nel controllo. Attualmente sono hardcoded.

Ecco il mio XAML funzionante, finora:

  0:0:0.2                             

E il codice dietro:

 using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace Smt.Controls { public partial class SlideCheckBox : CheckBox { public SlideCheckBox() { InitializeComponent(); Loaded += OnLoaded; } public static readonly DependencyProperty CheckedTextProperty = DependencyProperty.Register("CheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Checked Text")); public string CheckedText { get { return (string)GetValue(CheckedTextProperty); } set { SetValue(CheckedTextProperty, value); } } public static readonly DependencyProperty UncheckedTextProperty = DependencyProperty.Register("UncheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Unchecked Text")); public string UncheckedText { get { return (string)GetValue(UncheckedTextProperty); } set { SetValue(UncheckedTextProperty, value); } } public static readonly RoutedCommand SlideCheckBoxClicked = new RoutedCommand(); void OnLoaded(object sender, RoutedEventArgs e) { Style style = TryFindResource("SlideCheckBoxStyle") as Style; if (!ReferenceEquals(style, null)) { Style = style; } } void OnSlideCheckBoxClicked(object sender, ExecutedRoutedEventArgs e) { IsChecked = !IsChecked; } } } 

Il problema si presenta quando provo a colbind l’attributo “A” nelle DoubleAnimations alla larghezza effettiva del testo, esattamente come nel ControlTemplate. Se lego i valori a una larghezza effettiva di un elemento nel ControlTemplate, il controllo viene visualizzato come casella di controllo vuota (la mia class base). Tuttavia, sono vincolante per gli stessi ActualWidths nello stesso ControlTemplate senza problemi. Sembra essere il CheckBox.Resources che ha un problema con esso.

Ad esempio, il seguente lo romperà:

   

Non so se questo è dovuto al fatto che sta tentando di associare a un valore che non esiste finché non viene eseguito un passaggio di rendering o se è qualcos’altro. Qualcuno ha qualche esperienza con questo tipo di associazione di animazione?

Ho avuto situazioni simili in ControlTemplate s dove volevo bind l’attributo “A” a un valore (invece di codificarlo a fondo) e alla fine ho trovato una soluzione .

Nota a margine rapida: se scorri sul web, troverai esempi di persone che possono utilizzare l’associazione dati per le proprietà “Da” o “A”. Tuttavia, in quegli esempi gli storyboard non sono in uno Style o in un ControlTemplate . Se lo Storyboard si trova in uno Style o in un ControlTemplate, dovrai utilizzare un approccio diverso, come questa soluzione.

Questa soluzione risolve il problema del freezable perché anima semplicemente un doppio valore da 0 a 1. Funziona con un uso intelligente della proprietà Tag e di un convertitore di moltiplicazione. Si utilizza una multibinding per associare sia una proprietà desiderata che la “scala” (il Tag), che vengono moltiplicati insieme. Fondamentalmente l’idea è che il valore del Tag sia ciò che si anima e il suo valore agisce come una “scala” (da 0 a 1) portando il valore dell’attributo desiderato a “fondo scala” dopo aver animato il Tag su 1.

Puoi vedere questo in azione qui . Il punto cruciale di questo è questo:

       0.0                         

Con questo convertitore di valori:

 public class MultiplyConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { double result = 1.0; for (int i = 0; i < values.Length; i++) { if (values[i] is double) result *= (double)values[i]; } return result; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new Exception("Not implemented"); } } 

Per quanto ne so, non è ansible associare l’animazione a / da perché l’animazione deve essere libera.

Vedi questo link per qualche altro modo su questo problema: http://blogs.microsoft.co.il/blogs/tamir/archive/2007/03/19/How-to-bind-to-Animation-To-and- DA-properties.aspx

Ho implementato questa cosa esatta.

                               

codice dietro.

 namespace YOURNAMESPACE.UserControls { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; ///  /// Interaction logic for SliderControl.xaml ///  public partial class SliderControl : UserControl { public static readonly DependencyProperty IsLeftVisibleProperty = DependencyProperty.RegisterAttached( "IsLeftVisible", typeof(bool), typeof(SliderControl), new UIPropertyMetadata(true, IsLeftVisibleChanged)); public static readonly DependencyProperty LeftTextProperty = DependencyProperty.RegisterAttached( "LeftText", typeof(string), typeof(SliderControl), new UIPropertyMetadata(null, LeftTextChanged)); public static readonly DependencyProperty RightTextProperty = DependencyProperty.RegisterAttached( "RightText", typeof(string), typeof(SliderControl), new UIPropertyMetadata(null, RightTextChanged)); ///  /// Initializes a new instance of the  class. ///  public SliderControl() { this.InitializeComponent(); } public string LeftText { get; set; } public string RightText { get; set; } [AttachedPropertyBrowsableForType(typeof(SliderControl))] public static bool GetIsLeftVisible(SliderControl sliderControl) { return (bool)sliderControl.GetValue(IsLeftVisibleProperty); } [AttachedPropertyBrowsableForType(typeof(SliderControl))] public static string GetLeftText(SliderControl sliderControl) { return (string)sliderControl.GetValue(LeftTextProperty); } public static void SetIsLeftVisible(SliderControl sliderControl, bool value) { sliderControl.SetValue(IsLeftVisibleProperty, value); } public static void IsLeftVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { SliderControl slider = d as SliderControl; if ((bool)e.NewValue == true) { slider.RunAnimation("ShowLeftStoryboard"); } else { slider.RunAnimation("ShowRightStoryboard"); } } [AttachedPropertyBrowsableForType(typeof(SliderControl))] public static string GetRightText(SliderControl sliderControl) { return (string)sliderControl.GetValue(RightTextProperty); } public static void SetLeftText(SliderControl sliderControl, string value) { sliderControl.SetValue(LeftTextProperty, value); } public static void SetRightText(SliderControl sliderControl, string value) { sliderControl.SetValue(RightTextProperty, value); } private static void LeftTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { SliderControl slider = d as SliderControl; slider.LeftText = e.NewValue as string; } private static void RightTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { SliderControl slider = d as SliderControl; slider.RightText = e.NewValue as string; } private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) { this.checkBox.Width = e.NewSize.Width; CheckBox cb = this.checkBox; var controlTemplate = cb.Template; DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel; Storyboard story = dockPanel.Resources["ShowLeftStoryboard"] as Storyboard; DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames; SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame; // must manipulate this in code behind, binding does not work, // also it cannot be inside of a control template // because storyboards in control templates become frozen and cannot be modified sk.Value = cb.Width / 2; if (cb.IsChecked == true) { story.Begin(cb, cb.Template); } } private void CheckBox_Checked(object sender, RoutedEventArgs e) { this.RunAnimation("ShowLeftStoryboard"); } private void CheckBox_Unchecked(object sender, RoutedEventArgs e) { this.RunAnimation("ShowRightStoryboard"); } private void RunAnimation(string storyboard) { CheckBox cb = this.checkBox; var controlTemplate = cb.Template; DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel; if (dockPanel != null) { Storyboard story = dockPanel.Resources[storyboard] as Storyboard; DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames; SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame; story.Begin(cb, cb.Template); } } } } 

IValueConverter

 namespace YOURNAMESPACE.ValueConverters { using System; using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Data; ///  /// Does basic math operations, eg. value = 60 parameter = "*2 + 1", result = 121 ///  ///  [ValueConversion(typeof(double), typeof(double))] public class MathConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { double d = (double)value; string mathExpression = d.ToString() + parameter; if (mathExpression.Contains("^")) { throw new Exception("Doesn't handle powers or square roots"); } DataTable table = new DataTable(); table.Columns.Add("expression", typeof(string), mathExpression); DataRow row = table.NewRow(); table.Rows.Add(row); double ret = double.Parse((string)row["expression"]); if (ret <= 0) { return 1d; } return ret; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { // not implemented return null; } } } 

esempio di utilizzo:

  

Mi piace la soluzione di @Jason Frank. Tuttavia, è ancora più semplice e meno sobject a errori se non si utilizza il tag, ma ad es. La proprietà Width di un elemento dummy Border vuoto. È una proprietà doppia nativa, quindi non c’è bisogno della syntax e puoi nominare il Border proprio come faresti con una variabile come questa:

              

Ciò rende le associazioni molto più leggibili.

L’animazione è sempre 0. 1, come ha sottolineato Jason:

      

Quindi associa tutto ciò che desideri animare alla larghezza del bordo fittizio. In questo modo è ansible anche convertire i convertitori a catena in un modo simile:

  

In combinazione con il MathConverter puoi fare praticamente qualsiasi cosa negli stili: https://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML