在WPF中设置TextBox的前景色动画

7
有没有办法为 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>

我的已尝试的故事板如下:

<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>

他们都不起作用。 有什么想法吗? 这是可能的吗?


您正在尝试对PART_ContentHostSolidColorBrush进行动画处理,但它并不包含任何画刷。您是否尝试分配一些初始画刷?(还有您的内容放在哪里?) - Vlad
好的,我仍然看到 <ScrollViewer x:Name="PART_ContentHost" ... Background="{x:Null}"/>。所以你是想在一个空对象上动画化一个属性,对吗? - Vlad
不,我根本没有对“Background”进行动画处理。目标属性是“Foreground”。 - amiry jd
哦,我明白了。你正在尝试在PART_ContentHost上动画化一个附加属性Textblock.Foreground。但是它仍然是一样的:PART_ContentHost没有任何值,所以它是null。你能否尝试给它分配一些值? - Vlad
不,我没有对“Textblock.Foreground”进行动画处理,但我尝试了问题中提到的所有故事板;是的,在每个故事板的使用中,我为所有属性设置了正确的值(包括您提到的属性)。 - amiry jd
好的,我不知道 ScrollViewer 有它自己的属性“Foreground”。但无论如何,这个属性是null的,因为没有为PART_ContentHost分配。以及附加的属性TextElement.Foreground。还有未命名的 Grid也是一样。 - Vlad
3个回答

12

感谢所有试图帮助我的人,我已经找到了答案。看起来当我们将TextBox.Foreground属性设置为资源时,故事板无法对其进行动画处理。因此,样式应该像这样:

<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>

这是我唯一遇到的问题。但需要记住的是,当我们想在Storyboard中定位一个模板化的父项时,不必将其绑定。我们只需要将其留下:

<!-- 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>

这对我有用。


这里 有一个有效的示例。


2
我不知道你是如何让它工作的。对我来说,它仍然导致不可变颜色异常。 - nmclean
1
@nmclean 我的错误。请使用(TextBox.Foreground).(SolidColorBrush.Color)作为目标属性。它可以正常工作。 - amiry jd
1
我也无法让它工作...我遇到了不可变错误,不得不在我的答案中加以解决。 - Stephen Hewlett
我正在创建一个示例项目。请等待几分钟,以便我可以将其上传到Github上。 - amiry jd
我真的很惊讶看到它能工作...我必须仔细地看看有什么不同! 我已经注意到了一些细微差别,这可能会使我的解决方案在某些情况下更好 :P。如果我将样式中的Foreground的SolidColorBrush的颜色更改为硬编码值('Red','Violet'等)或者使用StaticResource而不是DynamicResource进行绑定,则它将不起作用。这是一个潜在的“意外问题”,以后可能会出现...我建议在代码中加上注释,以防其他人在后面对其进行更改。 - Stephen Hewlett
我通过使用StaticResourceDynamicResource和硬编码值(包括命名颜色和十六进制代码)进行了测试。它只能与DynamicResource一起正常工作,为什么呢?实际上,我是一个新手WPFer,不知道原因。但你的解决方案的问题在于我们实际上有两个TextBox。如果我们想要为其他VisualState设置样式,我们将会有更加复杂和难以阅读的代码。 - amiry jd

9
这比我想象的更棘手。这是我的原始答案:

这绝对是可能的 - 这就是ColorAnimationXXX类的作用。

您的代码与此处的代码示例非常相似(链接),该示例使用ColorAnimation来动画化颜色。示例中的属性接受一个Brush(就像TextBox.Foreground一样),该Brush在XAML中定义并赋予名称,以便可以轻松地通过动画引用它。

所以在您的情况下,相关的代码将是:

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

并且:

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

理论上来说,这很好,直到我意识到它在样式中不起作用。而样式内的网格的Background属性很容易通过以下方式进行动画处理:

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

在动画中找到一个会对文本前景产生影响的属性是相当困难的。起初,我尝试使用 TextElement.Foreground,这似乎很直观,我能在 Grid 和 ScrollViewer 等级上设置此属性,我期望它将对其下方所有子对象产生影响 - 包括包含 TextBox 文本的底层对象。我的假设是 TextBox 内容将被内部设置为 TextBlock,并遵从设置在它上面的 Foreground 附加属性的值。但事实证明我错误了,PART_ContentHost ScrollViewer 的内容由 TextBox 中的控制逻辑设置为不服从与 TextBox 本身之间的对象树中任何 Foreground 依赖属性的更低级别对象。

问题在于如何在正在被样式化的 TextBox 的样式中设置 TextBox 的 Foreground 属性。为了测试,我尝试使用双向 TemplatedParent 绑定来设置此属性。我认为我正确地设置了 SolidColorBrush 的 Color 的 PropertyPath,但它仍然无效,因为该点的 Color 属性显然是不可变的。我认为这个问题在这里有记录。

除了它不能工作之外,内部设置 Foreground 属性似乎不对,因为外部使用者会期望对该属性的值有控制权。所以,考虑到 TextBox 的 Foreground 不会遵从样式中的任何设置,我得出结论:最好使用 TextBox 样式内的嵌套 TextBox 实现此功能。外层样式包含状态管理器和大部分布局,然后内部 TextBox 有自己的样式和控件模板,专门用于显示文本部分。外部样式可以设置内部 TextBox 的 Foreground 属性,内部 TextBox 会遵从该属性,而且关键是外部样式可以在状态管理器中设置此值。

<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>

我很想听听其他人对这种方法的看法,以及是否存在我没有考虑到的任何问题。根据我的尝试解决问题并检查结果应用程序,这是目前我能看到的最简单的解决方案。


如果在内部的TextBox上设置Foreground =“{TemplateBinding Foreground}”,这仍然有效吗?当前版本似乎没有让用户设置“正常”颜色的方法。 - nmclean
说实话,我很高兴能够走到这一步,所以我没有检查 - 我认为它应该可以正常工作,如果不行,那么内部文本框的前景可以在视觉状态管理器的其他状态中设置。 - Stephen Hewlett

2
您可以将 Storyboard.Target 绑定到 TemplatedParent:
<ColorAnimationUsingKeyFrames
        Storyboard.Target="{Binding RelativeSource={RelativeSource TemplatedParent}}"
        Storyboard.TargetProperty="(TextBox.Foreground).(SolidColorBrush.Color)">
    <EasingColorKeyFrame KeyTime="0" Value="{StaticResource MouseOverColor}" />
</ColorAnimationUsingKeyFrames>

很遗憾,我只能在样式中没有设置正常前景笔刷并直接在文本框元素上设置时才能使其工作:

<TextBox Foreground="{StaticResource NormalBrush}" Style="{StaticResource RegularTextBox}" />

如果在样式中设置,触发MouseOver状态会抛出"Cannot animate '(0).(1)' on an immutable object instance." 编辑:如果在初始化后再次设置TextBox前景色也会发生这种情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接