在MFC 的 Document/View 框架下打印文档相比于纯手工SDK打印文档, 已经变得简易多了. 但是: 多页文档的打印总是那么麻烦!麻烦在于你得一个字符一个字符的累加去计算你每一页文档应当打印多少字符, codeproject上很多的打印框架, 甚至都有在非文档-视图模式下的打印框架功能丰富,简便易用,堪称打印神器,是程序员们的居家旅行的必备良码, 但是多页文档的打印总是不尽人意. 国产的号称多页打印,源码开放的下载下来一看恶心的受不了,他妈我不想打印了,我想打人了. 更加有些二笔用病毒压缩包欺骗单纯的程序员们,我一氧化钙你大爷!
搜了N久,找到这么一篇文章:使用RichEdit打印http://www.codeproject.com/KB/printing/richeditprint.aspx?fid=13948&fr=1#xx0xx 打印总算完成了. 但是也诚如文章开头所说:
The preview is sucks!
这是取自另外一篇文章的代码,我做了一些修改:
BOOL PrintRich(CRichEditCtrl *pCtrl ,const CString& Title) { CDC ThePrintDC; //create a device context to use CPrintDialog PrintDialog(FALSE); //make a print dialog too if(PrintDialog.DoModal( ) == IDOK) //pressed the ok button on the print dialog? { ThePrintDC.Attach(PrintDialog.GetPrinterDC());//if so get settings } else { return FALSE; //leave this procedure, before anything bad happens } DOCINFO di; //make a docinfo structure ::ZeroMemory(&di, sizeof(DOCINFO)); di.cbSize = sizeof(DOCINFO); //set size member di.lpszDocName = Title;//set doc name, was passed via another funtion FORMATRANGE fr; //format range structure ::ZeroMemory(&fr, sizeof(FORMATRANGE)); HDC hdc = ThePrintDC.GetSafeHdc(); //get handle to DC we are using fr.hdc = hdc; //Set meber in FormatRange struct fr.hdcTarget = hdc; //Set member in FormatRange struct //This bit here will get all the dimentions of the printer setup int nHorizRes = ThePrintDC.GetDeviceCaps(HORZRES), //width P in MM nVertRes = ThePrintDC.GetDeviceCaps(VERTRES), //hight in raster lines nLogPixelsX = ThePrintDC.GetDeviceCaps(LOGPIXELSX), //pixels per inch along x nLogPixelsY = ThePrintDC.GetDeviceCaps(LOGPIXELSY); //pixels per inch along y //set the printable area of printer in the FormatRange struct fr.rcPage.left = 0; //these 2 mean top left fr.rcPage.top = 0; fr.rcPage.right = (nHorizRes * 1440 / nLogPixelsX);//these 2 mean bottom right fr.rcPage.bottom = (nVertRes * 1440 / nLogPixelsY); //equation changes pixel to TWIPS // Set up some margins all around. Make them one inch //results vary on printers depending on setup fr.rc.left = fr.rcPage.left + 1100; // 1440 TWIPS = 1 inch. fr.rc.top = fr.rcPage.top + 1440; fr.rc.right = fr.rcPage.right - 1100; fr.rc.bottom = fr.rcPage.bottom - 1440; //select all text for printing CHARRANGE &cr = fr.chrg; cr.cpMin=0; cr.cpMax=-1;//-1 selects all //get length of document, used for more than one page long CharRange = pCtrl->GetTextLength();; int ErrorStatus=0; //Start Printing //ThePrintDC.SetAbortProc(&AbortProc(hdc, )); //dont know how callbacks works yet ThePrintDC.StartDoc(&di); //start printing document long LastChar = -1;//will store document length do { ThePrintDC.StartPage(); //start the page cr.cpMin = LastChar + 1;//Change charrange struct for next page cr.cpMax = -1; LastChar = pCtrl->FormatRange( &fr, TRUE );//send text to DC, and record index //of last fitting char ErrorStatus=ThePrintDC.EndPage(); //end this page, and record status if (cr.cpMin >= LastChar) break; cr.cpMin = LastChar; cr.cpMax = CharRange; //using last char printed }while(LastChar < CharRange && ErrorStatus > 0); //while there is stuff to print out there //is not an error(error is -No) //if there is an error or printing has finished //have to make sure AbortDoc is called instead of EndDoc if there has been a problem // delete pDlg; CRect rcClient; pCtrl->GetClientRect(&rcClient); pCtrl->DisplayBand(&rcClient); switch(ErrorStatus) { case SP_ERROR: { ThePrintDC.AbortDoc(); AfxMessageBox(_T("There was a general printing error, please check printer is working properly, connected, on line etc."), MB_OK | MB_ICONEXCLAMATION); return FALSE; } case SP_APPABORT: { ThePrintDC.AbortDoc(); return FALSE; } case SP_USERABORT: { ThePrintDC.AbortDoc(); return FALSE; } case SP_OUTOFDISK: { ThePrintDC.AbortDoc(); AfxMessageBox(_T("out of disk")); return FALSE; } case SP_OUTOFMEMORY: { ThePrintDC.AbortDoc(); AfxMessageBox(_T("out of memeory")); return FALSE; } default: { ThePrintDC.EndDoc(); return TRUE; } } }
只此一个函数,即可完成多页打印功能,而你只需要提供一个RichEdit控件的指针即可.
但是在实际测试的过程中却总有问题:
do { ThePrintDC.StartPage(); ... LastChar = pCtrl->FormatRange( &fr, TRUE ); // LastChar 达不到 CharRange的值 ... ErrorStatus=ThePrintDC.EndPage(); }while(LastChar < CharRange && ErrorStatus > 0);
LastChar 达不到 CharRange的值,但是当我加上这么一行代码,文档也能完全打印出来(参见上面函数完整代码):
if (cr.cpMin >= LastChar) break;
这是什么道理咩??UNICODE字符集问题咩?
从RichEdit20W它就已经支持UNICODE了. 但是在某些方面它又表现的不支持UNICODE,比如StreamIn
void CAutoRichEditCtrl::SetRTF(CString sRTF) { // Put the RTF string sRTF into the rich edit control. // Read the text in EDITSTREAM es; es.dwError = 0; es.pfnCallback = CBStreamIn; es.dwCookie = (DWORD) &sRTF; StreamIn(SF_RTF, es); // Do it. } /* Callback function to stream an RTF string into the rich edit control. */ DWORD CALLBACK CAutoRichEditCtrl::CBStreamIn(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) { // We insert the rich text here. /* This function taken from CodeGuru.com http://www.codeguru.com/richedit/rtf_string_streamin.shtml Zafir Anjum */ CString *pstr = (CString *) dwCookie; if (pstr->GetLength() < cb) { *pcb = pstr->GetLength(); memcpy(pbBuff, (LPCSTR) *pstr, *pcb); pstr->Empty(); } else { *pcb = cb; memcpy(pbBuff, (LPCSTR) *pstr, *pcb); *pstr = pstr->Right(pstr->GetLength() - cb); } return 0; }
这两个函数在使用多字节字符集编译的工程中表现良好,但是在UNICODE字符集下就会出现乱码
向下面一样,将字符串转换一下后则运行无误
void CAutoRichEditCtrl::SetRTF(CString sRTF) { // Put the RTF string sRTF into the rich edit control. // Read the text in EDITSTREAM es; es.dwError = 0; es.pfnCallback = CBStreamIn; // fixed by qiuchengw -- unicode is not right CAnsi ansi(sRTF); std::string str(ansi); es.dwCookie = (DWORD)&str; // es.dwCookie = (DWORD) &sRTF; StreamIn(SF_RTF, es); // Do it. } /* Callback function to stream an RTF string into the rich edit control. */ DWORD CALLBACK CAutoRichEditCtrl::CBStreamIn(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) { // We insert the rich text here. /* This function taken from CodeGuru.com http://www.codeguru.com/richedit/rtf_string_streamin.shtml Zafir Anjum */ std::string *pstr = (std::string *)dwCookie; // CString *pstr = (CString *) dwCookie; int len = pstr->length(); if (len < cb) { *pcb = len; memcpy(pbBuff,pstr->c_str(),len); pstr->clear(); } else { *pcb = cb; memcpy(pbBuff,pstr->c_str(),cb); *pstr = pstr->substr(cb); } /* if (pstr->GetLength() < cb) { *pcb = pstr->GetLength(); memcpy(pbBuff, (LPCSTR) *pstr, *pcb); pstr->Empty(); } else { *pcb = cb; memcpy(pbBuff, (LPCSTR) *pstr, *pcb); *pstr = pstr->Right(pstr->GetLength() - cb); } */ return 0; }
最近比较忙, 没有做什么更多测试,也不深入研究了,下面的两个问题,烦请高人解决:
1. 是否StreamIn真的不支持UNICODE,还是我某些设置没有做好(比如在StreamIn之前应该做什么初始化之类的)?
2. 那个打印是否也真的是UNICODE的问题? 有更优雅的解决方法不? 我那个"解决方法"太暴力, 简直成了我一块心病啊!
byebye