Search code examples
delphitreeviewselectionownerdrawn

How to draw TTreeView's styled selection rectangle during AdvancedCustomDrawItem?


I'm doing custom TTreeView drawing from scratch using OnAdvancedCustomDrawItem event, and I wonder how to render these selection and hot rectangles correctly in the background of my owner-draw items? They are Vista/7 styled so I cannot simply fill the background in some solid color.

enter image description here

I tried to draw my items at cdPostPaint stage, but if I leave DefaultDraw := True atcdPrePaint stage to draw selection background, the complete default drawing occurs, including text of items.

procedure TForm1.TreeView1AdvancedCustomDrawItem(Sender: TCustomTreeView;
  Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage; var PaintImages,
  DefaultDraw: Boolean);
begin
  case Stage of
    cdPreErase:
    begin
      DefaultDraw := True;
    end;

    cdPostErase:
    begin
      DefaultDraw := True;
    end;

    cdPrePaint:
    begin
      // I thought this will paint only the selected/hot backgrounds, 
      // however this will paint whole item, including text.
      DefaultDraw := True;
    end;

    cdPostPaint:
    begin
      DefaultDraw := False;

      // painting my owner-draw text
      // .........
    end;
  end;

  PaintImages := False;
end;

Solution

  • Here is my solution (tested).

    Note the TreeView must have HotTrack := True to draw hot items normally.

    There also must be additional drawing when themes not enabled.

    uses
      UxTheme,
      Themes;
    
    const
      TreeExpanderSpacing = 6;
    
    procedure TForm1.DrawExpander(ACanvas: TCanvas; ATextRect: TRect; AExpanded: Boolean;
      AHot: Boolean);
    var
      ExpanderRect: TRect;
      Graphics: IGPGraphics;
      Points: array of TGPPoint;
      Brush: IGPBrush;
      Pen: IGPPen;
      ThemeData: HTHEME;
      ElementPart: Integer;
      ElementState: Integer;
      ExpanderSize: TSize;
      UnthemedColor: TColor;
    begin
      if ThemeServices.ThemesEnabled then
      begin
        if AHot then
          ElementPart := TVP_HOTGLYPH
        else
          ElementPart := TVP_GLYPH;
    
        if AExpanded then
          ElementState := GLPS_OPENED
        else
          ElementState := GLPS_CLOSED;
    
        ThemeData := OpenThemeData(TreeView1.Handle, VSCLASS_TREEVIEW);
        GetThemePartSize(ThemeData, ACanvas.Handle, ElementPart, ElementState, nil,
          TS_TRUE, ExpanderSize);
        ExpanderRect.Left := ATextRect.Left - TreeExpanderSpacing - ExpanderSize.cx;
        ExpanderRect.Right := ExpanderRect.Left + ExpanderSize.cx;
        ExpanderRect.Top := ATextRect.Top + (ATextRect.Bottom - ATextRect.Top - ExpanderSize.cy) div 2;
        ExpanderRect.Bottom := ExpanderRect.Top + ExpanderSize.cy;
        DrawThemeBackground(ThemeData, ACanvas.Handle, ElementPart, ElementState, ExpanderRect, nil);
        CloseThemeData(ThemeData);
      end
      else
      begin
    
        // Drawing expander without themes enabled
    
        Graphics := TGPGraphics.Create(ACanvas.Handle);
        Graphics.SmoothingMode := SmoothingModeHighQuality;
    
        ExpanderRect := ATextRect;
        ExpanderRect.Right := ATextRect.Left - TDPIAware.GetScaledSize(TreeExpanderSpacing96dpi);
        ExpanderRect.Left := ATextRect.Left - TDPIAware.GetScaledSize(TreeExpanderSpacing96dpi) -
          TDPIAware.GetScaledSize(Max(TreeExpanderCollapsedWidth96dpi, TreeExpanderExpandedWidth96dpi));
    
        if ASelected then
          UnthemedColor := ColorToRGB(clHighlightText)
        else
          if AExpanded then
            UnthemedColor := clBlack
          else
            UnthemedColor := clGray;
    
        SetLength(Points, 3);
        if AExpanded then
        begin
          Points[0] := TGPPoint.Create(ExpanderRect.Right, ExpanderRect.Top +
            (ExpanderRect.Bottom - ExpanderRect.Top - TreeExpanderExpandedHeight96dpi) div 2);
          Points[1] := TGPPoint.Create(ExpanderRect.Right, ExpanderRect.Top +
            (ExpanderRect.Bottom - ExpanderRect.Top + TreeExpanderExpandedHeight96dpi) div 2);
          Points[2] := TGPPoint.Create(ExpanderRect.Right - TreeExpanderExpandedWidth96dpi,
            ExpanderRect.Top + (ExpanderRect.Bottom - ExpanderRect.Top +
            TreeExpanderExpandedHeight96dpi) div 2);
          Brush := TGPSolidBrush.Create(TGPColor.CreateFromColorRef(UnthemedColor));
          Graphics.FillPolygon(Brush, Points);
        end
        else
        begin
          Points[0] := TGPPoint.Create(ExpanderRect.Right - TreeExpanderCollapsedWidth96dpi,
            ExpanderRect.Top + (ExpanderRect.Bottom - ExpanderRect.Top -
            TreeExpanderCollapsedHeight96dpi) div 2);
          Points[1] := TGPPoint.Create(ExpanderRect.Right,
            ExpanderRect.Top + (ExpanderRect.Bottom - ExpanderRect.Top) div 2);
          Points[2] := TGPPoint.Create(ExpanderRect.Right - TreeExpanderCollapsedWidth96dpi,
            ExpanderRect.Top + (ExpanderRect.Bottom - ExpanderRect.Top +
            TreeExpanderCollapsedHeight96dpi) div 2);
          Pen := TGPPen.Create(TGPColor.CreateFromColorRef(UnthemedColor));
          Graphics.DrawPolygon(Pen, Points);
        end;
      end;
    end;
    
    procedure TForm1.TreeView1AdvancedCustomDrawItem(Sender: TCustomTreeView;
      Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage; var PaintImages,
      DefaultDraw: Boolean);
    var
      NodeRect: TRect;
      NodeTextRect: TRect;
      Text: string;
      ThemeData: HTHEME;
      TreeItemState: Integer;
    begin
      if Stage = cdPrePaint then
      begin
        NodeRect := Node.DisplayRect(False);
        NodeTextRect := Node.DisplayRect(True);
    
        // Drawing background
        if (cdsSelected in State) and Sender.Focused then
          TreeItemState := TREIS_SELECTED
        else
          if (cdsSelected in State) and (cdsHot in State) then
            TreeItemState := TREIS_HOTSELECTED
          else
            if cdsSelected in State then
              TreeItemState := TREIS_SELECTEDNOTFOCUS
            else
              if cdsHot in State then
                TreeItemState := TREIS_HOT
              else
                TreeItemState := TREEITEMStateFiller0;
    
        if TreeItemState <> TREEITEMStateFiller0 then
        begin
          ThemeData := OpenThemeData(Sender.Handle, VSCLASS_TREEVIEW);
          DrawThemeBackground(ThemeData, Sender.Canvas.Handle, TVP_TREEITEM, TreeItemState,
            NodeRect, nil);
          CloseThemeData(ThemeData);
        end;
    
        // Drawing expander
        if Node.HasChildren then
          DrawExpander(Sender.Canvas, NodeTextRect, Node.Expanded, cdsHot in State);
    
        // Drawing main text
        SetBkMode(Sender.Canvas.Handle, TRANSPARENT);
        SetTextColor(Sender.Canvas.Handle, clBlue);
    
        Text := Node.Text;
        Sender.Canvas.TextRect(NodeTextRect, Text,
          [tfVerticalCenter, tfSingleLine, tfEndEllipsis, tfLeft]);
    
        // Some extended drawing...
    
      end;
    
      PaintImages := False;
      DefaultDraw := False;
    end;