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?
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.
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.
You can bind Storyboard.Target
to the TemplatedParent:
<ColorAnimationUsingKeyFrames
Storyboard.Target="{Binding RelativeSource={RelativeSource TemplatedParent}}"
Storyboard.TargetProperty="(TextBox.Foreground).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>
Unfortunately, I could only get this to work when the normal foreground brush is not set in the style, and directly set on the TextBox element:
<TextBox Foreground="{StaticResource NormalBrush}" Style="{StaticResource RegularTextBox}" />
If it is set in the style, triggering the MouseOver state throws "Cannot animate '(0).(1)' on an immutable object instance." edit: This also happens if you set the TextBox foreground again after it is initialized.