Introduction
This article mainly shows the usage of a tree grid control which is inherited from Chris Maunder's famous CGridCtrl
. Ok folks, this is hopefully my last article related to the grid controls.
Background
In one my projects, I was required to come up with a Super Grid Control. I have used the ideas used there to develop a Grid Control inherited class which has similar features to a tree control.
There is another article on CodeProject related to this http://www.codeproject.com/KB/miscctrl/gridtreectrl.aspx, but I couldn't get into terms with the usage of the Tree concept used there. I needed something more similar with regards to inserting and deleting items in a tree.
Main Concept
The main concept of giving the tree feature to the grid is playing with its cells. What I did was develop a class which contains the tree related data structures such as tree item (similar to TVITEM
), and tree item's children and its states, etc. inside a class CTreeNode
. There is also another class with some APIs calling of which will give the space needed to draw a tree line and tree buttons, etc. and from that information draw the necessary parts of a tree (the lines and buttons) in the extra space allocated beside the normal drawing of cell.
I named the class CTreeCell
and it looks something like this:
class CTreeCell
{
friend class CTreeGridCtrl;
public:
CTreeCell(CGridCell* cell);
virtual BOOL TreeBtnHitTest(CPoint point, CRect rectCell, BOOL& bClickedOnBtnImgArea);
virtual void OnGetTreeCellExtent(CDC* pDC, CSize& size);
virtual BOOL OnDrawingTreeCell(CDC* pDC, int nItem, int nSubItem,
CRect& rect, BOOL bEraseBkgnd = TRUE);
virtual BOOL OnEditingTreeCell(int nItem, int nSubItem,
CRect& rect, CPoint point, UINT nID, UINT nChar);
virtual BOOL MeasureTreeButtonRect(CRect& rect);
virtual int GetTreeButtonMarginHorz();
virtual int GetTreeButtonMarginVert();
virtual int GetTreeBtnRightPadding();
virtual void DrawTreeItemButton(CDC* pDC, CRect rcButtonArea);
virtual BOOL MeasureTreeButtonImageRect(CRect& rect);
GTV_HTREENODE GetTreeItem();
int GetItemID();
int GetTreeItemLevel();
void SetExpanded(BOOL bExpanded = TRUE);
BOOL IsExpanded();
BOOL HasButton();
DWORD GetState() const;
BOOL IsEnabled() const;
UINT GetMargin() const;
protected:
virtual BOOL OnMeasureTreeButtonRect(CRect& rect, int nTreeItemLevel);
virtual BOOL CopyFromTreeCell(const CTreeCell& cell);
CPoint m_ptVertLineOrigin;
GTV_HTREENODE m_hTreeItem;
int m_nTreeItemLevel;
int m_nItem;
CGridCell* m_pGridCell;
};
I hope the comments in the APIs are clear enough to understand what are the purposes of the APIs in the class.
So the main idea is, a CGridCellBase
derived class will also derive this class to have the tree features incorporated in the cell.
For example:
class CTreeGridCellCheck : public CGridCellCheck, public CTreeCell
{
DECLARE_DYNCREATE(CTreeGridCellCheck)
public:
CTreeGridCellCheck();
virtual CSize GetCellExtent(CDC* pDC);
virtual BOOL Draw(CDC* pDC, int nItem, int nSubItem,
CRect rect, BOOL bEraseBkgnd = TRUE);
virtual BOOL Edit(int nItem, int nSubItem, CRect rect,
CPoint point, UINT nID, UINT nChar);
virtual void OnClick(CRect cellRect, CPoint PointCellRelative);
virtual void SetCoords(int nRow, int )
{
m_nItem = nRow;
}
protected:
virtual void OnCopy(const CGridCellBase& cell);
};
And you'd only override the required virtual functions of the CGridCellBase
as follows:
Derive a CTreeGridCell Class from CGridCell Class
For simplicity, instead of a check cell, let me give an example of a regular cell:
CTreeGridCell::CTreeGridCell() : CTreeCell(this)
{
}
CSize CTreeGridCell::GetCellExtent(CDC* pDC)
{
CSize size = CGridCell::GetCellExtent(pDC);
OnGetTreeCellExtent(pDC, size);
return size;
}
BOOL CTreeGridCell::Draw(CDC* pDC, int nItem, int nSubItem,
CRect rect, BOOL bEraseBkgnd )
{
BOOL bRet = FALSE;
if(OnDrawingTreeCell(pDC, nItem, nSubItem, rect, bEraseBkgnd))
bRet = CGridCell::Draw(pDC, nItem, nSubItem, rect, bEraseBkgnd);
return bRet;
}
BOOL CTreeGridCell::Edit(int nItem, int nSubItem,
CRect rect, CPoint point, UINT nID, UINT nChar)
{
BOOL bRet = FALSE;
if(OnEditingTreeCell(nItem, nSubItem, rect, point, nID, nChar))
bRet = CGridCell::Edit(nItem, nSubItem, rect, point, nID, nChar);
return bRet;
}
void CTreeGridCell::OnCopy(const CGridCellBase& cell)
{
const CTreeCell* pCell = dynamic_cast<const CTreeCell*>(&cell);
if(pCell)
{
this->CopyFromTreeCell(*pCell);
}
}
I had to make some slight changes in the original CGridCtrl
and its cells such as calling of OnCopy
while overriding the =
operator of a cell and in OnDraw
and some relevant methods which will be evident if the source code is browsed and made a windiff between my changed CGridCtrl
and the original code.
The most important control here is CGridCtrl
inherited class CTreeGridCtrl
. Those who are over interested can look up the implementation of the APIs in this class. But let me come to the point which is most important and that is using the code.
The CTreeGridCtrl Useful APIs at a Glance
GTV_HTREENODE InsertItem(LPCTSTR strHeading, GTV_HTREENODE hParent = GLVI_ROOT,
GTV_HTREENODE hInsertAfter = GLVI_LAST , BOOL bRefresh = TRUE);
GTV_HTREENODE InsertItem( GV_ITEM* pItem, GTV_HTREENODE hParent = GLVI_ROOT,
GTV_HTREENODE hInsertAfter = GLVI_LAST, BOOL bRefresh = TRUE);
int InsertItem(LPCTSTR strHeading, int nItem, BOOL bRefresh);
int InsertItem(GV_ITEM* pItem, BOOL bRefresh);
BOOL DeleteItem(GTV_HTREENODE hNode);
BOOL DeleteItem(int nItem);
BOOL DeleteAllItems();
BOOL ExpandTreeNode(GTV_HTREENODE hNode);
BOOL CollapseTreeNode(GTV_HTREENODE hNode);
void ShowTreeNodeButton(GTV_HTREENODE hNode, BOOL bShow = TRUE);
BOOL IsShownTreeNodeButton(GTV_HTREENODE hNode);
BOOL MoveTreeNode(GTV_HTREENODE hNode, GTV_HTREENODE hNewParent,
GTV_HTREENODE hInsertAfter = GLVI_LAST);
int FindRowIndex(GTV_HTREENODE hNode);
BOOL FindChildrenRowRange(GTV_HTREENODE hParentNode, CItemRange& range);
void SetTreeLineColors(COLORREF clrTreeVertLine, COLORREF clrTreeHorzLine);
COLORREF GetHorzTreeLineColor();
COLORREF GetVertTreeLineColor();
BOOL SortItems(int nColumn, BOOL bAscending, LPARAM data = 0);
BOOL SortItems(PFNLVCOMPARE pfnCompare, int nColumn, BOOL bAscending, LPARAM data = 0);
BOOL SortTextItems(int nColumn, BOOL bAscending, LPARAM data = 0);
BOOL SortItems(GTV_HTREENODE hParent, int nColumn,
BOOL bAscending, LPARAM data, BOOL bRecursive = TRUE);
BOOL SortItems(GTV_HTREENODE hParent,
PFNLVCOMPARE pfnCompare, int nColumn, BOOL bAscending,
LPARAM data = 0, BOOL bRecursive = TRUE);
BOOL NodeHasChildren(GTV_HTREENODE hNode);
GTV_HTREENODE GetChildNode(GTV_HTREENODE hNode);
GTV_HTREENODE GetNextSiblingNode(GTV_HTREENODE hNode);
GTV_HTREENODE GetPrevSiblingNode(GTV_HTREENODE hNode);
GTV_HTREENODE GetParentNode(GTV_HTREENODE hNode);
GTV_HTREENODE GetRootNode();
GTV_HTREENODE GetTreeNode(int nItem);
CTreeCell* GetTreeCell(GTV_HTREENODE hItem);
void TraverseNodes(GTV_HTREENODE hParentItem, int& nRowIndex);
BOOL SetItemCount(int nItems);
int GetItemCount()
{
return GetRowCount();
}
int SetChildItemCount(int nParentItemIndex, int nItems);
BOOL ShowRow(int nRow, BOOL bShow = TRUE);
BOOL SetTreeColumnCellTypeID(enum CellTypeID cellType);
BOOL SetCellTypeID(int nRow, int nColumn, enum CellTypeID cellType);
CellTypeID GetCellTypeID(int nRow, int nColumn);
BOOL SetColumnCellTypeID(int nColumn, enum CellTypeID cellType);
void SetCellText(int row, int col, LPCTSTR pszText);
void SetCellState(int row, int col, DWORD nState);
DWORD GetCellState(int row, int col);
void SetTreeColumn(int nTreeCol)
{
m_nTreeCol = nTreeCol;
}
int GetTreeColumn()
{
return m_nTreeCol;
}
Using the Code
Here is a look at how to initialize the tree:
GTV_HTREENODE hItem1;
GTV_HTREENODE hItem2;
GTV_HTREENODE hItem3;
GTV_HTREENODE hItem4;
GTV_HTREENODE hItem5;
GTV_HTREENODE hItemChild;
GTV_HTREENODE hItemChild1;
GTV_HTREENODE hItemChild2;
GTV_HTREENODE hItemChild3;
GTV_HTREENODE hItemChild4;
GTV_HTREENODE hItemChild2leveldown;
GTV_HTREENODE hItem3Child1;
GTV_HTREENODE hItem4Child;
GTV_HTREENODE hItem4Child1;
GTV_HTREENODE hItem2Child1;
m_nFixCols = 1;
m_nFixRows = 1;
m_nCols = 1;
m_nRows = 1;
TRY {
m_Grid.SetFixedRowCount(m_nFixRows);
m_Grid.SetFixedColumnCount(m_nFixCols);
}
CATCH (CMemoryException, e)
{
e->ReportError();
return;
}
END_CATCH
m_Grid.SetRedraw(FALSE);
m_Grid.InsertColumn(_T("Tree Column 0"));
m_Grid.InsertColumn(_T("Column 1"));
m_Grid.InsertColumn(_T("Column 2"));
m_Grid.InsertColumn(_T("Column 3"));
m_Grid.InsertColumn(_T("Column 4"));
m_Grid.InsertColumn(_T("Column 5"));
m_Grid.SetRedraw(TRUE);
hItem1 = m_Grid.InsertItem(_T("Root Item1"));
hItem2 = m_Grid.InsertItem(_T("Root ItemX2"));
hItem4 = m_Grid.InsertItem(_T("Root Item4"));
GV_ITEM Item;
Item.mask = GVIF_TEXT;
Item.row = 0;
Item.col = 0;
Item.strText = _T("Root Item3");
Item.iImage = rand()%m_ImageList.GetImageCount();
Item.mask |= (GVIF_IMAGE);
hItem3 = m_Grid.InsertItem(&Item);
hItemChild = m_Grid.InsertItem(_T("Item1-Child 1"), hItem1);
hItemChild1 = hItemChild;
hItemChild2 = m_Grid.InsertItem(_T("Item1-Child 2"), hItem1);
hItemChild3 = m_Grid.InsertItem(_T("Item1-Child 3"), hItem1);
hItem2Child1 = m_Grid.InsertItem(_T("Item2-Child 1"), hItem2);
m_Grid.InsertItem(_T("Item1-Child 1-Child1"), hItemChild);
m_Grid.InsertItem(_T("2 level down"), hItemChild);
Item.mask &= ~GVIF_IMAGE;
Item.strText = _T("2 level down 2");
hItemChild2leveldown = m_Grid.InsertItem(&Item, hItemChild);
hItem3Child1 = m_Grid.InsertItem(_T("Item3-Child 1"), hItem3);
m_Grid.SetRedraw(FALSE);
CString str;
for (int row = 1; row < m_Grid.GetItemCount(); row++)
{
for (int col = 2; col < m_Grid.GetColumnCount(); col++)
{
Item.mask = GVIF_TEXT;
Item.row = row;
Item.col = col;
if (col < 1)
str.Format(_T("Row %d"), row);
else
str.Format(_T("%d"),row*col);
Item.strText = str;
if (col == 4)
{
Item.iImage = rand()%m_ImageList.GetImageCount();
Item.mask |= (GVIF_IMAGE);
}
m_Grid.SetItem(&Item);
if(row == 4 && col == 4)
{
m_Grid.SetCellTypeID(row, m_Grid.GetTreeColumn(), CT_CHECKBOX);
}
}
}
hItem5 = m_Grid.InsertItem(_T("Moved Item"));
m_Grid.MoveTreeNode(hItem5, hItem4);
GTV_HTREENODE
is a typedef
of a pointer to the CTreeNode
class which contains the necessary tree children and tree state such as expanded/collapsed, etc. related information.
This also keeps the corresponding CGridCtrl
row pointer to map between the Grid
and the actual tree structure which is maintained by the CTreeGridCtrl
.
Points of Interest
The sorting of the tree items is the most interesting part. I have given a provision in the CTreeGridCtrl
to attach and detach a range of rows from the grid. Using this feature, I detach a set of child rows at the deepest level, sort it and then attach to the parent node and run the same process recursively also for the parent nodes and the parent of parent nodes up to the top level which ends below the root node.
History
- 9th November, 2010: Article uploaded