TextBox template padding issue
This really looks like a weird bug to me unless someone has a better explanation for this behavior.
ScrollViewer
(PART_ContentHost) internally uses a Template
like:
<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>
The interesting bit is:
<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}" />
Now to fix your issue you can just set the Margin there to 0 instead of {TemplateBinding Padding}
and you'll get your desired output.
But why did we need to do that?
TemplateBinding Padding
seems to be ignoring the value set directly on the ScrollViewer
which is in the inner scope and picks the Padding value inherited from the Parent(Button
) which is 15.
Ok that's weird, but what's worse is it's only for Padding. Foreground
, Background
, Margin
are all fine when set directly on ScrollViewer
they override the TextBox
's fields. I even to confirm moved the Padding
set directly on the TextBox
in usage to a default Style setter, to see if some precedence case was the problem.
It did not seem to be. Got same output.
Padding is defined in System.Windows.Controls.Control
which is the same class Foreground and Background are from that ScrollViewer
inherits. Not sure why padding alone is behaving differently.
I also tried changing the presenter to something like
<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}" />
It print's 15,15,15,15. Doesn't do that for the Margin
.
Same effect with a {Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}, Path=Padding}
binding.
I saw one post saying ScrollViewer
does not pass properties set on it to it's children. Don't really get that since if that was the case how did Background
, Margin
and sorts be fine to over-ride? Whats so special about Padding
? If it is valid behavior, I don't really see how to get rid of that behavior without Templating the ScrollViewer
as well and it's one confusing implementation.
This weird behavior is coming from TextBoxBase
.
It overrides metadata for some of its dependency properties and for the Padding
property it looks like:
Control.PaddingProperty.OverrideMetadata(
typeof (TextBoxBase),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(TextBoxBase.OnScrollViewerPropertyChanged)));
If you take a look at the OnScrollViewerPropertyChanged
handler, you'll notice that it passes the value of the changed property to its ScrollViewer
:
if (newValue == DependencyProperty.UnsetValue)
textBoxBase.ScrollViewer.ClearValue(e.Property);
else
textBoxBase.ScrollViewer.SetValue(e.Property, newValue);
So, no matter what Padding
value you set in Control Template, it will be overwritten with the local value by TextBox
in runtime.
To compensate this padding you can set a negative margin to ScrollViewer
in the template:
<ScrollViewer
x:Name="PART_ContentHost"
Margin="{TemplateBinding Padding, Converter={StaticResource InvertThicknessConverter}}"
/>
where InvertThicknessConverter
is a value converter which negates every component of the passed thickness value:
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);
}
}