Author Topic: Treeview: full-width background coloring of nodes ... easy?  (Read 371 times)


  • Sr. Member
  • ****
  • Posts: 284
Treeview: full-width background coloring of nodes ... easy?
« on: August 12, 2022, 10:54:48 am »
For a treeview (windows x64) i'm trying a bit of customization that looks like
a.- Selected tree node looks "full-width" backgrounded (like when using the RowSelect property, --> but not changing the background color to lightgray when the control is loosing the focus, for certian reasons)
b.- Nodes hovered with the mouse do look "full-width" backgrounded as well (using a slightly differing backgorund color).
    Plus, keeping (remembering) the hovered line at mouse wheel scroll. (You may see this in recent apps, MS Explorer, Thunderbird, ..) 

So it appears to be a simple thing (changing a background color only) and one might expect that doing somothing like that could succeed:
Code: Pascal  [Select][+][-]
  1.    if cdsSelected in State then
  2.       if stage = cdPrePaint then begin
  3.           Sender.Canvas.Brush.Color := $00FFE8CC;
  4.           DefaultDraw := False;
  5.           RFull := Node.DisplayRect(false);
  6.           Sender.Canvas.FillRect(RFull);
  7.           exit;
  8.       end;

Apparently, for to have any effect that would need DefaultDraw := False (within AdvancedCustomDrawItem).
And now the elements from "ShowLines", expand sign, icon and text would be needed to be redrawn afterwards by hand.
But the latter (at least the lines, expand sign etc.) is a lot of stuff within (DoiPaintNode)  and the app would need to reinvent this wheel (integrate a lot of code).
It works fine (for both cases), but requires a lot of code duplication from the within an app (may easily grow by 300 lines. Too much to incest for a paricular visual customization).

So i'm wondering whether for this "simple task" (full row background color for nodes if wished) somebody already tried simiiar and got an easier clue or trick?
Does mean: changing the row's background color before the DefaultDraw does it's job for lines, expand sign, icon etc.
Is that possible somehow instead of doing a lot of code cloning?


  • Hero Member
  • *****
  • Posts: 4750
Re: Treeview: full-width background coloring of nodes ... easy?
« Reply #1 on: August 13, 2022, 04:43:50 pm »
Getting the display rectangle should be enough.

You need to offset the rectangle's X axes to the width of the display panel of the Treeview or what ever you wish.

The only true wisdom is knowing you know nothing


  • Sr. Member
  • ****
  • Posts: 284
Re: Treeview: full-width background coloring of nodes ... easy?
« Reply #2 on: August 13, 2022, 07:51:07 pm »
Yes, but the full width rectangle is already provided: via Node.DisplayRect with parameter 'false'.
Only, using this Rect for FillRect now - Sender.Canvas.FillRect(RFull); - simply will result in a colored bar of course (no icon, no text etc.), understandable.

Restricting this FillRect to  'if stage = cdPrePaint', then it won't have any effect at all.
Unless when saying: DefaultDraw := False. Now the fillRect comes into effect, but would require to paint expand sign, lines, icon, text afterwards too..

The situation (not a problem, and not an issue at all of course) seems to be: you want to change the backcolor for the whole line? Then please do all of the stuff and implement the whole code needed for that (for lines, expand sign &  co)  by yourself.
Does work flawlessly (tested), but is somehow unsatisfying to see the large overhead. So the question if i may have missed a way around here to access the background color.


  • Sr. Member
  • ****
  • Posts: 284
Re: Treeview: full-width background coloring of nodes ... easy?
« Reply #3 on: August 16, 2022, 03:32:18 pm »
Don't know if there are few responses because the topic is not of interest or because most are thinking: not possible.
As demo i add a small gif sample for to show what i ssucceeded to try out because i found it to be quite nice.

The chanllenge is only, why such a big overhead described, approx. 300 lines of code cloning in the app (for to paint lines, expand sign etc., which already does exist), plus the need for to include themes.pas, only for to change a full line backcolor?

I thought about further and had an idea.
A solution could be a simple one liner within, DoPaintNode.

The idea is: let me (the app) draw the line backcolor and node text (both easy) and else do your DefaultDraw .... but without painting the line background.
That means: please let me draw the item background, without either overwriting it afterwards (DefaultDraw true), or (DefaultDraw false:) force me to do a lot of things redundantly.
The idea simply is: please skip the background draw optionally by demand for certain situations, the app wants to do that itself and not been overridden by the DefaultDraw.
This is the one liner (a simple if). I tried it out and it worked fine:

Code: Pascal  [Select][+][-]
  1.     // draw background
  2.     if not FSkipDrawLineBackground then     // <=== added line. requires def and init of the public variable within comctrls.pp though
  3.        DrawBackground(NodeSelected, NodeRect);

In my use cases, i would set this veriable to true just if needed and now simply spend some very few code lines for background and node text, that's all.
One could care about the line backcolor and text paint, but not to struggle with lines, icon, expand sign.
Example (for highlight line at "Selected", at treeview focused or unfocused), the AdvancedCustomDrawItem could look like as:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.TreeView1AdvancedCustomDrawItem(Sender: TCustomTreeView;
  2.             Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage;
  3.             var PaintImages, DefaultDraw: Boolean);
  4. var RFull, RText: Trect;
  5. begin
  6.      DefaultDraw := True;
  7.      Sender.FSkipDrawLineBackground := False;
  8.      if cdsSelected In State then begin
  9.         Sender.FSkipDrawLineBackground := True;
  10.         if stage = cdPrePaint then begin
  11.             Sender.Canvas.Brush.Color := $00FFE8CC;
  12.             RFull := Node.DisplayRect(false);
  13.             Sender.Canvas.FillRect(RFull);
  14.             exit;
  15.         end;
  16.         DefaultDraw := False;
  17.         with Sender.Canvas do begin
  18.            RText := Node.DisplayRect(True);
  19.            Sender.Canvas.Brush.Color := $00FFE8CC;
  20.            Sender.Canvas.FillRect(RText);
  21.            inc(RText.Top, 2);
  22.            inc(RText.Left, 4);
  23.            inc(RText.Right, 4);
  24.            DrawText(Handle, PChar(Node.Text), -1, RText, DT_LEFT or DT_VCENTER);
  25.         end;
  26.     end;
  27. end;

Test project attached )which does assume the change mentioned). It shows a full line background  (just as with RowSelect, but without graying it at focus change).
For he "highlight line at mouse over" the principle is juse the same, with a bit more logic around (--> not covered in the test project).

So this does allow much more flexibility for certain vertain customization.
What do you think? Would it make sense to provide this possibility?

(With an additional var, alternatives are possible. But i think this one is the simplest).


TinyPortal © 2005-2018