支持打印的树视图





5.00/5 (8投票s)
用于向树形视图添加打印功能的代码。
打印树形视图不像调用 WM_PAINT
消息那么简单,默认打印只会打印树形视图的可见部分。在 Mike Wild 撰写的文章 支持打印的树形控件 中,已经完成了一项非常好的工作。但是,树形项目本身的绘制需要由程序完成,这需要大量的程序代码。 在本文中,我提出了一种更简单的方法,通过调用默认的 WM_PAINT
消息来绘制树形视图。 通过一些技巧,程序可以打印树形视图的整个区域,而不受当前窗口大小的限制(水平和垂直方向)。 此外,在打印期间会删除树形视图窗口的背景颜色,因为通常在打印期间不需要背景颜色。 程序还会自动分页,因为树形视图可能超过一页。 标题和页脚也会插入到打印中。
该程序的技巧在于,程序会扩大窗口大小以覆盖树形视图的整个边界,然后程序调用 WM_PAINT
消息以对 DC 执行默认打印。 然后删除 DC 的背景颜色。 代码是从文章 设置背景颜色 修改而来。 准备好的设备相关形式的树形位图不能直接发送到打印机 DC,可能会获得意外的结果。 使用 DDBToDIB()
函数将设备相关位图转换为 DIB。 此函数是从 将 DDB 转换为 DIB 复制而来。 之后,使用 StretchDIBits()
函数将 DIB 发送到打印机 DC。
如果树形视图比纸张大小长,程序将确定每页的最大行数,并相应地将树形视图分页为几页。 打印后,窗口将恢复到其原始大小和位置。 除了打印树形视图外,程序还会将准备好的树形视图位图复制到剪贴板,供用户将位图保存到其他位置。
树形视图头文件
必须使用类向导声明几个消息处理函数,即 OnPreparePrinting()
、OnBeginPrinting()
、OnPrepareDC()
、OnPrint()
和 OnEndPrinting()
。
在树形视图头文件中,包含以下变量声明和函数声明
// 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 ); <!-- end the block of source code -->
树形视图实现文件
在树形视图构造函数中,添加以下行。 这是指向更容易访问与树形视图关联的 CTreeCtrl
类的指针。
CPrtTViewView::CPrtTViewView() { Tree=&GetTreeCtrl(); } <!-- end the block of source 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; } <!-- end the block of source code -->