利用XML实现通用WEB报表打印(3)

.Net Web控件方案的实现和扩充

   软件原理:

  该软件的原理其实很简单,就是要方便的解析出定义好的XML格式标记,解读出文件中标记的参数定义,最后将这些信息还原成打印机输出的图形格式。

  为了能表达出复杂的报表样式,我们需要定义一些标记,在这些标记中附加上具体的样式信息,作用类似HTML的标签,而我们的解析程序就相当于IE浏览器,所不同的是IE将图形输出到屏幕,而我们是将图形输出到打印机,由于打印机相对于显示屏的特殊性(例如分页),因此我们不能直接采用网页浏览器的标签解析功能来打印,需要自己来做一个满足需要的"打印浏览器"。

  针对大多数报表的功能需要,我只定义了两种格式标签:文本(text)和表格(table),它们的具体属性定义和另外一些设置性的标签定义请参考前文,这里再补充一幅结构图帮助读者理解。如下所示:

  结构设计:

  为了描述所有的样式标记,我先定义了一个抽象基类PrintElement,它拥有一个虚拟方法Draw,然后对应表格和文本,从PrintElement派生出两个子类,分别是Table和Text,我还创建了一个Parser类用来解析不同的样式标记和创建对应的对象,它拥有一个静态的方法CreateElement,用来根据不同的格式标签创建出对应的对象。结构图如下所示:


  读过《设计模式》的读者一定已经看出来了,这种设计应用了设计模式中的一个非常著名的模式:Abstract Factory。这里使用该模式的好处就是让标签对象和解析器都独立出来,降低了系统的耦合度,有利于今后在需要的时候可以很容易的增加其它的格式标签(下文将会举一个实例)和方便的更换不同的用户界面(图中Client表示Windows应用程序或者是网页插件)。

  代码实现:

  首先,创建一个"Windows控件库"的新项目,在项目名称处写入RemotePrint,如下图所示:


  然后把新建项目中的那个默认的UserControl1类,它的构造函数名和文件名都改成PrintControl。再将它的背景颜色设置为白色,添加三个按纽,并将它们的Enable属性都设置为false,Anchor属性设置为Bottom, Right,再添加一个Label控件用来显示程序状态,它的Anchor属性设置为Left。如下图所示:


  再从控件栏中拖入三个打印对象:PrintDocument, PageSetupDialog, PrintPreviewDialog,如下图所示:


  将其中的pageSetupDialog1和printPreviewDialog1的Document属性均设置为printDocument1。

  然后为项目添加一个PrintElement的新类,代码如下:

using System;
using System.Xml;
using System.Drawing;

namespace RemotePrint
{
public class PrintElement
{
public PrintElement()
{
}

public virtual bool Draw(Graphics g)
{
return false;
}
}
}

  该类中只有一个虚拟方法Draw,注意它规定需要返回一个bool值,这个值的作用是用来指示标签是否在页内打印完毕。

  然后再添一个Table的新类,代码如下:

using System;
using System.Xml;
using System.Drawing;

namespace RemotePrint
{
public class Table : PrintElement
{
private XmlNode table;
public static int count = 0, pc = 1;

public Table(XmlNode Table)
{
table = Table;
}

public override bool Draw(Graphics g)
{
file://表格坐标
int tableX = int.Parse(table.Attributes["x"].InnerText);
int tableY = int.Parse(table.Attributes["y"].InnerText);
int x = tableX, y = tableY;
DrawTopLine(g, table);//画表格顶线
Pen pen = new Pen(Color.FromName(table.Attributes["bordercolor"].InnerText),
float.Parse(table.Attributes["border"].InnerText));
int trheight = 0;
file://表头
foreach(XmlNode tr in table["tablehead"].ChildNodes)
{
trheight = int.Parse(tr.Attributes["height"].InnerText);
DrawTR(x, y, tr, pen, g);
y += trheight;
}
file://表项
for(int i = 0; i < int.Parse(table.Attributes["maxlines"].InnerText); i++)
{
XmlNode tr = 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;
file://表底
foreach(XmlNode tr in table["tablefoot"].ChildNodes)
{
trheight = int.Parse(tr.Attributes["height"].InnerText);
DrawTR(x, y, tr, pen, g);
y += trheight;
}
int currentpage = pc;
pc++;
bool hasPage = false;

if(count < table["tablebody"].ChildNodes.Count - 1)
{
hasPage = true;//需要继续打印
}
else
{
count = 0;
pc = 1;
hasPage = false;//表格打印完毕
}
return hasPage;
}

private void DrawTopLine(Graphics g, XmlNode table)
{
Pen pen = new Pen(Color.FromName(table.Attributes["bordercolor"].InnerText),
float.Parse(table.Attributes["border"].InnerText));
int width = 0;
foreach(XmlNode td in table.FirstChild.FirstChild)
{
width += int.Parse(td.Attributes["width"].InnerText);
}
int x = int.Parse(table.Attributes["x"].InnerText);
int y = int.Parse(table.Attributes["y"].InnerText);
g.DrawLine(pen, x, y, x + width, y);
}

file://画表格行
private void DrawTR(int x, int y, XmlNode tr, Pen pen, Graphics g)
{
int height = int.Parse(tr.Attributes["height"].InnerText);
int width;
g.DrawLine(pen, x, y, x, y + height);//画左端线条
foreach(XmlNode td in tr)
{
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;
}
}

file://画单元格
private void DrawTD(int x, int y, int width, int height, XmlNode td, Graphics g)
{
Brush brush = new SolidBrush(Color.FromName(td.Attributes["bgcolor"].InnerText));
g.FillRectangle(brush, x, y, width, height);
FontStyle style = FontStyle.Regular;
file://设置字体样式
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;
Font font = new Font(td.Attributes["fontname"].InnerText,
float.Parse(td.Attributes["fontsize"].InnerText), style);
brush = new SolidBrush(Color.FromName(td.Attributes["fontcolor"].InnerText));
StringFormat sf = new StringFormat();
file://设置对齐方式
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;
RectangleF rect = new RectangleF( (float)x, (float)y, (float)width, (float)height);
g.DrawString(td.InnerText, font, brush, rect, sf);
}
}
}

  Table类将table标签内部的解析和打印独立出来,全部在类的内部完成,这样,我们在对顶层标签解析的时候只要是碰到table标签就直接交给Table类去完成,不需要再关心其实现细节。

  再添加一个Text类,代码如下:

using System;
using System.Xml;
using System.Drawing;

namespace RemotePrint
{
public class Text : PrintElement
{
private XmlNode text = null;
public Text(XmlNode Text)
{
text = Text;
}
public override bool Draw(Graphics g)
{
Font font = new Font(text.Attributes["fontname"].InnerText,
int.Parse(text.Attributes["fontsize"].InnerText));
Brush brush = new SolidBrush(Color.FromName(text.Attributes["fontcolor"].InnerText));
g.DrawString(text.InnerText, font, brush, float.Parse(text.Attributes["x"].InnerText),
float.Parse(text.Attributes["y"].InnerText));
return false;
}
}
}

  同Table类一样,Text类完成对text标签的解析和打印,不过因为text的简单性,它的代码也少了很多。它们两者同样继承自PrintElement,都重载了Draw方法的实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值