Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Simple but Useful WPF Container

0.00/5 (No votes)
2 Dec 2011 1  
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 SplitContainers 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:

  • Child1PropertyPanel1 to hold the controls, registered as UIPropertyMetadata
  • Child2PropertyPanel2 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 
{ 
/// <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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here