7

我正在尝试重新模板 aTextBox以使两个边框之间有填充;但是,即使我将 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[文本字段]15]

如何正确强制 PART_ContentHost (scrollviewer / textfield) 具有零填充?

4

3 回答 3

9

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

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。Foreground, Background,Margin直接设置时都很好,ScrollViewer它们会覆盖TextBox's 字段。我什至确认将使用中的Padding集合直接移动TextBox到默认样式设置器,以查看是否存在某些优先级情况。

似乎没有。得到相同的输出。

填充定义在System.Windows.Controls.Control哪个类 Foreground 和 BackgroundScrollViewer继承自同一个类。不知道为什么单独填充的行为不同。

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

<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,这是一个令人困惑的实现。

于 2013-05-07T18:51:05.990 回答
3

这种奇怪的行为来自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);
    }
}
于 2014-10-10T13:14:19.487 回答
1

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

<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>
于 2017-01-26T01:51:29.263 回答