{"id":103,"date":"2009-09-29T10:00:50","date_gmt":"2009-09-29T08:00:50","guid":{"rendered":"http:\/\/www.limilabs.com\/blog\/?p=103"},"modified":"2016-09-09T11:04:20","modified_gmt":"2016-09-09T09:04:20","slug":"printing-in-webbrowser-control-custom-header-and-footer","status":"publish","type":"post","link":"https:\/\/www.limilabs.com\/blog\/printing-in-webbrowser-control-custom-header-and-footer","title":{"rendered":"Printing in WebBrowser control (custom header and footer)"},"content":{"rendered":"<p>This is going to be a complicated one. Whole code\/working sample is near the end of the article.<\/p>\n<p><strong>The Task:<\/strong><br \/>\nEnable HTML printing using WebBrowser content <strong>but modify standard header and footer<\/strong>.<\/p>\n<p><a href=\"\/blog\/wp-content\/uploads\/2009\/09\/SampleApp.png\"><img loading=\"lazy\" decoding=\"async\" src=\"\/blog\/wp-content\/uploads\/2009\/09\/SampleApp.png\" alt=\"SampleApp\" title=\"SampleApp\" width=\"532\" height=\"325\" class=\"alignnone size-full\" \/><\/a><\/p>\n<p>Unfortunately, when we print, <strong>nasty footer and header appear<\/strong>, and there is no way to get rid of them:<br \/>\n<a href=\"\/blog\/wp-content\/uploads\/2009\/09\/NastyFooter.png\"><img loading=\"lazy\" decoding=\"async\" src=\"\/blog\/wp-content\/uploads\/2009\/09\/NastyFooter.png\" alt=\"NastyFooter\" title=\"NastyFooter\" width=\"542\" height=\"87\" class=\"alignnone size-full\" \/><\/a><\/p>\n<p>ShowPrintDialog method comment explicitly says that header and footer can not be modified:<br \/>\n<a href=\"\/blog\/wp-content\/uploads\/2009\/09\/ShowPrintDialog.png\"><img loading=\"lazy\" decoding=\"async\" src=\"\/blog\/wp-content\/uploads\/2009\/09\/ShowPrintDialog.png\" alt=\"ShowPrintDialog\" title=\"ShowPrintDialog\" width=\"479\" height=\"58\" class=\"alignnone size-full\" \/><\/a><\/p>\n<p><strong>The Big Plan:<\/strong><br \/>\n1. First we create IEPrinting.dll written in c++ and managed c++, it exposes one extremely simple helper class: <em>PrintHelper<\/em>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ PrintHelper.h\r\n#pragma once\r\nusing namespace System;\r\nnamespace IEPrinting\r\n{\r\n\tpublic ref class PrintHelper\r\n\t{\r\n\t\tpublic:\r\n\t\t\tstatic void Print(IntPtr^ ptrIWebBrowser2, String^ header, String^ footer);\r\n\t};\r\n}\r\n<\/pre>\n<p>2.<br \/>\nFrom now on things get complicated:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ This is the main DLL file.\r\n#include &quot;stdafx.h&quot;\r\n#include &quot;PrintHelper.h&quot;\r\n\r\nnamespace IEPrinting\r\n{\r\n#pragma unmanaged\r\n\r\nint UnmanagedPrint(IWebBrowser2* webOC, BSTR header, BSTR footer)\r\n{\r\n\tSAFEARRAYBOUND psabBounds&#x5B;1];\r\n\tSAFEARRAY *psaHeadFoot;\r\n\tHRESULT hr = S_OK;\r\n\r\n\t\/\/ Variables needed to send IStream header to print operation.\r\n\tHGLOBAL hG = 0;\r\n\tIStream *pStream= NULL;\r\n\tIUnknown *pUnk = NULL;\r\n\tULONG lWrote = 0;\r\n\tLPSTR sMem = NULL;\r\n\r\n\t\/\/ Initialize header and footer parameters to send to ExecWB().\r\n\tpsabBounds&#x5B;0].lLbound = 0;\r\n\tpsabBounds&#x5B;0].cElements = 3;\r\n\tpsaHeadFoot = SafeArrayCreate(VT_VARIANT, 1, psabBounds);\r\n\tif (NULL == psaHeadFoot) {\r\n\t\t\/\/ Error handling goes here.\r\n\t\tgoto cleanup;\r\n\t}\r\n\tVARIANT vHeadStr, vFootStr, vHeadTxtStream;\r\n\tlong rgIndices;\r\n\tVariantInit(&amp;vHeadStr);\r\n\tVariantInit(&amp;vFootStr);\r\n\tVariantInit(&amp;vHeadTxtStream);\r\n\r\n\t\/\/ Argument 1: Header\r\n\tvHeadStr.vt = VT_BSTR;\r\n\tvHeadStr.bstrVal = header;\r\n\tif (vHeadStr.bstrVal == NULL) {\r\n\t\tgoto cleanup;\r\n\t}\r\n\r\n\t\/\/ Argument 2: Footer\r\n\tvFootStr.vt = VT_BSTR;\r\n\tvFootStr.bstrVal = footer;\r\n\tif (vFootStr.bstrVal == NULL) {\r\n\t\tgoto cleanup;\r\n\t}\r\n\r\n\t\/\/ Argument 3: IStream containing header text. Outlook and Outlook\r\n         \/\/ Express use this to print out the mail header.\r\n\tif ((sMem = (LPSTR)CoTaskMemAlloc(512)) == NULL) {\r\n\t\tgoto cleanup;\r\n\t}\r\n\t\/\/ We must pass in a full HTML file here, otherwise this\r\n         \/\/ becomes corrupted when we print.\r\n\tsprintf_s(sMem, 512, &quot;&lt;html&gt;&lt;body&gt;&lt;strong&gt;Printed By:&lt;\/strong&gt;\r\n\t Custom WebBrowser Host 1.0&lt;p&gt;&lt;\/body&gt;&lt;\/html&gt;&quot;);\r\n\r\n\t\/\/ Allocate an IStream for the LPSTR that we just created.\r\n\thG = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, strlen(sMem));\r\n\tif (hG == NULL) {\r\n\t\tgoto cleanup;\r\n\t}\r\n\thr = CreateStreamOnHGlobal(hG, TRUE, &amp;pStream);\r\n\tif (FAILED(hr)) {\r\n\t\t\/\/ATLTRACE(_T(&quot;OnPrint::Failed to create stream from HGlobal: %lXn&quot;), hr);\r\n\t\tgoto cleanup;\r\n\t}\r\n\thr = pStream-&gt;Write(sMem, strlen(sMem), &amp;lWrote);\r\n\tif (SUCCEEDED(hr)) {\r\n\t    \/\/ Set the stream back to its starting position.\r\n\t\tLARGE_INTEGER pos;\r\n\t\tpos.QuadPart = 0;\r\n\t\tpStream-&gt;Seek((LARGE_INTEGER)pos, STREAM_SEEK_SET, NULL);\r\n\t\thr = pStream-&gt;QueryInterface(IID_IUnknown, reinterpret_cast&lt;void **&gt;(&amp;pUnk));\r\n\t\tvHeadTxtStream.vt = VT_UNKNOWN;\r\n\t\tvHeadTxtStream.punkVal = pUnk;\r\n\t}\r\n\r\n\trgIndices = 0;\r\n\tSafeArrayPutElement(psaHeadFoot, &amp;rgIndices, static_cast&lt;void *&gt;(vHeadStr));\r\n\trgIndices = 1;\r\n\tSafeArrayPutElement(psaHeadFoot, &amp;rgIndices, static_cast&lt;void *&gt;(&amp;vFootStr));\r\n\trgIndices = 2;\r\n\tSafeArrayPutElement(psaHeadFoot, &amp;rgIndices, static_cast&lt;void *&gt;(&amp;vHeadTxtStream));\r\n\r\n\t\/\/NOTE: Currently, the SAFEARRAY variant must be passed by using\r\n\t\/\/ the VT_BYREF vartype when you call the ExecWeb method.\r\n\tVARIANT vArg;\r\n\tVariantInit(&amp;vArg);\r\n\tvArg.vt = VT_ARRAY | VT_BYREF;\r\n\tvArg.parray = psaHeadFoot;\r\n\r\n\t\/\/OLECMDEXECOPT_PROMPTUSER\r\n\thr = webOC-&gt;ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER, &amp;vArg, NULL);\r\n\r\n\tif (FAILED(hr)) {\r\n\t\t\/\/ATLTRACE(_T(&quot;DoPrint: Call to WebBrowser's ExecWB failed: %lXn&quot;), hr);\r\n\t\tgoto cleanup;\r\n\t}\r\n\treturn 1;\r\n\t\/\/WebBrowser control will clean up the SAFEARRAY after printing.\r\n\tcleanup:\r\n\tVariantClear(&amp;vHeadStr);\r\n\tVariantClear(&amp;vFootStr);\r\n\tVariantClear(&amp;vHeadTxtStream);\r\n\tif (psaHeadFoot) {\r\n\t\tSafeArrayDestroy(psaHeadFoot);\r\n\t}\r\n\tif (sMem) {\r\n\t\tCoTaskMemFree(sMem);\r\n\t}\r\n\tif (hG != NULL) {\r\n\t\tGlobalFree(hG);\r\n\t}\r\n\tif (pStream != NULL) {\r\n\t\tpStream-&gt;Release();\r\n\t\tpStream = NULL;\r\n\t}\r\n\t\/\/bHandled = TRUE;\r\n\treturn 0;\r\n}\r\n#pragma managed\r\n\r\n\tvoid PrintHelper::Print(IntPtr^ ptrIWebBrowser2, String^ header,  String^ footer)\r\n\t{\r\n\t\tIWebBrowser2* pBrowser = (IWebBrowser2 *)ptrIWebBrowser2-&gt;ToPointer();\r\n\r\n\t\tIDispatch *pDisp;\r\n\t\tpBrowser-&gt;get_Document(&amp;pDisp);\r\n\r\n\t\tIHTMLDocument2 *pDoc;\r\n\t\tpDisp-&gt;QueryInterface&lt;ihtmldocument2&gt;(&amp;pDoc);\r\n\r\n\t\tIHTMLElement *body;\r\n\t\tpDoc-&gt;get_body(&amp;body);\r\n\r\n\t\tBSTR p;\r\n\t\tbody-&gt;get_innerHTML(&amp;p);\r\n\r\n\t\tIntPtr pHeader = Runtime::InteropServices::Marshal::StringToBSTR(header);\r\n\t\tIntPtr pFooter = Runtime::InteropServices::Marshal::StringToBSTR(footer);\r\n\r\n\t\tUnmanagedPrint(pBrowser, (BSTR)pHeader.ToPointer(), (BSTR)pFooter.ToPointer());\r\n\t}\r\n}\r\n<\/pre>\n<p>3.<br \/>\nNow we need to generate C# version of the <em>IWebBrowser2 <\/em>COM interface:<br \/>\nIt can be generated from idl -> tlb -> dll and the referenced from the WindowsForms app.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmidl ExDisp.Idl \/tlb ExDisp.tlb\r\npause\r\ntlbimp ExDisp.tlb \/out:ExDisp.dll\r\npause\r\n<\/pre>\n<p>4.<br \/>\nThen we create WindowsForms project, and create custom control inheriting<br \/>\nfrom <em>WebBrowser <\/em> control.<br \/>\nWe need this to access IWebBrowser2 interface (defined in ExDisp.dll):<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing ExDisp;\r\nusing WebBrowser = System.Windows.Forms.WebBrowser;\r\n\r\nnamespace WindowsFormsApplication1.MyBrowser\r\n{\r\n    public class MyWebBrowser : WebBrowser\r\n    {\r\n        public IWebBrowser2 axIWebBrowser2;\r\n\r\n        protected override void AttachInterfaces(object nativeActiveXObject)\r\n        {\r\n            base.AttachInterfaces(nativeActiveXObject);\r\n            this.axIWebBrowser2 = (IWebBrowser2) nativeActiveXObject;\r\n        }\r\n\r\n        protected override void DetachInterfaces()\r\n        {\r\n            base.DetachInterfaces();\r\n            this.axIWebBrowser2 = null;\r\n        }\r\n    };\r\n}\r\n<\/pre>\n<p>5.<br \/>\nFinally we create a Form and add the printing code there:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nprivate void _btnPrint_Click(object sender, EventArgs e)\r\n{\r\n    IntPtr ptr = Marshal.GetComInterfaceForObject(\r\n        webBrowser1.axIWebBrowser2,\r\n        typeof(IWebBrowser2));\r\n    PrintHelper.Print(ptr, &quot;this is my header&quot;, &quot;this is my footer&quot;);\r\n}\r\n<\/pre>\n<p>Modified the header and footer:<br \/>\n<a href=\"\/blog\/wp-content\/uploads\/2009\/09\/NiceFooter.png\"><img loading=\"lazy\" decoding=\"async\" src=\"\/blog\/wp-content\/uploads\/2009\/09\/NiceFooter.png\" alt=\"NiceFooter\" title=\"NiceFooter\" width=\"498\" height=\"39\" class=\"alignnone size-full\" \/><\/a><\/p>\n<p><strong>The Zip:<\/strong><br \/>\n<a href='\/blog\/wp-content\/uploads\/2009\/09\/IEPrinting.zip'>IEPrinting<\/a><\/p>\n<p><strong>References:<\/strong><br \/>\n<a href=\"http:\/\/support.microsoft.com\/kb\/267240\" rel=\"nofollow\">http:\/\/support.microsoft.com\/kb\/267240<\/a><br \/>\n<a href=\"http:\/\/thedotnet.com\/nntp\/97691\/showpost.aspx\" rel=\"nofollow\">http:\/\/thedotnet.com\/nntp\/97691\/showpost.aspx<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[15],"class_list":["post-103","post","type-post","status-publish","format-standard","hentry","category-programming","tag-c"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/posts\/103"}],"collection":[{"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/comments?post=103"}],"version-history":[{"count":3,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/posts\/103\/revisions"}],"predecessor-version":[{"id":5098,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/posts\/103\/revisions\/5098"}],"wp:attachment":[{"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/media?parent=103"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/categories?post=103"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.limilabs.com\/blog\/wp-json\/wp\/v2\/tags?post=103"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}