自定义的C#颜色动画

huangapple go评论75阅读模式
英文:

Custom ColorAnimation in C#

问题

Here is the translated content:

所以我有一个大量的样式和控件模板存储在资源字典中,其中一些样式包含带有颜色动画的故事板。问题是我需要将这些颜色动画的“To”属性绑定到用户选择的颜色,但这个帮助我解决了这部分问题。但该解决方案的唯一问题是它只能替代双重动画。

我尝试使用以下代码创建自己的颜色动画:

SolidColorBrush brush = new SolidColorBrush(System.Windows.Media.Color.FromRgb(0,0,0));
if (values[1] is string && values[2] is string && values[0] is double)
{
     System.Drawing.Color color = ColorTranslator.FromHtml(values[1].ToString());
     double r = System.Convert.ToInt16(color.R);
     double g = System.Convert.ToInt16(color.G);
     double b = System.Convert.ToInt16(color.B);
     System.Drawing.Color color2 = ColorTranslator.FromHtml(values[2].ToString());
     double r2 = System.Convert.ToInt16(color2.R);
     double g2 = System.Convert.ToInt16(color2.G);
     double b2 = System.Convert.ToInt16(color2.B);
     int r3 = System.Convert.ToInt32(r + ((r2 - r) * (double)values[0]));
     int g3 = System.Convert.ToInt32(g + ((g2 - g) * (double)values[0]));
     int b3 = System.Convert.ToInt32(b + ((b2 - b) * (double)values[0]));
     brush = new SolidColorBrush(System.Windows.Media.Color.FromRgb(System.Convert.ToByte(r3), System.Convert.ToByte(g3), System.Convert.ToByte(b3)));
}
            
return brush;

但这看起来不如WPF的颜色动画,所以我想知道是否有更好的方法来实现我的目标,如果没有,如何使我的颜色动画更像WPF的颜色动画。

英文:

So I have a large number of styles and control templates in a resource dictionary, some of these styles have storyboards with color animations. The problem was that I need to bind the "To" property of those color animations to whatever color the user had picked, but this helped me with that part of the problem. The only issue with that solution was that it was only a replacement for a double animations.

I tried making my own color animation with the following code:

SolidColorBrush brush = new SolidColorBrush(System.Windows.Media.Color.FromRgb(0,0,0));
if (values[1] is string && values[2] is string && values[0] is double)
{
     System.Drawing.Color color = ColorTranslator.FromHtml(values[1].ToString());
     double r = System.Convert.ToInt16(color.R);
     double g = System.Convert.ToInt16(color.G);
     double b = System.Convert.ToInt16(color.B);
     System.Drawing.Color color2 = ColorTranslator.FromHtml(values[2].ToString());
     double r2 = System.Convert.ToInt16(color2.R);
     double g2 = System.Convert.ToInt16(color2.G);
     double b2 = System.Convert.ToInt16(color2.B);
     int r3 = System.Convert.ToInt32(r + ((r2 - r) * (double)values[0]));
     int g3 = System.Convert.ToInt32(g + ((g2 - g) * (double)values[0]));
     int b3 = System.Convert.ToInt32(b + ((b2 - b) * (double)values[0]));
     brush = new SolidColorBrush(System.Windows.Media.Color.FromRgb(System.Convert.ToByte(r3), System.Convert.ToByte(g3), System.Convert.ToByte(b3)));
}
            
return brush;

But this doesn't look as good as the color animation from wpf, so I'm asking if there is a better way to do what I am doing, and if not how do I make my color animation more like wpf's.

答案1

得分: 2

You should extend ColorAnimation to keep the default animation behavior or implementation details in general.

For example, create a DynamicColorAnimation class that extends ColorAnimation and define binding properties on it. The point is that you can't set Binding expressions inside a Storyboard as this would prevent the Freezable (in this case the ColorAnimation) from being frozen. To solve this, you can just collect the actual Binding values and then resolve them explicitly during runtime.

The behavior is similar to the e.g. DataGridBoundColumn.Binding property (for example of the extended DataGridTextColumn).
In our case we have two such binding properties named FromBinding and ToBinding. And because we won't define those properties as dependency properties, the Freezable will ignore the Binding values (allowing the DynamicColorAnimation to be frozen as required): the Binding in this case is not evaluated by the dependency property system and is instead treated like an ordinary property value.

If DynamicColorAnimation.FromBinding or DynamicColorAnimation.ToBinding is not set, the DynamicColorAnimation behaves like a normal ColorAnimation and falls back to the ColorAnimation.From or ColorAnimation.To property values.
ColorAnimation.From and ColorAnimation.To have precedence over DynamicColorAnimation.FromBinding and DynamicColorAnimation.ToBinding.
Both can be mixed, for example FromBindng and To.

public class DynamicColorAnimation : ColorAnimation
{
  // Helper class to resolve bindings
  internal class BindingResolver<TValue> : DependencyObject
  {
    // Define property of type 'object' to make it nullable by default.
    // Introduces boxing in case 'TValue' is a 'ValueType'.
    public object ResolvedBindingValue
    {
      get => (object)GetValue(ResolvedBindingValueProperty);
      set => SetValue(ResolvedBindingValueProperty, value);
    }

    public static readonly DependencyProperty ResolvedBindingValueProperty = DependencyProperty.Register(
      "ResolvedBindingValue",
      typeof(object),
      typeof(BindingResolver<TValue>),
      new PropertyMetadata(default));

    // Returns 'false' when the binding couldn't be resolved
    public bool TryResolveBinding(BindingBase bindingBase, out TValue? resolvedBindingValue)
    {
      _ = BindingOperations.SetBinding(this, ResolvedBindingValueProperty, bindingBase);
      resolvedBindingValue = (TValue)this.ResolvedBindingValue;

      return this.ResolvedBindingValue is not null;
    }
  }

  public BindingBase? FromBinding { get; set; }
  public BindingBase? ToBinding { get; set; }
  private BindingResolver<Color> BindingValueProvider { get; }

  public DynamicColorAnimation()
  {
    this.BindingValueProvider = new BindingResolver<Color>();
  }

  // Because we extend ColorAnimation which is a Freezable, 
  // we must override Freezable.CreateInstanceCore too
  protected override Freezable CreateInstanceCore()
    => new DynamicColorAnimation();

  protected override Color GetCurrentValueCore(Color defaultOriginValue, Color defaultDestinationValue, AnimationClock animationClock)
  {
    Color fromColor = this.From ?? defaultOriginValue;
    Color toColor = this.To ?? defaultDestinationValue;

    if (!this.From.HasValue
      && this.FromBinding is not null)
    {
      // Ignore the default value to give the defaultOriginValue 
      // parameter precedence in case the binding didn't resolve
      if (this.BindingValueProvider.TryResolveBinding(this.FromBinding, out Color bindingValue))
      {
        fromColor = bindingValue;
      }
    }

    if (!this.To.HasValue
      && this.ToBinding is not null)
    {
      // Ignore the default value to give the defaultOriginValue 
      // parameter precedence in case the binding didn't resolve
      if (this.BindingValueProvider.TryResolveBinding(this.ToBinding, out Color bindingValue))
      {
        toColor = bindingValue;
      }
    }

    return base.GetCurrentValueCore(fromColor, toColor, animationClock);
  }
}

Usage Example

<Style x:Key="ButtonStyle"
TargetType="Button">
<Style.Resources>
<!-- Some dynamic resources -->
<SolidColorBrush x:Key="FromColor"
Color="DarkRed" />
<SolidColorBrush x:Key="ToColor"
Color="Orange" />
</Style.Resources>
<Style.Triggers>
<EventTrigger RoutedEvent="MouseMove">
<BeginStoryboard>
<Storyboard>
<!-- This example binds to the dynamic SolidColorBrush resources. 
Of course, the bindings can use anything as source as usual -->
<DynamicColorAnimation Storyboard.TargetProperty="Background.Color"
Duration="0:0:5"
FillBehavior="Stop"
FromBinding="{Binding Source={StaticResource FromColor}, Path=Color}"
ToBinding="{Binding Source={StaticResource ToColor}, Path=Color}">
</local:DynamicColorAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
英文:

You should extend ColorAnimation to keep the default animation behavior or implementation details in general.

For example, create a DynamicColorAnimation class that extends ColorAnimation and define binding properties on it. The point is that you can't set Binding expressions inside a Storyboard as this would prevent the Freezable (in this case the ColorAnimation) from being frozen. To solve this, you can just collect the actual Binding values and then resolve them explicitly during runtime.

The behavior is similar to the e.g. DataGridBoundColumn.Binding property (for example of the extended DataGridTextColumn).
In our case we have two such binding properties named FromBinding and ToBinding. And because we won't define those properties as dependency properties, the Freezable will ignore the Binding values (allowing the DynamicColorAnimation to be frozen as required): the Binding in this case is not evaluated by the dependency property system and is instead treated like an ordinary property value.

If DynamicColorAnimation.FromBinding or DynamicColorAnimation.ToBinding is not set, the DynamicColorAnimation behaves like a normal ColorAnimation and falls back to the ColorAnimation.From or ColorAnimation.To property values.
ColorAnimation.From and ColorAnimation.To have precedence over DynamicColorAnimation.FromBinding and DynamicColorAnimation.ToBinding.
Both can be mixed, for example FromBindng and To.

public class DynamicColorAnimation : ColorAnimation
{
  // Helper class to resolve bindings
  internal class BindingResolver<TValue> : DependencyObject
  {
    // Define property of type 'object' to make it nullable by default.
    // Introduces boxing in case 'TValue' is a 'ValueType'.
    public object ResolvedBindingValue
    {
      get => (object)GetValue(ResolvedBindingValueProperty);
      set => SetValue(ResolvedBindingValueProperty, value);
    }

    public static readonly DependencyProperty ResolvedBindingValueProperty = DependencyProperty.Register(
      "ResolvedBindingValue",
      typeof(object),
      typeof(BindingResolver<TValue>),
      new PropertyMetadata(default));

    // Returns 'false' when the binding couldn't be resolved
    public bool TryResolveBinding(BindingBase bindingBase, out TValue? resolvedBindingValue)
    {
      _ = BindingOperations.SetBinding(this, ResolvedBindingValueProperty, bindingBase);
      resolvedBindingValue = (TValue)this.ResolvedBindingValue;

      return this.ResolvedBindingValue is not null;
    }
  }

  public BindingBase? FromBinding { get; set; }
  public BindingBase? ToBinding { get; set; }
  private BindingResolver<Color> BindingValueProvider { get; }

  public DynamicColorAnimation()
  {
    this.BindingValueProvider = new BindingResolver<Color>();
  }

  // Because we extend ColorAnimation which is a Freezable, 
  // we must override Freezable.CreateInstanceCore too
  protected override Freezable CreateInstanceCore()
    => new DynamicColorAnimation();

  protected override Color GetCurrentValueCore(Color defaultOriginValue, Color defaultDestinationValue, AnimationClock animationClock)
  {
    Color fromColor = this.From ?? defaultOriginValue;
    Color toColor = this.To ?? defaultDestinationValue;

    if (!this.From.HasValue
      && this.FromBinding is not null)
    {
      // Ignore the default value to give the defaultOriginValue 
      // parameter precedence in case the binding didn't resolve
      if (this.BindingValueProvider.TryResolveBinding(this.FromBinding, out Color bindingValue))
      {
        fromColor = bindingValue;
      }
    }

    if (!this.To.HasValue
      && this.ToBinding is not null)
    {
      // Ignore the default value to give the defaultOriginValue 
      // parameter precedence in case the binding didn't resolve
      if (this.BindingValueProvider.TryResolveBinding(this.ToBinding, out Color bindingValue))
      {
        toColor = bindingValue;
      }
    }

    return base.GetCurrentValueCore(fromColor, toColor, animationClock);
  }
}

Usage Example

<Style x:Key="ButtonStyle"
TargetType="Button">
<Style.Resources>
<!-- Some dynamic resources -->
<SolidColorBrush x:Key="FromColor"
Color="DarkRed" />
<SolidColorBrush x:Key="ToColor"
Color="Orange" />
</Style.Resources>
<Style.Triggers>
<EventTrigger RoutedEvent="MouseMove">
<BeginStoryboard>
<Storyboard>
<!-- This example binds to the dynamic SolidColorBrush resources. 
Of course, the bindings can use anything as source as usual -->
<DynamicColorAnimation Storyboard.TargetProperty="Background.Color"
Duration="0:0:5"
FillBehavior="Stop"
FromBinding="{Binding Source={StaticResource FromColor}, Path=Color}"
ToBinding="{Binding Source={StaticResource ToColor}, Path=Color}">
</local:DynamicColorAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>

huangapple
  • 本文由 发表于 2023年5月21日 06:42:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76297604.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定