Printing in WebBrowser control (custom header and footer)
This is going to be a complicated one. Whole code/working sample is near the end of the article.
The Task:
Enable HTML printing using WebBrowser content but modify standard header and footer.
Unfortunately, when we print, nasty footer and header appear, and there is no way to get rid of them:
ShowPrintDialog method comment explicitly says that header and footer can not be modified:
The Big Plan:
1. First we create IEPrinting.dll written in c++ and managed c++, it exposes one extremely simple helper class: PrintHelper:
// PrintHelper.h #pragma once using namespace System; namespace IEPrinting { public ref class PrintHelper { public: static void Print(IntPtr^ ptrIWebBrowser2, String^ header, String^ footer); }; }
2.
From now on things get complicated:
// This is the main DLL file. #include "stdafx.h" #include "PrintHelper.h" namespace IEPrinting { #pragma unmanaged int UnmanagedPrint(IWebBrowser2* webOC, BSTR header, BSTR footer) { SAFEARRAYBOUND psabBounds[1]; SAFEARRAY *psaHeadFoot; HRESULT hr = S_OK; // Variables needed to send IStream header to print operation. HGLOBAL hG = 0; IStream *pStream= NULL; IUnknown *pUnk = NULL; ULONG lWrote = 0; LPSTR sMem = NULL; // Initialize header and footer parameters to send to ExecWB(). psabBounds[0].lLbound = 0; psabBounds[0].cElements = 3; psaHeadFoot = SafeArrayCreate(VT_VARIANT, 1, psabBounds); if (NULL == psaHeadFoot) { // Error handling goes here. goto cleanup; } VARIANT vHeadStr, vFootStr, vHeadTxtStream; long rgIndices; VariantInit(&vHeadStr); VariantInit(&vFootStr); VariantInit(&vHeadTxtStream); // Argument 1: Header vHeadStr.vt = VT_BSTR; vHeadStr.bstrVal = header; if (vHeadStr.bstrVal == NULL) { goto cleanup; } // Argument 2: Footer vFootStr.vt = VT_BSTR; vFootStr.bstrVal = footer; if (vFootStr.bstrVal == NULL) { goto cleanup; } // Argument 3: IStream containing header text. Outlook and Outlook // Express use this to print out the mail header. if ((sMem = (LPSTR)CoTaskMemAlloc(512)) == NULL) { goto cleanup; } // We must pass in a full HTML file here, otherwise this // becomes corrupted when we print. sprintf_s(sMem, 512, "<html><body><strong>Printed By:</strong> Custom WebBrowser Host 1.0<p></body></html>"); // Allocate an IStream for the LPSTR that we just created. hG = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, strlen(sMem)); if (hG == NULL) { goto cleanup; } hr = CreateStreamOnHGlobal(hG, TRUE, &pStream); if (FAILED(hr)) { //ATLTRACE(_T("OnPrint::Failed to create stream from HGlobal: %lXn"), hr); goto cleanup; } hr = pStream->Write(sMem, strlen(sMem), &lWrote); if (SUCCEEDED(hr)) { // Set the stream back to its starting position. LARGE_INTEGER pos; pos.QuadPart = 0; pStream->Seek((LARGE_INTEGER)pos, STREAM_SEEK_SET, NULL); hr = pStream->QueryInterface(IID_IUnknown, reinterpret_cast<void **>(&pUnk)); vHeadTxtStream.vt = VT_UNKNOWN; vHeadTxtStream.punkVal = pUnk; } rgIndices = 0; SafeArrayPutElement(psaHeadFoot, &rgIndices, static_cast<void *>(vHeadStr)); rgIndices = 1; SafeArrayPutElement(psaHeadFoot, &rgIndices, static_cast<void *>(&vFootStr)); rgIndices = 2; SafeArrayPutElement(psaHeadFoot, &rgIndices, static_cast<void *>(&vHeadTxtStream)); //NOTE: Currently, the SAFEARRAY variant must be passed by using // the VT_BYREF vartype when you call the ExecWeb method. VARIANT vArg; VariantInit(&vArg); vArg.vt = VT_ARRAY | VT_BYREF; vArg.parray = psaHeadFoot; //OLECMDEXECOPT_PROMPTUSER hr = webOC->ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER, &vArg, NULL); if (FAILED(hr)) { //ATLTRACE(_T("DoPrint: Call to WebBrowser's ExecWB failed: %lXn"), hr); goto cleanup; } return 1; //WebBrowser control will clean up the SAFEARRAY after printing. cleanup: VariantClear(&vHeadStr); VariantClear(&vFootStr); VariantClear(&vHeadTxtStream); if (psaHeadFoot) { SafeArrayDestroy(psaHeadFoot); } if (sMem) { CoTaskMemFree(sMem); } if (hG != NULL) { GlobalFree(hG); } if (pStream != NULL) { pStream->Release(); pStream = NULL; } //bHandled = TRUE; return 0; } #pragma managed void PrintHelper::Print(IntPtr^ ptrIWebBrowser2, String^ header, String^ footer) { IWebBrowser2* pBrowser = (IWebBrowser2 *)ptrIWebBrowser2->ToPointer(); IDispatch *pDisp; pBrowser->get_Document(&pDisp); IHTMLDocument2 *pDoc; pDisp->QueryInterface<ihtmldocument2>(&pDoc); IHTMLElement *body; pDoc->get_body(&body); BSTR p; body->get_innerHTML(&p); IntPtr pHeader = Runtime::InteropServices::Marshal::StringToBSTR(header); IntPtr pFooter = Runtime::InteropServices::Marshal::StringToBSTR(footer); UnmanagedPrint(pBrowser, (BSTR)pHeader.ToPointer(), (BSTR)pFooter.ToPointer()); } }
3.
Now we need to generate C# version of the IWebBrowser2 COM interface:
It can be generated from idl -> tlb -> dll and the referenced from the WindowsForms app.
midl ExDisp.Idl /tlb ExDisp.tlb pause tlbimp ExDisp.tlb /out:ExDisp.dll pause
4.
Then we create WindowsForms project, and create custom control inheriting
from WebBrowser control.
We need this to access IWebBrowser2 interface (defined in ExDisp.dll):
using ExDisp; using WebBrowser = System.Windows.Forms.WebBrowser; namespace WindowsFormsApplication1.MyBrowser { public class MyWebBrowser : WebBrowser { public IWebBrowser2 axIWebBrowser2; protected override void AttachInterfaces(object nativeActiveXObject) { base.AttachInterfaces(nativeActiveXObject); this.axIWebBrowser2 = (IWebBrowser2) nativeActiveXObject; } protected override void DetachInterfaces() { base.DetachInterfaces(); this.axIWebBrowser2 = null; } }; }
5.
Finally we create a Form and add the printing code there:
private void _btnPrint_Click(object sender, EventArgs e) { IntPtr ptr = Marshal.GetComInterfaceForObject( webBrowser1.axIWebBrowser2, typeof(IWebBrowser2)); PrintHelper.Print(ptr, "this is my header", "this is my footer"); }
Modified the header and footer:
The Zip:
IEPrinting
References:
http://support.microsoft.com/kb/267240
http://thedotnet.com/nntp/97691/showpost.aspx
March 30th, 2014 at 15:58
Hi, how do i remove the text “Printed By: Custom WebBrowser Host 1.0” ?
i wrote empty string in printhelper.cpp row 62 , but after compile i still get the complete description.
Thanks.
March 30th, 2014 at 17:29
@fabrizio
Have you cleaned the solution and recompiled?
March 31st, 2014 at 10:41
i’m not very expert on c++, in your solution i replaced on PrintHelper.cpp row 60 with this code
// We must pass in a full HTML file here, otherwise this
// becomes corrupted when we print.
sprintf_s(sMem, 512, “”);
if i run the solution e print the text is not showed, if i compile (i can only in debug if try in release i get the error cause windows form application reference IEprinting.dll from debug folder) e move the dll inside my appliucation the text “print by……” is showed again.
Hope u can help me, maybe u can send me only the dll without that text.
Very Thanks
August 11th, 2014 at 15:14
I’m having trouble compiling the project in Visual Studio 2010, it does not find the DLL IEPrinting, you can help me?
August 12th, 2014 at 09:10
@fabrizio
The line should look like this:
“);sprintf_s(sMem, 512, “
August 12th, 2014 at 09:10
@Fernando
Have you compiled the IEPrinting project?
August 12th, 2014 at 09:16
@Fernando
Change the target framework for WinForms app to .net 4.0
August 12th, 2014 at 19:07
Ok it worked!, I changed the framework to 4.0, thank you!
April 22nd, 2017 at 18:13
Hi is posible that this work, with a javascript event “window.print” from a web that load in the web browser?
April 23rd, 2017 at 07:14
@Xavier,
This code works for WebBrowser control only. Are you using WebBrowser control?
January 23rd, 2018 at 11:04
Nice work, works perfectly. It’s extremely useful to be able to customize the headers and footers without touching the registry. If I may ask: what license is this code under? Can I use the code/compiled library in a commercial project?
January 24th, 2018 at 08:09
@Jon
Thanks! You are free to use this code anyway you want.