I always found WinForms SplitContainer very useful. This is because it is easy very intuitive to program and also very flexible. It is intuitive because it automatically arranges two panels along a direction (vertical/horizontal) and puts a splitter within them and with the right alignment. It is flexible too, because you can build endless layout complexity just nesting more SplitContainers.

Ok, now focus on WPF. How can we implement a SplitContainer in WPF? The answer is obvious: we must use Grid.

The xaml code for Grid simulating SplitContainer behaviour is straightforward:

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*"/>

<ColumnDefinition Width="Auto" MinWidth="4"/>

<ColumnDefinition Width="*"/>

</Grid.ColumnDefinitions>

<RichTextBox Grid.Column="0"/>

<GridSplitter Grid.Column="1" ResizeBehavior="PreviousAndNext" Width="4" VerticalAlignment="Stretch"/>

<RichTextBox Grid.Column="2"/>

</Grid>

Previous code builds a SplitContainer with horizontal orientation. Now immagine that you want to let your users change the orientation of the split at runtime. Of course there is a way to get the problem solved using Grid. You need to add at least 3 row definitions and handle correctly the switch between orientations. I guess that it will be even more complicated if you decide to split between more than two children or add them at runtime.

The problem here is that we are using the most flexible panel avilable in WPF to solve a problem that is apparently very easy.

I'm expecting to use code like following to intergrate a SplitContainer in my application:

 <Grid>

<yd:SplitContainer ResizeOrientation="Horizontal">

<RichTextBox></RichTextBox>

<RichTextBox></RichTextBox>

</yd:SplitContainer>

</Grid>

A SplitContaier must be smart enough to put a splitter between children with the right alignment. So if one want to add children at runtime simply add it to the Children collection of the container. Evenmore a single dependency property should define the arrangment direction.

I wrote a SplitContainer control that corrently I'm using to develop next version of AvalonDock and I hope that it will usefull to someone.

Here a short list of requirements of SplitContainer:

  1. It must contains only resizable elements, without imposing users to add splitters in correct positions. This means that splitters can't be present in the logical children collection of SplitContainer.
  2. Direction of arrangement must be governed by a single property (ResizeOrientation) of type Orientation.
  3. Of course SplitContaneir should be able to manage more than two child elements whit settable size.

The second and third requirements are implemented in the MeasureOverride/ArrangeOverride overloads. First method enumerate all the childrens giving them the available size according to their ContentSize dependency property value. In the second method the cycle is repeated this time to set their exact location and size. ContentSize is a dependency property defined by SplitContainer class and it is of type GridLength. I decided to use GridLength instead of a simple double because one can set proportional size between children with similar behaviour of Grid columns/rows.

I found first requirement not trivial. At the end of some tests I decided to use the excellent work by Dr.WPF. In fact SplitContainer derives from LogicalPanel which itself derives from ConceptualPanel. Last one is a panel which mantains a "disconnected" list of children. LogicalPanel is a ConcentualPanel that ensure its children to be at least logical children too.

I'll try to explain better the concept. In following figure I used another excellent tool that I guess every WPF developers use. MOLE by Josh Smith and Karl Shifflett is a VS visualizer that let developers explore logical and visual tree of a WPF application. Left side of the image show the logical tree of an application using SplitContainer, on the right side the visual tree of the same application is shown.

As you can see logical tree shows only two textboxes under the split container, instead of the visual tree that shows the splitter too. This means that if you enumerate the children of a SplitContainer using for an example LogicalTreeHelper.GetChildren() you'll get only the textboxes according to the first requirement.

Because every elements added to a LogicalPanel became automatically its logical child the only thing to do is to add it to the visual tree. On the other hand a splitter is automatically added by SplitContainer to the visual tree only.

Here the code which perfoms what said:

 protected override void OnLogicalChildrenChanged(UIElement childAdded, UIElement childRemoved)

{

if (childAdded != null)

{

 

if (VisualChildrenCount > 0)

{

Splitter split = new Splitter(this);

//Splitter is added on only in the visual tree

AddVisualChild(split);

}

//child is alreay present in the logical tree

AddVisualChild(childAdded);

}

 

if (childRemoved != null)

{

for (int i = 0; i < VisualChildrenCount - 1; i += 2)

{

if (childRemoved == GetVisualChild(i))

{

//remove the child from visual tree along with corresponding splitter

RemoveVisualChild(childRemoved);

RemoveVisualChild(GetVisualChild(i));

break;

}

}

}

base.OnLogicalChildrenChanged(childAdded, childRemoved);

}

Download SplitContainer source code and a sample project.

SplitContainer.rar (113.58 kb)

 

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
14 Comments