Easy extensibility with poly list controls

Bookmarks: 

Sunday, June 20, 2010

The TMS Advanced Poly List controls were designed from the ground up with extensibility in mind. Both at design time and at run time, custom item class instances can be added to the list. The developers guide explains how this can be done with a simple sample showing how to create an item class that shows a triangle. While this shows the techniques needed, displaying a triangle is of limited usefulness. For an application, we needed an item with a progress bar. We could reuse the smooth controls progress bar drawing with the added bonus that it looks nicer than an average progress bar. All in all, in about 30 minutes, a new item class that shows a progress bar is ready to be used.

You can download the sample with the full source code of the new progress bar item class here so you can easily add this progress bar in any of the TMS Poly List controls you're using.
Now, for those interested to learn how this was created, here is a short description:

1) Create class descending from TCustomItem
TCustomItem is defined in the unit GDIPCustomItem. Create a new unit with a new class descending from TCustomItem
type
  TProgressItem = class(TCustomItem)
   end;
2) Override & Implement some basic virtual methods of TCustomItem
function CreateNewItem(AOwner: TComponent): TCustomItem;
This function creates a new instance of the item
function GetClassType: TComponentClass;
This function returns the type of the class
function CustomClassName: String;
This function returns a friendly class description

3) Add properties relevant for the progress bar
This consists of the progress bar minimum, maximum and position properties. A property to control whether the value is displayed and the format to use for displaying the value and most importantly, the Appearance property that holds all color / gradient settings for the progressbar:
type
  TProgressItem = class(TCustomItem)
  published
    property Appearance: TGDIPProgress;
    property Min: integer;
    property Max: integer;
    property Margin: integer;
    property Position: integer;
    property ShowValue: boolean;
    property ValueFormat: string;
  end;
Note that the property setters call the method Changed. This is sufficient to have the item immediately updated in the list to reflect the changes of the properties.

4) Implement the custom drawing
The custom drawing is implemented by overriding the virtual method DrawInRect()
The DrawInRect method parameters return the graphics device context, the default item appearance and the rectangle of the item. The default item appearance holds settings such as normal state background fill, selected state background fill, hovered state background fill. In the case of this TProgressItem, the TMS smooth controls TGDIPProgress class is used. This class has a method that draws the progress bar on the graphics device context.

5) Register the class
In the Register procedure, the function RegisterPolyItem(TProgressItem) is called and when installed via a package in the IDE, this makes the progressbar available in the designer.

Using the new TProgressItem class
Using the new class is equally simple. The code snippet here shows how a new instance of the TProgressItem is inserted in the poly list control and its properties being initialized:
var
  pi: TProgressItem;
begin
  pi := TProgressItem(AdvVerticalPolyList2.InsertItem(1, TProgressItem));
  pi.Min := 0;
  pi.Max := 100;
  pi.Position := 75;
  pi.ValueFormat := 'Download progress: %d%%';
  pi.Margin := 4;
end;


The full source code of the TProgressItem class is here:
unit GDIPProgressItem;

interface

uses
  Classes, GDIPCustomItem, AdvGDIP, GDIPFill;

type
  TProgressItem = class(TCustomItem)
  private
    FAppearance: TGDIPProgress;
    FMax: integer;
    FMin: integer;
    FPosition: integer;
    FValueFormat: string;
    FShowValue: boolean;
    FMargin: integer;
    procedure SetAppearance(const Value: TGDIPProgress);
    procedure SetMax(const Value: integer);
    procedure SetMin(const Value: integer);
    procedure SetPosition(const Value: integer);
    procedure SetShowValue(const Value: boolean);
    procedure SetValueFormat(const Value: string);
    procedure SetMargin(const Value: integer);
  protected
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    function CreateNewItem(AOwner: TComponent): TCustomItem; override;
    function GetClassType: TComponentClass; override;
    class function CustomClassName: String; override;
    procedure DrawInRect(g: TGPGraphics; ItemAppearance: TItemAppearance; R: TGPRectF); override;
  published
    property Appearance: TGDIPProgress read FAppearance write SetAppearance;
    property Min: integer read FMin write SetMin default 0;
    property Max: integer read FMax write SetMax default 100;
    property Margin: integer read FMargin write SetMargin default 2;
    property Position: integer read FPosition write SetPosition default 0;
    property ShowValue: boolean read FShowValue write SetShowValue default true;
    property ValueFormat: string read FValueFormat write SetValueFormat;
  end;


procedure Register;

implementation

uses
  SysUtils, Graphics, Windows;

{ TProgressItem }

procedure TProgressItem.Assign(Source: TPersistent);
begin
  inherited;
  FAppearance.Assign((Source as TProgressItem).Appearance);
end;

constructor TProgressItem.Create(AOwner: TComponent);
begin
  inherited;

  FShowValue := true;
  FValueFormat := '%d%%';
  FMargin := 2;
  Height := 40;

  FAppearance := TGDIPProgress.Create;

  with FAppearance do
  begin
    BackGroundFill.Color := $00FFD2AF;
    BackGroundFill.ColorTo := $00FFD2AF;
    BackGroundFill.BorderColor := $00C0C0C0;

    ProgressFill.Color := $00EBFDFF;
    ProgressFill.ColorTo := $00ABEBFF;
    ProgressFill.ColorMirror := $0069D6FF;
    ProgressFill.ColorMirrorTo := $0096E4FF;
    ProgressFill.BorderColor := clSilver;

    ProgressFill.GradientMirrorType := gtVertical;
  end;
end;

function TProgressItem.CreateNewItem(AOwner: TComponent): TCustomItem;
begin
  Result := TProgressItem.Create(AOwner);
end;

class function TProgressItem.CustomClassName: String;
begin
  Result := 'Progressbar item';
end;

destructor TProgressItem.Destroy;
begin
  FAppearance.Free;
  inherited;
end;

procedure TProgressItem.DrawInRect(g: TGPGraphics;
  ItemAppearance: TItemAppearance; R: TGPRectF);
var
  f: TGDIPFill;
  dr,tr: TRect;
  valstr: string;
  h,tw,th: integer;
begin
  if Visible then
  begin
    DoItemStartDraw(Self, g, Self, r);
    DoInternalItemStartDraw(Self, g, Self, r);

    f := GetFill(ItemAppearance);

    if Assigned(f) then
      f.Fill(g, r);

    if ItemAppearance.Focus and (State = isSelected) then
      DrawFocus(g, r, ItemAppearance);

    dr := Rect(Trunc(r.X), Trunc(r.Y), Trunc(R.X + R.Width), Trunc(R.Y + R.Height));
    InflateRect(dr, -Margin, - Margin);
    h := dr.Bottom - dr.Top;

    if ShowValue then
      h := h div 2;

    tr := Rect(dr.Left, dr.Top, dr.Right, dr.Top + h);

    // draw progressbar
    FAppearance.Draw(g, tr ,FMin,FMax,FPosition);

    if ShowValue then
    begin
      tr := Rect(dr.Left, dr.Top + h, dr.Right, dr.Top + 2*h);
      valstr := Format(FValueFormat, [FPosition]);
      // measure text value size
      th := g.DrawText(valstr, length(valstr), tr, GetFont(ItemAppearance), DT_CALCRECT);
      tw := tr.Right - tr.Left;
      // center text
      tr := Rect(dr.Left + (dr.Right - dr.Left - tw) div 2, dr.Top + (dr.Bottom - dr.Top - th + h) div 2, dr.Right, dr.Bottom);
      // draw text
      g.DrawText(valstr, length(valstr), tr, GetFont(ItemAppearance), 0);
    end;

    DoItemEndDraw(Self, g, Self, r);
    DoInternalItemEndDraw(Self, g, Self, r);
  end;
end;

function TProgressItem.GetClassType: TComponentClass;
begin
  Result := TProgressItem;
end;

procedure TProgressItem.SetAppearance(const Value: TGDIPProgress);
begin
  FAppearance.Assign(Value);
end;


procedure TProgressItem.SetMargin(const Value: integer);
begin
  if (FMargin <> Value) then
  begin
    FMargin := Value;
    Changed;
  end;
end;

procedure TProgressItem.SetMax(const Value: integer);
begin
  if (FMax <> Value) then
  begin
    FMax := Value;
    Changed;
  end;
end;

procedure TProgressItem.SetMin(const Value: integer);
begin
  if (FMin <> Value) then
  begin
    FMin := Value;
    Changed;
  end;
end;

procedure TProgressItem.SetPosition(const Value: integer);
begin
  if (FPosition <> Value) then
  begin
    FPosition := Value;
    Changed;
  end;
end;

procedure TProgressItem.SetShowValue(const Value: boolean);
begin
  if FShowValue <> Value then
  begin
    FShowValue := Value;
    Changed;
  end;
end;

procedure TProgressItem.SetValueFormat(const Value: string);
begin
  if (FValueFormat <> Value) then
  begin
    FValueFormat := Value;
    Changed;
  end;
end;

procedure Register;
begin
  RegisterPolyItem(TProgressItem);
end;

end.


Bruno Fierens


Bookmarks: 

This blog post has not received any comments yet.



Add a new comment:
Author:
Email:
  You will receive a confirmation mail with a link to validate your comment, so please use a valid email address.
Comment:
 
Change Image
Fill in the characters from the image above:
 

All fields are required.
 




Previous  |  Next  |  Index