Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating a TextBox.Foreground in WPF

Is there anyway to animate a TextBox.ForegroundProperty?

<Color x:Key="NormalColor">#FF666666</Color>
<SolidColorBrush x:Key="NormalBrush" Color="{StaticResource NormalColor}" />

<Color x:Key="MouseOverColor">#FF666666</Color>
<SolidColorBrush x:Key="MouseOverBrush" Color="{StaticResource MouseOverColor}" />

<ControlTemplate x:Key="RegularTextBoxTemplate" TargetType="{x:Type TextBox}">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
                <VisualStateGroup.Transitions>
                    <VisualTransition GeneratedDuration="0:0:0.1"/>
                </VisualStateGroup.Transitions>
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="MouseOver">
                    <Storyboard>
                        <!-- storyboard to animating foreground here... -->
                    </Storyboard>
                </VisualState>
            </VisualStateGroup >
        </VisualStateManager>
        <ScrollViewer x:Name="PART_ContentHost" 
                      BorderThickness="0"
                      IsTabStop="False"
                      Background="{x:Null}"/>
    </Grid>
</ControlTemplate>

<Style x:Key="RegularTextBox" TargetType="{x:Type TextBox}">
    <Setter Property="Foreground" Value="{StaticResource NormalBrush}"/>
    <Setter Property="Template" Value="{StaticResource RegularTextBoxTemplate}"/>
</Style>

My tried storyboards are:

<ColorAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentHost"
                  Storyboard.TargetProperty="(Foreground).(SolidColorBrush.Color)">
    <EasingColorKeyFrame KeyTime="0" Value="{StaticResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>

<ColorAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentHost"
              Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)">
    <EasingColorKeyFrame KeyTime="0" Value="{StaticResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>

<ColorAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentHost"
          Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
    <EasingColorKeyFrame KeyTime="0" Value="{StaticResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>

<ColorAnimationUsingKeyFrames
                  Storyboard.TargetProperty="(Foreground).(SolidColorBrush.Color)">
    <EasingColorKeyFrame KeyTime="0" Value="{StaticResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>

<ColorAnimationUsingKeyFrames
              Storyboard.TargetProperty="(TextBox.Foreground).(SolidColorBrush.Color)">
    <EasingColorKeyFrame KeyTime="0" Value="{StaticResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>

<ColorAnimationUsingKeyFrames
              Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)">
    <EasingColorKeyFrame KeyTime="0" Value="{StaticResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>

<ColorAnimationUsingKeyFrames
          Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
    <EasingColorKeyFrame KeyTime="0" Value="{StaticResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>

None of them work. Any idea? Is it even possible?

like image 819
amiry jd Avatar asked Aug 24 '13 21:08

amiry jd


2 Answers

Well, thanks to all that were trying to help me, I found my answer. It seems when we set TextBox.Foreground property to a resource, the storyboard cannot animate it. So, the style should be something like this:

<Style x:Key="RegularTextBox" TargetType="{x:Type TextBox}">
    <Setter Property="Foreground">
        <Setter.Value>
            <SolidColorBrush Color="{DynamicResource NormalColor}"/>
        </Setter.Value>
    </Setter>
    <Setter Property="Template" Value="{StaticResource RegularTextBoxTemplate}"/>
</Style>

This was the only problem I had. But there is a note to remember. When we want to target a templated parent in a storyboard, it's not necessary to bind to it. We just need to leave it:

<!-- It's not necessary to set Storyboard.TargetName in storyboard -->
<!-- It will automatically target the TemplatedParent -->
<ColorAnimationUsingKeyFrames
              Storyboard.TargetProperty="(TextBox.Foreground).(SolidColorBrush.Color)">
    <EasingColorKeyFrame KeyTime="0" Value="{DynamicResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>

This works for me.


Here is a working example.

like image 100
amiry jd Avatar answered Sep 22 '22 03:09

amiry jd


This was more problematic than I thought. Here is my original answer:


It's definitely possible - that's what the ColorAnimationXXX classes are for.

Your code is very similar to the code example here, which animates a colour with the ColorAnimation instead. The property in the example takes a Brush (just like TextBox.Foreground) which is defined in XAML and given a name so that it can be referenced easily by the animation.

So in your case the pertinent code would be:

<VisualState Name="...">
   <Storyboard>
      <ColorAnimation To="Green" 
                      Storyboard.TargetName="tbBrush" 
                      Storyboard.TargetProperty="Color"/>
    </Storyboard>
</VisualState>

and:

<TextBox.Foreground>
  <SolidColorBrush x:Name="tbBrush" Color="#FF666666"/>
</TextBox.Foreground>

That was all very well, in theory, until I realised it didn't work in a style. Whereas the Background property of the Grid within the style is easily animatable, with something like:

Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)"

it is significantly more difficult to find a property to animate that will have an effect on the Foreground of the text. Initially I tried TextElement.Foreground, which seems intuitive, and I was able to set this property at the Grid and ScrollViewer levels which I expected to have an effect on all the child objects underneath - including whatever object it is at the bottom level that contains the text of the TextBox. My assumption was that the TextBox content would be internally set to a TextBlock, which would obey the value of the Foreground attached property set on it. It seems that my assumption was incorrect, and the content of the PART_ContentHost ScrollViewer is set by the control logic within TextBox to a lower level object that does not obey any of the Foreground dependency properties in the object tree between the top level TextBox and itself.

The problem then is how to set the Foreground property of the TextBox within the style of the TextBox being styled. For testing, I tried to set this with a TwoWay TemplatedParent binding. I think I got the PropertyPath to the Color of the SolidColorBrush right, but it still didn't work as the Color property was apparently immutable at that point. I believe this issue is documented here.

On top of the fact that it doesn't work, setting the Foreground property internally did not seem right as external consumers would expect to be in control of the value of that property. So, given that the Foreground of a TextBox will not obey anything in a style, I came to the conclusion that the functionality is best implemented with a nested TextBox within the TextBox style. The outer style contains the state manager and most of the layout, then the inner TextBox has its own style and control template that is designed just to display the text bit. The outer style is able to set the Foreground property of the inner TextBox, which the inner one will obey, and crucially the outer one can set this value in the state manager.

<ControlTemplate x:Key="RegularTextBoxTemplate" TargetType="{x:Type TextBox}"> 
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
                <VisualStateGroup.Transitions>
                    <VisualTransition GeneratedDuration="0:0:0.1"/>
                </VisualStateGroup.Transitions>
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="MouseOver">
                    <Storyboard>
                        <ColorAnimation To="HotPink"
                            Storyboard.TargetName="InternalTextBox"
                            Storyboard.TargetProperty="(TextBox.Foreground).(SolidColorBrush.Color)"/>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <TextBox Foreground="Black" Text="{TemplateBinding Text}" x:Name="InternalTextBox">
            <TextBox.Style>
                <Style TargetType="{x:Type TextBox}">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type TextBox}">
                                <Grid Background="{x:Null}">
                                    <ScrollViewer x:Name="PART_ContentHost"
                                        BorderThickness="0"
                                        IsTabStop="False"
                                        Background="{x:Null}" />
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </TextBox.Style>
        </TextBox>
    </Grid>
</ControlTemplate>

<Style x:Key="RegularTextBox" TargetType="{x:Type TextBox}">
    <Setter Property="Template" Value="{StaticResource RegularTextBoxTemplate}"/>
</Style>

I would be interested in hearing other people's comments on this approach, and whether there are any problems with it that I have not considered. Based on my attempts at solving the problem and snooping the resultant application, it is the simplest solution I can see at the moment.

like image 26
Stephen Hewlett Avatar answered Sep 24 '22 03:09

Stephen Hewlett