A simple splitter with bumps

Recently someone asked me what splitter component I had used in my (very secret) resource editor. The splitter is a very simple descendant of the standard VCL TSplitter. The only difference from the standard one is that my custom splitter sports a grab bar, bumps, knobs or whatever they are called.

The source for the custom splitter is only about 40 lines of code so I thought I might as well just post it here and let everyone benefit from it. The source also demonstrates a very useful technique that I often use to customize the standard VCL components without the hassle of having to create design time packages.
It’s really very simple so without further ado here comes:

The source

unit amSplitter;
// -----------------------------------------------------------------------------
// TSplitter enhanced with grab bar
// The original author is Anders Melander, anders@melander.dk, http://melander.dk
// Copyright © 2008 Anders Melander
// -----------------------------------------------------------------------------
// License:
// Creative Commons Attribution-Share Alike 3.0 Unported
// http://creativecommons.org/licenses/by-sa/3.0/
// -----------------------------------------------------------------------------
 
interface
 
uses
  ExtCtrls;
 
//------------------------------------------------------------------------------
//
//      TSplitter enhanced with grab bar
//
//------------------------------------------------------------------------------
type
  TSplitter = class(ExtCtrls.TSplitter)
  protected
    procedure Paint; override;
  end;
 
implementation
 
uses
  Windows, Graphics, Controls, Classes;
 
//------------------------------------------------------------------------------
//
//      TSplitter enhanced with grab bar
//
//------------------------------------------------------------------------------
procedure TSplitter.Paint;
var
  R: TRect;
  X, Y: integer;
  DX, DY: integer;
  i: integer;
  Brush: TBitmap;
begin
  R := ClientRect;
  Canvas.Brush.Color := Color;
  Canvas.FillRect(ClientRect);
 
  X := (R.Left+R.Right) div 2;
  Y := (R.Top+R.Bottom) div 2;
  if (Align in [alLeft, alRight]) then
  begin
    DX := 0;
    DY := 3;
  end else
  begin
    DX := 3;
    DY := 0;
  end;
  dec(X, DX*2);
  dec(Y, DY*2);
 
  Brush := TBitmap.Create;
  try
    Brush.SetSize(2, 2);
    Brush.Canvas.Brush.Color := clBtnHighlight;
    Brush.Canvas.FillRect(Rect(0,0,1,1));
    Brush.Canvas.Pixels[0, 0] := clBtnShadow;
    for i := 0 to 4 do
    begin
      Canvas.Draw(X, Y, Brush);
      inc(X, DX);
      inc(Y, DY);
    end;
  finally
    Brush.Free;
  end;
 
end;
 
end.

 Usage

  1. Place a standard TSplitter on a form.
  2. Add amSplitter to the interface section uses clause.
    interface
     
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, ExtCtrls,
      amSplitter;
  3. Compile and Run .

Wait! What?

Notice that I do not register the custom TSplitter class as a component via the Register() design time function. Instead I rely on a handy trick that allows me to use the standard TSplitter at design-time, but use my custom TSplitter at run-time.

The trick

The first part of the trick is that the custom component class name must be the same as the component we want to replace:

type
  TSplitter = class(ExtCtrls.TSplitter)

The second part of the trick is to reference the unit where the custom splitter is declared after the unit where the standard splitter is declared. TSplitter is declared in the ExtCtrls unit and our replacement is declared in the amSplitter unit, so:

interface
 
uses
  ExtCtrls, amSplitter;

The net effect of this is that when the form that contains a TSplitter component is streamed in from the DFM resource, the streaming system will instantiate a copy of the custom TSplitter instead of the standard TSplitter.

I most often use the above technique when I have found a bug in the VCL. This enables me to fix the problem without patching the VCL source.

An even simpler example

For test purposes and for custom components that are only used on one form you don’t even need to put the code in a separate unit. Just declare the custom class in the same unit as the form, but before the form is declared.
For example try the following:

  1. Create a new form.
  2. Place a TPanel on the form.
  3. Modify the unit like this:
    interface
     
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, ExtCtrls;
     
    type
      // Declare the custom TPanel class *before* the form is declared
      TPanel = class(ExtCtrls.TPanel)
      public
        constructor Create(AOwner: TComponent); override;
      end;
     
      TForm1 = class(TForm)
        Panel1: TPanel;
      private
      protected
      public
      end;
     
    implementation
     
    {$R *.dfm}
     
    constructor TPanel.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner: TComponent);
      Color := clRed;
    end;
  4. Compile and run.

Easy, huh?