Recent

Author Topic: Can TreeView Node Colours be unique?  (Read 1227 times)

bungayst

  • New Member
  • *
  • Posts: 15
Can TreeView Node Colours be unique?
« on: December 01, 2023, 07:09:53 pm »
Can each rectangle around the treeview expansion text ("+" and "-" in my case), be changed after the node has been added to the TreeView and can that rectangle be a unique colour for each node in the tree?

wp

  • Hero Member
  • *****
  • Posts: 12476
Re: Can TreeView Node Colours be unique?
« Reply #1 on: December 01, 2023, 10:46:31 pm »
Normally you do such things by providing a draw handler in some kind of OnCustomDraw event. The problem is that the treeview paints in several phases, and it is not quite easy to get it right because everything must be drawn by yourself.

Fortunately, unlike Delphi, the Lazarus TreeView also has an event OnCustomDrawArrow in which you can redraw the +/- box for collapsing/expanding a node without having to touch the rest of the tree node - see attached project which uses the following event handler for it:
Code: Pascal  [Select][+][-]
  1. uses
  2.   LCLIntf, LCLType;
  3.  
  4. const
  5.   LEVEL_COLOR_COUNT = 3;
  6.   LEVEL_COLORS_COLLAPSED: array[0..LEVEL_COLOR_COUNT-1] of TColor = (clGreen, clLime, clBlack);
  7.   LEVEL_COLORS_EXPANDED: array[0..LEVEL_COLOR_COUNT-1] of TColor = (clRed, clMaroon, clGray);
  8.  
  9.  
  10. procedure TForm1.TreeView1CustomDrawArrow(Sender: TCustomTreeView;
  11.   const ARect: TRect; ACollapsed: Boolean);
  12. var
  13.   arrow: String;
  14.   w, h: Integer;
  15.   R: TRect;
  16.   level: Integer;
  17.   oldBkMode: Integer;
  18. begin
  19.   // Calculate the level of the node being painted
  20.   level := (ARect.Left + 1) div TTreeView(Sender).Indent;
  21.  
  22.   // Set up the canvas for drawing the "arrow"
  23.   Sender.Canvas.Brush.Color := clWindow;
  24.   Sender.Canvas.Font.Assign(Sender.Font);
  25.   if ACollapsed then
  26.   begin
  27.     if level < LEVEL_COLOR_COUNT then
  28.       Sender.Canvas.Pen.Color := LEVEL_COLORS_COLLAPSED[level]
  29.     else
  30.       Sender.Canvas.Pen.Color := clWindowText;
  31.     arrow := '+';
  32.   end else
  33.   begin
  34.     if level < LEVEL_COLOR_COUNT then
  35.       Sender.Canvas.Pen.Color := LEVEL_COLORS_EXPANDED[level]
  36.     else
  37.       Sender.Canvas.Pen.Color := clWindowText;
  38.     arrow := '-';
  39.   end;
  40.   Sender.Canvas.Font.Color := Sender.Canvas.Pen.Color;
  41.  
  42.   // Draw the box
  43.   R := ARect;
  44.   InflateRect(R, -2, -2);
  45.   Sender.Canvas.Rectangle(R);
  46.  
  47.   // Draw the +/- as text
  48.   w := Sender.Canvas.TextWidth(arrow);
  49.   h := Sender.Canvas.TextHeight(arrow);
  50.   // enable transparent text
  51.   oldBkMode := SetBkMode(Sender.Canvas.Handle, TRANSPARENT);
  52.   // Draw the text
  53.   Sender.Canvas.TextOut(
  54.     (ARect.Left+ARect.Right - w) div 2,
  55.     (ARect.Top+ARect.Bottom - h) div 2, arrow
  56.   );
  57.   // Restore the background mode
  58.   SetBkMode(Sender.Canvas.Handle, oldBkMode);
  59. end;

There is one speciality: Unfortunately the event handler does not get the node to be painted, and this makes it difficult to draw different arrows for different node levels. After some experimenting I found out that each node level is indented by the value of the tree's Indent property and offset by -1. From this information the nodel level can be calculated from the left edge of the rectangle passed to the event: level := (ARect.Left+1) div TreeView.Indent

bungayst

  • New Member
  • *
  • Posts: 15
Re: Can TreeView Node Colours be unique?
« Reply #2 on: December 02, 2023, 12:34:27 am »
 Thanks for the reply, I have something close to that working, and it's close, but what I really need to be able to do is allow the user to select a custom colour for an existing node. To that end I put a popup menu in and when a node is right-clicked the user selects "Assign Colour" from the menu. This brings up the ColorDialog, can they select a color, click OK and have the rectangle colourised using the selection from the dialog.

 

wp

  • Hero Member
  • *****
  • Posts: 12476
Re: Can TreeView Node Colours be unique?
« Reply #3 on: December 02, 2023, 01:08:11 am »
Hmmm... I think this is not possible this way because the OnCustomDrawArrow does not know which node was clicked. So you have to go the hard way and draw everything yourself - find in the attachment another demo which custom-draws the entire nodes, including line colors and node icons. The demo was written in another context and uses icons from the imagelist to draw the +/- symbols. But you certainly find the position in the code where you can put in your way of drawing them.

Another problem is how to store the color selected by the user. There are no nice solutions (except subclassing TTreeNode, but this is more work..., it's quite late here). One way would be to "abuse" the node's StateIndex to store the color in it - this should work unless you assign an imagelist to the tree's StateImages. The other way would be to extend the node.Text and append the color to it. This is possible since you draw the nodes yourself and can remove the appended color from it before drawing. But it might cause trouble when you search nodes or sort them. It depends on your specific application.

wp

  • Hero Member
  • *****
  • Posts: 12476
Re: Can TreeView Node Colours be unique?
« Reply #4 on: December 02, 2023, 01:23:19 pm »
Sorry to push you onto the hacker's track. The "real" solution based on a subclassed TTreeNode is not much more difficult:
  • Declare a new class descending from TTreeNode having the color as a property. The following code reserves color fields for arrow, text and background colors:
Code: Pascal  [Select][+][-]
  1. type
  2.   TColoredTreeNode = class(TTreeNode)
  3.   private
  4.     FTextColor: TColor;
  5.     FBkColor: TColor;
  6.     FArrowColor: TColor;
  7.   public
  8.     constructor Create(AnOwner: TTreeNodes); override;
  9.     property ArrowColor: TColor read FArrowColor write FArrowColor;
  10.     property BkColor: TColor read FBkColor write FBkColor;
  11.     property TextColor: TColor read FTextColor write FTextColor;
  12.   end;
  13.  
  14. constructor TColoredTreeNode.Create(AnOwner: TTreeNodes);
  15. begin
  16.   inherited;
  17.   FTextColor := clWindowText;
  18.   FBkColor := clWindow;
  19.   FArrowColor := clWindowText;
  20. end;
  • Then write a handler for the TreeView's OnCreateNodeClass event to tell the tree that it should create instances of TColoredTreeNode rather than TTreeNode.
Code: Pascal  [Select][+][-]
  1. procedure TForm1.TreeView1CreateNodeClass(Sender: TCustomTreeView;
  2.   var NodeClass: TTreeNodeClass);
  3. begin
  4.   NodeClass := TColoredTreeNode;
  5. end;
  • Whenever you need one of the color properties cast the Node to TColoredTreeNode, e.g. in TreeView.OnCustomDrawItem:
Code: Pascal  [Select][+][-]
  1.   Sender.Canvas.Font.Color := TColoredTreeNode(Node).TextColor;
    Find a tested sample project in the attachment. It uses a popup menu to allow the user to select colors for each right-clicked node.

    bungayst

    • New Member
    • *
    • Posts: 15
    Re: Can TreeView Node Colours be unique?
    « Reply #5 on: December 03, 2023, 06:53:16 pm »
    Thank you WP! I think extending the class is definitely the way to go and I'll get to that today.

    Bart

    • Hero Member
    • *****
    • Posts: 5469
      • Bart en Mariska's Webstek
    Re: Can TreeView Node Colours be unique?
    « Reply #6 on: December 03, 2023, 07:21:27 pm »
    Can each rectangle around the treeview expansion text ("+" and "-" in my case), be changed after the node has been added to the TreeView and can that rectangle be a unique colour for each node in the tree?
    No, at least not always.
    Iit's a pigeon hole example: the number of available colors might be less than the number of expandable/collapsable nodes, especially if the colors need to be distinguishable (when sitting next to each other) by the human eye.

    (Sorry, could not resist.)

    Bart

     

    TinyPortal © 2005-2018