Changing the text color and text background color of a TreeView Nodes

(also changing individual text styles) 

 

The TTreeView control by default will always display the text background color as clWindow.  This is annoying since it is real easy to change the background color of the TreeView by changing the Color property.  Fortunately, with a little bit of work, we can change the text background color, the text color, and the text styles of the TreeNodes.  Below I describe two methods.  The first, simpler, method, doesn't require subclassing a TreeView or trapping any messages.  The second, more robust method requires deriving a new TreeView from TTreeView and trapping the WM_PAINT message. 
 
KEYWORDS:  WM_PAINT, TControlCanvas 
 

METHOD 1:  The goal is to create a new TControlCanvas and set it's Control property to the TreeView.  This will give
you a Canvas to draw on.  Then every time the TreeView is painted, you have to update the TreeView by painting on this
Canvas.  To do this, you'll have to trap the WM_PAINT message sent to the Form. 

 

 

//in header... 
TControlCanvas *TVCanvas; 

    void __fastcall PaintHandler(TMessage &Msg); 

BEGIN_MESSAGE_MAP 
    MESSAGE_HANDLER(WM_PAINT, TMessage, PaintHandler) 
END_MESSAGE_MAP(TForm)

 

 

 

//---------------------------------------------------------------------------
//in source... 
__fastcall TForm1::TForm1(TComponent* Owner) 
        : TForm(Owner) 

    TVCanvas = new TControlCanvas(); 
    TVCanvas->Control = TreeView1; 
    TVCanvas->Font = TreeView1->Font; 

    //Draw text transparently! 
    TVCanvas->Brush->Style = bsClear; 

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) 

    delete TVCanvas; 

 

//Implement painting the TreeView text background 
void __fastcall TForm1::PaintHandler(TMessage &Msg) 

    //let everything else draw first 
    TForm::Dispatch(&Msg); 

    //manage drawing the TreeView 
    for (int index = 0; index < TreeView1->Items->Count; index++) 
    { 
        if (index != TreeView1->Selected->Index) 
        { 
            TRect NodeRect = TreeView1->Items->Item[index]->DisplayRect(true); 

            //FillRect the text background 
            //You can even fill in a different color for each node 
            TVCanvas->Brush->Color = TreeView1->Color; 
            TVCanvas->FillRect(NodeRect); 

            //Change the font color of individual items 
            switch(index) 
            { 
              case 0: TVCanvas->Font->Color = clRed; 
                      break
              case 1: TVCanvas->Font->Color = clGreen; 
                      break
              case 2: TVCanvas->Font->Color = clBlue; 
                      break
              default: TVCanvas->Font->Color = clBlack; 
            } 

            TVCanvas->TextOut(NodeRect.Left + 2, NodeRect.Top + 1,
TreeView1->Items->Item[index]->Text); 
        } 
    } 

void __fastcall TForm1::TreeView1Change(TObject *Sender, TTreeNode *Node) 

   Refresh(); 

void __fastcall TForm1::FormPaint(TObject *Sender) 

   TreeView1->Refresh(); 

 

METHOD 2:  Ok, that's one way to do it.  The other, more robust way is to derive your own TreeView from TTreeView, and trap the WM_PAINT message in your derived class...  
 

 

//in derived MyTreeView header... 
private
    TControlCanvas *TVCanvas; 

public
    __fastcall TMyTreeView(TComponent* Owner); 
    __fastcall TMyTreeView::~TMyTreeView(); 
    virtual void __fastcall PaintIt(TMessage &Message); 

BEGIN_MESSAGE_MAP 
    MESSAGE_HANDLER(WM_PAINT, TMessage, PaintIt); 
END_MESSAGE_MAP(TTreeView)

 


 

//---------------------------------------------------------------------------
//in derived MyTreeView source... 

__fastcall TMyTreeView::TMyTreeView(TComponent* Owner) 
        : TTreeView(Owner) 

    TVCanvas = new TControlCanvas(); 
    TVCanvas->Control = this
    TVCanvas->Brush->Style = bsClear; 

__fastcall TMyTreeView::~TMyTreeView() 

    delete TVCanvas; 

 

//Manage drawing the TreeView background and text 
void __fastcall TMyTreeView::PaintIt(TMessage &Msg) 

    TTreeView::Dispatch(&Msg); 

    for (int index = 0; index < Items->Count; index++) 
    { 
        if (index != Selected->Index) 
        { 
            TRect NodeRect = Items->Item[index]->DisplayRect(true); 

            //FillRect the text background 
            //You can even fill in a different color for each node 
            TVCanvas->Brush->Color = Color; 
            TVCanvas->FillRect(NodeRect); 

            //Change the font color of individual items 
            switch(index) 
            { 
              case 0: TVCanvas->Font->Color = clRed; 
                      break
              case 1: TVCanvas->Font->Color = clGreen; 
                      break
              case 2: TVCanvas->Font->Color = clBlue; 
                      break
              default: TVCanvas->Font->Color = clBlack; 
            } 

            TVCanvas->TextOut(NodeRect.Left + 2, NodeRect.Top + 1, Items->Item[index]->Text);

        } 
    } 
 

 

  There are some things to note, specifically in the line... 

TRect NodeRect = Items->Item[index]->DisplayRect(true); 

If you set the parameter to the DisplayRect method to false, then NodeRect will be the entire line of the TreeNode,
including the buttons and images (if present).  This is in fact one way to draw your own TreeView buttons, and indeed, adds
a greater degree of control.  The problem is that it will be difficult to tell how it is connected.  It can be done by testing it's
previous node and next node, but it is somewhat of a hassle.  Also, if you do have an imagelist set to MyTreeView, you'll
have to offset the text appropriately (if
DisplayRect(false) is specified).  Either way, it can be done without
CustomDraw, and the second method allows you nearly complete control over the look of your TreeView.