Printing A Tree |
|
This is a piece of code adapted from my "Class Map" project. It is assumed that the DC coming in has already had the desired font selected into it.
There is no download because you can copy-and-paste directly from this page. Or you can download the slight variant from the ClassMap project download
The printing is controlled by state managed in an instance of a GraphicInfo structure which is passed around the various calls. The initial caller must set up the parameters that control the appearance, and if printing, the page size.
#pragma once class GraphicInfo { public: GraphicInfo() { VerticalLineOffset = 0; ItemHeight = 0; LineWidth = 0; FontHeight = 0; PageRect.SetRectEmpty(); Printing = FALSE; PageStarted = FALSE; CurrentPosition = CPoint(0,0); } /******************************** | | +----Class | | | ..| The .. is the VerticalLineOffset ********************************/ int VerticalLineOffset; /******************************** | | +----Class 1___________________V | | | | | +----Class 2___________________ ^ ItemHeight is the vertical separation ********************************/ int ItemHeight; /******************************** | | +----Class |....|....|....| The .... is the LineWidth ********************************/ int LineWidth; int FontHeight; BOOL Printing; BOOL PageStarted; CRect PageRect; // printer page size when printing CPoint CurrentPosition; };
void DrawTree(CDC & dc, CTreeCtrl & ctl, HTREEITEM item, GraphicInfo & info) { CPoint start = info.CurrentPosition; if(info.Printing && !info.PageStarted) { /* start new page */ dc.StartPage(); info.CurrentPosition.y = info.PageRect.top; info.PageStarted = TRUE; } /* start new page */ IndentDrawing(dc, ctl, item, info); if(ctl.GetRootItem() != item) { /* vertical line */ int vertoffset; // The vertical offset above the current line /**************** drawn by IndentDrawing drawn here ___^____ | / \ V | | | | | or | | | | + ****************/ CPoint pos = info.CurrentPosition; //************************************ // Draws either a partial line if we are at the top or a full line if // we are within // +--- name // | <-- partial line here // +--- // | <-- full line here // +--- // The 'offset' tells how high to draw the line // so it draws either // xxxxxxx // | // or // | // | //************************************ if(ctl.GetPrevSiblingItem(item) == NULL) { /* case 1 */ vertoffset = (info.ItemHeight / 2) - (3 * info.FontHeight) / 4; // case 1 } /* case 1 */ else { /* case 2 */ vertoffset = (info.ItemHeight / 2); } /* case 2 */ vertoffset += info.FontHeight / 2; dc.MoveTo(pos.x + info.VerticalLineOffset, pos.y - vertoffset); // case 2 dc.LineTo(pos.x + info.VerticalLineOffset, pos.y + (info.ItemHeight / 2)); info.CurrentPosition = CPoint(start.x, pos.y + (info.ItemHeight / 2)); IndentDrawing(dc, ctl, item, info); } /* vertical line */ //**************************************************** // draw the horizontal line // +--- // --->| |<--- info.VerticalLineOffset //**************************************************** CPoint pos = info.CurrentPosition; CPoint startline = CPoint(pos.x + info.VerticalLineOffset, pos.y); dc.MoveTo(startline); CPoint endline = CPoint(pos.x + info.LineWidth, pos.y); dc.LineTo(endline); CPoint textpos = endline; dc.SetBkMode(TRANSPARENT); //--------------------------------------------------------------------------------------- // I promised it would get worse. For reasons equally unfathmomable (see the discussion // where I set m_hAttribDC), CDC::TextOut insists that the m_hAttribDC be *different* // than the m_hDC. This makes no sense, either, but to avoid annoying ASSERT messages, // I set it to NULL here! Then I have to set it back so CDC::GetCurrentPosition works! dc.m_hAttribDC = NULL; textpos.y -= info.FontHeight / 2; dc.TextOut(textpos.x, textpos.y, _T(" ") + ctl.GetItemText(item)); dc.m_hAttribDC = dc.m_hDC; //--------------------------------------------------------------------------------------- info.CurrentPosition = CPoint(start.x, textpos.y + info.ItemHeight); if(info.Printing && info.CurrentPosition.y > info.PageRect.bottom) { /* end page */ dc.EndPage(); info.PageStarted = FALSE; info.CurrentPosition.y = info.PageRect.top; } /* end page */ HTREEITEM child = ctl.GetChildItem(item); if(child != NULL) { /* write childrew */ DrawTree(dc, ctl, child, info); } /* write childrew */ HTREEITEM sib = ctl.GetNextSiblingItem(item); if(sib != NULL) DrawTree(dc, ctl, sib, info); } // DrawTree
void IndentDrawing(CDC & dc, CTreeCtrl & ctl, HTREEITEM item, GraphicInfo & info) { HTREEITEM parent = ctl.GetParentItem(item); if(parent == NULL) { /* top level */ return; } /* top level */ IndentDrawing(dc, ctl, parent, info); if(ctl.GetNextSiblingItem(parent) != NULL) { /* draw vertical bar */ int save = dc.SaveDC(); CPoint pos = info.CurrentPosition; dc.MoveTo(pos.x + info.VerticalLineOffset, pos.y - (info.ItemHeight / 2) - (info.FontHeight / 2)); dc.LineTo(pos.x + info.VerticalLineOffset, pos.y + (info.ItemHeight / 2)); dc.RestoreDC(save); info.CurrentPosition.x += info.VerticalLineOffset; } /* draw vertical bar */ int save = dc.SaveDC(); dc.SelectStockObject(NULL_PEN); dc.LineTo(info.CurrentPosition.x + info.LineWidth, info.CurrentPosition.y); CPoint newpos = dc.GetCurrentPosition(); dc.RestoreDC(save); info.CurrentPosition.x += info.LineWidth; } // IndentDrawing
This shows how I set up to print one particular output
void CCallGraphView::OnFilePrint() { CPrintDialog dlg(FALSE, PD_RETURNDC); if(dlg.DoModal() != IDOK) return; CDC dc; dc.Attach(dlg.GetPrinterDC()); CFont font; font.Attach(::GetStockObject(DEFAULT_GUI_FONT)); LOGFONT lf; font.GetLogFont(&lf); lf.lfHeight *= 10; font.Detach(); font.CreatePointFontIndirect(&lf, &dc); dc.SelectObject(&font); font.GetLogFont(&lf); TEXTMETRIC tm; dc.GetTextMetrics(&tm); GraphicInfo info; info.FontHeight = tm.tmHeight + tm.tmInternalLeading; info.LineWidth = 3 * tm.tmAveCharWidth; info.VerticalLineOffset = info.LineWidth / 2; info.ItemHeight = (3 * info.FontHeight) / 2; CSize sz; sz.cx = dc.GetDeviceCaps(HORZRES); sz.cy = dc.GetDeviceCaps(VERTRES); CSize dpi; dpi.cx = dc.GetDeviceCaps(LOGPIXELSX); dpi.cy = dc.GetDeviceCaps(LOGPIXELSY); const double HorizontalMargin = 0.5; const double VerticalMargin = 0.5; CSize margin; margin.cx = (int)((double)dpi.cx * HorizontalMargin); margin.cy = (int)((double)dpi.cy * VerticalMargin); info.PageRect.SetRectEmpty(); info.PageRect.left = margin.cx; info.PageRect.top = margin.cy; info.PageRect.right = sz.cx - 2 * margin.cx; info.PageRect.bottom = sz.cy - 2 * margin.cy; info.CurrentPosition = CPoint(info.PageRect.left, info.PageRect.top); info.Printing = TRUE; /**************************************************************** margin.cy |<--------------------- sz.cx ---------------------->| | -->| |<--margin.cx margin.cx-->| |<-- V +----------------------------------------------------+------------- | | ^ | (info.PageRect.left, info.PageRect.top) | | | *------------------------------------------+ |---------- | | | | | ^ | | | | | | | | | | | | : : : : | : : : : sz.cy : : : : | | | | | | | | | | | V | | +------------------------------------------* |---------- | | (info.PageRect.right ,info.PageRect.bottom) | | | | V +----------------------------------------------------+------------- ^ | margin.cy ****************************************************************/ CString docname; docname.LoadString(IDS_CALLGRAPH_DOC_NAME); dc.StartDoc(docname); DrawTree(dc, c_CallGraph, c_CallGraph.GetRootItem(), info); if(info.PageStarted) dc.EndPage(); dc.EndDoc(); dc->DeleteDC(); } // CCallGraphView::OnFilePrint
There is a method, CWnd::Print, which will send a WM_PRINT message to the window. This is not particularly useful for the purpose I needed. I wanted to print the entire, expanded tree, whether it was visible in the window or not, and whether or not a node was expanded in the display. The WM_PRINT mechanism renders exactly what is in the window, as displayed. If all you need is the contents of the actual window, then WM_PRINT will work, but that is not the problem I needed to solve.
It should be obvious how to modify the code above to handle collapsed nodes. When a recursion occurs, the state of the HTREEITEM can be checked, and if it is in a collapsed state, then the recursive call for its child nodes will not happen. I leave this as an Exercise For The Reader.
I didn't need to print bitmaps (as one might in a tree control that represented, say, file folders and files) so I did not include code to do that.
Page headings and footings are left as an Exercise For The Reader.
Date | Change |
2-Jun-07 |
Updated code to represent the latest version |
Added discussion of why CWnd::Print is inadequate for the task this solves | |
Added an example of doing the setup |
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.