Forum > LCL

Can TreeView Node Colours be unique?

(1/2) > >>

bungayst:
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:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---uses  LCLIntf, LCLType; const  LEVEL_COLOR_COUNT = 3;  LEVEL_COLORS_COLLAPSED: array[0..LEVEL_COLOR_COUNT-1] of TColor = (clGreen, clLime, clBlack);  LEVEL_COLORS_EXPANDED: array[0..LEVEL_COLOR_COUNT-1] of TColor = (clRed, clMaroon, clGray);  procedure TForm1.TreeView1CustomDrawArrow(Sender: TCustomTreeView;  const ARect: TRect; ACollapsed: Boolean);var  arrow: String;  w, h: Integer;  R: TRect;  level: Integer;  oldBkMode: Integer;begin  // Calculate the level of the node being painted  level := (ARect.Left + 1) div TTreeView(Sender).Indent;   // Set up the canvas for drawing the "arrow"  Sender.Canvas.Brush.Color := clWindow;  Sender.Canvas.Font.Assign(Sender.Font);  if ACollapsed then  begin    if level < LEVEL_COLOR_COUNT then      Sender.Canvas.Pen.Color := LEVEL_COLORS_COLLAPSED[level]    else      Sender.Canvas.Pen.Color := clWindowText;    arrow := '+';  end else  begin    if level < LEVEL_COLOR_COUNT then      Sender.Canvas.Pen.Color := LEVEL_COLORS_EXPANDED[level]    else      Sender.Canvas.Pen.Color := clWindowText;    arrow := '-';  end;  Sender.Canvas.Font.Color := Sender.Canvas.Pen.Color;   // Draw the box  R := ARect;  InflateRect(R, -2, -2);  Sender.Canvas.Rectangle(R);   // Draw the +/- as text  w := Sender.Canvas.TextWidth(arrow);  h := Sender.Canvas.TextHeight(arrow);  // enable transparent text  oldBkMode := SetBkMode(Sender.Canvas.Handle, TRANSPARENT);  // Draw the text  Sender.Canvas.TextOut(    (ARect.Left+ARect.Right - w) div 2,    (ARect.Top+ARect.Bottom - h) div 2, arrow  );  // Restore the background mode  SetBkMode(Sender.Canvas.Handle, oldBkMode);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:
 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:
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:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---type  TColoredTreeNode = class(TTreeNode)  private    FTextColor: TColor;    FBkColor: TColor;    FArrowColor: TColor;  public    constructor Create(AnOwner: TTreeNodes); override;    property ArrowColor: TColor read FArrowColor write FArrowColor;    property BkColor: TColor read FBkColor write FBkColor;    property TextColor: TColor read FTextColor write FTextColor;  end; constructor TColoredTreeNode.Create(AnOwner: TTreeNodes);begin  inherited;  FTextColor := clWindowText;  FBkColor := clWindow;  FArrowColor := clWindowText;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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TForm1.TreeView1CreateNodeClass(Sender: TCustomTreeView;  var NodeClass: TTreeNodeClass);begin  NodeClass := TColoredTreeNode;end;
* Whenever you need one of the color properties cast the Node to TColoredTreeNode, e.g. in TreeView.OnCustomDrawItem:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---  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.

Navigation

[0] Message Index

[#] Next page

Go to full version