.Net DizzyCoding

How to add grab handle in Splitter of SplitContainer

Problem :

There used to be 3 dots in the splitter bar of a SplitContainer. Just like there are three lines in question details text box on StackOverflow that shows it can be grabbed. How can I do this with the splitter bar of a SplitContainer in .NET?

Solution :

Not that I have anything against Alex’s answer, but I thought I’d share this solution as it looks a bit nicer to me (on an XP machine anyway?).

private void SplitContainer_Paint(object sender, PaintEventArgs e)
{
    var control = sender as SplitContainer;
    //paint the three dots'
    Point[] points = new Point[3];
    var w = control.Width;
    var h = control.Height;
    var d = control.SplitterDistance;
    var sW = control.SplitterWidth;

    //calculate the position of the points'
    if (control.Orientation == Orientation.Horizontal)
    {
        points[0] = new Point((w / 2), d + (sW / 2));
        points[1] = new Point(points[0].X - 10, points[0].Y);
        points[2] = new Point(points[0].X + 10, points[0].Y);
    }
    else
    {
        points[0] = new Point(d + (sW / 2), (h / 2));
        points[1] = new Point(points[0].X, points[0].Y - 10);
        points[2] = new Point(points[0].X, points[0].Y + 10);
    }

    foreach (Point p in points)
    {
        p.Offset(-2, -2);
        e.Graphics.FillEllipse(SystemBrushes.ControlDark,
            new Rectangle(p, new Size(3, 3)));

        p.Offset(1, 1);
        e.Graphics.FillEllipse(SystemBrushes.ControlLight,
            new Rectangle(p, new Size(3, 3)));
    }
}

Hope this pleases someone? Haa!

That isn’t implemented. If you’d like that feature, it’s best you derive the SplitContainer and override the OnPaint method.


Update 1

Here’s some code to do what you requested. It is in VB.NET and the dot placement can do with some tweaking. Overall, the code works as expected.

Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Drawing

Public Class SplitContainerEx
    Inherits SplitContainer

    ''' <summary>Determines the thickness of the splitter.</summary>
    <DefaultValue(GetType(Integer), "5"), Description("Determines the thickness of the splitter.")> _
           Public Overridable Shadows Property SplitterWidth() As Integer
        Get
            Return MyBase.SplitterWidth
        End Get
        Set(ByVal value As Integer)
            If value < 5 Then value = 5

            MyBase.SplitterWidth = value
        End Set
    End Property

    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e)
        'paint the three dots
        Dim points(2) As Point
        Dim pointRect = Rectangle.Empty

        'calculate the position of the points
        If Orientation = Windows.Forms.Orientation.Horizontal Then
            points(0) = New Point((MyBase.Width  2), SplitterDistance + (SplitterWidth  2))
            points(1) = New Point(points(0).X - 10, points(0).Y)
            points(2) = New Point(points(2).X + 10, points(0).Y)
            pointRect = New Rectangle(points(1).X - 2, points(1).Y - 2, 25, 5)
        Else
            points(0) = New Point(SplitterDistance + (SplitterWidth  2), (MyBase.Height  2))
            points(1) = New Point(points(0).X, points(0).Y - 10)
            points(2) = New Point(points(0).X, points(0).Y + 10)
            pointRect = New Rectangle(points(1).X - 2, points(1).Y - 2, 5, 25)
        End If

        e.Graphics.FillRectangle(Brushes.Gray, pointRect)

        For Each p In points
            p.Offset(-1, -1)
            e.Graphics.FillEllipse(Brushes.Black, New Rectangle(p, New Size(3, 3)))
        Next
    End Sub
End Class

Update 2

I’m putting up the C# equivalent because you tagged your question so.
If vb makes you sick, learn to head over to Convert VB.NET to C# – Developer Fusion and do the VB to C# conversion.

using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;

public class SplitContainerEx : SplitContainer
{

    /// <summary>Determines the thickness of the splitter.</summary>
    [DefaultValue(typeof(int), "5"), Description("Determines the thickness of the splitter.")]
    public virtual new int SplitterWidth {
        get { return base.SplitterWidth; }
        set {
            if (value < 5)
                value = 5;

            base.SplitterWidth = value;
        }
    }

    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
        base.OnPaint(e);
        //paint the three dots
        Point[] points = new Point[3];
        Rectangle pointRect = Rectangle.Empty;

        //calculate the position of the points
        if (Orientation == System.Windows.Forms.Orientation.Horizontal) {
            points[0] = new Point((int)(base.Width / 2), SplitterDistance + (int)(SplitterWidth / 2));
            points[1] = new Point(points[0].X - 10, points[0].Y);
            points[2] = new Point(points[2].X + 10, points[0].Y);
            pointRect = new Rectangle(points[1].X - 2, points[1].Y - 2, 25, 5);
        } else {
            points[0] = new Point(SplitterDistance + (int)(SplitterWidth / 2), (int)(base.Height / 2));
            points[1] = new Point(points[0].X, points[0].Y - 10);
            points[2] = new Point(points[0].X, points[0].Y + 10);
            pointRect = new Rectangle(points[1].X - 2, points[1].Y - 2, 5, 25);
        }

        e.Graphics.FillRectangle(Brushes.Gray, pointRect);

        foreach (Point p in points) {
            p.Offset(-1, -1);
            e.Graphics.FillEllipse(Brushes.Black, new Rectangle(p, new Size(3, 3)));
        }
    }
}

The closest you can come without painting it yourself is to change the BorderStyle to Fixed3D. That will give you a sort of “bar” in between the two panels.

If you don’t like the sunken look on the two panels themselves, you can sort of “hide” the outer borders by putting your splitpanel inside of another panel and setting its Location to a negative value (e.g. -n,-n) and its size to its parent panel size + 2*n. Then I’d set the Anchor to Top | Left | Bottom | Right so that it stays that way as you resize the parent panel.

It’s kind of a kludge, but something I’ve certainly considered doing as I hate that there’s no indication of where the “grab point” is.

I liked shousper’s and Alex’s answers, but they seemed a little ‘complex’ for my taste; there seemed to be more code that I would have thought necessary.

Shaun’s answer also works (I also found that one in MSDN), but in the application I’m working on, the ‘grab handles’ became quite distracting, since they almost fill the splitter, and we have quite a few of them.

So I came up with this, which is in-between. No arrays, no new rectangles. Would take very little additional work for the ‘3D’ effect from the ‘accepted’ answer, but the 3 plain dots worked for me:

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
    base.OnPaint(e);

    Point centerPoint = new Point(SplitterRectangle.Left - 1 + SplitterRectangle.Width / 2, SplitterRectangle.Top - 1 + SplitterRectangle.Height / 2);

    e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X, centerPoint.Y, 3, 3);
    if (Orientation == System.Windows.Forms.Orientation.Horizontal) {
        e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X - 10, centerPoint.Y, 3, 3);
        e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X + 10, centerPoint.Y, 3, 3);
    } else {
        e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X, centerPoint.Y - 10, 3, 3);
        e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X, centerPoint.Y + 10, 3, 3);
    }
}

I didn’t really want to do all the grunt work of drawing a grab handle as I was writing an in-house admin tool and that was overkill. As per this article on MSDN you can sub-class the SplitContainer, override the OnPaint(PaintEventArgs) method and include the following line:

ControlPaint.DrawGrabHandle(e.Graphics, SplitterRectangle, true, Enabled);

This will draw a basic dividing line between the panes.

What I prefer is to just add a paint handler. This means that you don’t need to derive a new class and the static function is easily reusable if you put it into a file that can be shared by various projects – just remember to use “add as link” so that if you edit the file, it will be changed for all projects that include it. The main fault with this is it doesn’t automatically handle changing the color of the control.

...
mySplitContainer.Paint += CustomPaint.PaintSplitterWithHandle;
...

public static class CustomPaint {
  public static void PaintSplitterWithHandle(object sender, PaintEventArgs p) {
    SplitContainer splitter = sender as SplitContainer;
    if (splitter == null) return;
    if (splitter.Orientation == Orientation.Horizontal)
        p.Graphics.DrawLine(Pens.DarkGray, 
           0, splitter.SplitterDistance + (splitter.SplitterWidth / 2),
           splitter.Width, splitter.SplitterDistance + (splitter.SplitterWidth / 2));
    else
        p.Graphics.DrawLine(Pens.DarkGray, 
            splitter.SplitterDistance + (splitter.SplitterWidth / 2), 0,
            splitter.SplitterDistance + (splitter.SplitterWidth / 2), splitter.Height);
  }
}
Exit mobile version