- Gordon Zhang
Introduction
Over the years, I have been benefited from CodeProject, it is time for me to contribute a little, and hope someone can benefit from my successes and mistakes. I only started working on WPF for less than a month, so it may have a lot of mistakes. So use it at your own risk free of charge. But any comments or improvements will be appreciated.
Enough for the blah…
There are 2 or 3 good WPF SplitContainer
s over the internet, one is from Open Source CodePlex, you can have the source code from it. But to me, it is too complicated (Since I am new to WPF). Here I am going to utilize WPF Grid
and GridSplitter
to create the custom control with very simple code behind it, it serves my purpose anyway.
Using the Code
Class for SplitContainer from Custom Control
It is very simple, the class is derived from Control
, and it will have four DependencyProperties
:
Child1Property
– Panel1
to hold the controls, registered as UIPropertyMetadata
Child2Property
– Panel2
to hold the controls, registered as UIPropertyMetadata
SplitterDistanceProperty
– to set the distance for the splitter, double value
OrientationProperty
– to set the orientation for the splitter, enumeration value.
Here are the code snippets:
namespace SplitContainer
{
public enum Orientation
{
Vertical = 0,
Horizontal
};
public class SplitContainer : Control
{
public static readonly DependencyProperty Child1Property;
public static readonly DependencyProperty Child2Property;
public static readonly DependencyProperty SplitterDistanceProperty;
public static readonly DependencyProperty OrientationProperty;
static SplitContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitContainer),
new FrameworkPropertyMetadata(typeof(SplitContainer)));
Child1Property = DependencyProperty.Register
("Child1", typeof(UIElement), typeof(SplitContainer),
new UIPropertyMetadata(null));
Child2Property = DependencyProperty.Register(
"Child2", typeof(UIElement), typeof(SplitContainer),
new UIPropertyMetadata(null));
SplitterDistanceProperty = DependencyProperty.Register(
"SplitterDistance", typeof(double), typeof(SplitContainer),
new FrameworkPropertyMetadata(100.0,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
OrientationProperty = DependencyProperty.Register("Orientation",
typeof(Orientation), typeof(SplitContainer),
new UIPropertyMetadata(null));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
public UIElement Child1
{
get { return (UIElement)GetValue(Child1Property); }
set { SetValue(Child1Property, value); }
}
public UIElement Child2
{
get { return (UIElement)GetValue(Child2Property); }
set { SetValue(Child2Property, value); }
}
public double SplitterDistance
{
get { return (double)GetValue(SplitterDistanceProperty); }
set { SetValue(SplitterDistanceProperty, value); }
}
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
}
}
Styles for the Splitter
We set the styles for the for the Vertical
and Horizontal
as the following so that it looks nicer:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key ="Color1">#E3EFFF</Color>
<Color x:Key="Color5">#C0DBFF</Color>
-->
<LinearGradientBrush x:Key="GridSplitterHorzBackgroundBrush"
EndPoint="0,1" StartPoint="0,0">
<GradientStop Offset="0" Color="{StaticResource Color1}"/>
<GradientStop Offset="1" Color="{StaticResource Color5}"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="GridSplitterVertBackgroundBrush"
EndPoint="0,1" StartPoint="1,1">
<GradientStop Offset="0" Color="{StaticResource Color5}"/>
<GradientStop Offset="1" Color="{StaticResource Color1}"/>
</LinearGradientBrush>
-->
<Style x:Key="VerticalGridSplitterStyle" TargetType="{x:Type GridSplitter}">
<Setter Property="UIElement.SnapsToDevicePixels" Value="True"/>
<Setter Property="UIElement.Focusable" Value="False"/>
<Setter Property="ShowsPreview" Value="True"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
-->
<Setter Property="Height" Value="Auto"/>
<Setter Property="Width" Value="5"/>
<Setter Property="Background" Value="{DynamicResource GridSplitterVertBackgroundBrush}"/>
</Style>
-->
<Style x:Key="HorizontalGridSplitterStyle" TargetType="{x:Type GridSplitter}">
<Setter Property="UIElement.SnapsToDevicePixels" Value="True"/>
<Setter Property="UIElement.Focusable" Value="False"/>
<Setter Property="ShowsPreview" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
-->
<Setter Property="Height" Value="5"/>
<Setter Property="Width" Value="Auto"/>
<Setter Property="Background" Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
</Style>
</ResourceDictionary>
XAML for SplitContainer Itself
Here is how we make the splitcontainer utilize the Grid
and GridSplitter
in XAML:
<Style TargetType="{x:Type local:SplitContainer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SplitContainer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Name="Column_1"
Width="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=SplitterDistance}"/>
<ColumnDefinition Name="Column_2" Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions >
<RowDefinition Name="Row_1" Height="*"/>
<RowDefinition Name="Row_2" Height="0"/>
</Grid.RowDefinitions>
-->
<Rectangle Name="Border_Outer" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
Grid.ColumnSpan="2" Stroke="{DynamicResource GridSplitterVertBackgroundBrush}"
Fill="{DynamicResource GridSplitterVertBackgroundBrush}"
StrokeThickness="1" Margin="0,0,0,0"/>
<Rectangle Name="Border_Inner" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
Grid.ColumnSpan="2" Stroke="Black" Fill="White" StrokeThickness="1" Margin="0,1,0,1"/>
-->
<ListView Name="child_1" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
BorderBrush="{DynamicResource GridSplitterVertBackgroundBrush}"
BorderThickness="0.5,0.5,0.5,0.5" Margin="1,1,5,1" />
<ListView Name="child_2" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2"
BorderBrush="Black" BorderThickness="0.5,0.5,0.5,0.5" Margin="0.,1,1,1" />
-->
<GridSplitter Name="gridSpliter" Grid.Column="0" Grid.Row="0"
Grid.RowSpan="2" Style="{StaticResource VerticalGridSplitterStyle}"/>
<ContentPresenter Name="CP_1" Margin="0,1,5,1" Grid.Column="0"
Grid.Row="0" Grid.RowSpan="2" ContentSource="Child1"/>
<ContentPresenter Name="CP_2" Margin="0,1,0,1" Grid.Column="1"
Grid.Row="0" Grid.RowSpan="2" ContentSource="Child2"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="local:SplitContainer.Orientation" Value="Horizontal">
<Setter TargetName="Column_1" Property="Width" Value="*"/>
<Setter TargetName="Column_2" Property="MaxWidth" Value="0"/>
<Setter TargetName="Row_1" Property="Height"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=SplitterDistance}"/>
<Setter TargetName="Row_2" Property="Height" Value="*"/>
<Setter TargetName="Border_Outer" Property="Stroke"
Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
<Setter TargetName="Border_Outer" Property="Fill"
Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
<Setter TargetName="Border_Inner" Property="Stroke"
Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
<Setter TargetName="Border_Inner" Property="Margin" Value="1,0,1,0"/>
<Setter TargetName="child_1" Property="Margin" Value="1,1,1.0,5"/>
<Setter TargetName="child_1" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="child_1" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="child_1" Property="BorderBrush"
Value="{DynamicResource GridSplitterHorzBackgroundBrush}"/>
<Setter TargetName="child_2" Property="Margin" Value="1,0,1,0"/>
<Setter TargetName="child_2" Property="Grid.Row" Value="1"/>
<Setter TargetName="child_2" Property="Grid.Column" Value="0"/>
<Setter TargetName="child_2" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="child_2" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="gridSpliter" Property="Style"
Value="{StaticResource HorizontalGridSplitterStyle}"/>
<Setter TargetName="gridSpliter" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="gridSpliter" Property="Grid.Row" Value="0"/>
<Setter TargetName="gridSpliter" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="gridSpliter" Property="VerticalAlignment" Value="Bottom"/>
<Setter TargetName="CP_1" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="CP_1" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="CP_1" Property="Margin" Value="1,1,1.5,5"/>
<Setter TargetName="CP_2" Property="Margin" Value="0,1,1,1"/>
<Setter TargetName="CP_2" Property="Grid.RowSpan" Value="1"/>
<Setter TargetName="CP_2" Property="Grid.ColumnSpan" Value="2"/>
<Setter TargetName="CP_2" Property="Grid.Row" Value="1"/>
<Setter TargetName="CP_2" Property="Grid.Column" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
We use the property trigger to change the splitContainer
orientation. We also draw some rectangle inside each child to make it looks like the WindowsForms SplitContainer
. These two lines of code are important:
<ContentPresenter Name="CP_1" Margin="0,1,5,1" Grid.Column="0"
Grid.Row="0" Grid.RowSpan="2" ContentSource="Child1"/>
<ContentPresenter Name="CP_2" Margin="0,1,0,1" Grid.Column="1"
Grid.Row="0" Grid.RowSpan="2" ContentSource="Child2"/>
They expose the Child1
and Child2
to the applications and make them hold the controls.
Use the SplitContainer in an Application
It is quite easy to use it, you create the application, under the Window, you just need to put SplitContainer
under it as in the demo project:
<Grid>
<SC:SplitContainer Orientation="Horizontal" SplitterDistance="200">
<SC:SplitContainer.Child1>
<SC:SplitContainer Orientation="Vertical">
</SC:SplitContainer>
</SC:SplitContainer.Child1>
<SC:SplitContainer.Child2>
<StackPanel Orientation="Horizontal">
<Button Content="Button on Second Panel" Margin="0" Width="276" />
</StackPanel>
</SC:SplitContainer.Child2>
</SC:SplitContainer>
</Grid>
One thing for the default splitter to show the colors, you need to put the following line in your App.XAML as:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/
SplitContainer;component/Themes/Splitter.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
That is all to it.