Introduction
I had a case where a design had content which would be visible under a header in both the on and off cases. This was a design that was modified from a design that only had content in the ToggleOn
case. An Expander
worked fine for this case, but not for the new design. Thus, a new Control
was designed based on a ContentControl
. However, in this case, the Control
was a lot more complex than just this simple Control
since it has been customized for a specific application there were DependencyProperty
definitions that support the Content
of the Expander Header
.
As I proceeded with the design, I learned a lot about the possibilities, in particular how to simplify, and avoid using code-behind. The initial version had almost all code behind, but I was able to remove the code-behind for everything but the DependencyProperty
definition by using Template Binding
and Triggers.
The Design
The XAML is as follows:
<Style TargetType="{x:Type local:ToggleButtonContentBanner}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ToggleButtonContentBanner}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Border x:Name="PART_HeaderBorder"
Grid.ColumnSpan="3"
Height="{Binding HeaderHeight,
RelativeSource={RelativeSource TemplatedParent}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{Binding Background,
RelativeSource={RelativeSource TemplatedParent}}">
<ToggleButton x:Name="StateToggleButton"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsChecked="{Binding IsChecked,
RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentControl x:Name="PART_HeaderImageControl"
Grid.Column="0"
Content="{TemplateBinding HeaderImage}" />
<TextBlock x:Name="PART_HeaderTextControl"
Grid.Column="1"
VerticalAlignment="Center"
FontSize="{Binding FontSize,
RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{Binding Foreground,
RelativeSource={RelativeSource TemplatedParent}}"
Text="{TemplateBinding HeaderText}" />
<Button x:Name="HelpButton"
Grid.Column="2"
Width="24"
Height="24"
Margin="5"
Padding="0"
HorizontalAlignment="Left"
Command="{TemplateBinding HelpCommand}"
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
<Border Width="22"
Height="22"
BorderBrush="{Binding Foreground,
RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="2"
CornerRadius="12">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"
FontWeight="Bold"
Foreground="{Binding Foreground,
RelativeSource={RelativeSource TemplatedParent}}"
Text="?" />
</Border>
</Button>
</Grid>
</ToggleButton>
</Border>
<ContentControl x:Name="PART_ToggleOnContent"
Grid.Row="1"
Grid.ColumnSpan="3"
Content="{TemplateBinding ToggleOnContent}" />
<ContentControl x:Name="PART_ToggleOffContent"
Grid.Row="2"
Grid.ColumnSpan="3"
Content="{TemplateBinding ToggleOffContent}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="PART_ToggleOnContent"
Property="Visibility" Value="Visible" />
<Setter TargetName="PART_ToggleOffContent"
Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="IsChecked" Value="false">
<Setter TargetName="PART_ToggleOnContent"
Property="Visibility" Value="Collapsed" />
<Setter TargetName="PART_ToggleOffContent"
Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Most of this code is for the header or the part, and the two ContentControl
definitions for the XAML content for the toggle on and toggle off states. There is also the Trigger
that will cause the Visibility
of these two ContentControl
definitions to be Visible
or Collapsed
depending on the value of the IsChecked
property for the Root control.
The C# code for this Control
is almost all just DependencyProperty
definitions:
public class ToggleButtonContentBanner : ContentControl
{
static ToggleButtonContentBanner()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ToggleButtonContentBanner),
new FrameworkPropertyMetadata(typeof(ToggleButtonContentBanner)));
}
public object ToggleOnContent
{
get { return (object)GetValue(ToggleOnContentProperty); }
set { SetValue(ToggleOnContentProperty, value); }
}
public static readonly DependencyProperty ToggleOnContentProperty =
DependencyProperty.Register("ToggleOnContent", typeof(object),
typeof(ToggleButtonContentBanner), new PropertyMetadata(null));
public DependencyObject ToggleOffContent
{
get { return (DependencyObject)GetValue(ToggleOffContentProperty); }
set { SetValue(ToggleOffContentProperty, value); }
}
public static readonly DependencyProperty ToggleOffContentProperty =
DependencyProperty.Register("ToggleOffContent", typeof(DependencyObject),
typeof(ToggleButtonContentBanner), new PropertyMetadata(null));
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register("HeaderText", typeof(string),
typeof(ToggleButtonContentBanner),
new PropertyMetadata(null));
public Brush HeaderBackground
{
get { return (Brush)GetValue(HeaderBackgroundProperty); }
set { SetValue(HeaderBackgroundProperty, value); }
}
public static readonly DependencyProperty HeaderBackgroundProperty =
DependencyProperty.Register("HeaderBackground", typeof(Brush),
typeof(ToggleButtonContentBanner), new PropertyMetadata(null));
public double HeaderHeight
{
get { return (double)GetValue(HeaderHeightProperty); }
set { SetValue(HeaderHeightProperty, value); }
}
public static readonly DependencyProperty HeaderHeightProperty =
DependencyProperty.Register("HeaderHeight", typeof(double),
typeof(ToggleButtonContentBanner), new PropertyMetadata(0.0d));
public object HeaderImage
{
get { return GetValue(HeaderImageProperty); }
set { SetValue(HeaderImageProperty, value); }
}
public static readonly DependencyProperty HeaderImageProperty =
DependencyProperty.Register("HeaderImage", typeof(object),
typeof(ToggleButtonContentBanner),
new PropertyMetadata(null));
public ICommand HelpCommand
{
get { return (ICommand)GetValue(HelpCommandProperty); }
set { SetValue(HelpCommandProperty, value); }
}
public static readonly DependencyProperty HelpCommandProperty =
DependencyProperty.Register("HelpCommand", typeof(ICommand),
typeof(ToggleButtonContentBanner), new PropertyMetadata(null));
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool),
typeof(ToggleButtonContentBanner), new PropertyMetadata(false));
}
Normally, if a Style
is created with a ControlTemplate
, the customization only allows use of properties that already exist for the control. This can be limiting. It is also possible to create controls using a UserControl
. This is a very flexible Control
, but from what I understand, a UserControl
has a greater performance impact--at least when I was working for Microsoft on a project, we were not allowed to use the UserControl
because of this performance penalty. The significant advantage of the UserControl
is that it is easier to design since Visual Studio WYSIWYG designer for the UserControl
but not for a ControlTemplate
or a DataTemplates
.
Both a ControlTemplate
and a UserControl
have the advantage of being able to write code-behind, but that is sort of like putting code behind on Window--It is considered better to not have code behind. As can be seen, this control has more available than available in a ContentControl
that this Control
is derived from, but there is nothing in the code-behind besides the DependencyProperty
definitions. This is done by defining each needed DependencyProperty
. Then this DependencyProperty
is available for use by using RelativeSource TemplatedParent Binding
. It is also possible to the TemplateBinding
but I have run into difficulty directly using TemplateBinding
when values were updated. I have so far not run into problems using the RelativeSource Binding
.
Using the Code
This Control
is very special purpose and a lot of the attributes are to support this particular requirement, such as the HeaderText
, HeaderImage
, HelpCommand
. The IsChecked
, ToggleOnContent
and ToggleOffContent
are the properties that actually provide this custom type Expander
capability:
<local:ToggleButtonContentBanner VerticalAlignment="Top"
HeaderText="Experiment Applications"
HelpCommand="{Binding HelpCommand}"
IsChecked="True"
Style="{StaticResource CreateExperimentExpander}">
<local:ToggleButtonContentBanner.HeaderImage>
<Image Source="MyImage.jpg"/>
</local:ToggleButtonContentBanner.HeaderImage>
<local:ToggleButtonContentBanner.ToggleOffContent>
<Border Height="100"
Background="Red">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="The Toggle Banner is On" />
</Border>
</local:ToggleButtonContentBanner.ToggleOffContent>
<local:ToggleButtonContentBanner.ToggleOnContent>
<Border Height="100"
Background="Green">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="The Toggle Banner is Off" />
</Border>
</local:ToggleButtonContentBanner.ToggleOnContent>
</local:ToggleButtonContentBanner>
Points of Interest
The following are some of the things that should be takeaways:
- When designing a custom
Control
, it may be possible to eliminate the code behind by using Template Binding
and Triggers
.
- Create new properties that can be used with
Triggers
and Template Binding
, and then used when the Control
is used.
TemplateBinding
may work, but may have update issues, so probably better to use RelativeSource TemplatedParent
.
History
- 10/02/2017: Initial version