Click here to Skip to main content
Click here to Skip to main content

A custom-drawn TreeList Control

, 17 Mar 2000
Rate this:
Please Sign up or sign in to vote.
A custom-drawn tree-list hybrid, with explanations on how the control was developed.

Sample Image - TreeListCtrlGerolf.gif

Introduction

This treelist control is an owner-drawn tree control. I know that there are many treelist controls in CodeProject or CodeGuru, but I didn't find one that fits to my needs. Most of them override the WM_PAINT message. That's very simple but a little bit slow (in my tests) if you have many tree items (about 500 and more).

David Lantsman's article helped me a lot, so also the article of Garen Hartunian about custom drawn tree controls.

I mixed everything I found about tree list controls and custom-drawn tree controls and made a new class: CTreeListView. You can use this class like a CTreeView and manipulate columns like in CListView. I took the definitions of the functions from these classes so that it is mostly compatible to the MFC classes. But now, it's enough gossip.

How to use CTreeListView in your project

Just add the class to your project and inherit your view from CTreeListView instead of CTreeView. Now, replace all calls to your earlier base class (perhaps CTreeView) with CTreeListView. That's it. Now, you can begin parameterising the class like below in OnInitialUpdate. You see me inserting columns and pictures, and also items and subitems. It's nearly the same as in CTreeView and CListView. The functions are described below (look here for functions). After that, you will find the overview over the classes (overview of classes, the complete documentation can be found here. Download class reference - 27.6 Kb) and the description of the implementation of the drawing functions (look here for implementation).

Example of use of CTreeListView

void CTreeListCtrlView::OnInitialUpdate()
{
    CTreeListView::OnInitialUpdate();

    m_Images.Create(16,16, ILC_COLOR | ILC_MASK, 0, 1);
    CBitmap bm;
    bm.LoadBitmap(IDB_BITMAP1);
    m_Images.Add(&bm, RGB(255, 255, 255));
    bm.DeleteObject();
    bm.LoadBitmap(IDB_BITMAP2);
    m_Images.Add(&bm, RGB(255, 255, 255));

    CTreeCtrl& ctrl = GetTreeCtrl();

    InsertColumn(0, _T("first column"), LVCFMT_LEFT, 200);
    InsertColumn(1, _T("second column"), LVCFMT_LEFT, 200);
    InsertColumn(2, _T("third column"), LVCFMT_LEFT, 200);

    ctrl.SetImageList(&m_Images, TVSIL_NORMAL);
    ctrl.SetImageList(&m_Images, TVSIL_STATE);

    HTREEITEM hItem, hItem2;

    hItem = ctrl.InsertItem(_T("ItemText1"), 0, 1);
    SetSubItemText(hItem,    1,_T("Subitem    1"));
    SetSubItemText(hItem, 2, 
      _T("Subitem2################################################"));
    hItem2 = ctrl.InsertItem(_T("ItemText  1.2"), 0, 1, hItem);
    SetSubItemText(hItem2, 1, _T("Subitem 1"));
    SetSubItemText(hItem2, 2, _T("Subitem 2"));
    hItem2 = ctrl.InsertItem(_T("ItemText 1.2"),0, 1, hItem);
    SetSubItemText(hItem2, 2, _T("Subitem 2"));
    for(long i = 2;i <  150; i++)
    {
        CString str;
        str.Format("ItemText %d", i);
        hItem = ctrl.InsertItem(str, 0, 1);
        SetSubItemText(hItem, 1, _T("Subitem 1"));
        SetSubItemText(hItem, 2, _T("Subitem 2"));
    }
}

Description of the public functions of CTreeListView:

  • int InsertColumn( int nCol, const LVCOLUMN* pColumn )

    Inserts a column in the CTreeListView. You have to fill the structure like in CListView.

  • int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat = LVCFMT_LEFT, int nWidth = -1, int nSubItem = -1 )

    Inserts a column in the CTreeListView. You have to fill the parameters like in CListView.

  • BOOL DeleteColumn( int nCol )

    Deletes the specified column and all texts that are specified for this column.

  • BOOL SetSubItemText( HTREEITEM hItem, int nSubItem, CString strBuffer)

    Sets the text of a specified tree control item (hItem) and a specified column (nSubItem). nSubItem = 0 means the first column (main name).

  • BOOL GetSubItemText( HTREEITEM hItem, int nSubItem, CString& strBuffer)

    Returns the text of a specified tree control item (hItem) and a specified column (nSubItem). nSubItem = 0 means the first column (main name). You will receive the text in strBuffer.

  • CImageList* GetHeaderImageList()

    Retrieves the imagelist that is used by the header.

  • CImageList* SetHeaderImageList( CImageList * pImageList)

    Sets the imagelist that the header can use to display images.

  • CTreeCtrl& GetTreeCtrl()

    This function returns a reference to the tree control that is used. You can use this reference to delete or insert items. Do not change item texts with this function, because the texts are saved as subitem texts, and you will only see them.

  • void ShowHeader(bool bShow)

    This function hides and shows the header (column headers). You can control this by the bShow parameter (true means show the header, false means hide the header).

Here are the classes and structures that I use

  • struct _HeaderData

    Contains the data for one column so I can get it quickly at painting time without asking the header control.

  • class CMyTreeObj : public CObject

    Represents one object in the tree with all its columns.

  • class CMyTreeCtrl : public CTreeCtrl

    An overridden tree control that enables me to acknowledge when an item is inserted in the tree. The class sends a user defined message with a HTREEITEM parameter.

  • class CTreeListView : public CView

    Main view containing the treelist. I didn't inherit from CTreeView because this subclasses CTreeCtrl and I didn't find out how to resize the tree to put a header control on top of it. But this way is also OK.

Implementation

Drawing of the TreeItems

To use ownerdraw in tree controls, you can override the WM_NOTIFY message handler of the parent class of the control. There, you can test if the notify code is NM_CUSTOMDRAW. I've overridden TVN_DELETEITEM to delete my row for column texts for each item. I've also overridden HDN_ENDTRACK for the header control so I can update my internal structure of header data that I use for drawing:

BOOL CTreeListView::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
    LPNMHDR pNmhdr = (LPNMHDR)lParam;

    //TreeCtrl notifications
    if(m_ctrlTree.m_hWnd == pNmhdr->hwndFrom)
    {
        switch (pNmhdr->code)
        {
        case NM_CUSTOMDRAW:
            return OnCustomdrawTree(pNmhdr, pResult);
        case TVN_DELETEITEM:
            return OnDeleteItem(pNmhdr, pResult);
        }
    }
    //HeaderCtrl notifications
    else if(m_ctrlHeader.m_hWnd == pNmhdr->hwndFrom)
    {
        switch (pNmhdr->code)
        {
        case HDN_ENDTRACK:
            return OnEndTrack(pNmhdr, pResult);
        }
    }

    return CView::OnNotify(wParam, lParam, pResult);
}

This function is a bit tricky.

I use static variables to save the data of one object from CDDS_ITEMPREPAINT to CDDS_ITEMPOSTPAINT notification. The CDDS_ITEMPOSTPAINT notification follows immediately the drawing of this object. There will be no CDDS_ITEMPREPAINT for another object before. But most data is only send with the CDDS_ITEMPREPAINT notification, so I have to save it. Before drawing (CDDS_PREPAINT notification), I have to set the Viewport in case of the horizontal scrollbar that I use. The way I use it is described at David Lantsman's article. I use it the same way.

At the CDDS_ITEMPREPAINT notification, I set the foreground color and the background color to the same value, so you won't see the drawing of the tree. I could have done this also by not giving it the texts, but then I would have to implement many things (like jump to an item by pressing a key) by myself. I wanted to use as much as possible of the originally tree.

BOOL CTreeListView::OnCustomdrawTree(LPNMHDR pNmhdr, LRESULT* pResult)
{
    static CRect    rcItem;
    static CPoint    poi;
    static bool        bFocus;

    BOOL bRet = FALSE;

    LPNMTVCUSTOMDRAW pCustomDraw = (LPNMTVCUSTOMDRAW)pNmhdr;
    switch (pCustomDraw->nmcd.dwDrawStage)
    {
        case CDDS_PREPAINT:
            // Need to process this case and set
            // pResult to CDRF_NOTIFYITEMDRAW,
            // otherwise parent will never receive
            // CDDS_ITEMPREPAINT notification. (GGH)
            *pResult = CDRF_NOTIFYITEMDRAW;

            // reposuition the viewport so the TreeCtrl
            // DefWindowProc doesn't draw to 
            // viewport 0/0
            ::SetViewportOrgEx(pCustomDraw->nmcd.hdc, m_nOffset, 0, NULL);
            bRet = TRUE;
            break;

        case CDDS_ITEMPREPAINT:
            // set the background and foregroundcolor of the item 
            // to the background,
            // so you don't see the default drawing of the text
            pCustomDraw->clrText = m_colBackColor;
            pCustomDraw->clrTextBk = m_colBackColor;

            // reset the focus, because it will be drawn of us
            bFocus = false;
            if(    pCustomDraw->nmcd.uItemState & CDIS_FOCUS)
            {
                bFocus = true;
            }

            pCustomDraw->nmcd.uItemState &= ~CDIS_FOCUS;

            // remember the drawing rectangle
            // of the item so we can draw it ourselves
            m_ctrlTree.GetItemRect((HTREEITEM) pCustomDraw->nmcd.dwItemSpec, 
                                                                &rcItem, TRUE);
            rcItem.right = 
              (pCustomDraw->nmcd.rc.right > m_nHeaderWidth) ? 
              pCustomDraw->nmcd.rc.right : m_nHeaderWidth;

            // we want to get the CDDS_ITEMPOSTPAINT notification
            *pResult = CDRF_NOTIFYPOSTPAINT;
            bRet = TRUE;
            break;

        case CDDS_ITEMPOSTPAINT:

            // draw the item
            DrawTreeItem(bFocus, rcItem, pCustomDraw->nmcd.hdc, 
                         (HTREEITEM) pCustomDraw->nmcd.dwItemSpec);
            bRet = TRUE;
            break;
    }

    return bRet;
}

This function is very easy, I draw a focus rectangle and a focus background (if necessary). Then I draw the column texts in a for loop. The single rectangles are calculated from the rcItem parameter and the HeaderData structure. I use that way, because it would be slower if I get the rectangle and alignment from the header control each time I need it.

void CTreeListView::DrawTreeItem(bool bFocus, CRect rcItem, HDC hdc, HTREEITEM hItem)
{
    COLORREF colText = m_colText;

    // if the item has got the focus,
    // we have to draw a sorouinding rectangle and fill
    // a rect blue
    if(bFocus == true)
    {
        RECT rcFocus = rcItem;
        rcFocus.left = 1;
        ::DrawFocusRect(hdc, &rcFocus);

        ::FillRect(hdc, &rcItem, (HBRUSH)m_BackBrush.m_hObject);

        colText = m_colHilightText;
    }

    // always write text without background
    ::SetBkMode(hdc, TRANSPARENT);
    ::SetTextColor(hdc, colText);

    // draw all columns of the item
    RECT rc = rcItem;
    for(long i=0; i < m_nNrColumns; i++)
    {
        if(i != 0)
            rc.left = m_vsCol[i].rcDefault.left;
        rc.right = m_vsCol[i].rcDefault.right;
        CString str = m_Entries[hItem].m_strColumns[i];

        ::DrawText(hdc, str, -1, &rc, 
                   DT_BOTTOM | DT_SINGLELINE | 
                   DT_WORD_ELLIPSIS | m_vsCol[i].nAlingment);
    }
}

If an item is to be deleted, I have to delete the strings for its columns. They are stored in a CMap<...>, so I only have to delete the object in the map that is mapped with the item's handle.

BOOL CTreeListView::OnDeleteItem(LPNMHDR pNmhdr, LRESULT* pResult)
{
    UNUSED_PARAM(pResult);

    BOOL bRet = TRUE;

    NMTREEVIEW* pnmtv = (NMTREEVIEW*) pNmhdr;

    m_Entries.RemoveKey(pnmtv->itemOld.hItem);

    return bRet;
}

I post the WM_SIZE message, because I recalculate my HeaderData structure in that message handler. I don't post the message, because the header control must update itself before. This is a solution for the timing problem in this point.

BOOL CTreeListView::OnEndTrack(LPNMHDR pNmhdr, LRESULT* pResult)
{
    UNUSED_PARAM(pResult);
    UNUSED_PARAM(pNmhdr);

    // we need to post this message
    // so the header control can take the time to save 
    // the information of the new sizes
    // and we can then get it from the control
    PostMessage(WM_SIZE);
    return FALSE;
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Gerolf Reinwardt
Software Developer Siemens AG
Germany Germany
No Biography provided

Comments and Discussions

 
GeneralEnabled option TVS_SHOWSELALWAYS PinmemberPetr Stejskal22-Feb-08 21:36 
QuestionHow to do it in Visual J++ ?! Pinmemberroparo28-Sep-06 5:04 
GeneralChanging header font and size Pinmembervivelesours10-May-06 6:45 
Questionenhancements of this control required.. Pinmemberabhishekvyas25-Nov-05 3:05 
Questiontransparent backgound? PinmemberLukeV18-Nov-04 12:14 
Questionhow to edit the content of cell of list? Pinmemberfreehawk30-Mar-04 17:17 
QuestionHow to add grid line? Pinmemberfreehawk29-Mar-04 15:03 
AnswerRe: How to add grid line? Pinmemberdf49431-Mar-07 22:00 
In "CTreeListView::DrawTreeItem", simply add following lines of code :
 
int xOffset = 0;
for (int i=0; i
QuestionRe: How to add grid line? Pinmemberjanethinking21-May-09 7:27 
GeneralAdding/removing columns in a non-empty CTreeListView Pinmembereurodude20-Feb-04 8:13 
GeneralCObject : base class undefined PinmemberBigMuscle31-Jan-04 7:22 
GeneralRe: CObject : base class undefined PinmemberGerolf Kuehnel1-Feb-04 20:09 
GeneralFantastic Job! PinmemberKartan11-Sep-03 19:29 
GeneralDialog PinsussAnonymous16-Apr-03 1:12 
GeneralRe: Dialog Pinmemberzaphoed29-Nov-06 13:14 
GeneralGetting the WM_RBUTTONDOWN message.. Pinmemberdooz12318-Mar-03 9:21 
GeneralRe: Getting the WM_RBUTTONDOWN message.. PinmemberGerolf Kuehnel5-May-03 21:59 
GeneralSelection Pinmemberdebitsch22-Nov-02 2:36 
GeneralAdd column PinsussATAO7-Nov-02 2:52 
GeneralColor of ScrollBar PinsussJavier Martinez23-Sep-02 8:32 
GeneralRe: Color of ScrollBar PinmemberGerolf Kuehnel23-Sep-02 19:53 
GeneralChange Color of Text and BK PinsussLeoIVII21-Aug-02 10:18 
GeneralRe: Change Color of Text and BK PinmemberGerolf Kuehnel1-Sep-02 21:24 
GeneralUse it with XP Pinmemberbluntagain3-Jul-02 7:53 
GeneralRe: Use it with XP PinmemberGerolf Kuehnel1-Sep-02 21:14 
GeneralRe: Use it with XP PinmemberMartin Komara23-Feb-03 1:28 
GeneralProblem while Doing New (CTRL + N) PinmemberEhud22-Jun-02 23:56 
GeneralRe: Problem while Doing New (CTRL + N) PinmemberGerolf Kuehnel1-Sep-02 21:24 
QuestionCmcTreeListView source? PinmemberVinay Kothiyal3-May-02 14:31 
AnswerRe: CmcTreeListView source? PinmemberGerolf Kuehnel5-May-02 19:51 
GeneralRe: CmcTreeListView source? Pinmembervkkothiyal6-May-02 8:52 
GeneralRe: CmcTreeListView source? PinmemberGerolf Kuehnel6-May-02 19:49 
QuestionMultirow selection? PinmemberAnonymous10-Apr-02 5:14 
AnswerRe: Multirow selection? PinmemberGerolf Kuehnel21-Apr-02 19:51 
GeneralRe: Multirow selection? PinmemberGerolf Kuehnel21-Apr-02 19:53 
GeneralSort the TreeList PinmemberAnonymous15-Mar-02 4:27 
GeneralRe: Sort the TreeList PinmemberGerolf Kuehnel21-Apr-02 19:50 
Generalsort PinmemberAnonymous14-Mar-02 21:50 
GeneralRe: sort PinmemberGerolf Kuehnel21-Apr-02 19:50 
GeneralModification to the header's font PinmemberDak Lozar3-Mar-02 15:02 
QuestionWM_PAINT is slow? Pinmemberreal name15-Feb-02 4:44 
AnswerRe: WM_PAINT is slow? PinmemberGerolf Kühnel18-Feb-02 19:59 
GeneralRe: WM_PAINT is slow? Pinmemberreal name18-Feb-02 20:03 
GeneralRe: WM_PAINT is slow? PinmemberAnonymous18-Feb-02 20:34 
AnswerRe: WM_PAINT is slow? PinmemberDak Lozar3-Mar-02 1:33 
GeneralRe: WM_PAINT is slow? PinmemberGerolf Kuehnel3-Mar-02 20:19 
Generalstatus-bitmap before itemtext PinmemberStefan Ludwig8-Dec-01 13:24 
GeneralRe: status-bitmap before itemtext PinmemberGerolf Kuehnel9-Dec-01 20:29 
GeneralCompiler Error PinmemberGnascher4-Dec-01 5:34 
GeneralRe: Compiler Error PinmemberGerolf Kuehnel4-Dec-01 19:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 18 Mar 2000
Article Copyright 2000 by Gerolf Reinwardt
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid