Click here to Skip to main content
15,867,324 members
Articles / Desktop Programming / WPF

A Simple but Useful WPF Container

Rate me:
Please Sign up or sign in to vote.
4.67/5 (6 votes)
2 Dec 2011CPOL2 min read 37.6K   1.6K   12   5
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:

C#
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:

XML
<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:

XML
<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:

XML
<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:

XML
<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:

XML
<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionCan't do drag&drop for me Pin
jim191017-Nov-12 17:58
jim191017-Nov-12 17:58 
AnswerRe: Can't do drag&drop for me Pin
jim191018-Nov-12 13:56
jim191018-Nov-12 13:56 
QuestionUsing the SplitContainer in an Application - resize the two parts at runtime Pin
abrasat23-Feb-12 3:51
abrasat23-Feb-12 3:51 
Hi,
I tried to use the SplitContainer custom control into a WPF Application (VS 2010). The SplitContainer control is compiled into an own DLL, which is referenced by the WPF Application. I added the components, exactly as in your demo project, into the xaml file. I can compile and start the WPF application, but I cannot resize the 2 parts of the SplitContainer at runtime.
With your demo application this is possible, but you dont have the custom control into an own assembly.
What could be the problem when using a separate assembly for the SplitContainer ?
Thanks in advance.
Regards,
Abra
QuestionMy vote of 5 Pin
bscaer4-Jan-12 8:30
bscaer4-Jan-12 8:30 
GeneralMy vote of 5 Pin
Wooters2-Dec-11 6:08
Wooters2-Dec-11 6:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.