Background
I was looking at how to embed content in a Control
, and found this article, and used the CustomControl that was included in the How to Embed Arbitrary Content in a WPF Control by Ivan Krivyakov, I had one issue with this particular solution and that was Binding
. Ivan Krivyokov did not broach this subject in his article and sample.
Introduction
I needed a ScrollViewer
with some buttons in the Horizontal ScrollBar
. Therefore this article will focus on creating embedded content in a ScrollViewer
.
The Control
The control consists of two parts, the C# class
derived from a ScrollViewer
, and the associated XAML. The C# code is:
public class ScrollBarToolsScrollViewer : ScrollViewer
{
static ScrollBarToolsScrollViewer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ScrollBarToolsScrollViewer),
new FrameworkPropertyMetadata(typeof(ScrollBarToolsScrollViewer)));
}
public object ScrollBarTools
{
get { return (object)GetValue(ScrollBarToolsProperty); }
set { SetValue(ScrollBarToolsProperty, value); }
}
public static readonly DependencyProperty ScrollBarToolsProperty=
DependencyProperty.Register("ScrollBarTools", typeof(object),
typeof(ScrollBarToolsScrollViewer),
new UIPropertyMetadata(null, OnScrollBarToolsChanged));
private static void OnScrollBarToolsChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var scrollViewer = (ScrollBarToolsScrollViewer)d;
var control = e.NewValue as FrameworkElement;
if (control != null)
if (scrollViewer.ScrollBarToolsDataContext == null) control.DataContext
= scrollViewer.DataContext;
else control.DataContext = scrollViewer.ScrollBarToolsDataContext;
}
public object ScrollBarToolsDataContext
{
get { return (object)GetValue(ViewBarToolsDataContextProperty); }
set { SetValue(ViewBarToolsDataContextProperty, value); }
}
public static readonly DependencyProperty ViewBarToolsDataContextProperty =
DependencyProperty.Register("ViewBarToolsDataContext", typeof(object),
typeof(ScrollBarToolsScrollViewer), new PropertyMetadata(null,
OnScrollBarToolsDataContextChanged));
private static void OnScrollBarToolsDataContextChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var scrollViewer = (ScrollBarToolsScrollViewer)d;
var control = scrollViewer.ScrollBarTools as FrameworkElement;
if (control != null)
if (scrollViewer.ScrollBarToolsDataContext == null) control.DataContext
= scrollViewer.DataContext;
else control.DataContext = scrollViewer.ScrollBarToolsDataContext;
}
}
This code adds two DependencyProperties
to the ScrollViewer
. One is for the content to be to the right of the horizontal ScrollBar
, the horizontal ScrollBar
width being adjusted to accomodate the UIElement
and its content. The UIElement
can contain a Panel
such as a StackPanel
, which can contain controls, such as Button
controls. The second allows the DataContext
for the content specified in these tools.
Whenever the ScrollBarTools
or ScrollBarToolsDataContext
is changed, the DataContext
for the ScrollBarTools
is set. If a ScrollBarToolsDataContext
is specified, that is set as the DataContext
, otherwise the DataContext
of the ScrollViewer
is set as the DataContext
of the ScrollBarTools
. Interestingly, if the DataContext
for the ScrollBarTools
was not set, then the DataContext
would be the ScrollBarToolsScrollViewer
.
I pulled the Style for ScrollViewer
that is in the Generic.xaml file in the Themes folder from existing code:
<Style x:Key="{x:Type local:ScrollBarToolsScrollViewer}"
TargetType="{x:Type local:ScrollBarToolsScrollViewer}">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ScrollBarToolsScrollViewer}">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
Margin="{TemplateBinding Padding}"
CanContentScroll="{TemplateBinding CanContentScroll}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<ScrollBar x:Name="PART_VerticalScrollBar"
Grid.Column="1"
AutomationProperties.AutomationId="VerticalScrollBar"
Cursor="Arrow"
Maximum="{TemplateBinding ScrollableHeight}"
Minimum="0.0"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=VerticalOffset, Mode=OneWay}" />
<Grid Grid.Row="1" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollBar x:Name="PART_HorizontalScrollBar"
AutomationProperties.AutomationId="HorizontalScrollBar"
Cursor="Arrow"
Maximum="{TemplateBinding ScrollableWidth}"
Minimum="0.0"
Orientation="Horizontal"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding
ComputedHorizontalScrollBarVisibility}"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=HorizontalOffset, Mode=OneWay}" />
<ContentPresenter Grid.Column="1"
ContentSource="ScrollBarTools"
DataContext="{Binding \
RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The location for the ScrollBarTools
is in an added Grid element that contains the horizontal ScrollBar
and the new ScrollBarTools
. Since this Style
depends on existing default Style
defined for the ScrollBar
, it should look very similar to the other ScrollViewer
elements in the design.
History
2016/09/27: Initial Version.