A Print Enabled Tree View

I've benefited a lot from Codeguru, and now is my turn to contribute a bit. 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 in the 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 determine the maximum rows per page and paginates the tree view into several pages accordingly. After printing, the window is restored to the original size and position. Besides printing the tree view, the program also copies the prepared tree view bitmap to the clipboard for 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:


// Attributes
public:
	CTreeCtrl* Tree;

protected:
	CImageList m_treeicon;

private:
	CRect rcBounds;
	int m_nCharWidth;
	int m_nRowHeight;
	int m_nRowsPerPage;
	HANDLE hDIB;
	WINDOWPLACEMENT WndPlace;

// Operations
public:
	void PrintHeadFoot(CDC *pDC, CPrintInfo *pInfo);
	HANDLE DDBToDIB( CBitmap& bitmap, DWORD dwCompression, CPalette* pPal );

Tree view implementation file:

At 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();
}

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();

	// Find the total number of visible items & the right most coordinate
	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);

	// Find the entire print boundary
	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;

	// Get text width
	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);

	// Find rows per page
	int nPageHeight = pDC->GetDeviceCaps(VERTRES);
	m_nRowsPerPage = (int)((double)nPageHeight/d)/m_nRowHeight-TOP_MARGIN-BOTTOM_MARGIN;

	// Set maximum pages
	int pages=(ItemCount-1)/m_nRowsPerPage+1;
	pInfo->SetMaxPage(pages);

	// Create a memory DC compatible with the paint DC
	CPaintDC dc(this);
	CDC MemDC;
	MemDC.CreateCompatibleDC(&dc);

	// Select a compatible bitmap into the memory DC
	CBitmap bitmap;
	bitmap.CreateCompatibleBitmap(&dc, rcBounds.Width(), rcBounds.Height() );
	MemDC.SelectObject(&bitmap);

	// Enlarge window size to include the whole print area boundary
	GetWindowPlacement(&WndPlace);
	MoveWindow(0,0,::GetSystemMetrics(SM_CXEDGE)*2+rcBounds.Width(),
		::GetSystemMetrics(SM_CYEDGE)*2+rcBounds.Height(),FALSE);
	ShowScrollBar(SB_BOTH,FALSE);

	// Call the default printing
	Tree->EnsureVisible(Tree->GetRootItem());
	CWnd::DefWindowProc( WM_PAINT, (WPARAM)MemDC.m_hDC, 0 );

	// Now create a mask
	CDC MaskDC;
	MaskDC.CreateCompatibleDC(&dc);
	CBitmap maskBitmap;

	// Create monochrome bitmap for the mask
	maskBitmap.CreateBitmap( rcBounds.Width(), rcBounds.Height(), 1, 1, NULL );
	MaskDC.SelectObject( &maskBitmap );
	MemDC.SetBkColor( ::GetSysColor( COLOR_WINDOW ) );

	// Create the mask from the memory DC
	MaskDC.BitBlt( 0, 0, rcBounds.Width(), rcBounds.Height(), &MemDC,
		rcBounds.left, rcBounds.top, SRCCOPY );

	// Copy image to clipboard
	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();

	// Copy the image in MemDC transparently
	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);

	// Map logical unit of screen to printer unit
	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) 
{
	// Save dc state
	int nSavedDC = pDC->SaveDC();

	// Set font
	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,				// hDC
		0,							// DestX
		0,							// DestY
		rcBounds.Width(),			// nDestWidth
		height,						// nDestHeight
		rcBounds.left,				// SrcX
		rcBounds.Height()-top-height,	// SrcY
		rcBounds.Width(),			// wSrcWidth
		height,						// wSrcHeight
		lpDIBBits,					// lpBits
		&bmInfo,					// lpBitsInfo
		DIB_RGB_COLORS,				// wUsage
		SRCCOPY);					// dwROP

	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;

	// Print App title on top left corner
	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;

	// Print draw page number at bottom center
	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() );

	// The function has no arg for bitfields
	if ( dwCompression == BI_BITFIELDS )
		return NULL;

	// If a palette has not been supplied use defaul palette
	hPal = (HPALETTE) pPal->GetSafeHandle();
	if (hPal==NULL)
		hPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE);

	// Get bitmap information
	bitmap.GetObject(sizeof(bm),(LPSTR)&bm);

	// Initialize the bitmapinfoheader
	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;

	// Compute the size of the  infoheader and the color table
	int nColors = (1 << bi.biBitCount);
	if ( nColors > 256 ) 
		nColors = 0;
	dwLen = bi.biSize + nColors * sizeof(RGBQUAD);

	// We need a device context to get the DIB from
	hDC = ::GetDC(NULL);
	hPal = SelectPalette(hDC,hPal,FALSE);
	RealizePalette(hDC);

	// Allocate enough memory to hold bitmapinfoheader and color table
	hDIB = GlobalAlloc(GMEM_FIXED,dwLen);

	if (!hDIB)
	{
		SelectPalette(hDC,hPal,FALSE);
		::ReleaseDC(NULL,hDC);
		return NULL;
	}

	lpbi = (LPBITMAPINFOHEADER)hDIB;

	*lpbi = bi;

	// Call GetDIBits with a NULL lpBits param, so the device driver 
	// will calculate the biSizeImage field 
	GetDIBits(hDC, (HBITMAP)bitmap.GetSafeHandle(), 0L, (DWORD)bi.biHeight,
		(LPBYTE)NULL, (LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS);

	bi = *lpbi;

	// If the driver did not fill in the biSizeImage field, then compute it
	// Each scan line of the image is aligned on a DWORD (32bit) boundary
	if (bi.biSizeImage == 0)
	{
		bi.biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) 
			* bi.biHeight;

		// If a compression scheme is used the result may infact be larger
		// Increase the size to account for this.
		if (dwCompression != BI_RGB)
			bi.biSizeImage = (bi.biSizeImage * 3) / 2;
	}

	// Realloc the buffer so that it can hold all the bits
	dwLen += bi.biSizeImage;
	if (handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE))
		hDIB = handle;
	else
	{
		GlobalFree(hDIB);

		// Reselect the original palette
		SelectPalette(hDC,hPal,FALSE);
		::ReleaseDC(NULL,hDC);
		return NULL;
	}

	// Get the bitmap bits
	lpbi = (LPBITMAPINFOHEADER)hDIB;

	// FINALLY get the DIB
	BOOL bGotBits = GetDIBits( hDC, (HBITMAP)bitmap.GetSafeHandle(),
							0L,                             // Start scan line
							(DWORD)bi.biHeight,             // # of scan lines
							(LPBYTE)lpbi                    // address for bitmap bits
							+ (bi.biSize + nColors * sizeof(RGBQUAD)),
							(LPBITMAPINFO)lpbi,             // address of bitmapinfo
							(DWORD)DIB_RGB_COLORS);         // Use RGB for color table

	if( !bGotBits )
	{
		GlobalFree(hDIB);

		SelectPalette(hDC,hPal,FALSE);
		::ReleaseDC(NULL,hDC);
		return NULL;
	}

	SelectPalette(hDC,hPal,FALSE);
	::ReleaseDC(NULL,hDC);
	return hDIB;
}

Download demo project - 49 KB



Comments

  • Achieve The Insiders Info On The mulberry bags Before You're Too Late

    Posted by hekvvddtest on 05/03/2013 03:02pm

    The medial side has a herringbone outsole that provides equal gripping cheap Air Jordan Shoes Store china. [url=http://nikeairmax2013.topvipshoes.co.uk/]air max 2013[/url] [url=http://airmax.topvipshoes.co.uk/]air max[/url] in all directions maintaining durability and support factors.

    Reply
  • Current market Announcement - gucci Defined as Essential Right now

    Posted by incockDak on 03/29/2013 08:03am

    The Secret rule the gucci-arena Is Rather Simple and easy! [url=http://growth-management.alachua.fl.us/comprehensive_planning/gucci.html]gucci 財布[/url] All new longchamp E book Tells Methods To Dominate The longchamp Arena [url=http://growth-management.alachua.fl.us/comprehensive_planning/saclongchamp.php]Sac longchamps[/url] XblHtuXqwCpc [url=http://running-nike0.seesaa.net/]nike ランニング[/url]AdhQpzRrkHqa [[url=http://free-nike-nikeo.seesaa.net/]ナイキ フリー[/url]EzhDgyUxpIkg [url=http://xn--nike-ul4c5c5fyqb.seesaa.net/]nike スニーカー[/url]CgrUckUtlOvl [url=http://nikejapan0.seesaa.net/]ナイキスニーカー[/url]VspZgsWziAbw [url=http://nikesneakersjp.seesaa.net/]nike[/url]HilZzwAueNyb [url=http://nikegolf00.seesaa.net/]ナイキ[/url]FjqRxnOgxDga [url=http://nikeshoes00.seesaa.net/]シューズナイキ[/url]OgqUshTirGxa [url=http://sneaker-adidas-jp.seesaa.net/]adidas スニーカー[/url]BmeMaqUbgDkc

    Reply
  • Drop Whining And Start a personal adidas shoes Program In exchange

    Posted by expopmerm on 03/18/2013 01:54am

    The 40 MostLoonie nike shoes Secrets... And How To Utilize them!!|Quick guide explains the most important workings of the nike shoes in addition to those things one should do straight away.}[url=http://www.nikejapan.asia/]ナイキ air [/url] Discover who is expounding on adidas shoes and the main reason why you ought to be afraid. [url=http://www.adidasjapan.biz/]アディダス シューズ[/url] Update- adidas shoes Will certainly Have An Essential role In Any Site administration Gossip- gucci May Play Substantial role In Any Website administration [url=http://www.guccijp.asia/]gucci 財布[/url] Great new chloe Guide Will show How To Dominate The chloe Market [url=http://www.chloejp.biz/]chloe 財布[/url] Practical ideas on how to learn nearly anything there is to find out around chanel purse in nine clear-cut steps. [url=http://www.chaneljp.biz/]財布 chanel[/url] Here is how to discover just about every thing there is to know regarding chanel bags in Five straightforward steps.A Lazy Man's Method For The nike shoes Achievement [url=http://www.adidasjapan.asia/]adidas シューズ[/url] A strong double twirl on nike shoes [url=http://www.nikejp.biz/]nike スニーカー[/url] Actual Tips To Learn about nike shoes And Ways In Which One Might Be part of The nike shoes Elite

    Reply
  • I need help

    Posted by Legacy on 02/25/2003 12:00am

    Originally posted by: Sascha

    Hello to everybody.

    I have a problem. I set up a new MFC - Project with a dialog based application.
    I add a form to my project with a CTreeCtrl on it.
    Now I have 2 froms. The first form is my "login-form" and the second is the on with the tree.
    Now I want to Insert Items out of an oracle database, in the three when I have a successful login. I want to insert items in the tree while the login form is open.(The other form is closed)
    The compiler give me a untreated exception in mfc42.dll.

    Must I open the form with the tree for that operation?
    Did I need a pointer?
    What errors did I made?

    Could anybody help me?

    My code for the Tree is : (inster)

    TV_INSERTSTRUCT TreeCtrlItem;

    TreeCtrlItem.hParent = TVI_ROOT;
    TreeCtrlItem.hInsertAfter = TVI_LAST;
    TreeCtrlItem.item.mask = TVIF_TEXT | TVIF_PARAM;
    TreeCtrlItem.item.pszText = "SAMPLES";
    TreeCtrlItem.item.lParam = 0;
    HTREEITEM hTreeItem1 =
    m_structure_tree.InsertItem(&TreeCtrlItem);

    Reply
  • Very Nice Work

    Posted by Legacy on 11/07/2002 12:00am

    Originally posted by: William Campbell


    Anyone needing to do any printing in an MDI /SDI app need not look further than this code, excellent.

    Reply
  • Vertical Scroll Bar Fix

    Posted by Legacy on 05/06/2002 12:00am

    Originally posted by: Paul Belikian

    http://www.codeproject.com/treectrl/prttview.asp#xx2329xx

    Reply
  • If no default printer

    Posted by Legacy on 08/28/2001 12:00am

    Originally posted by: Mary

    if there is no default printer, on closing the print preview the tree view is lost....

    can you help me on how to display the print preview contents on a screen even if there is no printer attached.

    Reply
  • Printing large trees/lists

    Posted by Legacy on 06/27/2001 12:00am

    Originally posted by: Dave Barratt

    I converted Koay Kah Hoe excellent code to print a virtual list view in report mode with good results. But I was wondering if anyone has a technique for overcoming the size limitation of approx 25 pages caused by stretching the bitmap beyond memory limitations?

    If anyone wants the list view print code I can let them have it.

    Reply
  • Is it possible to convert to an OCX?

    Posted by Legacy on 12/14/2000 12:00am

    Originally posted by: Ram

    Hi
    Is it possible to convert this to an OCX file, so that it can be used in a VB application. Can anyone give any ideas regarding this.

    Thanks for ur help

    Reply
  • GlobalReAlloc

    Posted by Legacy on 05/11/2000 12:00am

    Originally posted by: Janet Aiken

    I'm running with BoundsChecker on in VCPP 6.0. When it hits the GlobalReAlloc function I get a memory leak error. Anyone have any ideas why or how I can fix this. Thanks

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds