unit PngCheckListBox;

interface

uses
  Windows, Classes, CheckLst, pngimage, PngFunctions;

type
  TPngCheckListBox = class(TCheckListBox)
  private
    FPngUnchecked: TPngImage;
    FPngChecked: TPngImage;
    FPngOptions: TPngOptions;
    FPngGrayed: TPngImage;
    procedure SetPngChecked(const Value: TPngImage);
    procedure SetPngUnchecked(const Value: TPngImage);
    procedure SetPngOptions(const Value: TPngOptions);
    procedure SetPngGrayed(const Value: TPngImage);
  protected
    procedure DrawItem(Index: Integer; ARect: TRect; State: TOwnerDrawState); override;
    function GetCheckWidth: Integer; reintroduce;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property PngChecked: TPngImage read FPngChecked write SetPngChecked;
    property PngUnchecked: TPngImage read FPngUnchecked write SetPngUnchecked;
    property PngGrayed: TPngImage read FPngGrayed write SetPngGrayed;
    property PngOptions: TPngOptions read FPngOptions write SetPngOptions default [pngBlendOnDisabled];
  end;

implementation

uses
  Graphics, StdCtrls, Math;

{ TPngCheckListBox }

constructor TPngCheckListBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FPngChecked := TPngImage.Create;
  FPngUnchecked := TPngImage.Create;
  FPngGrayed := TPngImage.Create;
end;

destructor TPngCheckListBox.Destroy;
begin
  FPngChecked.Free;
  FPngUnchecked.Free;
  FPngGrayed.Free;
  inherited Destroy;
end;

procedure TPngCheckListBox.DrawItem(Index: Integer; ARect: TRect; State:
  TOwnerDrawState);

  procedure DrawCheck(R: TRect; AState: TCheckBoxState; AEnabled: Boolean);
  var
    Png: TPngImage;
    OldColor: TColor;
  begin
    //Draws the check image, if it's a PNG, otherwise the inherited would have
    //been called
    OldColor := Canvas.Brush.Color;
    Canvas.Brush.Color := Color;
    Canvas.FillRect(R);
    Canvas.Brush.Color := OldColor;
    case AState of
      cbUnchecked: Png := FPngUnchecked;
      cbChecked: Png := FPngChecked;
    else
      Png := FPngGrayed;
    end;
    DrawPNG(Png, Canvas, Rect(R.Left, R.Top, R.Left + Png.Width, R.Top +
      Png.Height), FPngOptions);
  end;

  procedure DrawText;
  var
    Flags: Integer;
    Data: string;
  begin
    //Draws the text for an item
    if Assigned(OnDrawItem) then
      OnDrawItem(Self, Index, ARect, State)
    else begin
      Canvas.FillRect(ARect);
      if Index < Items.Count then begin
        Flags := DrawTextBiDiModeFlags(DT_SINGLELINE or DT_VCENTER or
          DT_NOPREFIX);
        if not UseRightToLeftAlignment then
          Inc(ARect.Left, 2)
        else
          Dec(ARect.Right, 2);
        Data := '';
        if (Style in [lbVirtual, lbVirtualOwnerDraw]) then
          Data := DoGetData(Index)
        else
          Data := Items[Index];
        Windows.DrawText(Canvas.Handle, PChar(Data), Length(Data), ARect, Flags);
      end;
    end;
  end;

var
  R: TRect;
  SaveEvent: TDrawItemEvent;
  ACheckWidth: Integer;
  Enable: Boolean;
begin
  if FPngChecked.Empty and FPngUnchecked.Empty and FPngGrayed.Empty then
    inherited DrawItem(Index, ARect, State)
  else begin
    ACheckWidth := GetCheckWidth;
    if Index < Items.Count then begin
      R := ARect;
      Enable := Self.Enabled and ItemEnabled[Index];
      if not Header[Index] then begin
        if not UseRightToLeftAlignment then begin
          R.Right := ARect.Left;
          R.Left := R.Right - ACheckWidth;
        end
        else begin
          R.Left := ARect.Right;
          R.Right := R.Left + ACheckWidth;
        end;
        DrawCheck(R, Self.State[Index], Enable);
      end
      else begin
        Canvas.Font.Color := HeaderColor;
        Canvas.Brush.Color := HeaderBackgroundColor;
      end;
      if not Enable then
        Canvas.Font.Color := clGrayText;
    end;

    if (Style = lbStandard) and Assigned(OnDrawItem) then begin
      //Force lbStandard list to ignore OnDrawItem event.
      SaveEvent := OnDrawItem;
      OnDrawItem := nil;
      try
        DrawText;
      finally
        OnDrawItem := SaveEvent;
      end;
    end
    else
      DrawText;
  end;
end;

function TPngCheckListBox.GetCheckWidth: Integer;
begin
  //CheckWidth is equal to the widest PNG
  if not (FPngChecked.Empty and FPngUnchecked.Empty and FPngGrayed.Empty) then
    Result := Max(FPngChecked.Width, Max(FPngUnchecked.Width, FPngGrayed.Width))
  else
    Result := inherited GetCheckWidth;
end;

procedure TPngCheckListBox.SetPngChecked(const Value: TPngImage);
begin
  //This is all neccesary, because you can't assign a nil to a TPngImage
  if Value = nil then begin
    FPngChecked.Free;
    FPngChecked := TPngImage.Create;
  end
  else
    FPngChecked.Assign(Value);
  Repaint;
end;

procedure TPngCheckListBox.SetPngUnchecked(const Value: TPngImage);
begin
  //This is all neccesary, because you can't assign a nil to a TPngImage
  if Value = nil then begin
    FPngUnchecked.Free;
    FPngUnchecked := TPngImage.Create;
  end
  else
    FPngUnchecked.Assign(Value);
  Repaint;
end;

procedure TPngCheckListBox.SetPngGrayed(const Value: TPngImage);
begin
  //This is all neccesary, because you can't assign a nil to a TPngImage
  if Value = nil then begin
    FPngGrayed.Free;
    FPngGrayed := TPngImage.Create;
  end
  else
    FPngGrayed.Assign(Value);
  Repaint;
end;

procedure TPngCheckListBox.SetPngOptions(const Value: TPngOptions);
begin
  if FPngOptions <> Value then begin
    FPngOptions := Value;
    Repaint;
  end;
end;

end.
