Printing a tree view is not as simple as calling WM_PAINT
message, the default printing only prints visible parts of the tree view. In the article contributed by Mike Wild entitled A Print Enabled Tree Control , a really good job has been done. However, the drawing of the tree items has to be done by the program itself, which requires a long program code. In this article, I propose a simpler way to draw the tree view by calling the default WM_PAINT
message. The program can print the whole region of the tree view not restricted by the current window size (horizontally and vertically) by playing some tricks. Also, the background color of the tree view window is removed during printing, as background color is normally unwanted during printing. The program does pagination automatically as the tree view might exceed one page. Header and footer are inserted in the printing, too.
The trick behind the program is, the program enlarges the window size to cover the entire boundary of the tree view, then the program calls the WM_PAINT
message to perform default printing to a DC. The background color of the DC is then removed. The code is modified from the article Setting a background color. The prepared tree bitmap in its device-dependent form cannot be sent directly to the printer DC, unexpected result might be obtained. The device-dependent bitmap is converted to DIB using DDBToDIB()
function. This function is copied from Converting DDB to DIB. After that, the DIB is sent to the printer DC using StretchDIBits()
function.
If the tree view is longer than the paper size, the program determines the maximum rows per page and paginates the tree view into several pages accordingly. After printing, the window is restored to its original size and position. Besides printing the tree view, the program also copies the prepared tree view bitmap to the clipboard for the user to save the bitmap elsewhere.
Tree view header file
Several message handling functions have to be declared using the class wizard, i.e. OnPreparePrinting()
, OnBeginPrinting()
, OnPrepareDC()
, OnPrint()
, and OnEndPrinting()
.
In the tree view header file, include the following variable declarations and function declaration:
public:
CTreeCtrl* Tree;
protected:
CImageList m_treeicon;
private:
CRect rcBounds;
int m_nCharWidth;
int m_nRowHeight;
int m_nRowsPerPage;
HANDLE hDIB;
WINDOWPLACEMENT WndPlace;
public:
void PrintHeadFoot(CDC *pDC, CPrintInfo *pInfo);
HANDLE DDBToDIB( CBitmap& bitmap,
DWORD dwCompression, CPalette* pPal );
<!-- end the block of source code -->
Tree view implementation file
In the tree view constructor, add the following line. This is a pointer for easier access to the CTreeCtrl
class associated with the tree view.
CPrtTViewView::CPrtTViewView()
{
Tree=&GetTreeCtrl();
}
<!-- end the block of source code -->
After creating the message handlers, replace them with the following code:
#define LEFT_MARGIN 4
#define RIGHT_MARGIN 4
#define TOP_MARGIN 4
#define BOTTOM_MARGIN 4
BOOL CPrtTViewView::OnPreparePrinting(CPrintInfo* pInfo)
{
return DoPreparePrinting(pInfo);
}
void CPrtTViewView::OnBeginPrinting(CDC* pDC,
CPrintInfo* pInfo)
{
HTREEITEM hItem=Tree->GetRootItem();
Tree->GetItemRect(hItem,rcBounds,TRUE);
m_nRowHeight = rcBounds.Height();
int ItemCount=0;
do
{
ItemCount++;
CRect rc;
Tree->GetItemRect(hItem,rc,TRUE);
if (rc.right>rcBounds.right)
rcBounds.right=rc.right;
hItem=Tree->GetNextItem(hItem,
TVGN_NEXTVISIBLE);
}
while (hItem);
int ScrollMin,ScrollMax;
GetScrollRange(SB_HORZ,&ScrollMin,
&ScrollMax);
rcBounds.left=0;
if (ScrollMax>rcBounds.right)
rcBounds.right=ScrollMax+1;
rcBounds.top=0;
rcBounds.bottom=m_nRowHeight*ItemCount;
CDC *pCtlDC = Tree->GetDC();
if (NULL == pCtlDC) return;
TEXTMETRIC tm;
pCtlDC->GetTextMetrics(&tm);
m_nCharWidth = tm.tmAveCharWidth;
double d = (double)pDC->GetDeviceCaps(LOGPIXELSY)/
(double)pCtlDC->GetDeviceCaps(LOGPIXELSY);
ReleaseDC(pCtlDC);
int nPageHeight = pDC->GetDeviceCaps(VERTRES);
m_nRowsPerPage = (int)((double)nPageHeight/d)/
m_nRowHeight-TOP_MARGIN-BOTTOM_MARGIN;
int pages=(ItemCount-1)/m_nRowsPerPage+1;
pInfo->SetMaxPage(pages);
CPaintDC dc(this);
CDC MemDC;
MemDC.CreateCompatibleDC(&dc);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(&dc,
rcBounds.Width(), rcBounds.Height() );
MemDC.SelectObject(&bitmap);
GetWindowPlacement(&WndPlace);
MoveWindow(0,0,
::GetSystemMetrics(SM_CXEDGE)*2+rcBounds.Width(),
::GetSystemMetrics(SM_CYEDGE)*2+rcBounds.Height(),
FALSE);
ShowScrollBar(SB_BOTH,FALSE);
Tree->EnsureVisible(Tree->GetRootItem());
CWnd::DefWindowProc( WM_PAINT,
(WPARAM)MemDC.m_hDC, 0);
CDC MaskDC;
MaskDC.CreateCompatibleDC(&dc);
CBitmap maskBitmap;
maskBitmap.CreateBitmap(rcBounds.Width(),
rcBounds.Height(), 1, 1, NULL);
MaskDC.SelectObject( &maskBitmap );
MemDC.SetBkColor(::GetSysColor(COLOR_WINDOW));
MaskDC.BitBlt( 0, 0, rcBounds.Width(),
rcBounds.Height(), &MemDC,
rcBounds.left, rcBounds.top,
SRCCOPY );
CBitmap clipbitmap;
clipbitmap.CreateCompatibleBitmap(&dc,
rcBounds.Width(), rcBounds.Height());
CDC clipDC;
clipDC.CreateCompatibleDC(&dc);
CBitmap* pOldBitmap =
clipDC.SelectObject(&clipbitmap);
clipDC.BitBlt( 0, 0, rcBounds.Width(),
rcBounds.Height(), &MemDC,
rcBounds.left, rcBounds.top, SRCCOPY);
OpenClipboard();
EmptyClipboard();
SetClipboardData(CF_BITMAP,
clipbitmap.GetSafeHandle());
CloseClipboard();
clipDC.SelectObject(pOldBitmap);
clipbitmap.Detach();
MemDC.SetBkColor(RGB(0,0,0));
MemDC.SetTextColor(RGB(255,255,255));
MemDC.BitBlt(rcBounds.left, rcBounds.top,
rcBounds.Width(), rcBounds.Height(),
&MaskDC, rcBounds.left,
rcBounds.top, MERGEPAINT);
CPalette pal;
hDIB=DDBToDIB(bitmap, BI_RGB, &pal );
}
void CPrtTViewView::OnPrepareDC(CDC* pDC,
CPrintInfo* pInfo)
{
CTreeView::OnPrepareDC(pDC, pInfo);
pDC->SetMapMode(MM_ANISOTROPIC);
CClientDC dcScreen(NULL);
pDC->SetWindowExt(dcScreen.GetDeviceCaps(LOGPIXELSX),
dcScreen.GetDeviceCaps(LOGPIXELSX));
pDC->SetViewportExt(pDC->GetDeviceCaps(LOGPIXELSX),
pDC->GetDeviceCaps(LOGPIXELSX));
}
void CPrtTViewView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
int nSavedDC = pDC->SaveDC();
CFont Font;
LOGFONT lf;
CFont *pOldFont = GetFont();
pOldFont->GetLogFont(&lf);
lf.lfHeight=m_nRowHeight-1;
lf.lfWidth=0;
Font.CreateFontIndirect(&lf);
pDC->SelectObject(&Font);
PrintHeadFoot(pDC,pInfo);
pDC->SetWindowOrg(-1*(LEFT_MARGIN*m_nCharWidth),
-m_nRowHeight*TOP_MARGIN);
int height;
if (pInfo->m_nCurPage==pInfo->GetMaxPage())
height=
rcBounds.Height()-
((pInfo->m_nCurPage-1)*m_nRowsPerPage*m_nRowHeight);
else
height=m_nRowsPerPage*m_nRowHeight;
int top=(pInfo->m_nCurPage-1)*m_nRowsPerPage*m_nRowHeight;
pDC->SetBkColor(RGB(255,255,255));
pDC->SetTextColor(RGB(0,0,0));
LPBITMAPINFOHEADER lpbi;
lpbi = (LPBITMAPINFOHEADER)hDIB;
int nColors = lpbi->biClrUsed ?
lpbi->biClrUsed : 1 << lpbi->biBitCount;
BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB;
LPVOID lpDIBBits;
if( bmInfo.bmiHeader.biBitCount > 8 )
lpDIBBits = (LPVOID)((LPDWORD)(bmInfo.bmiColors +
bmInfo.bmiHeader.biClrUsed) +
((bmInfo.bmiHeader.biCompression == BI_BITFIELDS) ?
3 : 0));
else
lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
HDC hDC=pDC->GetSafeHdc();
StretchDIBits(hDC,
0,
0,
rcBounds.Width(),
height,
rcBounds.left,
rcBounds.Height()-top-height,
rcBounds.Width(),
height,
lpDIBBits,
&bmInfo,
DIB_RGB_COLORS,
SRCCOPY);
pDC->SelectObject(pOldFont);
pDC->RestoreDC( nSavedDC );
}
void CPrtTViewView::OnEndPrinting(CDC* pDC, CPrintInfo* pInfo)
{
GlobalFree(hDIB);
SetWindowPlacement(&WndPlace);
RedrawWindow();
}
void CPrtTViewView::PrintHeadFoot(CDC *pDC, CPrintInfo *pInfo)
{
CClientDC dcScreen(NULL);
CRect rc;
rc.top=m_nRowHeight*(TOP_MARGIN-2);
rc.bottom = (int)((double)(pDC->GetDeviceCaps(VERTRES)*
dcScreen.GetDeviceCaps(LOGPIXELSY))
/(double)pDC->GetDeviceCaps(LOGPIXELSY));
rc.left = LEFT_MARGIN*m_nCharWidth;
rc.right = (int)((double)(pDC->GetDeviceCaps(HORZRES)*
dcScreen.GetDeviceCaps(LOGPIXELSX))
/(double)pDC->GetDeviceCaps(LOGPIXELSX))-
RIGHT_MARGIN*m_nCharWidth;
CString sTemp;
sTemp=GetDocument()->GetTitle();
sTemp+=" object hierarchy";
CRect header(rc);
header.bottom=header.top+m_nRowHeight;
pDC->DrawText(sTemp, header,
DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER);
rc.top = rc.bottom - m_nRowHeight*(BOTTOM_MARGIN-1);
rc.bottom = rc.top + m_nRowHeight;
sTemp.Format("Page %d/%d",pInfo->m_nCurPage,
pInfo->GetMaxPage());
pDC->DrawText(sTemp,rc,
DT_CENTER | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER);
}
HANDLE CPrtTViewView::DDBToDIB(CBitmap& bitmap,
DWORD dwCompression, CPalette* pPal)
{
BITMAP bm;
BITMAPINFOHEADER bi;
LPBITMAPINFOHEADER lpbi;
DWORD dwLen;
HANDLE hDIB;
HANDLE handle;
HDC hDC;
HPALETTE hPal;
ASSERT( bitmap.GetSafeHandle() );
if ( dwCompression == BI_BITFIELDS )
return NULL;
hPal = (HPALETTE) pPal->GetSafeHandle();
if (hPal==NULL)
hPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE);
bitmap.GetObject(sizeof(bm),(LPSTR)&bm);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bm.bmWidth;
bi.biHeight = bm.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = bm.bmPlanes * bm.bmBitsPixel;
bi.biCompression = dwCompression;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
int nColors = (1 << bi.biBitCount);
if ( nColors > 256 )
nColors = 0;
dwLen = bi.biSize + nColors * sizeof(RGBQUAD);
hDC = ::GetDC(NULL);
hPal = SelectPalette(hDC,hPal,FALSE);
RealizePalette(hDC);
hDIB = GlobalAlloc(GMEM_FIXED,dwLen);
if (!hDIB)
{
SelectPalette(hDC,hPal,FALSE);
::ReleaseDC(NULL,hDC);
return NULL;
}
lpbi = (LPBITMAPINFOHEADER)hDIB;
*lpbi = bi;
GetDIBits(hDC, (HBITMAP)bitmap.GetSafeHandle(),
0L, (DWORD)bi.biHeight,
(LPBYTE)NULL, (LPBITMAPINFO)lpbi,
(DWORD)DIB_RGB_COLORS);
bi = *lpbi;
if (bi.biSizeImage == 0)
{
bi.biSizeImage =
((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8)
* bi.biHeight;
if (dwCompression != BI_RGB)
bi.biSizeImage = (bi.biSizeImage * 3) / 2;
}
dwLen += bi.biSizeImage;
if (handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE))
hDIB = handle;
else
{
GlobalFree(hDIB);
SelectPalette(hDC,hPal,FALSE);
::ReleaseDC(NULL,hDC);
return NULL;
}
lpbi = (LPBITMAPINFOHEADER)hDIB;
BOOL bGotBits = GetDIBits( hDC,
(HBITMAP)bitmap.GetSafeHandle(),
0L,
(DWORD)bi.biHeight,
(LPBYTE)lpbi
+ (bi.biSize + nColors * sizeof(RGBQUAD)),
(LPBITMAPINFO)lpbi,
(DWORD)DIB_RGB_COLORS);
if( !bGotBits )
{
GlobalFree(hDIB);
SelectPalette(hDC,hPal,FALSE);
::ReleaseDC(NULL,hDC);
return NULL;
}
SelectPalette(hDC,hPal,FALSE);
::ReleaseDC(NULL,hDC);
return hDIB;
}
<!-- end the block of source code -->