65.9K
CodeProject 正在变化。 阅读更多。
Home

定制ASP.NET数据网格打印

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.65/5 (16投票s)

2004年8月2日

5分钟阅读

viewsIcon

180201

downloadIcon

3266

提供ASP.NET数据网格自定义打印的解决方案

引言

您尝试过打印ASP.NET中的数据网格吗?如果您尝试过,您就会遇到数据分页导致信息截断的问题。其结果是单词被切断,下一页的网格没有列标题。

当我遇到这个问题时,我必须找到一个解决方案,它能给我灵活性,让我精确地确定每一页打印的内容。

我没有提供本文的源代码,仅仅是因为代码过于针对我的特定应用程序,会令人困惑。但请耐心阅读本文,因为我尝试提供一个实现您自己解决方案的基本框架。

背景

该解决方案使用了Microsoft的打印模板,因此只能与IE 5.5或更高版本兼容。据我所知,这是唯一支持的环境。如果这满足您的要求,那么请继续阅读...

详细信息

解决方案的关键在于位于ASP.NET页面上的Active-X控件。在撰写本文时,该Active-X是用C++编写的,但我想知道是否有人能让它在C#中工作。Active-X在ASP.NET页面上实例化,并设置参数。所需的参数如下:

  • ServerAddress - 托管ASP.NET页面的计算机名称,例如server1;
  • XML - 数据文件,例如data.xml;
  • Template - 打印模板文件,例如template.html;

在ASP.NET页面上还有两个服务器变量和一个用于Active-X的div。

<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>

onload事件中,我们创建Active-X对象并传入相关参数。

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;
  }
}

Active-X控件有自己的用户界面,这就是我设置ASP.NET窗体上控件宽度和高度的原因。Active-X的用户界面有2个按钮,其中一个按钮直接打印,另一个按钮会弹出打印预览窗口。下面是用户界面的示例代码。

<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>

每个按钮都有一个特殊的onclick事件,它会导航到一个虚构的地址。Active-X然后捕获OnBeforeNavigate2消息并执行其魔术。

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;
  }

这实际上是构建模板文件(位于服务器上)的路径,并将数据文件添加为参数。

例如:http:\\server1\webAplication1\template.html?data.xml

然后它会回调浏览器来打印或打印预览模板。

打印模板

我在本文顶部链接的Microsoft打印模板示例应用程序在独立应用程序层面做了类似的事情。该示例允许您指定一个模板文件并按“打印预览”按钮。尝试选择“Template7.htm - 构建用户界面”选项,然后按“打印预览”按钮。

查看template7的源代码,您会发现在底部是HTML页面的主体。上面是处理打印部分JavaScript。很棒的一点是,由于它在客户端运行,我们可以访问当前的打印机页面大小、边距等...

现在,这并没有解决我打印数据网格的问题,因为网格仍然会溢出到下一页。如果您查看文件template.html,您应该会发现主要区别在于Init()函数。

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();
}

首先,加载data.xml文件(基于URL)。然后调用GenerateHTMLPages函数(存储在一个单独的JavaScript文件中),该函数将data.xml文件编译成单独的HTML文件,用于每一页打印(稍后详细介绍)。

最后一步是添加第一页,然后依赖于OnRectComplete消息的到来。当消息到来时,我们调用AddNewPage来添加新页面。这会发生在每个添加的页面上,直到所有页面都添加完毕。如果您查看Microsoft的示例模板文件,您会看到OnRectComplete消息会检查事件是否是由于contentOverflow引起的。正是这导致了数据网格溢出到下一页,依此类推。由于我们已经生成了HTML页面,所以我们不需要处理这种情况。

GenerateHTMLPages函数会接收数据文件和可用的可打印区域作为参数。此时,如何实现完全取决于您。
我所做的是遍历我的数据文件,根据每行20毫米的高度这一名义值来确定每页需要容纳多少行。这样,我就可以为每一页打印生成一个全新的HTML页面,并在其中放置我想要的内容,然后将其保存为临时文件(如下所示)。AddNewPage函数会自动处理每个页面的拾取,基于页码。例如:output1.HTML,output2.HTML,依此类推...

// 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;
}

在处理页眉和页脚时,我也做了类似的事情。我只是编写了一个返回包含页眉和页脚信息的HTML字符串的方法。打印模板会处理其余的部分。

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);
    }
  }
}

就是这样。希望我在信息流方面没有遗漏任何内容,但正如您可能已经意识到的那样,您——开发者——还有很多工作要做才能在您的系统上实现这一点。从我的角度来看,我希望这能为您提供一个起点。本文主要讨论打印数据网格,但这并不是最终目的。通过控制每一页的打印内容,您可以决定打印什么。

供参考,一篇关于打印模板的出色教程可在此处找到。

关注点

虽然在我看来,这样做本质上看似简单的事情却是一条极其曲折的路线,但这是我发现的唯一一种能让我实现客户端自定义逐页打印的灵活性。我通过这段代码学到了很多,希望我没有完全错过一个更简单的解决方案。

历史

  • 版本1:2004年7月27日 - 原始
© . All rights reserved.