VC++
MFC Tutorial: Print the contents of the list
View control, CListCtrl, Custom Draw
|
|
I tried to use the Print
code available in your site for List
View Control. That was not good enough. The sample
sends WM_PAINT message to itself after adjusting printer
DC. The problem with this is this will only print
currently visible items only and will not take care of
Margins etc. It does not take care of number of pages etc
also.
I wrote a slightly
enhanced printing
functionality here. This priting
is divided into two main functions on the MFC
line. OnBeginPrint and OnPrint as shiown in the following
code. These will ideally be called form view
OnFileBeginPrint and OnFilePrint functions.
I have a string list to
take the header content. Each string in a string list will
be painted in a separate row.
It has a DrawRow
functions which will paint the row on screen (in case of
Owner draw control fron
OnDrawItem code) or on printer. As a matter of fact my
CSapListControl has a lot of functionality like, full row
selection, alternate color bands, TreeLike List
controlwith expand and collpase features, virtaul function
for Sorting, OnGetDispInfo etc. All these functions will
be called depending on the flags set. For exaple it has
OnDrawItem function, it will not be called if the
Owner draw flag is set. All my list controls in my project
are derived from this. I will send you all the code for
this class, when I get some time to write description for
all the functions.
All the members starting
with m_ are class variables.
#define
HEADER_HEIGHT 4
#define
FOOTER_HEIGHT 3
#define
LEFT_MARGIN 8
#define
RIGHT_MARGIN 4
void
CSAPListCtrl::OnBeginPrint(CDC *pDC, CPrintInfo *pInfo)
{
//
OnBeginPrinting() is called after the user has committed
to
// printing
by OK'ing the Print dialog, and after the framework
// has
created a CDC object for the printer or the preview view.
// This is
the right opportunity to set up the page range.
// Given
the CDC object, we can determine how many rows will
// fit on a
page, so we can in turn determine how many printed
// pages
represent the entire document.
if(NULL
== pDC || NULL == pInfo)
return;
int
nMaxRowCount = GetItemCount();
if(!nMaxRowCount)
return;
//let us do
all dimesions in List
Control
units (Device) rather than
//printer
device units. Since we have more control on them
CDC *pCtlDC = GetDC();
if(NULL
== pCtlDC)
return
;
TEXTMETRIC tm;
pCtlDC->GetTextMetrics(&tm);
m_nCharWidth = tm.tmAveCharWidth;
pDC->SetMapMode(MM_ANISOTROPIC);
CRect rc;
GetItemRect(0,
&rc, LVIR_BOUNDS);
m_nRowHeight = rc.Height();
int
nMargins = (LEFT_MARGIN+RIGHT_MARGIN)*m_nCharWidth;
//this will
optimize the column widths. If we have more column
than
//screen
width and horizontal scroll on List control
//this will
reduce the column widths proportonately to fit all of
them
//on the
page. If we have fewer column all the column
//widths
will increased propertionately.
pDC->SetWindowExt(rc.Width() +
nMargins, pCtlDC->GetDeviceCaps(LOGPIXELSX));
pDC->SetViewportExt(pDC->GetDeviceCaps(HORZRES),pDC->GetDeviceCaps(LOGPIXEL
SX));
double
d = (double)pDC->GetDeviceCaps(LOGPIXELSY)/(double)pCtlDC->GetDeviceCaps(LOGPIX
ELSY);
ReleaseDC(pCtlDC);
nMargins = (int)(m_nRowHeight*d);
int
nPageHeight = pDC->GetDeviceCaps(VERTRES);
m_nRowsPerPage = nPageHeight/nMargins;
//nMargins reused
m_nRowsPerPage -= (HEADER_HEIGHT+FOOTER_HEIGHT);
m_nRowsPerPage -= 1; //adjustment
for list control header
int
nMaxPage = nMaxRowCount/m_nRowsPerPage + 1;
pInfo->SetMaxPage(nMaxPage);
pInfo->m_nCurPage = 1; //
start printing at page# 1
}
void
CSAPListCtrl::OnPrint(CDC *pDC, CPrintInfo *pInfo)
{
if(NULL
== pDC || NULL == pInfo)
return;
// Draw
as many rows as will fit on the printed
page.
// Clip the
printed page so that there is no partially shown
// row at
the bottom of the page (the same row which will be fully
// shown at
the top of the next page).
int
nStartRow = 0;
int
nEndRow = 0;
int
nMaxRowCount = GetItemCount();
nStartRow = (pInfo->m_nCurPage
- 1)*m_nRowsPerPage;
nEndRow =
nStartRow+m_nRowsPerPage;
if(nEndRow
> nMaxRowCount)
nEndRow = nMaxRowCount;
//create
bold font for header and footer
CFont *pOldFont
= NULL;
CFont
BoldFont;
LOGFONT lf;
pOldFont = GetFont();;
pOldFont->GetLogFont(&lf);
lf.lfWeight = FW_BOLD;
BoldFont.CreateFontIndirect(&lf);
pOldFont = pDC->SelectObject(&BoldFont);
int
nPrevBkMode = pDC->SetBkMode(TRANSPARENT);
//print the
header
PrintHeader(pDC, pInfo);
//Print the
footer
PrintFooter(pDC, pInfo);
pDC->SetBkMode(nPrevBkMode);
pDC->SelectObject(pOldFont);
BoldFont.DeleteObject();
//Set
origin to print header and Header control. Keep Y position
at 0 to
//print
header information
//Change Y
position of origin to print List control header
//Adjust
the List control origin to start printing after page
margins
pDC->SetWindowOrg(-1*(LEFT_MARGIN*m_nCharWidth),
-1*HEADER_HEIGHT*m_nRowHeight);
//send a
message to Header control to print itsef. There is little
scope to improve printing header.
//I have
m_HeaderCtrl in my class. If not take by HeaderCtrl =
GetDlgItem(0);
m_HeaderCtrl.SendMessage(WM_PAINT,
(WPARAM)pDC->m_hDC);
//Chage
window position to take care of ListControl Horizontal
scrolling.
//if List
control is scrolled to left horizontally the above window
origin
//will not
start painting from first column, instead it starts
painting from
//first
visible column, because rcBounds etc are will have -ve
left value
//Same
thing with vertical scrolling also
CRect
rcBounds;
GetItemRect(nStartRow, &rcBounds,
LVIR_BOUNDS);
//offset
top margin of rcBounds by ListControl header
CRect rc;
m_HeaderCtrl.GetClientRect(&rc);
rcBounds.OffsetRect(0, -rc.Height());
pDC->OffsetWindowOrg(rcBounds.left,
rcBounds.top);
//start
printing rows
for(int
i = nStartRow; i < nEndRow; i++)
DrawRow(pDC, i);
//SetWindowOrg
back for next page
pDC->SetWindowOrg(0,0);
return;
}
void
CSAPListCtrl::PrintHeader(CDC *pDC, CPrintInfo *pInfo)
{
CRect
rc(pInfo->m_rectDraw);
CString
sTemp;
//Print
App title
rc.left += LEFT_MARGIN*m_nCharWidth;
rc.right -= RIGHT_MARGIN*m_nCharWidth;
rc.bottom = rc.top+m_nRowHeight;
//print
App title on top right margin
sTemp.LoadString(AFX_IDS_APP_TITLE);
pDC->DrawText(sTemp, &rc,
DT_RIGHT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER);
//print
Header. One row for each string
POSITION pos = NULL;
pos =
m_HeaderList.GetHeadPosition();
while(pos)
{
sTemp = m_HeaderList.GetNext(pos);
pDC->DrawText(sTemp,
&rc, DT_LEFT | DT_SINGLELINE | DT_NOPREFIX |
DT_VCENTER);
rc.OffsetRect(0, m_nRowHeight);
}
}
//print
footer with a line and date, and page number
void
CSAPListCtrl::PrintFooter(CDC *pDC, CPrintInfo *pInfo)
{
CRect
rc(pInfo->m_rectDraw);
//draw line
rc.left += LEFT_MARGIN*m_nCharWidth;
rc.right -= RIGHT_MARGIN*m_nCharWidth;
rc.top = rc.bottom -
FOOTER_HEIGHT*m_nRowHeight;
rc.bottom = rc.top + m_nRowHeight;
pDC->MoveTo(rc.left, rc.top);
pDC->LineTo(rc.right, rc.top);
//draw page
number
CString sTemp ;
rc.OffsetRect(0, m_nRowHeight/2);
sTemp.Format(IDS_PRINT_PAGE_TITLE,
pInfo->m_nCurPage);
pDC->DrawText(sTemp,-1,rc,
DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP |
DT_VCENTER);
CTime t =
CTime::GetCurrentTime();
sTemp = t.Format("%c");
pDC->DrawText(sTemp,-1,rc,
DT_RIGHT | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP |
DT_VCENTER);
}
//this is drawing
code copied RowList sample and from www.codeguru.com site
int CSAPListCtrl::DrawRow(CDC
*pDC, int nItem)
{
CImageList
*pImageList = NULL;
CFont
*pOldFont = NULL;
CFont
BoldFont;
CString
sLabel;
UINT
dtFlags = DT_SINGLELINE|DT_NOPREFIX|DT_VCENTER;
int
nSaveDC = pDC->SaveDC();
if(!pDC->IsPrinting())
dtFlags |= (DT_NOCLIP | DT_END_ELLIPSIS);
//no clip because we add ellipsis at
the end
// get item
data
LV_ITEM lvi;
lvi.mucancode.net = LVIF_IMAGE |
LVIF_STATE | LVIF_INDENT;
lvi.iItem=nItem;
lvi.iSubItem=0;
lvi.stateMucancode.net=0xFFFF;
// get all state flags
GetItem(&lvi);
BOOL bHighlight = ((lvi.state
& LVIS_DROPHILITED) || ((lvi.state & LVIS_SELECTED)
&& ((GetFocus() == this)
|| (GetStyle() & LVS_SHOWSELALWAYS))));
//Get
rectangles for painting
CRect
rcBounds, rcLabel, rcIcon;
GetItemRect(nItem, rcBounds,
LVIR_BOUNDS);
GetItemRect(nItem, rcLabel,
LVIR_LABEL);
GetItemRect(nItem, rcIcon,
LVIR_ICON);
CRect
rcCol(rcBounds);
CRect
rcWnd;
sLabel = GetItemText(nItem, 0);
//Label
offset
int
offset = pDC->GetTextExtent(_T(" "), 1).cx;
CRect
rcHighlight;
int
nExt = 0;
switch(m_nHighlight)
{
case
HIGHLIGHT_NORMAL:
nExt = pDC->GetOutputTextExtent(sLabel).cx + offset;
rcHighlight = rcLabel;
if(rcLabel.left
+ nExt < rcLabel.right)
rcHighlight.right = rcLabel.left + nExt;
break;
case
HIGHLIGHT_ALLCOLUMNS:
rcHighlight = rcBounds;
rcHighlight.left = rcLabel.left;
break;
case
HIGHLIGHT_ROW:
GetClientRect(&rcWnd);
rcHighlight = rcBounds;
rcHighlight.left = rcLabel.left;
rcHighlight.right = rcWnd.right;
break;
default:
rcHighlight.left = rcLabel.left;
break;
}
//draw
highlight. printing may not be required
if(bHighlight)
{
pDC->SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
pDC->SetBkColor(::GetSysColor(COLOR_HIGHLIGHT));
pDC->FillRect(rcHighlight,
&CBrush(::GetSysColor(COLOR_HIGHLIGHT)));
}
else
{
pDC->SetBkColor(::GetSysColor(COLOR_WINDOW));
pDC->FillRect(rcHighlight,
&CBrush(::GetSysColor(COLOR_WINDOW)));
}
//set clip
region
rcCol.right = rcCol.left +
GetColumnWidth(0);
//Nice to
have regions but they are not working on printer
DC we may need
//to take
get device caps to support regions. Does not seems to help
much now
//CRgn
rgn;
//rgn.CreateRectRgnIndirect(&rcCol);
//pDC->SelectClipRgn(&rgn);
//rgn.DeleteObject();
//Draw
state icon
if(lvi.state
& LVIS_STATEIMAGEMucancode.net)
{
int
nImage = ((lvi.state & LVIS_STATEIMAGEMucancode.net) >>
12) - 1;
pImageList = GetImageList(LVSIL_STATE);
//offset
the state image icon indent levels.
nExt = rcCol.left + lvi.iIndent*rcIcon.Width();
//nExt reused
if(pImageList)
pImageList->Draw(pDC, nImage, CPoint(nExt, rcCol.top),
ILD_TRANSPARENT);
}
//Draw
Normal and overlay icon
pImageList = GetImageList(LVSIL_SMALL);
//assuming printing in report mode
only
if(pImageList)
{
UINT nOvlImageMucancode.net = lvi.state & LVIS_OVERLAYMucancode.net;
pImageList->Draw(pDC, lvi.iImage, CPoint(rcIcon.left,
rcIcon.top),
(bHighlight?ILD_BLEND50:0)|ILD_TRANSPARENT|nOvlImageMucancode.net);
}
//if state
image mucancode.net is on and indent is 0 then consider it as
Server row
if((lvi.state
& LVIS_STATEIMAGEMucancode.net) && !lvi.iIndent)
{
//create
bold font
LOGFONT lf;
pOldFont = GetFont();;
pOldFont->GetLogFont(&lf);
lf.lfWeight = FW_BOLD;
BoldFont.CreateFontIndirect(&lf);
pOldFont = pDC->SelectObject(&BoldFont);
rcLabel.right = rcBounds.right; //draw
server name to full row width
}
//Draw item
label
rcLabel.left += offset/2;
rcLabel.right -= offset;
dtFlags |= DT_LEFT;
pDC->DrawText(sLabel, rcLabel,
dtFlags);
if((lvi.state
& LVIS_STATEIMAGEMucancode.net) && !lvi.iIndent)
{
pOldFont = pDC->SelectObject(pOldFont);
BoldFont.DeleteObject();
//focus
rect if required
if(lvi.state
& LVIS_FOCUSED && (GetFocus() == this))
pDC->DrawFocusRect(rcHighlight);
pDC->RestoreDC(nSaveDC);
return
0;
}
//dRAW
LABELS FOR REMAINING COLUMNS
LV_COLUMN lvc;
lvc.mucancode.net = LVCF_FMT|LVCF_WIDTH;
if(m_nHighlight
== HIGHLIGHT_NORMAL)
{
pDC->SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
pDC->SetBkColor(::GetSysColor(COLOR_WINDOW));
}
rcBounds.right =
rcHighlight.right > rcBounds.right ?
rcHighlight.right:rcBounds.right;
//Nice to
have regions but they are not working on printer DC we may
need
//to take
get device caps to support regions. Does not seems to help
much now
//rgn.CreateRectRgnIndirect(&rcBounds);
//pDC->SelectClipRgn(&rgn);
//rgn.DeleteObject();
for(int
nColumn = 1; GetColumn(nColumn, &lvc); nColumn++)
{
rcCol.left = rcCol.right;
rcCol.right += lvc.cx;
//draw
background if needed
if(m_nHighlight
== HIGHLIGHT_NORMAL)
pDC->FillRect(rcCol, &CBrush(::GetSysColor(COLOR_WINDOW)));
sLabel = GetItemText(nItem, nColumn);
if(sLabel.IsEmpty())
continue;
//Get
the text justification
UINT nJustify = DT_LEFT;
switch(lvc.fmt
& LVCFMT_JUSTIFYMucancode.net)
{
case
LVCFMT_RIGHT:
nJustify = DT_RIGHT;
break;
case
LVCFMT_CENTER:
nJustify = DT_CENTER;
break;
default:
break;
}
rcLabel = rcCol;
rcLabel.left += offset;
rcLabel.right -= offset;
dtFlags &= ~DT_RIGHT;
dtFlags &= ~DT_CENTER;
dtFlags |= nJustify;
pDC->DrawText(sLabel,
-1, rcLabel, dtFlags);
}
//focus
rect if required
if(lvi.state
& LVIS_FOCUSED && (GetFocus() == this))
pDC->DrawFocusRect(rcHighlight);
pDC->RestoreDC(nSaveDC);
return
0;
}
void
CSAPListCtrl::SetHeaderString(CStringList *HeaderList)
{
m_HeaderList.RemoveAll();
m_HeaderList.AddTail(HeaderList);
}
|