Nathan Dunlap

Nathan Dunlap

Creating resizable panels with splitter bars

Sample code is located here

Jonathan Russ, a developer on the Avalon Demo team, mentioned that somebody was asking how to do resizable frames on the newsgroups. I threw some simple XAML together that created the panels and defined the visuals of the splitter bar and handed it off to him, fully expecting to see some nasty code solution that left my mind numb trying to figure out how I could reuse it.

Boy was I happy when he got back to me with a really simple solution that I have found is really easy to reuse in a lot of scenarios. I love experiences that really show me that the designer/developer split is going to be really graceful. It's pretty cool when the design can start with me mocking up something and handing it off to the developer who can then build logic into it. It's even better when the developer logic isn't so intertwined with my styles and layout that I can hardly touch it without breaking something. This way I get to use my design skills at the prototype stages and early skeletal stages, and I also get to get my hands dirty in the real code at the fit and finish stages. All you designers who have spent hours refining your designs only to have your designs mangled when they get implemented in real code... your day is coming.

Basically the way I create a simple layout that can be resized is to fill a DockPanel with one element that has a defined width and another element that uses DockPanel.Dock="fill". Then when I update the width of the first element the second element always resizes automatically.

<DockPanel Width="100%" Height="100%">
    <Canvas ID="LeftPanel" Width="200px" Height="100%" DockPanel.Dock="left"></Canvas>
    <Canvas ID="MainPanel" DockPanel.Dock="Fill"></Canvas>
</DockPanel>

Then all I need to do is create an element for the splitter bar.

<DockPanel Width="100%" Height="100%">
    <Canvas ID="LeftPanel" Width="200px" Height="100%" DockPanel.Dock="left"></Canvas>
    <Canvas ID="VerticalSplitter" Width="10" Height="100%" DockPanel.Dock="left" />
    <Canvas ID="MainPanel" DockPanel.Dock="Fill"></Canvas>
</DockPanel>

This is where I used some Avalon shapes like ellipse and rectangle to make the splitter UI. Then I handed it off to Jonathan.

Basically what Jonathan did was to use a Thumb control inside of the element that we defined as the splitter. Then when the user clicks on the Thumb we get the HorizontalChange property from the DragDeltaEventArgs event. So when the splitter is dragged we measure the width of LeftPanel plus the value of change that has occurred during the drag. That value is what is set as the new value for LeftPanel.Width.

private void ThumbDragHorizontal(object sender, DragDeltaEventArgs e)
{
   double width = LeftPanel.Width.Value + e.HorizontalChange;
   LeftPanel.Width = new Length(width);
}

One problem that occurs is if you drag beyond the dimensions of the window some pretty crazy layout stuff happens. So Jonathan built in some constraints that sets the value of LeftPanel to the maximum width of the window when that happens. He needed to make sure he knew the width of the window whenever the window was resized so he hooked up a new LayoutManager.LayoutUpdated event on PageLoaded.

private double maxPanelWidth;

// This is called when the page is loaded to create a
// new event that fires whenever the window is resized.

private void PageLoaded(object sender, EventArgs args)
{
    LayoutManager lm = LayoutManager.GetLayoutManagerFor(sender as UIElement);
    lm.LayoutUpdated += new EventHandler(LayoutUpdated);
}

// Whenever the page is resized we set the value for maxPanelWidth
private void LayoutUpdated(object sender, EventArgs args)
{
    maxPanelWidth = (LeftPanel.Parent as FrameworkElement).Width.Value;
}


private void ThumbDragHorizontal(object sender, DragDeltaEventArgs e)
{
    double width = LeftPanel.Width.Value + e.HorizontalChange;
    if (width < 10)
        width = 10;
// when the new value for LeftPanelWidth is above the value of the
//  Window size we set the value to maxPanelWidth

    else if (width > maxPanelWidth)
        width = maxPanelWidth;
    LeftPanel.Width = new Length(width);
}

I took what Jonathan gave me and did the same thing but this time I used DockPanel.Dock="top" and the VerticalChange property to create a vertical splitter. Then I tried something a little different and I placed some visuals on my second panel with the DockPanel.Dock="fill" that represented the bottom panel. I hooked up a thumb element in this panel that monitored both the vertical and horizontal change and now I can resize both panels at the same time.

Here is what the final app looks like.

PostTypeIcon
20,611 Views