Click here to Skip to main content
Click here to Skip to main content

A Print Enabled Tree View

, 17 Jan 2000
Rate this:
Please Sign up or sign in to vote.
Code to add printing capabilities to a Tree View.

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:

// 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 -->

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

    // 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 -->

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Koay Kah Hoe

Malaysia Malaysia
No Biography provided

Comments and Discussions

 
QuestionIssue with large number of tree items [modified] PinmemberBitsJ26-Sep-12 22:45 
GeneralMy vote of 2 Pinmembersofexindia18-Jul-10 5:49 
GeneralRe: My vote of 2 PinmemberIain Clarke, Warrior Programmer5-Sep-10 23:08 
GeneralPrinting a TreeCtrl not in TreeView in Dialog Pinmemberasfur30-Jun-07 12:53 
Questioncscrollview printing? Pinmemberxxhimanshu28-Jun-04 17:59 
QuestionIs there a way to do this in C#? PinmemberMark Pitman11-Feb-04 8:55 
AnswerRe: Is there a way to do this in C#? PinmemberWimpie Ratte6-Feb-07 2:38 
GeneralRe: Is there a way to do this in C#? PinmemberMark Pitman6-Feb-07 4:03 
Generalitem restriction problem PinmemberShaun Garratt27-Aug-02 0:39 
GeneralPrinting a TreeCtrl not in TreeView PinmemberAnonymous5-Jun-02 4:54 
GeneralRe: Printing a TreeCtrl not in TreeView PinmemberAndreas Kuhtz19-Aug-02 22:42 
Generalprinting the text of the tree PinmemberFrankie19-Apr-01 13:04 
I've used this print enabled tree for 2 projects. It works great for one, but will only print the lines and +/- boxes that connect the child and sibling nodes on the other. Any body have any ideas on why?
GeneralVertical Scrollbar Printing Fix PinsussGeorge Poulose6-Jun-00 15:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141015.1 | Last Updated 18 Jan 2000
Article Copyright 2000 by Koay Kah Hoe
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid