淡化样式之间的过渡

时间:2009-09-29 13:41:22

标签: c# wpf silverlight

我有一个标签控件,我使用转换器根据bool属性切换其样式,在我的viewmodel上使用IsCheckedOut,如下所示:

    <UserControl.Resources>
      <Converters:BooleanStyleConverter 
       x:Key="BooleanStyleConverter " 
       StyleFalse="{StaticResource HeaderLabelStyle}" 
       StyleTrue="{StaticResource HeaderLabelHighlightedStyle}" />
     </UserControl.Resources>

    <Label Style="{Binding Path=IsCheckedOut, 
Converter={StaticResource BooleanStyleConverter}}">
    some content here
    </Label>

转换器只返回两种样式之一:

public class BooleanStyleConverter : IValueConverter 
    {
        public Style StyleFalse { get; set; }
        public Style StyleTrue { get; set; }
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if ((bool)value)
            {
                return StyleTrue;
            }
            return StyleFalse;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

样式看起来像这样:

<Style x:Key="HeaderLabelHighlightedStyle" TargetType="{x:Type Label}">
 <Setter Property="Template">
  <Setter.Value>
   <ControlTemplate TargetType="Label">
    <Border Background="{StaticResource RedGradient}">
     <ContentPresenter />
    </Border>
   </ControlTemplate>
  </Setter.Value>
 </Setter>
</Style>

<Style x:Key="HeaderLabelHighlightedStyle" TargetType="{x:Type Label}">
 <Setter Property="Template">
  <Setter.Value>
   <ControlTemplate TargetType="Label">
    <Border Background="{StaticResource BlueGradient}">
     <ContentPresenter />
    </Border>
   </ControlTemplate>
  </Setter.Value>
 </Setter>
</Style>

因此,当IsCheckedOut为true时,标签会显示红色背景,当它为false时会获得蓝色背景(好吧,样式有点复杂,但你明白了)。 现在,我希望在样式之间进行转换,以便在IsCheckedOut更改时新颜色渐渐消失。 有谁知道我怎么能做到这一点?

3 个答案:

答案 0 :(得分:2)

抱歉,但你做错了。

在解决方案中,您可以获得极高的创造力和雄心壮志。但是你已经把一把5公斤的锤子砸在了一个图钉上。

在这种情况下,正确的解决方案是使用嵌套在VSM状态的Storyboards。 看起来你的UI基本上有2个状态:一个是某个业务逻辑值为真,另一个是虚假状态。请注意,上述区别是100%技术独立。在任何技术中,无论您想要实现什么,都会被视为UI的两种状态。

在Silverlight / WPF中,您可以实际创建VisualStateManager状态,而不是将模仿UI状态的内容混合在一起。

技术上它可以通过以下方式工作:
1.您的UserControl将有1个VisualStateGroup,其中包含2个VisualStates(一个用于true,另一个用于false) 那些VSM状态每个代表一个故事板 3.该故事板将改变您认为合适的模板或任何其他属性。

要了解VSM的基本知识,我强烈建议您在接下来的30分钟内观看以下VSM视频:http://expression.microsoft.com/en-us/cc643423.aspx(在“我如何?”下) 说真的,这些视频在解释VSM方面非常成功。与你的困境最相关的一个是“向控制中添加状态”,但我建议你观察所有这些。

在WPF中,您可以使用WPF Toolkit中的VisualStateManager。

答案 1 :(得分:1)

正如贾斯汀所说,你做错了什么,你可能想要退后五步并重新考虑你的方法......

但我真的很喜欢这个拼图:)。我在没有VSM的情况下解决了它,只是为了演示WPF的灵活性。这里的基本原则是使用依赖属性值强制。我们跟踪所有样式更改,但我们不会让新值从Coerce()函数中消失,直到我们完成转换动画。

要简化测试,只需复制/粘贴以下代码并检查它是否适合您:)。如果您想了解详细信息 - 请随时提出其他问题。

<强> XAML:

<Window x:Class="WpfApplication5.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:loc="clr-namespace:WpfApplication5"
        Title="Fade Styles"
        Width="320"
        Height="240">
  <Window.Resources>

    <SolidColorBrush x:Key="RedGradient" Color="Red" />
    <SolidColorBrush x:Key="BlueGradient" Color="Blue" />

    <Style x:Key="HeaderLabelStyle" TargetType="{x:Type Label}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Label">
            <Border Background="{StaticResource RedGradient}">
              <ContentPresenter />
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

    <Style x:Key="HeaderLabelHighlightedStyle" TargetType="{x:Type Label}">
      <Setter Property="Foreground" Value="White" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Label">
            <Border Background="{StaticResource BlueGradient}">
              <ContentPresenter />
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

    <loc:BooleanStyleConverter x:Key="BooleanStyleConverter"
                                           StyleFalse="{StaticResource HeaderLabelStyle}"
                                           StyleTrue="{StaticResource HeaderLabelHighlightedStyle}" />
  </Window.Resources>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Label Style="{Binding IsChecked, ElementName=chkToggle, Converter={StaticResource BooleanStyleConverter}}"
           loc:StyleAnimation.IsActive="True"
           Content="Some content here" />
    <CheckBox Grid.Row="1" Name="chkToggle" Content="Use Blue" />
  </Grid>
</Window>

loc上查看:StyleAnimation.IsActive =“True”

<强> C#

using System;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media.Animation;

namespace WpfApplication5
{
  public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();
    }
  }

  public class StyleAnimation : DependencyObject
  {
    private const int DURATION_MS = 200;

    private static readonly Hashtable _hookedElements = new Hashtable();

    public static readonly DependencyProperty IsActiveProperty =
      DependencyProperty.RegisterAttached("IsActive",
      typeof(bool),
      typeof(StyleAnimation),
      new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));

    public static bool GetIsActive(UIElement element)
    {
      if (element == null)
      {
        throw new ArgumentNullException("element");
      }

      return (bool)element.GetValue(IsActiveProperty);
    }

    public static void SetIsActive(UIElement element, bool value)
    {
      if (element == null)
      {
        throw new ArgumentNullException("element");
      }
      element.SetValue(IsActiveProperty, value);
    }

    static StyleAnimation()
    {
      // You can specify any owner type, derived from FrameworkElement.
      // For example if you want to animate style for every Control derived
      // class - use Control. If Label is your single target - set it to label.
      // But be aware: value coercion will be called every time your style is
      // updated. So if you have performance problems, probably you should
      // narrow owner type to your exact type.
      FrameworkElement.StyleProperty.AddOwner(typeof(Control),
                                              new FrameworkPropertyMetadata(
                                                null, new PropertyChangedCallback(StyleChanged), CoerceStyle));
    }

    private static object CoerceStyle(DependencyObject d, object baseValue)
    {
      var c = d as Control;
      if (c == null || c.Style == baseValue)
      {
        return baseValue;
      }

      if (CheckAndUpdateAnimationStartedFlag(c))
      {
        return baseValue;
      }
      // If we get here, it means we have to start our animation loop:
      // 1. Hide control with old style.
      // 2. When done set control's style to new one. This will reenter to this
      // function, but will return baseValue, since CheckAndUpdateAnimationStartedFlag()
      // will be true.
      // 3. Show control with new style.

      var showAnimation = new DoubleAnimation
      {
        Duration =
          new Duration(TimeSpan.FromMilliseconds(DURATION_MS)),
        To = 1
      };


      var hideAnimation = new DoubleAnimation
      {
        Duration = new Duration(TimeSpan.FromMilliseconds(DURATION_MS)),
        To = 0
      };

      hideAnimation.Completed += (o, e) =>
      {
        // To stress it one more: this will trigger value coercion again,
        // but CheckAndUpdateAnimationStartedFlag() function will reture true
        // this time, and we will not go to this loop again.
        c.CoerceValue(FrameworkElement.StyleProperty);
        c.BeginAnimation(UIElement.OpacityProperty, showAnimation);
      };

      c.BeginAnimation(UIElement.OpacityProperty, hideAnimation);
      return c.Style; // Return old style this time.
    }

    private static void StyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      // So what? Do nothing.
    }

    private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      var fe = d as FrameworkElement;
      if (fe == null)
      {
        return;
      }
      if (GetIsActive(fe))
      {
        HookStyleChanges(fe);
      }
      else
      {
        UnHookStyleChanges(fe);
      }
    }

    private static void UnHookStyleChanges(FrameworkElement fe)
    {
      if (_hookedElements.Contains(fe))
      {
        _hookedElements.Remove(fe);
      }
    }

    private static void HookStyleChanges(FrameworkElement fe)
    {
      _hookedElements.Add(fe, false);
    }

    private static bool CheckAndUpdateAnimationStartedFlag(Control c)
    {
      var hookedElement = _hookedElements.Contains(c);
      if (!hookedElement)
      {
        return true; // don't need to animate unhooked elements.
      }

      var animationStarted = (bool)_hookedElements[c];
      _hookedElements[c] = !animationStarted;

      return animationStarted;
    }
  }

  public class BooleanStyleConverter : IValueConverter
  {
    public Style StyleFalse { get; set; }
    public Style StyleTrue { get; set; }
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      if ((bool)value)
      {
        return StyleTrue;
      }
      return StyleFalse;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}

干杯:)

答案 2 :(得分:1)

我也有一个解决方案,因为我很喜欢你的问题:) 我在渐变上使用ColorAnimations解决了这个难题。看看:

<Window x:Class="WpfTest___App.DoEvents.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" MinHeight="300" MinWidth="300"
    Name="Window">
<Grid DataContext="{Binding ElementName=Window}">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Label Content="Some content here" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
        <Label.Background>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                <GradientStop Color="Blue" Offset="0"/>
                <GradientStop Color="LightBlue" Offset="0.4"/>
                <GradientStop Color="White" Offset="1"/>
            </LinearGradientBrush>
        </Label.Background>
        <Label.Style>
            <Style>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsCheckedOut}" Value="True">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <ColorAnimation Storyboard.TargetProperty="Background.GradientStops[0].Color" 
                                                    To="Red" Duration="0:0:5"/>
                                    <ColorAnimation Storyboard.TargetProperty="Background.GradientStops[1].Color" 
                                                    To="Orange" Duration="0:0:5"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <ColorAnimation Storyboard.TargetProperty="Background.GradientStops[0].Color" 
                                                    To="Blue" Duration="0:0:5"/>
                                    <ColorAnimation Storyboard.TargetProperty="Background.GradientStops[1].Color" 
                                                    To="LightBlue" Duration="0:0:5"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.ExitActions>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Label.Style>
    </Label>
    <Button Content="Change IsCheckedOut" Click="Button_Click" Grid.Row="1"/>
</Grid>

在后面的代码中,我创建了一个用于测试的Dependency属性和一个按钮的Click事件的监听器:

public partial class Window1 : Window
{
    public bool IsCheckedOut
    {
        get { return (bool)GetValue(IsCheckedOutProperty); }
        set { SetValue(IsCheckedOutProperty, value); }
    }
    public static readonly DependencyProperty IsCheckedOutProperty = DependencyProperty.Register("IsCheckedOut", typeof(bool), typeof(Window1), new PropertyMetadata(false));

    public Window1()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        IsCheckedOut = !IsCheckedOut;
    }
}

这也可以解决你的问题:)