The control described in Michal Mecinski's article already has everything you need, but there were several bugs and several missing methods (from my point of view), so I've tried to improve Michal's control a little.
Here is the list of improvements.
- The original control is CView-derived. This control is CStatic-derived that allows to use it in dialog-based applications.
- This control has improved scroll bars. If you noticed, the original control’s scrollbars are not displayed correctly;
- This control supports column formatting while the original control does not;
- This control allows to define the first column’s minimum size (if you use owner-drawing you can ignore this, but if not, then you have to define the minimum width for the first column to avoid problems with appearance);
- This control has convenient methods for adding columns and setting column text while the original one does not. I tried not to overweight the control by adding many methods, just added couple of methods that are really needed;
- The original control used incorrect colors for tree’s selected items and ours uses default system colors;
- This control supports owner-drawing that allows you to control every aspect of its appearance;
Control internals
The CColumnTreeCtrl control is implemented as container for the following child controls (see the figure below):
- The main header (CHeaderCtrl). It contains the columns you add to the control.
- Additional header (CHeaderCtrl). It is used to just fill empty space when the vertical scroll bar of child tree control (3) is displayed.
- The custom child tree control (CTreeCtrl). This child control contains items you add, and it also provides the vertical scroller.
- The horizontal scroll bar (CScrollBar). It is used to scroll the control in horizontal direction.
Using the code
Linking to your MFC project
You can use this control in Visual C++ 2003 and higher. I tried to compile it under Visual C++ 6.0, but it seems that it has too old SDK and doesn't not support common controls library ver. 6 (distributed with IE6).
Follow these simple steps to add CColumnTreeCtrl to your MFC dialog:
- Copy ColumnTreeCtrl.h and ColumnTreeCtrl.cpp file to your project folder. If you place these files to another place you may encounter some problems with linking to resources, so don't do that.
- Copy TREEBTNS.bmp file to your res folder. This bitmap will be used to draw buttons of tree control in owner-drawn mode.
- Add TREEBTNS.bmp to project resources, set its name to IDB_TREEBTNS.
- Add Static control to your dialog.
- Change its id to, for example
IDC_COLUMNTREE
- Right-click the static and choose "Add variable..." in context menu.
- In the "Variable name" field, type "
m_columnTree"
- In the header file of your dialog, Add "
#include "ColumnTreeCtrl.h"" line;
- In the header file of your dialog, replace "
CStatic m_columnTree;" to "CColumnTreeCtrl m_columnTree;";
Owner-drawing is the technique when all drawing is performed by user's drawing procedure. Although this is rather difficult to paint entire control, this gives you the opportunity to change its appearance as you wish. When owner-drawing is disabled, standard drawing code+custom drawing code is used.
By default owner-drawing is disabled. To enable it, uncomment this line in CColumnTreeCtrl.h:
Note: You must enable owner-drawing if you need to use background image to be placed on the control.
Adding and deleting columns
You should use these methods to add/remove columns:
int
CColumnTreeCtrl::InsertColumn(
int nCol,
LPCTSTR lpszColumnHeading,
int nFormat=0,
int nWidth=-1,
int nSubItem=-1
);
BOOL
CColumnTreeCtrl::DeleteColumn(
int nCol
);
Example:
m_columnTree.InsertColumn(0, _T("Name"), LVCFMT_LEFT, 180);
m_columnTree.InsertColumn(1, _T("Type"), LVCFMT_LEFT, 80);
m_columnTree.DeleteColumn(1);
Adding/Removing items
Example:
HTREEITEM hParent;
HTREEITEM hItem = m_columnTree.GetTreeCtrl().InsertItem( _T("3.5\" Floppy (A:)"), hParent );
m_columnTree.GetTreeCtrl().DeleteItem(hItem);
Retrieving and setting item text
You should use CColumnTreeCtrl::GetItemText() and CColumnTreeCtrl::SetItemText() method to get/set item or subitem text.
CString
CColumnTreeCtrl::GetItemText(
HTREEITEM hItem,
int nSubItem
);
void
SetItemText(
HTREEITEM hItem,
int nSubItem,
LPCTSTR lpszText
);
Example:
m_columnTree.SetItemText(hRoot, 1, _T("Removable"));
m_columnTree.SetItemText(hRoot, 2, _T("FAT12"));
Setting minimum width for the first column
void
CColumnTreeCtrl::SetFirstColumnMinWidth(
UINT uMinWidth
);
Accessing child CTreeCtrl and CHeaderCtrl
You can access child tree control and header control and call their methods as usual:
CCustomTreeChildCtrl&
CColumnTreeCtrl::GetTreeCtrl();
CHeaderCtrl& GetHeaderCtrl();
Example:
m_columnTree.GetTreeCtrl().ModifyStyle(TVS_HASLINES, 0);
Setting background bitmap
If owner-drawing is enabled you can specify what background bitmap to use.
BOOL
CCustomTreeChildCtrl::GetBkImage(
LVBKIMAGE* plvbkImage
) const;
BOOL
CCustomTreeChildCtrl::SetBkImage(
LVBKIMAGE* plvbkImage
);
Example:
LVBKIMAGE bk;
bk.xOffsetPercent = bk.yOffsetPercent = 70;
bk.hbm = LoadBitmap(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_BKGND));
m_columnTree.GetTreeCtrl().SetBkImage(&bk);
Processing child tree and header notifications
You can easily process notifications from child tree and header controls in your dialog's message map, because all notifications are forwarded to the parent.
Example:
ON_NOTIFY(NM_CLICK , IDC_COLUMNTREE, OnTreeClk)
void CMainDlg::OnTreeClk(NMHDR *pNMHDR, LRESULT *pResult)
{
NMTREEVIEW& nm = *(LPNMTREEVIEW)pNMHDR;
HTREEITEM hItem = m_columnTree.GetTreeCtrl().GetSelectedItem();
if(!hItem) return;
}