我有一个标签控件,我使用转换器根据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更改时新颜色渐渐消失。 有谁知道我怎么能做到这一点?
答案 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;
}
}
这也可以解决你的问题:)