为什么我无法重置用户控件中文本框的背景?

3
我已经创建了一个扩展ComboBox功能的UserControl,它可以以有趣且实用的方式进行操作。下拉时它看起来像这样: My user control 我在控件中添加了很多功能,并且它们都可以顺畅地运行。这使我相信我知道自己在做什么。你可能认为将UserControl的样式设置为可编辑的TextBox的背景刷是一个微不足道的事情。但事实上,它似乎是不可能完成的。我感到困惑。
UserControl的XAML代码(非常简略)如下:
<UserControl x:Class="MyApp.CodeLookupBox" x:Name="MainControl">
    <UserControl.Resources>
       <!-- tons of DataTemplates and Styles, most notably the style that
            contains the control template for the ComboBox -->
    <UserControl.Resources>
    <ComboBox x:Name="ComboBox" 
                   Margin="0" 
                   Style="{DynamicResource ComboBoxStyle1}" 
                   VerticalAlignment="Top"
                   ItemTemplate="{StaticResource GridViewStyleTemplate}"/>
</UserControl>

这个控件里有很多后台代码,主要是我用来选择下拉列表中使用的模板等方面的依赖属性。

让我感到困扰的是可编辑文本框。我希望能够从用户控件的样式中设置它的背景笔刷 - 例如,当我在我的XAML中声明一个这样的用户控件时,它使用类似于以下的样式:

<Style TargetType="{x:Type local:CodeLookupBox}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding IsRequired}" Value="True">
            <Setter Property="EditableTextBoxBackground" Value="{StaticResource RequiredFieldBrush}"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

我最开始只是设置了UserControl的背景色,但这只是设置了可编辑文本框后方的背景色。文本框本身仍然是白色的。

在ComboBox的模板内部,有一个样式控制着文本框:

<Style x:Key="ComboBoxEditableTextBox" TargetType="{x:Type TextBox}">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="AllowDrop" Value="true"/>
    <Setter Property="MinWidth" Value="0"/>
    <Setter Property="MinHeight" Value="0"/>
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <ScrollViewer 
                            x:Name="PART_ContentHost" 
                            Focusable="false" 
                            HorizontalScrollBarVisibility="Hidden" 
                            VerticalScrollBarVisibility="Hidden"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

还有一个 TextBox (在 ComboBox 的控件模板中)它的本质不好:

<TextBox 
    x:Name="PART_EditableTextBox" 
    Margin="{TemplateBinding Padding}" 
    Style="{StaticResource ComboBoxEditableTextBox}" 
    HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" 
    VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
    IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}"/>

现在,关于ComboBoxEditableTextBox样式,有一定的“人类不应该知道的事情”元素。那个ScrollViewer在里面做什么呢?我不知道。但是我可以告诉你,如果我注释掉设置TextBox的ControlTemplate部分的样式,会发生非常糟糕的事情。
我还知道这一点:如果我显式地将TextBox的Background刷子设置为样式的setter之一,什么都不会发生。如果我明确地在PART_EditableTextBox上设置背景,也不会发生任何事情。(我可以设置它的前景色或字体族,它们可以正常工作。)
但是,如果我明确设置那个ScrollViewer的背景为绿色,那么,哇,TextBox就变成了绿色。
好吧,那么TextBox忽略了自己的背景,并使用控件模板中的背景。实际上,严格来说,它也没有使用控件模板中的背景。当我设置ScrollViewer的背景时,周围有一个明显的边距,而不是颜色完全填充TextBox。但是,那个边距是白色的,而不是背景颜色。
除非我能弄清楚TextBox为什么忽略了它的背景,否则我必须接受调整ScrollViewer的事实。那么,我该如何让它从用户控件的EditableTextBoxBackground属性获取刷子呢?我已经将其作为依赖属性,并在更改时正确地引发PropertyChanged事件。我在神秘的ScrollViewer的XAML中像这样绑定它:
Background="{Binding ElementName=MainControl, 
    Path=EditableTextBoxBackground, 
    Converter={StaticResource DebuggingConverter}}"

我在调试转换器时设置了一个断点。当控件首次绘制时,它被触发两次。第一次,刷子的值为null。第二次,它是正确的值。如果我在我的UserControl的构造函数中设置属性,它可以工作。
所以这就是我知道的:我的UserControl的属性被正确设置了。TextBox样式上的绑定已经正确地绑定到了UserControl的属性上。TextBox控件模板中的ScrollViewer绑定到了正确的属性上。当属性更改时,属性引发PropertyChanged事件并推送值到ScrollViewer背景属性。
但是什么也没有发生。
所以我想问三个问题:1)为什么?2)那个ScrollViewer到底是做什么用的?我有些怀疑,但现在已经是凌晨一点了,很难表达我的想法。3)为什么Blend给我提供的控件模板与这里找到的更易理解的控件模板不同?
真的,任何帮助都将不胜感激。

“你可能认为将UserControl的样式设置为可编辑的TextBox的背景刷子是一个微不足道的问题。实际上,它似乎是不可能的。” - 哈哈,说得好。我现在也遇到了同样的问题。 - stone
1个回答

6

你有问题,我有答案。

1- 为什么ScrollViewer的Background绑定行为如此奇怪?

当一个TextBox第一次被测量时,它会实例化模板,这样就创建了ScrollViewer。一旦应用了模板,TextBox会检查ScrollViewerBackground属性是否当前具有空值。如果是,它会用Background.Transparent覆盖它。这样会断开你的绑定。

这就是为什么在构造函数中设置它可以工作,但在以后却不行的原因:TextBox看到了空值并用Background.Transparent覆盖了它,从而破坏了绑定。

2- ScrollViewer到底在做什么?

TextBox是一个Control,实际上并不处理任何文本呈现的详细信息 - 如果你浏览可视树,你会发现这是由另一个名为“TextView”的Visual处理的。 TextBox的主要工作实际上是呈现文本框周围的边框和/或允许您给它一个全新的外观。

TextBox需要一个模板,其中一个名为PART_ContentHost的元素是ContentPresenterScrollViewer。如果它是简单的ContentPresenter,则内部的“TextView”对象将被简单地添加到它上面。如果它是ScrollViewerTextBox还会连接一些额外的功能,例如在焦点放在文本框中时滚动文本到视图中。

ScrollViewer的生命周期是允许TextBox中的文本在水平方向上滚动,并且对于多行文本框也可以在垂直方向上滚动。

3- 为什么Blend给了我一个不同的控件模板?

Blend从引用的程序集中加载实际的ControlTemplate XAML,这里是PresentationFramework.dll和当前系统主题的关联主题dll。因此,它将加载你所安装的NET Framework版本中实际使用的内容。你链接的网站上的XAML只是示例代码,不是实际的NET Framework XAML。

我添加了另外两个相关问题:

4- 为什么设置TextBox的Background属性没有起作用?

WPF的Control子类实际上并没有自己实现Background属性。 Background DependencyProperty只是一个命名的画刷,如果控件模板需要,它可以绑定到该属性。这对于TextBox和其他任何Control都是正确的。默认的TextBox模板包括一个"chrome"对象,其中包含显示背景的代码,类似于您可能使用边框的方式。由于ComboBox已经显示了自己的"chrome",它使用自己的TextBox模板,其中包括ScrollViewer但不包括周围的chrome。这就是为什么在ComboBox中设置TextBoxBackground属性没有效果的原因。

5- 如何解决我的问题并绑定ComboBox内部TextBox的背景颜色

如果您可以接受白色边缘,那么您可以简单地将ScrollViewer包装在<Border>中,并在<Border>上设置背景。否则,您将不得不将所需的背景移入主ControlTemplate提供的chrome中,以用于ComboBox


哇,有没有一本书详细介绍这样的事情,还是基于经验+反射器? - Merlyn Morgan-Graham

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