利用XML实现通用WEB报表打印实现篇.docx
- 文档编号:4310922
- 上传时间:2022-11-29
- 格式:DOCX
- 页数:27
- 大小:276.47KB
利用XML实现通用WEB报表打印实现篇.docx
《利用XML实现通用WEB报表打印实现篇.docx》由会员分享,可在线阅读,更多相关《利用XML实现通用WEB报表打印实现篇.docx(27页珍藏版)》请在冰豆网上搜索。
利用XML实现通用WEB报表打印实现篇
专栏作品
续:
利用XML实现通用WEB报表打印(实现篇)
卢彦
引言:
在《利》刊出后,有大量的读者发E-Mail给我表示对该方案非常感兴趣,同时还询问具体如何实现报表格式的解析和打印细节并索取该程序的源代码。
读者的热情让我始料未及,虽然我一一对来信进行解答和发送了源代码,但是还是深感抱歉和遗憾,因为时间和精力的关系,我不可能对每封信都作出很详细的答复,而且我写的源代码也很乱,事实上,是我花了两个小时赶写出来的(原来的程序因为硬盘故障被销毁了),不但没有什么注解而且还不完善,包括一些标签还没有被实现。
为了弥补以前的缺憾,我花了一些时间改进了程序的结构,重写了全部的源代码,实现了所有标签的功能,下面就要开始讲解该程序的设计和编码过程,在看此文之前,强烈推荐您先阅读《利》一文来了解一下相关的概念,如果在该文中已经有清楚讲解的部分,本文将不再详细介绍,这里只将主要讲解《利》文没有提及或是介绍得不清楚和读者来信提问最多的部分。
软件原理:
该软件的原理其实很简单,就是要方便的解析出定义好的XML格式标记,解读出文件中标记的参数定义,最后将这些信息还原成打印机输出的图形格式。
为了能表达出复杂的报表样式,我们需要定义一些标记,在这些标记中附加上具体的样式信息,作用类似HTML的标签,而我们的解析程序就相当于IE浏览器,所不同的是IE将图形输出到屏幕,而我们是将图形输出到打印机,由于打印机相对于显示屏的特殊性(例如分页),因此我们不能直接采用网页浏览器的标签解析功能来打印,需要自己来做一个满足需要的"打印浏览器"。
针对大多数报表的功能需要,我只定义了两种格式标签:
文本(text)和表格(table),它们的具体属性定义和另外一些设置性的标签定义请参考《利》文,这里再补充一幅结构图帮助读者理解。
如下所示:
结构设计:
为了描述所有的样式标记,我先定义了一个抽象基类PrintElement,它拥有一个虚拟方法Draw,然后对应表格和文本,从PrintElement派生出两个子类,分别是Table和Text,我还创建了一个Parser类用来解析不同的样式标记和创建对应的对象,它拥有一个静态的方法CreateElement,用来根据不同的格式标签创建出对应的对象。
结构图如下所示:
读过《设计模式》的读者一定已经看出来了,这种设计应用了设计模式中的一个非常著名的模式:
AbstractFactory。
这里使用该模式的好处就是让标签对象和解析器都独立出来,降低了系统的耦合度,有利于今后在需要的时候可以很容易的增加其它的格式标签(下文将会举一个实例)和方便的更换不同的用户界面(图中Client表示Windows应用程序或者是网页插件)。
首先,创建一个"Windows控件库"的新项目,在项目名称处写入RemotePrint,如下图所示:
然后把新建项目中的那个默认的UserControl1类,它的构造函数名和文件名都改成PrintControl。
再将它的背景颜色设置为白色,添加三个按纽,并将它们的Enable属性都设置为false,Anchor属性设置为Bottom,Right,再添加一个Label控件用来显示程序状态,它的Anchor属性设置为Left。
如下图所示:
再从控件栏中拖入三个打印对象:
PrintDocument,PageSetupDialog,PrintPreviewDialog,如下图所示:
将其中的pageSetupDialog1和printPreviewDialog1的Document属性均设置为printDocument1。
然后为项目添加一个PrintElement的新类,代码如下:
usingSystem;
usingSystem.Xml;
usingSystem.Drawing;
namespaceRemotePrint
{
publicclassPrintElement
{
publicPrintElement()
{
}
publicvirtualboolDraw(Graphicsg)
{
returnfalse;
}
}
}
该类中只有一个虚拟方法Draw,注意它规定需要返回一个bool值,这个值的作用是用来指示标签是否在页内打印完毕。
然后再添一个Table的新类,代码如下:
usingSystem;
usingSystem.Xml;
usingSystem.Drawing;
namespaceRemotePrint
{
publicclassTable:
PrintElement
{
privateXmlNodetable;
publicstaticintcount=0,pc=1;
publicTable(XmlNodeTable)
{
table=Table;
}
publicoverrideboolDraw(Graphicsg)
{
//表格坐标
inttableX=int.Parse(table.Attributes["x"].InnerText);
inttableY=int.Parse(table.Attributes["y"].InnerText);
intx=tableX,y=tableY;
DrawTopLine(g,table);//画表格顶线
Penpen=newPen(Color.FromName(table.Attributes["bordercolor"].InnerText),
float.Parse(table.Attributes["border"].InnerText));
inttrheight=0;
//表头
foreach(XmlNodetrintable["tablehead"].ChildNodes)
{
trheight=int.Parse(tr.Attributes["height"].InnerText);
DrawTR(x,y,tr,pen,g);
y+=trheight;
}
//表项
for(inti=0;i { XmlNodetr=table["tablebody"].ChildNodes[count]; trheight=int.Parse(tr.Attributes["height"].InnerText); DrawTR(x,y,tr,pen,g); y+=trheight; count++; if(count==table["tablebody"].ChildNodes.Count) break; } x=tableX; //表底 foreach(XmlNodetrintable["tablefoot"].ChildNodes) { trheight=int.Parse(tr.Attributes["height"].InnerText); DrawTR(x,y,tr,pen,g); y+=trheight; } intcurrentpage=pc; pc++; boolhasPage=false; if(count { hasPage=true;//需要继续打印 } else { count=0; pc=1; hasPage=false;//表格打印完毕 } returnhasPage; } privatevoidDrawTopLine(Graphicsg,XmlNodetable) { Penpen=newPen(Color.FromName(table.Attributes["bordercolor"].InnerText), float.Parse(table.Attributes["border"].InnerText)); intwidth=0; foreach(XmlNodetdintable.FirstChild.FirstChild) { width+=int.Parse(td.Attributes["width"].InnerText); } intx=int.Parse(table.Attributes["x"].InnerText); inty=int.Parse(table.Attributes["y"].InnerText); g.DrawLine(pen,x,y,x+width,y); } //画表格行 privatevoidDrawTR(intx,inty,XmlNodetr,Penpen,Graphicsg) { intheight=int.Parse(tr.Attributes["height"].InnerText); intwidth; g.DrawLine(pen,x,y,x,y+height);//画左端线条 foreach(XmlNodetdintr) { width=int.Parse(td.Attributes["width"].InnerText); DrawTD(x,y,width,height,td,g); g.DrawLine(pen,x+width,y,x+width,y+height);//右线 g.DrawLine(pen,x,y+height,x+width,y+height);//底线 x+=width; } } //画单元格 privatevoidDrawTD(intx,inty,intwidth,intheight,XmlNodetd,Graphicsg) { Brushbrush=newSolidBrush(Color.FromName(td.Attributes["bgcolor"].InnerText)); g.FillRectangle(brush,x,y,width,height); FontStylestyle=FontStyle.Regular; //设置字体样式 if(td.Attributes["b"].InnerText=="true") style|=FontStyle.Bold; if(td.Attributes["i"].InnerText=="true") style|=FontStyle.Italic; if(td.Attributes["u"].InnerText=="true") style|=FontStyle.Underline; Fontfont=newFont(td.Attributes["fontname"].InnerText, float.Parse(td.Attributes["fontsize"].InnerText),style); brush=newSolidBrush(Color.FromName(td.Attributes["fontcolor"].InnerText)); StringFormatsf=newStringFormat(); //设置对齐方式 switch(td.Attributes["align"].InnerText) { case"center": sf.Alignment=StringAlignment.Center; break; case"right": sf.Alignment=StringAlignment.Near; break; default: sf.Alignment=StringAlignment.Far; break; } sf.LineAlignment=StringAlignment.Center; RectangleFrect=newRectangleF((float)x,(float)y, (float)width,(float)height); g.DrawString(td.InnerText,font,brush,rect,sf); } } } Table类将table标签内部的解析和打印独立出来,全部在类的内部完成,这样,我们在对顶层标签解析的时候只要是碰到table标签就直接交给Table类去完成,不需要再关心其实现细节。 再添加一个Text类,代码如下: usingSystem; usingSystem.Xml; usingSystem.Drawing; namespaceRemotePrint { publicclassText: PrintElement { privateXmlNodetext=null; publicText(XmlNodeText) { text=Text; } publicoverrideboolDraw(Graphicsg) { Fontfont=newFont(text.Attributes["fontname"].InnerText, int.Parse(text.Attributes["fontsize"].InnerText)); Brushbrush=newSolidBrush(Color.FromName(text.Attributes ["fontcolor"].InnerText)); g.DrawString(text.InnerText,font,brush,float.Parse (text.Attributes["x"].InnerText), float.Parse(text.Attributes["y"].InnerText)); returnfalse; } } } 同Table类一样,Text类完成对text标签的解析和打印,不过因为text的简单性,它的代码也少了很多。 它们两者同样继承自PrintElement,都重载了Draw方法的实现。 最后,我们还需要一个解析器用来解析顶层的标签和生成相应的对象,它在此模式中的作用就是一个"工厂类",负责生产出用户需要的"产品"。 代码如下: usingSystem; usingSystem.Xml; namespaceRemotePrint { publicclassParser { publicParser() { } publicstaticPrintElementCreateElement(XmlNodeelement) { PrintElementprintElement=null; switch(element.Name) { case"text": printElement=newText(element); break; case"table": printElement=newTable(element); break; default: printElement=newPrintElement(); break; } returnprintElement; } } } 好了,核心的解析和标签的具体打印方法已经完成了,现在我们回到PrintControl中编写一些代码来测试我们的成果。 首先,需要引用两个要用到的名称空间: usingSystem.Xml; usingSystem.Drawing.Printing; 然后,在打印之前,需要根据XML文件中的pagesetting标签来设置一下打印机的页面,所以我们先写一个方法来设置打印机。 在PrintControl类中增加一个私有的方法: privatevoidSettingPrinter(XmlNodeps) { //打印方向(纵/横) this.printDocument1.DefaultPageSettings.Landscape=bool.Parse(ps["landscape"].InnerText); //设置纸张类型 stringpapername=ps["paperkind"].InnerText; boolfitpaper=false; //获取打印机支持的所有纸张类型 foreach(PaperSizesizeinthis.printDocument1.PrinterSettings.PaperSizes) { if(papername==size.PaperName)//看该打印机是否有我们需要的纸张类型 { this.printDocument1.DefaultPageSettings.PaperSize=size; fitpaper=true; } } if(! fitpaper) { //假如没有我们需要的标准类型,则使用自定义的尺寸 this.printDocument1.DefaultPageSettings.PaperSize= newPaperSize("Custom",int.Parse(ps["paperwidth"].InnerText), int.Parse(ps["paperheight"].InnerText)); } } 接下来,我们类中添加一个XmlDocument的对象和一个静态变量计算页码: privateXmlDocumentdoc=newXmlDocument(); publicstaticintPages=1; 然后再控件的Load事件中为该对象加载XML报表数据,代码如下: privatevoidPrintControl_Load(objectsender,System.EventArgse) { try { //装载报表XML数据 this.label1.Text="正在加载报表数据,请稍侯..."; doc.Load("http: //localhost/report.xml"); this.label1.Text="报表数据加载完毕! "; this.button1.Enabled=this.button2.Enabled=this.button3.Enabled=true; } catch(Exceptionex) { this.label1.Text="出现错误: "+ex.Message; } } 请注意,我们这里只是装入了一个本地的测试数据文件(该文件的编写请参考《利》文),其实,完全可以改成装载网络上任何地方的静态或者动态的XML文件,例如以上的doc.Load("http: //localhost/report.xml")可以改写成: doc.Load(" doc.Load(" doc.Load(" 等等,只要装载的数据是符合我们规定的XML数据文档就可以。 然后在控件的构造函数中加入打印事件的委托: publicPrintControl() { InitializeComponent(); this.printDocument1.PrintPage+=newPrintPageEventHandler(this.pd_PrintPage); } 该委托方法的代码如下: privatevoidpd_PrintPage(objectsender,PrintPageEventArgsev) { Graphicsg=ev.Graphics; boolHasMorePages=false; PrintElementprintElement=null; foreach(XmlNodenodeindoc["root"]["reporttable"].ChildNodes) { printElement=Parser.CreateElement(node);//调用解析器生成相应的对象 try { HasMorePages=printElement.Draw(g);//是否需要分页 } catch(Exceptionex) { this.label1.Text=ex.Message; } } //在页底中间输出页码 Fontfont=newFont("黑体",12.0f); Brushbrush=newSolidBrush(Color.Black); g.DrawString("第"+Pages.ToString()+"页", font,brush,ev.MarginBounds.Width/2+ev.MarginBounds.Left-30, ev.PageBounds.Height-60); if(HasMorePages) { Pages++; } ev.HasMorePages=HasMorePages; } 三个按纽的Click事件代码分别如下: //页面设置 privatevoidbutton1_Click(o 如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。 copyright@ 2008-2022 冰点文档网站版权所有 经营许可证编号:鄂ICP备2022015515号-1