0% found this document useful (0 votes)
4 views23 pages

WPF Split Panel - CodeProject

The document discusses the creation of a custom split panel in WPF, detailing the motivations, requirements, and implementation challenges faced during development. It outlines various interface ideas and their pros and cons, ultimately leading to a refined design that allows for user-friendly child positioning and resizing. The final implementation derives from FrameworkElement rather than Panel, allowing for a more tailored control without the complications of ItemsControl handling.

Uploaded by

sermo.daniel
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views23 pages

WPF Split Panel - CodeProject

The document discusses the creation of a custom split panel in WPF, detailing the motivations, requirements, and implementation challenges faced during development. It outlines various interface ideas and their pros and cons, ultimately leading to a refined design that allows for user-friendly child positioning and resizing. The final implementation derives from FrameworkElement rather than Panel, allowing for a more tailored control without the complications of ItemsControl handling.

Uploaded by

sermo.daniel
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

CODEPROJECT Sign in

Fo r Tho s e Who Co de

Home Articles FAQ Community

WPF Split Panel


Aug 7, 2017 25 min read wo rds

C# .NET Dev WPF Advanced Show all

by LostTime76
27k Views 5/ 5
Contributor

Download Split_Panel.zip - 837.1 KB


Introduction
Today I will be explaining the concept behind and the implementation of a custom split panel
using WPF all the way from A to backwards R. The trek to build this thing has been a long
one; however, I am satisfied with the final implementation.

Motivation
My motivation for making the split panel was not to invent something that was not already
available, but really to get back into the swing of WPF development. I wanted to see if I could
develop a functional custom control that did the business. After that, I wanted to share my
experience in case the control could be of use to anybody and to provide a unique perspective
on the development. I used WPF way back in 2007- 08 and fell in love with it at first sight. I
then had a brief stint with it for several months at a job in 2011. After that, I hadn't touched it
since due to my job not being in PC UI development, but rather firmware development. Right
now, I have some down time at my job, so I wanted to get back into UI development.
Download Package
The zip file contains the WPF test program as well as my common library that I have been
building up for the past couple of years for various PC app needs. The relevent code for this
article is contained in the common library project in the WPF/Controls/SplitPanel folder. The
Themes folder contains the WPF styles for the WPF portion of the library.

Background
There are several split panel controls available that can be found from some brief reserarch,
but nothing had an interface that seemed quite right to me. Believe it or not, I spent about 3
weeks trying to implement the best interface for this control until I finally settled on the
current one. I must have rewritten the control about 10 times in the process. What I wanted
was a control that I could just add children to and splitters would automagically appear in the
middle of said children to allow resizing of them. That said, here are some of the interface
ideas I had:

1. Splitters and Children Collection


This interface allows the user to create sub containers by adding to the Splitters collection.

CODE

<SplitPanel>
<[Link]>
<Splitter Position="0.5"/>
<Splitter Position="0.4"/>
<Splitter Position="0.7"/>
<Splitter Position="0.2"/>
<Splitter Position="0.1"/>
</[Link]>

<Button Position="1"/>
<Button Position="2"/>
<Button Position="3"/>
<Button Position="0"/>
</SplitPanel>

Pros:

1. User can individually style each and every splitter.

2. Positioning each sub container by the user is easy because the user specifies the absolute
position of each splitter from 0 - 1.0 like a slider.
3. The user uses the position attached property on children to position children in sub
containers like a grid.

4. The user can have empty sub containers if they want by not specifying a child in a given sub
container via the position attached property.

Cons:

1. Synchronizing the absolute positions of splitters becomes a nightmare. Each splitter that is
added to the collection must have its position less than the position of the next splitter in
the collection. This hard requirement can seep all the way down to the measure and
arrange passes depending on how its implemented, making the code complicated.

2. A sorted insert into the splitters collection based on splitter position cannot be performed
because of how the WPF xaml loader works. The xaml loader adds splitters to the
Splitters collection before the Position DP of the splitter is set.

2. Size Definitions and Children Collection


Here we optionally replace the splitters collection with a size definitions collection that is like
the column definitions or row definitions of a grid. Overall we have up to 3 collections to
populate in order to perform splitting.

CODE

<SplitPanel>
<[Link]>
<SizeDefinition Size="Auto"/>
<SizeDefinition Size="2*"/>
</[Link]>

<[Link]>
<Splitter/>
<Splitter/>
</[Link]>

<Button Position="1"/>
<Button Position="2"/>
<Button Position="3"/>
<Button Position="0"/>
</SplitPanel>

Pros:
1. Positioning becomes relative based on the size definitions, therefore no hard requirements
inside each collection have to be made.
2. User can specify a grid length size: Auto, Pixel, or Star.

Cons:

1. Very hard to synchronize all the collections working together making the code complicated.

3. Recursive Split Panel


A well known implementation of a split panel is one that only allows 2 children inside the
panel and a splitter between them.

CODE

<SplitPanel Proportion="0.5">
<[Link]>
<Button/>
<[Link]>
<[Link]>
<Button/>
<[Link]>
</SplitPanel>

which would produce something like this:

So far so good. So let's try a slightly more complicated panel:


The xaml for this would look something like this:

CODE

<SplitPanel Proportion="0.3">
<[Link]>
<Button/>
<[Link]>

<[Link]>
<SplitPanel Proportion="0.5">
<[Link]>
<Button/>
<[Link]>

<[Link]>
<SplitPanel Proportion="0.5">
<[Link]>
<SplitPanel Proportion="0.6">
<[Link]>
<Button/>
<[Link]>

<[Link]>
<Button/>
<[Link]>
<SplitPanel/>
</[Link]>

<[Link]>
<Button/>
<[Link]>
</SplitPanel>
</[Link]>
</SplitPanel>
<[Link]>
</SplitPanel>

Pros:

1. Easy for the most basic use case of essentially a single panel.

Cons:
1. Complexity spirals out of control fast, I mean really fast, for even the simplest use cases
above a single panel. I need a degree in astrophysics just to decipher the layout of the
above code.
2. You need just about as many split panels as you do controls you want to place inside the
panels.

I went back to the drawing board to squeeze out my actual requirements for the panel and
came to some realizations.

Requirements
When I went back to the drawing board, I was basically pulling my hair out at this point,
because everything I had done up until this point ended in failure. I had a few attempts at an
implementation that threatened to work, but the code and interface smelled pretty bad.

After some deliberation, I cut the control down to the essentials. I still wanted people to be
able to style the splitters used inside the control, but maybe they don't have to style each and
every one. After all, a consistent UI should have the same style for controls of the same type
(such as splitters). So the user can style the splitters using a global style or providing a style
to the split panel using a DP when it internally adds splitters to the panel. Right now, my panel
does not offer a style DP for setting the style of splitters within the panel, because I don't
require this functionality, but it would be easy for somebody to add this DP to the panel to
extend it. Currently, I am just using a global style for all the splitters inside panels.

Once you get rid of the notion of allowing the user to style each and every splitter in the panel,
the splitters collection can be removed from user interaction and the panel can add splitters to
itself easily when children are added.

Nextly, how do we handle initial positions of the sub containers with children? Well, users are
used to star sizing in column or row definitions inside a grid, so let's go with that. The initial
position / sizing of a child inside the panel uses an attached dependency property called size
which implictly uses star sizing. I originally wanted to use the full grid length allowing users to
be able to specify auto, pixel, or star, but after trying this implementation, auto and pixel don't
really make sense and the code gets more complicated. Generally with the panel, I expect the
developer to specify initial positions for the children inside the panel and then let the user
resize using the splitters.. I mean that's the whole point of the panel in the first place. Using
star size has a side effect, which I don't really think matters much. The side effect is that when
the user resizes the control (which can be done by resizing the window), star sizing will take
effect and the children inside the panel will adjust their sizes in line with the star sizing. This
means its not possible to "fix" a child size during a panel resize operation. In the grand scheme,
I don't think this is an issue because the user can resize the window for example and then just
resize the children if they want to. The star sizing side effect also makes the code less
complicated because all of the other types of sizing do not have to be dealt with.
Now, let's discuss orientation. The panel has an orientation when it positions its children:
horizontal or vertical. Notice in my above discussions I am careful to say "size" and not "width"
or "height" of children. This is because "size" will apply to both orientations. For example, a size
of 1 (star) means the same thing for both horizontal or vertical orientations. Splitter controls
that are implictly added to the panel when children are added have an orientation DP that is
opposite of the parent panel's orientation. So if a panel has an orientation of horizontal, every
child splitter has an orientation of vertical. The orientation DP was added to the splitters so
that they can be styled based on orientation.

So after that lengthly discussion: here is the summary of requirements for the panel:

1. The panel has a children collection in which the user adds children and they get positioned
based on the panel orientation.
2. The panel has an orientation so it can position children horizontally or vertically. Any implict
splitters added to the panel have the opposite orientation from the parent panel for
consistency.
3. The panel uses star sizing for its children. The size of a child can be set through an attached
property: size.

4. The panel should expose a minimum child size DP that is used for resizing using splitters.
5. Whenever a child is added to the panel, an implicit splitter control used for resizing is
added to the panel as well. Splitter controls are only added to the panel if there are at
least 2 children in the panel.
6. The user of the panel has no access to the splitters inside the panel. The children collection
of the panel should be exposed as is; the panel should not be implictly adding controls to
the children collection that appear in the user's interface.

The xaml code interface then looks like this:

CODE

<SplitPanel Orientation="Horizontal" MinChildSize="50">


<Button/>
<Button/>
<Button [Link]="2"/>
</SplitPanel>

The above code would produce the following:


Implementation
The implementation of the panel is divided up into several items: the panel itself, the children
collection, and the splitter controls.

Split Panel
Derive from What?

The choice of what WPF element to derive from is an important one that will affect the
interface to the control. Most people religiously derive from Panel. Since the last revision of
this article, I did a little research and came upon this article:

[Link]
concept-in-WPF

This article explains what the features of panel are and also how the Children collection ties
into the panel class. Basically, deriving from the Panel class gives you a few benefits, which we
will discuss below.

ZIndex

Deriving from Panel gives you ZIndex handling if you decide to use the built in Children
collection (which most or all provided Panels in the framework do). The ZIndex attached
property can be set on a child and if the derived Panel supports it, such as a grid, the ZIndex
will have an effect on how children overlay each other.

For my custom split panel, ZIndex has no meaning. Therefore, if we lose the ability to set the
ZIndex on children, it won't be missed.

ItemsControl

This is quite arguably the biggest sticking point for a custom panel. The Panel class has built in
support for items controls, which means you can set a panel as the template of one. The WPF
system will automatically manage children added to the item control with the panel template.
For example, if a child is added or removed from the items control, the Panel class will handle
adding or removing the child to / from the panel as well.
I personally would have no issue with deriving from Panel if I could customize the Children
collection for the requirements in my requirements section. The biggest issue here though is
that the Panel's ItemsControl handling is tightly tied to internal code within the Children
UIElementCollection of the panel. As the article states, UIElementCollection gives off a fake
vibe that you can essentially override all of the collection behavior by allowing you to override
the generic collection operations: Add, Insert, Remove, Clear, etc.. However, there are internal
functions such as InternalAdd, InternalInsert, etc.. that do the heavy lifting for the collection
operations. These functions are marked internal and non virtual, so they cannot be overridden.
When your Panel is an ItemsHost as part of an ItemsControl, the Panel class uses these
internal functions instead of using the user overridable functions: Add, Insert, etc..

The above Panel / UIElementCollection behavior poses a big issue, because in order to
implement my SplitPanel, I need to run custom code every time children are added to the
collection. For example, I may need to add a splitter control to the panel when a child is added.
I think the WPF team made an oversight here, because the internal functions probably should
have been marked: protected internal virtual allowing users to override the behavior. Although
all seems bleak, there is one way that I found in the article to run custom code when
operations are performed on the collection using the internal functions.

The one way to run custom code lies in the Visual class. This class provides a callback called
OnVisualChildrenChanged. When an operation be it an InternalAdd, InternalRemove, Add,
whatever, is performed, the UIElementCollection ultimately will call AddVisualChild or
RemoveVisualChild. This triggers the OnVisualChildrenChangedCallback wherein now you can
run custom code such as what we need to do with the custom Split Panel.

So from the above discussion, there is in fact a way to derive the SplitPanel from Panel and
provide a custom collection through the Panel's CreateUIElementCollection callback and make
the interface work like I want; however, I still opted to derive from FrameworkElement. The
reason is because I did not build the SplitPanel to be part of an ItemsControl, and from a
logical standpoint, having a SplitPanel be an ItemsHost, just doesn't seem right. The children
of a SplitPanel can be resized using the splitters, and because of that the Panel inherits an
overall different class of functionality in my mind.

Children

The Panel class provides the Children and InternalChildren collections that work closely with
the ItemsControl handling. The Panel will handle properly hooking up each child to the parent
when the collection is modified.

In order to override this functionality and provide a custom collection, you can use the Panel's
CreateUIElementCollection callback when it goes and creates the Children collection for the
panel. The custom collection must derive from UIElementCollection. From what we have seen,
the UIElementCollection puts on a facade of being overridable / customizable, but really isnt,
because of the internal functions that are needed to fully customize it.
Derive from FrameworkElement

So in the end, the SplitPanel derives from FrameworkElement. The Panel class provides a
background DP that is manually rendered by overiding the OnRender function. The SplitPanel
does the same since it does not derive from Panel and needs to manually implement the
background DP.

In order to tell WPF that our panel will hold multiple children that will also be rendered, we
need to override a few FrameworkElement functions to make this happen. The functions are:
VisualChildrenCount, and GetVisualChild. The custom ElementCollection that is implemented
for the control holds all the Visuals that need to be rendered in the VisualTree of the control.
For the GetVisualChild override, we simply return the Visual at the given index, and for the
VisualChildrenCount we return the number of Visuals stored in the collection.

FrameworkElements that override the two above functions, seem to not be able to be styled
and as a result, anything that needs to be shown on the panel such as a Background must be
manually rendered in the OnRender function.

Layout

The meat of the code of the panel is contained in the measure and arrange overrides. The
overridden functions call either MeasureHorizontal, MeasureVertical, ArrangeHorizontal, and
ArrangeVertical depending on the orientation of the panel. The 2 pairs of functions do the
same things except the horizontal functions operate using width and the vertical functions
operate using height.

Measure

During the measure phase we calculate the star sizing for the children and also the size for
each of the splitters contained in the panel. The desired size for the splitters always take
precedence over measurement of the children. This means the splitters are measured first and
whatever size is left over goes to the children.

How is star sizing calculated for a child? Star sizing can be thought of as a proportion based
sizing mechanism. When you specify a star size, you are specifying a proportion of the available
size (width or height ) of the panel to be distributed to your child during layout. Let's take a
look at the picture:
The picture shows a panel with 3 children inside. The first child has a star size specified as 1,
the second specifies a star size of 1, and the third specifies a star size of 2. At the end of the
day, the actual widths of the children will be calculated as 25, 25, and 50. The calculation to
arrive at the actual widths is quite simple. Divide the available size by the addition of all the
star sizes of the children in the panel. In our case: 100 / (1 + 1 + 2) which yields 25. This
number in my implementation is called the "star unit size". Subsequently to calculate the actual
width of a child at this point, you take the star unit size and multiply by the star size of each
child. For the first two children, the calculation becomes: 1 * (25) which yields an actual width
of 25 for the children. For the last child, the calculation is: 2 * (25) which yields an actual width
of 50 for the child.

The same calculations are used for either horizontal or vertical orientations. The only difference
between the orientations is that you are either acting on width or height.

Keep in mind that the example is simplified. The example shows an available width to the
children of 100. The available width for the children is calculated by taking the width passed
into MeasureOverride and subtracting from it the addition of all of the desired sizes of each
splitter in the panel.

The code for the measure pass is shown below.

CS

private Size MeasureHorizontal(Size constraint)


{
// Save the panel size
Size panelSize = constraint;

// Measure the splitters


double width = HorizontalSplittersMeasure(constraint);

// Update the constraint


[Link] -= width;

// Measure the children


HorizontalChildrenMeasure(constraint);

// The panel occupies all of the available space


return panelSize;
}

private double HorizontalSplittersMeasure(Size constraint)


{
// Initialize variables
double width = 0;
// Iterate through all of the splitters
foreach (Splitter splitter in [Link])
{
// Measure the splitter
[Link](constraint);

// Get the width of the splitter


double splitterWidth = [Link];

// Update width based on the splitter desired size


width += splitterWidth;
[Link] -= splitterWidth;
}

// Return the measured width


return width;
}

private void HorizontalChildrenMeasure(Size constraint)


{
// Initialize variables
double width = [Link];

// Do we need to calculate the star size?


if ((width != 0) && ([Link] > 0))
{
// Calculate the star size
StarMeasure(width);
}

// Iterate through the children


foreach (UIElement child in Children)
{
// Set the constraint
[Link] = (GetSize(child) * starUnitSize);

// Measure the child


[Link](constraint);
}
}

private void StarMeasure(double constraint)


{
// Iterate through the children
foreach (UIElement child in Children)
{
// Update the star size
starUnitSize += GetSize(child);
}

// Is the star size valid?


if (starUnitSize == 0)
{
// Set the star size to the width of the constraint
starUnitSize = constraint;

// Set the size of the last child in the collection


SetSize([Link], starUnitSize);
}

// Calculate the star size


starUnitSize = (constraint / starUnitSize);
}

Arrange

The arrange pass is pretty simple. Previously in the measure pass, the star unit size was
calculated. The arrange code simply arranges child, splitter pairs until the last child in the panel
is reached. Then the last child is arranged. The reason it is done this way is because there is
one less splitter control in the panel compared to the number of children in the panel.

To arrange everything in the panel, we keep an offset variable. The children or splitters are
arranged left to right for a horizontal orientation or top to bottom for a vertical orientation. In
each case, the offset is updated after each item is arranged by the width or height of the item.

The width or height of a child can be calculated by taking the star unit size and multiplying by
the star size as explained in the measure section.

CS

private Size ArrangeHorizontal(Size size)


{
// Initialize variables
int numChildren = [Link];
double offset = 0;
double height = [Link];

// Iterate through all of the children but don't handle the last
one
for (int count = 0; count < (numChildren - 1); count++)
{
// Arrange the child
offset = HorizontalChildArrange(Children[count], offset,
height);

// Arrange the corresponding splitter


offset = HorizontalSplitterArrange([Link][count],
offset, height);
}

// If there is at least one child in the panel, then we need to


arrange the last child, because the the previous iteration arranges
// child / splitter pairs
if (numChildren > 0)
{
// Arrange the last child
HorizontalChildArrange([Link], offset, height);
}

// Return the size of the panel


return size;
}

private double HorizontalChildArrange(UIElement child, double offset,


double height)
{
// Do we have to arrange the child?
if (starUnitSize == 0)
{
// We do not have to arrange the child
return offset;
}

// Calculate the width of the child


double width = (GetSize(child) * starUnitSize);

// Arrange the child


[Link](new Rect(offset, 0, width, height));

// Return the new offset


return (offset + width);
}

private double HorizontalSplitterArrange(Splitter splitter, double


offset, double height)
{
// Get the width of the splitter
double width = [Link];

// Arrange the splitter


[Link](new Rect(offset, 0, width, height));

// Return the new offset


return (offset + width);
}

Orientation

The split panel implements an orientation property. Whenever a splitter is created through the
ElementCollection, the splitter binds to the SplitPanel's orientation property. The splitter then
uses the CoerceOrientation function to force the orientation of the splitter to be opposite to
that of the Panel.

ElementCollection
The element collection is what is directly accessible to the user to add children to the panel.
All operations in the collection are implemented in terms of Insert and RemoveAt because all
other collection operations can be implemented just by using these two functions.

The split panel creates an element collection inside its constructor. The panel exposes the
Children DP to remain consistent with other panels which internally accesses the custom
element collection.

Internally, the collection keeps another collection called Visuals. All splitters and child
elements are added to the visuals collection so that the SplitPanel can query them for the
VisualChildrenCount and GetVisualChild overrides.

Insert Operation

When a child is inserted into the collection, a new splitter control is implictly created if there is
already at least one element in the collection. The collection will then wire up the splitter and
child to the panel by calling AddLogicalChild and AddVisualChild on the elements. The
collection also adds the elements to the visuals collection.

Remove Operation

When a child is removed, a splitter is removed from the collection if there is at least one
element left in the collection. The collection will call RemoveLogicalChild and
RemoveVisualChild on the splitter and child elements to unhook them from the panel. The
collection will also remove the elements from the visuals collection.

Panel Invalidate

Whenever an operation on the collection is performed that modifes the collection, the panel's
InvalidateMeasure function must be called to tell the WPF layout system to execute a layout
pass on the panel.

Splitters
The splitter control is what is used to resize children inside the panel. The splitter overrides
the various mouse events to implement that behavior, but from a logical standpoint, how do
perform a resize using star sizing?

Star Sizing Resize

To figure out how resizing worked with star sizing, I actually had to perform some tests using
the grid splitter, because I didn't know how to do it off the top of my head. It was probably
something that could be figured out fairly easily given some thought, but I opted to take a
faster approach and implement a test program using a few children with grid splitters.

Let's look at a picture to see what grid splitter is doing to perform a resize on star children. In
the picture, we have a grid with 3 column definitions which have the width as follows: 1 star,
Auto, and 1 star. Then 2 child buttons and a grid splitter are put into the grid.

Before any resizing takes place on the left, we can see that the column definitions widths are
both 1 star and the actual widths of the 2 child buttons are 50.

When I first click on the grid splitter, it does something interesting. For each column definition
in the grid that has star sizing set for its width, the grid splitter transfers the actual width of
each corresponding child into the width of the column definition. Now notice the grid splitter
doesn't just set the column definition width to 50, but 50 star. This is important.

Now since the width of the column definition is set to an actual width that can be resized, the
grid splitter performs the resize operation between the children by just applying the mouse
delta to the width of the each column definition.

So overall, what the grid splitter is doing is converting each column definition that has a star
size to a "pixel" width which it can then use in resize operations. If it did not do this, it would
have to operate on the star sizes directly with more complicated math. The grid splitter will
perform this conversion each and every time you click on one so as to keep the size of the
definition in a fresh state for resizing.

Now remember when I said the grid splitter converts to actual width (star) and not just actual
width? Why is it doing this? If you run the math, converting to actual width (star) actually
keeps the proportions that were originally set by the user. Let's run the math for star sizing on
the panel before and after the conversion operation.

Before Resize

AvailableWidth = 100

Button1_Size = 1*

Button2_Size = 1*

StarUnitSize = (100 / (1 + 1)) = 50

Button1_Width = (StarUnitSize * Button1_Size) = (50 * 1) = 50

Button2_Width = (StarUnitSize * Button2_Size) = (50 * 1) = 50

After Conversion (Or Resize)

AvailableWidth = 100

Button1_Size = 50*

Button2_Size = 50*

StarUnitSize = (100 / (50 + 50)) = 1

Button1_Width = (StarUnitSize * Button1_Size) = (1 * 50) = 50

Button2_Width = (StarUnitSize * Button2_Size = (1 * 50) = 50

Application

So the conversion that the grid splitter is doing above is pretty neat because as we have
proven, changing the size of the column definitions keeps the original star sizing proportions.
So how do we apply this to the splitters in our panel? We do the exact same thing!

When the splitter control is about to perform a resize, it will cause a transfer of the current
actual size (width or height ) into each child's size attached property. It does this by calling
UpdateSize on the panel that owns the splitter. The panel iterates through all of its children
and does the transfer. Once the sizes of the children are updated, the splitter acquires the
needed information for the resize between two children and then executes the resize
operation using the various mouse events.

MinChildSize

The splitter control uses the MinChildSize DP of the panel to calculate the new size of the two
corresponding children it is resizing. The MinChildSize is not used by the panel to set a hard
limit on the minimum child size of a child something like MinWidth or MinHeight might do, but
just used by the splitter during resize.

The splitter allows a user to resize a control beyond its current size if the corresponding
control that is shrinking does not violate the MinChildSize DP.

Using the code


Using the control is fairly straightforward from xaml:

CS

<Window
xmlns="[Link]
xmlns:x="[Link]
Width="800" Height="800"
x:Class="[Link]"
Background="AliceBlue">

<SplitPanel Name="panel1" Margin="8" Background="AliceBlue"


Orientation="Vertical" MinChildSize="50">
<SplitPanel Name="panel2" MinChildSize="50">
<Button Name="button1" Content="Flip Orientation"/>
<Button Name="button2" Content="Add Child"/>
<Button Name="button3" Content="Remove Child"/>
</SplitPanel>
<SplitPanel Name="panel3" Background="AliceBlue"
Orientation="Vertical" MinChildSize="50">
<SplitPanel Name="panel4" Background="AliceBlue"
MinChildSize="50">
<Border BorderBrush="Black" BorderThickness="1"
Background="White"/>

<SplitPanel Name="panel5" Orientation="Vertical"


Background="AliceBlue" MinChildSize="50">
<Border [Link]="0.5" BorderBrush="Black"
BorderThickness="1" Background="White"/>
<Border BorderBrush="Black" BorderThickness="1"
Background="White"/>
<Border BorderBrush="Black" BorderThickness="1"
Background="White"/>
</SplitPanel>

<SplitPanel Name="panel6" Background="AliceBlue"


Orientation="Vertical" MinChildSize="50">
<Border BorderBrush="Black" BorderThickness="1"
Background="White"/>
<Border BorderBrush="Black" BorderThickness="1"
Background="White"/>
</SplitPanel>
</SplitPanel>

<SplitPanel Name="panel7" Background="AliceBlue"


Orientation="Horizontal" MinChildSize="50">
<Border BorderBrush="Black" BorderThickness="1"
Background="White"/>
<Border BorderBrush="Black" BorderThickness="1"
Background="White"/>
</SplitPanel>
</SplitPanel>
</SplitPanel>
</Window>

The demo program provided in the package, allows all major operations to be played with on a
split panel: insert / remove children, split child, combine children, and flip orientation. The demo
program code is fairly straightforward to understand, because it's basically all brute force. I
understand that the code is very sloppy, but I just needed something to test out split panels.

As it is not immediately apparent how the textboxes work in the demo program, I will briefly
explain here.

Set Panel

Set the panel index that is being operated on. Specify an index from 0 - NumPanels.

Set Index

Set the index into the Children collection of the split panel that is being operated on. For
example, an index of 1 would be Children[1].

Set Size
Set the star size of the child to be inserted into the panel.

Set Count

Set the number of children to combine with the child at the given index of the SplitPanel. A
value of 2 indicates combine the child at the given index with the next 2 children in the
collection. A value of -3 indicates combine the child at the given index with the previous 3
children in the collection. A value of 0 means don't combine any children.

Set Proportion

Sets the size proportion for a split operation. The valid range is -1 to 1. A value of -0.5 will
split the given child at the index exactly in two and place the new child before the existing
child. A value of 0.5 will split the given child at the index exactly in two and place the new child
after the existing child.

WPF Control Library


I am not a huge fan of specifying the xmlns:(Name Here) in my xaml, so I applied an xmlns
attribute in the assembly information to the default xaml
namespace: "[Link] This means that I
do not have to use the (Name Here):SplitPanel like syntax when I use the controls from xaml.
This suits my use case but may not suit yours, so you would end up having to remove that.

Final Notes
SplitChild

The split panel implements a SplitChild function that allows a child to be split in two based on
a proportion. The proportion can be specified from -1 to 1. A negative value will split the child
in two and add the new child before the existing child, and a positive value will split the child in
two and add the new child after the existing child. To split the child exactly in two, a value of
0.5 (-0.5) can be used.

CombineChildren

The split panel implements a CombineChildren function that can combine any number of
children inside the SplitPanel. The count parameter specifies the number of children to
combine. A negative value specifies that all the children before the starting index will be
combined, and a positive value specifies that all the children after the starting index will be
combined.

Difference between [Link] / SplitChild and [Link] /


CombineChildren
The difference between the pairs of functions is that the generic collection operations will
operate directly on the collection without taking the actual size of existing children into
account. For example, inserting a child into the Children of a split panel will cause a new layout
pass and you will see all of the children in the panel shuffle around to accomodate the new
size.

The SplitChild / CombineChildren functions take into account the current size of the existing
children in the panel. Therefore, when a split or combine is done, you will not see all of the
children that are not affected by the operation, shuffle around due to the new star sizes.

You can experiment with both types of operations and how they affect the layout using the
demo program.

History
8/7/17

1. Article Inception

8/8/17

1. Added pictures to the article


2. Rewrote various parts of the article

8/11/17

1. Updated SplitPanel control


2. Updated demo app
3. Rewrote various parts of the article in line with research and updated code

License
This article, along with any associated source code and files, is licensed under The Code
Project Open License (CPOL)

by LostTime76
27k Views 5/ 5
Contributor

Comments And Discussions (4) Sort: Newest

Share your thoughts

You need to be signed in to participate in the discussion. Sign in


dungxpt
D •••
20 no v 2018

Thanks for sharing! Just one issue that I hope you could help: the children lose binding
context so binding does not work. This happens only when I wrap them inside your
SplitPanel. Other containers like Grid, StackPanel work fine. Thanks!

0 0 Reply

Anonymous
A •••
15 o tt 2017

how do you modify width of space between panels

0 0 Reply

Hide reply

LostTime76 AUT HO R
L •••
20 o tt 2017

Not sure what you are asking; what do you mean by panels? Do you mean the
space between child elements inside a SplitPanel? There is a splitter control that
gets placed in between every two child elements of a SplitPanel. The splitter
control in the example is transparent and just allows you to to resize the child
elements inside the SplitPanel. To change the size of the splitter control, you
would modify the splitter control style. The height and width of the splitter control
is set based on the orientation of the control in the style.

0 0 Reply

webmaster442
W •••
7 ago 2017

A good picture says more than a thousand words. For this long article you should
conscider adding screenshots.

0 0 Reply

CODEPROJECT
Fo r Tho s e Who Co de

Advertise About Us Privacy Cookies Terms Of Service

Co pyright 1999-2025 © Co dePro ject. All Rights Res erved.

You might also like