【问题标题】:TextBox template padding issue文本框模板填充问题
【发布时间】:2013-05-07 17:07:44
【问题描述】:

我正在尝试重新模板 TextBox 以具有两个边界,中间有填充;但是,即使我将 PART_ContentHost 上的填充显式设置为零,控件的填充也始终应用于内部 ScrollViewer

小例子:

<Style TargetType="{x:Type TextBox}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <Border Padding="{TemplateBinding Padding}"
                        BorderThickness="1"
                        BorderBrush="Black"
                        Background="LightBlue">
                    <ScrollViewer Padding="0" Margin="0" Background="Turquoise"
                                  x:Name="PART_ContentHost" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<TextBox Padding="15"/>

结果是一个如下所示的文本框:[15[15 Textfield 15]15]

虽然我只是期望:[15[ Textfield ]15]

如何正确强制 PART_ContentHost(滚动查看器/文本字段)填充零?

【问题讨论】:

    标签: wpf textbox wpf-controls wpf-4.0


    【解决方案1】:

    除非有人对此行为有更好的解释,否则这对我来说确实是一个奇怪的错误。

    ScrollViewer(PART_ContentHost) 内部使用 Template 类似:

    <ControlTemplate TargetType="{x:Type ScrollViewer}">
      <Grid x:Name="Grid"
            Background="{TemplateBinding Background}">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="*" />
          <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Rectangle x:Name="Corner"
                    Grid.Row="1"
                    Grid.Column="1"
                    Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
        <ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
                                Grid.Row="0"
                                Grid.Column="0"
                                Margin="{TemplateBinding Padding}"
                                CanContentScroll="{TemplateBinding CanContentScroll}"
                                CanHorizontallyScroll="False"
                                CanVerticallyScroll="False"
                                Content="{TemplateBinding Content}"
                                ContentTemplate="{TemplateBinding ContentTemplate}" />
        <ScrollBar x:Name="PART_VerticalScrollBar"
                    Grid.Row="0"
                    Grid.Column="1"
                    AutomationProperties.AutomationId="VerticalScrollBar"
                    Cursor="Arrow"
                    Maximum="{TemplateBinding ScrollableHeight}"
                    Minimum="0"
                    ViewportSize="{TemplateBinding ViewportHeight}"
                    Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
                    Value="{Binding VerticalOffset,
                                    Mode=OneWay,
                                    RelativeSource={RelativeSource TemplatedParent}}" />
        <ScrollBar x:Name="PART_HorizontalScrollBar"
                    Grid.Row="1"
                    Grid.Column="0"
                    AutomationProperties.AutomationId="HorizontalScrollBar"
                    Cursor="Arrow"
                    Maximum="{TemplateBinding ScrollableWidth}"
                    Minimum="0"
                    Orientation="Horizontal"
                    ViewportSize="{TemplateBinding ViewportWidth}"
                    Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                    Value="{Binding HorizontalOffset,
                                    Mode=OneWay,
                                    RelativeSource={RelativeSource TemplatedParent}}" />
      </Grid>
    </ControlTemplate>
    

    有趣的是:

    <ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
                            Grid.Row="0"
                            Grid.Column="0"
                            Margin="{TemplateBinding Padding}"
                            CanContentScroll="{TemplateBinding CanContentScroll}"
                            CanHorizontallyScroll="False"
                            CanVerticallyScroll="False"
                            Content="{TemplateBinding Content}"
                            ContentTemplate="{TemplateBinding ContentTemplate}" />
    

    现在要解决您的问题,您只需将 Margin 设置为 0 而不是 {TemplateBinding Padding},您将获得所需的输出。

    但是为什么我们需要这样做呢?

    TemplateBinding Padding 似乎忽略了直接在内部范围内的ScrollViewer 上设置的值,并选择从 Parent(Button) 继承的 Padding 值,即 15。

    好吧,这很奇怪,但更糟糕的是它只适用于 Padding。 ForegroundBackgroundMargin 都可以直接在ScrollViewer 上设置它们覆盖TextBox 的字段。我什至确认将使用中的TextBox 上的Padding 集直接移动到默认样式设置器中,以查看是否存在某些优先级情况。

    好像没有。得到相同的输出。

    填充在System.Windows.Controls.Control 中定义,它与ScrollViewer 继承的Foreground 和Background 类相同。不知道为什么单独填充的行为会有所不同。

    我也尝试将演示者更改为类似

    <ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
                            Grid.Row="0"
                            Grid.Column="0"
                            Margin="{TemplateBinding Margin}"
                            CanContentScroll="{TemplateBinding CanContentScroll}"
                            CanHorizontallyScroll="False"
                            CanVerticallyScroll="False"
                            Content="{TemplateBinding Padding}"
                            ContentTemplate="{TemplateBinding ContentTemplate}" />
    

    打印的是 15,15,15,15。 Margin 不这样做。

    {Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}, Path=Padding} 绑定的效果相同。

    我看到一篇帖子说ScrollViewer 没有将其上设置的属性传递给它的孩子。不要真正明白这一点,因为如果是这样的话,BackgroundMargin 和排序如何可以超越? Padding 有什么特别之处?如果它是有效的行为,我真的不知道如何在不模板化 ScrollViewer 的情况下摆脱这种行为,这是一个令人困惑的实现。

    【讨论】:

    • 非常感谢您提供详细且经过充分测试的答案!我非常感谢有关滚动查看器模板的输入和信息。这当然是一个奇怪的案例,对我来说没有多大意义。
    【解决方案2】:

    这种奇怪的行为来自TextBoxBase。 它覆盖了一些依赖属性的元数据,对于Padding 属性,它看起来像:

    Control.PaddingProperty.OverrideMetadata(
        typeof (TextBoxBase), 
        new FrameworkPropertyMetadata(
            new PropertyChangedCallback(TextBoxBase.OnScrollViewerPropertyChanged)));
    

    如果您查看OnScrollViewerPropertyChanged 处理程序,您会注意到它将更改的属性的值传递给其ScrollViewer

      if (newValue == DependencyProperty.UnsetValue)
        textBoxBase.ScrollViewer.ClearValue(e.Property);
      else
        textBoxBase.ScrollViewer.SetValue(e.Property, newValue);
    

    因此,无论您在 Control Template 中设置什么 Padding 值,它都会在运行时被 TextBox 覆盖为本地值。

    要补偿此填充,您可以在模板中将负边距设置为 ScrollViewer

    <ScrollViewer 
        x:Name="PART_ContentHost" 
        Margin="{TemplateBinding Padding, Converter={StaticResource InvertThicknessConverter}}"
    />
    

    其中InvertThicknessConverter 是一个值转换器,它否定传递的厚度值的每个分量:

    public class InvertThicknessConverter: IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Thickness) return InvertThickness((Thickness)value);
            return value;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Thickness) return InvertThickness((Thickness)value);
            return value;
        }
    
        private static Thickness InvertThickness(Thickness value)
        {
            return new Thickness(-value.Left, -value.Top, -value.Right, -value.Bottom);
        }
    }
    

    【讨论】:

    • 有趣的发现!感谢您提供更多详细信息。虽然我可能会继续使用自定义滚动查看器样式来解决此问题,但有选项并了解问题的根源是很棒的。
    • 这实际上比接受的答案更好,因为您只是使用转换器而不是覆盖ControlTemplate;但是,它仅在您绑定到ScrollViewerPadding 时才有效,not Margin,在我的情况下。
    【解决方案3】:

    我在DataGridColumnHeader 上设置Padding 属性时遇到了这个问题,并通过指定一个填充属性关闭但不等于零来解决这个问题:

    <Style x:Key="CustomHeader" TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
        <!-- Why you might ask do we need a value that is near but not actually 0?  No idea, but if it is not there, "4" is used. -->
        <!-- See http://stackoverflow.com/questions/16424739/textbox-template-padding-issue -->
        <Setter Property="Padding" Value="0,0,0.000000000001,0" />
    </Style>
    

    【讨论】:

      【解决方案4】:

      对于那些来这里了解如何正确设置文本框模板中的填充:

      不仅ScrollViewer 可以托管您的内容。您可以改用Decorator。因此,将this answer 考虑在内,您可以使您的模板如下所示:

      <Style TargetType="{x:Type TextBox}">
          <Setter Property="Template">
              <Setter.Value>
                  <ControlTemplate TargetType="{x:Type TextBox}">
                      <Border BorderThickness="1"
                              BorderBrush="Black"
                              Background="LightBlue">
                          <ScrollViewer Background="Turquoise">
                              <Decorator x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
                          <ScrollViewer/>
                      </Border>
                  </ControlTemplate>
              </Setter.Value>
          </Setter>
      </Style>
      

      【讨论】:

        猜你喜欢
        • 2014-05-03
        • 2011-06-21
        • 2011-04-28
        • 1970-01-01
        • 2021-12-28
        • 1970-01-01
        • 1970-01-01
        • 2019-03-02
        • 2011-02-28
        相关资源
        最近更新 更多