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;

// BeginExample: TVCustomDraw
// Event: ComCtrls.TCustomTreeView.OnCustomDraw
// Property: ComCtrls.TCustomTreeView.Canvas
// Property: Graphics.TPicture.Bitmap
// Method: Graphics.TCanvas.StretchDraw

// BeginCode
{
The following example is taken from the custom draw demo. It
shows how the OnCustomDraw event handler draws the
background for the tree view before the items and lines are
drawn.
}
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 <> nil then
    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;
  end;
  DefaultDraw := FDefaultDraw;
  // Setting DefaultDraw to false here prevents all calls to
  // OnCustomDrawItem.
end;
// EndCode
// EndExample: TVCustomDraw

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;

// BeginExample: TVCustomDrawItem
// Event: ComCtrls.TCustomTreeView.OnCustomDrawItem
// Method: ComCtrls.TTreeNode.DisplayRect
// Property: ComCtrls.TTreeNode.Level
// Property: ComCtrls.TCustomTreeView.Indent

// BeginCode
{
The following example shows how the OnCustomDrawItem event 
handler draws items and lines of the tree view after the 
OnCustomDraw event handler has filled in the background.
} 
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 := False; // 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); // See the CustomDraw demo

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

      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;
// EndCode
// EndExample: TVCustomDrawItem

// BeginExample: TVGetImageIndex
// Event: ComCtrls.TCustomTreeView.OnGetImageIndex
// Event: ComCtrls.TCustomTreeView.OnGetSelectedIndex

// BeginCode
{
The following example shows how the OnGetImageIndex event 
handler can be used to indicate a different state for the
node.
} 
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;
// EndCode
// EndExample: TVGetImageIndex

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.
 
