A Simple but Useful WPF Container






4.67/5 (6 votes)
Utilize the Grid and GridSplitter to compose a SplitContainer
- 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 asUIPropertyMetadata
Child2Property
–Panel2
to hold the controls, registered asUIPropertyMetadata
SplitterDistanceProperty
– to set the distance for the splitter, double valueOrientationProperty
– to set the orientation for the splitter, enumeration value.
Here are the code snippets:
namespace SplitContainer
{
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:SplitContainer"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:SplitContainer;assembly=SplitContainer"
///
/// You will also need to add a project reference from the project
/// where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:CustomControl1/>
///
/// </summary>
public enum Orientation
{
Vertical = 0,
Horizontal
};
public class SplitContainer : Control
{
public static readonly DependencyProperty Child1Property;
/// <summary>Identifies the <see cref="Child2"/> dependency property.</summary>
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));
//It also set the default value to 100
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); }
}
/// <summary>Gets or sets the right or bottom child of the
/// <see cref="SplitContainer"/>, depending
/// on <see cref="Orientation"/>. This is a dependency property.</summary>
/// <value>If <see cref="Orientation"/>
/// is <see cref="System.Windows.Controls.Orientation.Vertical"/>,
/// the bottom child of the <see cref="SplitContainer"/>.
/// If <see cref="Orientation"/> is
/// <see cref="System.Windows.Controls.Orientation.Horizontal"/>,
/// the right child of the <see cref="SplitContainer"/>.</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>
<!-- GridSplitter Brushes -->
<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>
<!-- Vertical GridSplitter -->
<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="ResizeBehavior" Value="PreviousAndNext"/>
<Setter Property="ResizeDirection" Value="Columns"/>-->
<Setter Property="Height" Value="Auto"/>
<Setter Property="Width" Value="5"/>
<Setter Property="Background" Value="{DynamicResource GridSplitterVertBackgroundBrush}"/>
</Style>
<!-- Horizontal GridSplitter -->
<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="ResizeBehavior" Value="PreviousAndNext"/>
<Setter Property="ResizeDirection" Value="Rows"/>-->
<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>
<!--Define border-->
<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"/>
<!--Define the Child1 and Child2-->
<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" />
<!--Define Spliter-->
<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.