unit CustomDrawTreeView;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, ExtDlgs, StdCtrls, ExtCtrls, ColorGrd, ImgList, Menus;

type
  TCustomDrawForm = class(TForm)
    TV: TTreeView;
    ImageList: TImageList;
    MainMenu1: TMainMenu;
    Drawing1: TMenuItem;
    Font1: TMenuItem;
    Background1: TMenuItem;
    Color1: TMenuItem;
    Bitmap1: TMenuItem;
    DefaultDrawing1: TMenuItem;
    OnCustomDraw1: TMenuItem;
    OnCustomDrawItem1: TMenuItem;
    BrushStyle1: TMenuItem;
    Solid1: TMenuItem;
    Clear1: TMenuItem;
    Horizontal1: TMenuItem;
    Vertical1: TMenuItem;
    FDiagonal1: TMenuItem;
    BDiagonal1: TMenuItem;
    Cross1: TMenuItem;
    DiagCross1: TMenuItem;
    File1: TMenuItem;
    Exit1: TMenuItem;
    N2: TMenuItem;
    TVFontDialog: TFontDialog;
    Tile1: TMenuItem;
    Stretch1: TMenuItem;
    None1: TMenuItem;
    Selection1: TMenuItem;
    SelectedFontDialog: TFontDialog;
    BkgColorDialog: TColorDialog;
    SelBkgColorDialog: TColorDialog;
    SelectionBackground1: TMenuItem;
    ButtonColor1: TMenuItem;
    ButtonSize1: TMenuItem;
    ButtonColorDialog: TColorDialog;
    Image1: TImage;
    TreeView1: TMenuItem;
    Color2: TMenuItem;
    TVColorDialog: TColorDialog;
    CustomDraw1: TMenuItem;
    Font2: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure TVCustomDraw(Sender: TCustomTreeView; const ARect: TRect;
      var DefaultDraw: Boolean);
    procedure TVCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode;
      State: TCustomDrawState; var DefaultDraw: Boolean);
    procedure TVGetImageIndex(Sender: TObject; Node: TTreeNode);
    procedure TVGetSelectedIndex(Sender: TObject; Node: TTreeNode);
    procedure Exit1Click(Sender: TObject);
    procedure Selection1Click(Sender: TObject);
    procedure Color1Click(Sender: TObject);
    procedure SelectionBackground1Click(Sender: TObject);
    procedure Solid1Click(Sender: TObject);
    procedure None1Click(Sender: TObject);
    procedure OnCustomDraw1Click(Sender: TObject);
    procedure OnCustomDrawItem1Click(Sender: TObject);
    procedure TVExpanded(Sender: TObject; Node: TTreeNode);
    procedure ButtonColor1Click(Sender: TObject);
    procedure ButtonSize1Click(Sender: TObject);
    procedure Drawing1Click(Sender: TObject);
    procedure Color2Click(Sender: TObject);
    procedure CustomDraw1Click(Sender: TObject);
    procedure Font2Click(Sender: TObject);
  private
    FButtonSize: Integer;
    FDefaultDraw,
    FDefaultDrawItem: Boolean;
    FBackgroundColor: TColor;
    FBrushStyle: TBrushStyle;
    procedure DrawButton(ARect: TRect; Node: TTreeNode);
    procedure DrawImage(NodeRect: TRect; ImageIndex: Integer);
    procedure SetCustomDraw(Value: Boolean);
    { Private declarations }
  public
    { Public declarations }
  end;

var
  CustomDrawForm: TCustomDrawForm;

implementation

{$R *.DFM}

procedure TCustomDrawForm.FormCreate(Sender: TObject);
begin
  FBackgroundColor := clWindow;
  FDefaultDraw := True;
  FDefaultDrawItem := True;
  FBrushStyle := bsSolid;
  FButtonSize := 5;
  BkgColorDialog.Color := clWindow;
  SelBkgColorDialog.Color := clHighlight;
  TVFontDialog.Font.Assign(TV.Font);
  SelectedFontDialog.Font.Assign(TV.Font);
  SelectedFontDialog.Font.Color := clHighlightText;
  SelBkgColorDialog.Color := clHighlight;
  TVColorDialog.Color := TV.Color;
end;

procedure TCustomDrawForm.TVCustomDraw(Sender: TCustomTreeView; const ARect: TRect;
  var DefaultDraw: Boolean);
begin
//This event should be used to draw any background colors or images.
//ARect represents the entire client area of the TreeView.
//Use the TreeView's canvas to do the drawing.
//Note that drawing a background bitmap is not really supported by CustomDraw,
//so scrolling can get messy. Best to subclass the TreeView and handle scrolling
//messages.
  with TV.Canvas do
  begin
    if None1.Checked then //no picture
    begin
      Brush.Color := BkgColorDialog.Color;
      Brush.Style := FBrushStyle;
      FillRect(ARect);
    end else
      if Tile1.Checked then //tile bitmap
      begin
        Brush.Bitmap := Image1.Picture.Bitmap;
          FillRect(ARect);
        end else //Stretch across the canvas.
            StretchDraw(ARect, Image1.Picture.Bitmap);
  end;
  DefaultDraw := FDefaultDraw;
  //setting DefaultDraw to false here prevents all calls to OnCustomDrawItem.
end;

procedure TCustomDrawForm.DrawButton(ARect: TRect; Node: TTreeNode);
var
  cx, cy: Integer;
begin
  cx := ARect.Left + TV.Indent div 2;
  cy := ARect.Top + (ARect.Bottom - ARect.Top) div 2;
  with TV.Canvas do
  begin
    Pen.Color := ButtonColorDialog.Color;
    //draw horizontal line.
    if Node.HasChildren then
    begin
      PenPos := Point(cx+FButtonSize, cy);
      LineTo(ARect.Left + TV.Indent + FButtonSize, cy);
    end else
    begin
      PenPos := Point(cx, cy);
      LineTo(ARect.Left + TV.Indent + FButtonSize, cy);
    end;

    //draw half vertical line, top portion.
    PenPos := Point(cx, cy);
    LineTo(cx, ARect.Top-1);

    if ((Node.GetNextVisible <> nil) and (Node.GetNextVisible.Level = Node.Level))
    or (Node.GetNextSibling <> nil) then
    //draw bottom portion of half vertical line.
    begin
      PenPos := Point(cx, cy);
      LineTo(cx, ARect.Bottom+1);
    end;  

    if Node.HasChildren then
    begin
      //Let's try a circular button instead
      Ellipse(cx-FButtonSize, cy-FButtonSize, cx+FButtonSize, cy+FButtonSize);

      //draw the horizontal indicator.
      PenPos := Point(cx-FButtonSize+2, cy);
      LineTo(cx+FButtonSize-2, cy);
      //draw the vertical indicator if the node is collapsed
      if not Node.Expanded then
      begin
        PenPos := Point(cx, cy-FButtonSize+2);
        LineTo(cx, cy+FButtonSize-2);
      end;
    end;
        //now connect vertical lines of higher level nodes.
    Node := Node.Parent;
    while Node <> nil do
    begin
      cx := cx - TV.Indent;
      if Node.GetNextSibling <> nil then
      begin
        PenPos := Point(cx, ARect.Top);
        LineTo(cx, ARect.Bottom);
      end;
      Node := Node.Parent;
    end;
  end;
end;

procedure TCustomDrawForm.DrawImage(NodeRect: TRect; ImageIndex: Integer);
var
  cy: Integer;
begin
  cy := NodeRect.Top + (NodeRect.Bottom - NodeRect.Top) div 2;
  //center image in NodeRect.
  ImageList.Draw(TV.Canvas, NodeRect.Left, cy - TV.Images.Height div 2,
                 ImageIndex, True);
end;

procedure TCustomDrawForm.TVCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode;
  State: TCustomDrawState; var DefaultDraw: Boolean);
var
  NodeRect: TRect;
begin
  with TV.Canvas do
  begin
    //If DefaultDraw it is true, any of the node's font properties can be
    //changed. Note also that when DefaultDraw = True, Windows draws the
    //buttons and ignores our font background colors, using instead the
    //TreeView's Color property.
    if cdsSelected in State then
    begin
      Font.Assign(SelectedFontDialog.Font);
      Brush.Color := SelBkgColorDialog.Color;
    end;

    DefaultDraw := FDefaultDrawItem;
    //DefaultDraw = False means you have to handle all the item drawing yourself,
    //including the buttons, lines, images, and text.
    if not DefaultDraw then
    begin
      //draw the selection rect.
      if cdsSelected in State then
      begin
        NodeRect := Node.DisplayRect(True);
        FillRect(NodeRect);
      end;
      NodeRect := Node.DisplayRect(False);

      if None1.Checked then
      //no bitmap, so paint in the background color.
      begin
        Brush.Color := BkgColorDialog.Color;
        Brush.Style := FBrushStyle;
        FillRect(NodeRect)
      end
      else
        //don't paint over the background bitmap.
        Brush.Style := bsClear;

      NodeRect.Left := NodeRect.Left + (Node.Level * TV.Indent);
      //NodeRect.Left now represents the left-most portion of the expand button
      DrawButton(NodeRect, Node);

      NodeRect.Left := NodeRect.Left + TV.Indent + FButtonSize;
      //NodeRect.Left is now the leftmost portion of the image.
      DrawImage(NodeRect, Node.ImageIndex);

      NodeRect.Left := NodeRect.Left + ImageList.Width;
      //Now we are finally in a position to draw the text.

      TextOut(NodeRect.Left, NodeRect.Top, Node.Text);
    end;
  end;
end;

procedure TCustomDrawForm.TVGetImageIndex(Sender: TObject; Node: TTreeNode);
begin
  if Node.HasChildren then
    if Node.Expanded then
      Node.ImageIndex := 3
    else
      Node.ImageIndex := 0
  else
    Node.ImageIndex := 1;
end;

procedure TCustomDrawForm.TVGetSelectedIndex(Sender: TObject; Node: TTreeNode);
begin
  Node.SelectedIndex := Node.ImageIndex;
end;

procedure TCustomDrawForm.Exit1Click(Sender: TObject);
begin
  Close;
end;

procedure TCustomDrawForm.Selection1Click(Sender: TObject);
begin
  if SelectedFontDialog.Execute then
    TV.Repaint;
end;

procedure TCustomDrawForm.Color1Click(Sender: TObject);
begin
  if BkgColorDialog.Execute then
    TV.Repaint;
end;

procedure TCustomDrawForm.SelectionBackground1Click(Sender: TObject);
begin
  if SelBkgColorDialog.Execute then
    TV.Repaint;
end;

procedure TCustomDrawForm.Solid1Click(Sender: TObject);
begin
  with Sender as TMenuItem do
  begin
    FBrushStyle := TBrushStyle(Tag);
    Checked := True;
  end;
  TV.Repaint;
end;

procedure TCustomDrawForm.None1Click(Sender: TObject);
begin
  (Sender as TMenuItem).Checked := True;
  TV.Repaint;
end;

procedure TCustomDrawForm.OnCustomDraw1Click(Sender: TObject);
begin
  FDefaultDraw := not FDefaultDraw;
  OnCustomDraw1.Checked := FDefaultDraw;
  TV.Repaint;
end;

procedure TCustomDrawForm.OnCustomDrawItem1Click(Sender: TObject);
begin
  FDefaultDrawItem := not FDefaultDrawItem;
  OnCustomDrawItem1.Checked := FDefaultDrawItem;
  TV.Repaint;
end;

procedure TCustomDrawForm.TVExpanded(Sender: TObject; Node: TTreeNode);
begin
  TV.Repaint;
end;

procedure TCustomDrawForm.ButtonColor1Click(Sender: TObject);
begin
  if ButtonColorDialog.Execute then TV.Repaint;
end;

procedure TCustomDrawForm.ButtonSize1Click(Sender: TObject);
var
  S: string;
begin
  S := IntToStr(FButtonSize);
  if InputQuery('Change button size', 'Enter new size', S) then
    FButtonSize := StrToInt(S);
  TV.Repaint;  
end;

procedure TCustomDrawForm.Drawing1Click(Sender: TObject);
begin
  ButtonColor1.Enabled := not OnCustomDrawItem1.Checked;
  ButtonSize1.Enabled := ButtonColor1.Enabled;
end;

procedure TCustomDrawForm.Color2Click(Sender: TObject);
begin
  if TVColorDialog.Execute then
  begin
    TV.Color := TVColorDialog.Color;
    TV.Repaint;
  end;
end;

procedure TCustomDrawForm.SetCustomDraw(Value: Boolean);
begin
  if not Value then
  begin
    TV.OnCustomDraw := nil;
    TV.OnCustomDrawItem := nil;
  end else
  begin
    TV.OnCustomDraw := Self.TVCustomDraw;
    TV.OnCustomDrawItem := Self.TVCustomDrawItem;
  end;
  Drawing1.Enabled := Value;
  TV.Repaint;
end;

procedure TCustomDrawForm.CustomDraw1Click(Sender: TObject);
begin
  CustomDraw1.Checked := not CustomDraw1.Checked;
  SetCustomDraw(CustomDraw1.Checked);
end;

procedure TCustomDrawForm.Font2Click(Sender: TObject);
begin
  if TVFontDialog.Execute then
    TV.Font.Assign(TVFontDialog.Font);
end;

end.
 