Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Customised ASP.NET Printing for Data Grids

0.00/5 (No votes)
1 Aug 2004 1  
A solution to provide custom printing for data grids in ASP.NET

Introduction

Ever tried to print a data grid in ASP.NET? If you have you would have come up with the problem of the data filtering over onto the next page. The result of which are cut-off words and a grid on the next page without any column headers.

When I came up with this problem I had to find a solution that would give me the flexibility to determine exactly what went on each printed page.

I haven't provided the source code to this article simply because the code is too tailored to my particular application and would be confusing. But bear with the article because what I have done is try provide you with the basic framework for implementing your own solution to this problem.

Background

The solution uses Microsoft's print templates and therefore can only work with IE 5.5 or above. As far as I know this is the only supported environment. If this satisfies your requirements then read on...

Detail

The key to the solution is an Active-X control that lives on the ASP.NET page. At the time of writing this article the Active-X is written in C++ but I would like to know if anyone manages to get it to work in C#. The Active-X is instantiated on the ASP.NET page and the parameters are set. The parameters that are needed are as follows:

  • ServerAddress - the name of the computer hosting the ASP.NET pages e.g. server1;
  • XML - the data file e.g. data.xml;
  • Template - the print template file e.g. template.html;

Also on the ASP.NET page are two server variables and a div for the Active-X

<div id="printTemplateObject"></div>
<div id="svServer" style="VISIBILITY: hidden">
<%= Request.ServerVariables["SERVER_NAME"] %></div>
<div id="svURL" style="VISIBILITY: hidden">
<%= Request.ServerVariables["URL"] %></div>

In the onload event we create our Active-X object and pass in the relevant parameters

function CreateObject()
{
  var server = document.all("svServer");
  var url = document.all("svURL");
  var oText = "";
  var position = url.innerText.lastIndexOf("webApplication");
  var location = url.innerText.substr(0, position + 14);
  location = "http://" + server.innerText + location + "PrintTemplate.dll";

  var printTO = document.all("printTemplateObject");
  if(printTO != null)
  {
    oText += "<OBJECT id=\"printTemplateWindow\" ";
    oText += "style=\"Z-INDEX: 120; LEFT: 0px; WIDTH: 100%;" + 
      " POSITION: absolute; TOP: 800px; HEIGHT: 102px\" ";
    oText += "codebase=\"";
    oText += location;
    oText += "\"";
    oText += "classid=\"clsid:CDF583D3-041E-4D1A-AB51-19CE1D3A56A3\" ";
    oText += "name=\"printTemplateWindow\" ";
    oText += "VIEWASTEXT>";

    oText += "<PARAM NAME=\"ServerAddress\" VALUE=\"//";
    oText += server.innerText;
    oText += "/";
    oText += url.innerText;
    oText += "\">";

    oText += "<PARAM NAME=\"XML\" VALUE=\"";
    oText += "data.xml";
    oText += "\">";

    oText += "<PARAM NAME=\"Template\" VALUE=\"";
    oText += "template.html";
    oText += "\">";
    
    oText += "</OBJECT>";
    printTO.innerHTML = oText;
  }
}

The Active-X control has its own user interface and this is why I have set the width and height of the control on the ASP.NET form. The user interface of the Active-X has 2 buttons, where one button directly prints and the other button brings up a print preview window. Example code for the user interface is shown below:

<HTML>
<BODY id=theBody>
<TABLE width="90%" align="center" ID="Table1">
  <TR>
    <TD align="center">
      <BUTTON style="width: 50mm; height: 10mm;"
        onclick='navigate("##print##")' ID="Button1">Print</BUTTON>
    </TD>
    <TD align="center">
      <BUTTON style="width: 50mm; height: 10mm;" 
        onclick='navigate("##preview##")' ID="Button2">Print Preview</BUTTON>
    </TD>
  </TR>
</TABLE>
</BODY>
</HTML>

Each button has a special onclick event that navigates to a bogus address. The Active-X then captures the OnBeforeNavigate2 message and does its magic.

void CPrintTemplates::OnBeforeNavigate2(
  IDispatch* pDisp, VARIANT* URL, VARIANT* Flags, 
  VARIANT* TargetFrameName, 
  VARIANT* PostData, VARIANT* Headers, VARIANT_BOOL* Cancel )
{
  CString strURL(URL->bstrVal);

  IOleCommandTarget *pCmdTarg;
  m_spBrowser->QueryInterface(IID_IOleCommandTarget, (void **)&pCmdTarg);

  VARIANT vTemplatePath;
  V_VT(&vTemplatePath) = VT_BSTR;
  CString path = "http:";
  path += m_bstrServerAddress.Copy();
  path += m_bstrTemplate.Copy();
  path += "?";
  path += m_bstrXML.Copy();
  V_BSTR(&vTemplatePath) = path.AllocSysString();

  if(strURL.Left(9) == "##print##")
  {
    HRESULT hr = pCmdTarg->Exec(&CGID_MSHTML, IDM_PRINT, 
      OLECMDEXECOPT_PROMPTUSER, &vTemplatePath, NULL);
    *Cancel = true;
    return;
  }
  else if(strURL.Left(11) == "##preview##")
  {
    HRESULT hr = pCmdTarg->Exec(&CGID_MSHTML, IDM_PRINTPREVIEW, 
      OLECMDEXECOPT_PROMPTUSER, &vTemplatePath, NULL);
    *Cancel = true;
    return;
  }

What this essentially does is build up the path to the template file (that lives on the server) and adds the data file as a parameter

E.g. http:\\server1\webAplication1\template.html?data.xml

It then calls back to the browser to either print or print preview the template.

Print Templates

The Microsoft Print Templates example application that I linked too at the top of this article does much the same but in a stand-alone application sense. The example allows you to specify a template file and press the 'Print Preview' button. Try selecting the Template7.htm - Building a User Interface' option and press the 'Print Preview' button.

Take a look at the source of template7 and you'll see the main body of the HTML page at the bottom. Above it is the JavaScript that handles the printing side of things. The great thing about this is that as it runs client side we have access to the current printer page size, margins, etc...

Now this doesn't solve my problem of printing data grids as the grid will still spill over onto the next page. If you look at the file template.html you should see that the main difference is in the Init() function.

function Init()
{
  printingStarted = false;
  if(zoomcontainer != null)
  {
    zoomcontainer.innerText = "";
  }
  // load the xml document

  xmlObj = new ActiveXObject('MSXML2.DOMDocument');
  xmlObj.async = false;

  var templateLocation = document.URL.substr(
    0, document.URL.indexOf("?"));
  var position = templateLocation.lastIndexOf("webApplication1");
  var location = templateLocation.substr(0, position + 8);
  reportFile = location + "/" + 
    document.URL.substr(document.URL.indexOf("?") + 1, 
    document.URL.length);

  if(xmlObj.load(reportFile) == false)
  {
    alert("Failed to load document: " + 
      document.URL.substr(document.URL.indexOf("?") + 1, 
      document.URL.length));
    return;
  }

  zoomcontainer.style.zoom = "50%";
  ui.style.width = document.body.clientWidth;
  ui.style.height = "50px";
  pagecontainer.style.height = 
    document.body.clientHeight - ui.style.pixelHeight;

  InitClasses();
  file = GenerateHTMLPages(xmlObj,
    (printer.pageWidth - (printer.marginLeft + printer.marginRight))/100,
    (printer.pageHeight - (printer.marginTop + printer.marginBottom))/100);

  // add in all of the required pages

  AddFirstPage(file);
  pages = Pages();
}

Firstly, the data.xml file is loaded (based upon the URL). Then the function GenerateHTMLPages (stored in a seperate JavaScript file) is called which compiles the data.xml file into separate HTML files for each printed page (more on this later).

The final stage adds the first page and then relies on the OnRectComplete message coming in. When it does we call AddNewPage to add in the new page. This occurs for each added page until all of the pages have been added. If you look at the Microsoft example template file you'll see that the OnRectComplete messages checks to see if the event is due to contentOverflow. It is this that causes the overflowing of the data grid onto the next page and so on. As we have already generated our HTML pages we don't need to handle this case.

The function GenerateHTMLPages is passed the data file and the available printable area. At this point it really up to you how to implement this.
What I did is navigate through my data file and determine how many rows I wanted to fit on a page based upon a nominal figure of 20mm height for each row. With this I can now generate a totally new HTML page for each printed page and put what I want on it and then save it as a temporary file (shown below). The AddNewPage function handles the picking up of each page automatically based on the page number E.g. output1.HTML, output2.HTML, etc...

// file generateHTML.js

// this method saves the passed HTML text to file

function Save(file, pageNumber, htmlText)
{
  file = file + pageNumber;
  file = file + ".html"

  var fso = new ActiveXObject("Scripting.FileSystemObject");
  var folder = fso.GetSpecialFolder(2);

  file = folder + "/" + file;
  var a = fso.CreateTextFile(file, true);
     a.WriteLine(htmlText);
  a.Close();
  
  return file;
}

When it came to Headers and footers I did much the same. I simply wrote a method that returns a HTML string that contains the header and footer information. The print template takes care of the rest of it

function AddHeaderAndFooterToPage(pageNum)
{
    newHeader = "<DIV CLASS='headerstyle'>" + 
    GenerateHeaderFooterHTML(pageNum, GetHeaderFields(xmlObj)) + "</DIV>";
    newFooter = "<DIV CLASS='footerstyle'>" + 
    GenerateHeaderFooterHTML(pageNum, GetFooterFields(xmlObj)) + "</DIV>";
    
  if(document != null)
  {
    if(document.all("page" + pageNum) != null)
    {
      document.all("page" + 
        pageNum).insertAdjacentHTML("afterBegin", newHeader); 
      document.all("page" + 
        pageNum).insertAdjacentHTML("beforeEnd", newFooter);
    }
  }
}

And that's it. Hopefully I haven't missed anything out in terms of the flow of information but as you have probably realised by now there is still alot more for you; the developer; to do in order to get this working on you own system. From my point of view I hope that this serves as a first step for you. This article talks mainly about printing data grids but this is not the end of it. With control of each printed page it is up to you what gets printed.

For reference, a great tutorial on Print Templates is available here.

Points of Interest

Whilst this (in my opinion) is an incredible convoluted route to do in essence something which seems very simple, it is the only way that I have found that gives me the flexibility of client-side customized page-by-page printing. I have certainly learnt a great deal with this code and I hope more that I didn't completely miss the boat on a simpler solution.

History

  • Version 1 : 27th July 2004 - Original

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