开发者

Using a custom control property in an animation

How do I connect custom control properties that are being being animated inside a control template?

I am creating a custom button with several states, and I want to animate the color of the button text as the state changes. Normally, the text is gray, and it turns black on a MouseOver.

I created a NormalTextBrush for the black text, and a FadedTextBrush for the gray text:

<SolidColorBrush x:Key="NormalTextBrush" Color="Black" />
<SolidColorBrush x:Key="FadedTextBrush" Color="DarkSlateGray" />

So far, so good. My animations run without error. When the mouse goes over the button, the text goes from gray to black. But what I really want to do is let a developer using the control specify the text color.

So, I redefine both text 开发者_开发知识库brushes as bindings to the control's Foreground property. The FadedTextBrush uses a value converter to fade the Foreground color. The redefined resources look like this:

<SolidColorBrush x:Key="NormalTextBrush" Color="{Binding Path=Foreground, RelativeSource={RelativeSource TemplatedParent}}" />
<SolidColorBrush x:Key="FadedTextBrush" Color="{Binding Path=Foreground, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ColorConverter}, ConverterParameter='1.2'}" />

And that's where I run into trouble. WPF doesn't allow bindings on animations inside control templates. It throws an exception with the message "Cannot freeze this Storyboard timeline tree for use across threads." The issue is documented here.

And that brings me to my question: How do I set up my Brush resources so that they are wired to the Foreground property, but so that I can still use them with the animations in my control template?

Anyone who can answer this one, I'll buy you a beer the next time I see you! Thanks for your help.


You have to make do with the tools available to you. I would suggest two text blocks that occupy the same screen space, and then manipulate the opacity of each in a storyboard. Here is how to overlay two text blocks where the top one is transparent in the default state:

<Grid>
    <TextBlock x:Name="textBlock1" Foreground="Black"
        Margin="{TemplateBinding Padding}"
        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
        Text="{TemplateBinding Content}"/>
    <TextBlock x:Name="textBlock2" Foreground="Gray"
        Margin="{TemplateBinding Padding}"
        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
        Text="{TemplateBinding Content}" Opacity="0"/>
</Grid>

Then set up a visual state group with the visual state manager to swap their opacities:

<VisualStateGroup x:Name="CommonStates">
    <VisualStateGroup.Transitions>
        <VisualTransition GeneratedDuration="0:0:0.5"/>
    </VisualStateGroup.Transitions>
    <VisualState x:Name="Normal"/>
    <VisualState x:Name="MouseOver">
        <Storyboard>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="textBlock1">
                <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="textBlock2">
                <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </VisualState>
    <VisualState x:Name="Pressed"/>
    <VisualState x:Name="Disabled"/>
</VisualStateGroup>

or you can use your own storyboards. The point is that we are now manipulating constant values with the storyboard and yet you can set the foreground of the text block of each state using any method of you choosing.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜