网站空间有哪些,网络seo关键词优化技术,用php写的网站,搭建 网站的环节利用XML实现通用WEB报表打印(1) 卢彦 摘要开发B/S结构的应用程序最头疼的问题可能就是报表打印了#xff0c;由于只能采用浏览器来作为用户界面进行交互#xff0c;所以不能精确控制客户端的打印机。而很多B/S结构的应用程序常常需要完成非常复杂的报表打印任务。而靠IE自带的… 利用XML实现通用WEB报表打印(1) 卢彦 摘要 开发B/S结构的应用程序最头疼的问题可能就是报表打印了由于只能采用浏览器来作为用户界面进行交互所以不能精确控制客户端的打印机。而很多B/S结构的应用程序常常需要完成非常复杂的报表打印任务。而靠IE自带的页面打印功能一般不能满足需要。 采用Crystal Report是一种大型报表系统常用和推荐的解决方案但是如果我们只需要进行一些小规模的报表打印的话Crystal Report则显得庞大麻烦了一点可定制性也不太好它的打印实际上也是利用了IE的打印功能也不能精确控制打印效果而且需要您对它进行注册。 所以我们这里讨论的是另外一种办法简单来说如果您有下列需求中的任何一条那么就可以尝试采用本方案。 目录 方案适用性 方案原理 技术选择 可行性分析 伸缩性和安全性 方案设计图 格式定义 总结 作者方案适用性 1. 远程数据打印。需要打印的数据并不在本地必须进行远程读取。 2. 需要精确控制打印效果包括页面格式分页附加条目表格等。 3. 出于安全性考虑不能直接连接到数据库。 方案原理 其实原理很简单通过XML强大的自定义功能我们便能方便的自定义出我们所有需要的格式控制标签在服务器端进行动态编码后通过WEB服务器传到客户端然后在客户端进行格式解析根据服务器端定义的打印格式从客户端直接控制打印机打印出我们需要的报表。 技术选择 由于报表打印比较复杂为了能够精确控制打印格式不能采用WEB浏览器页面打印的方式进行报表打印工作只能采取自编程控制客户端的打印工作。由于.NET framework的winform可以直接嵌入到网页中我们在这里选用了该技术但是请注意我这么做并不代表.NET winform是唯一的选择其实您可以采用任何客户端代替它例如Java Applet或者ActiveX甚至是一个普通的应用程序都能行。 不允许直接连接到数据库因此只能采用XML文件进行中间数据交换格式通过普通WEB服务器的默认80端口进行数据传输。事实上我简直找不到其它更理想的方案了当然web service也许能算是一种但是它采用的是SOAP传输数据从原理上看应该和我们采用的XML属于同种类技术。 再补充说明一下我为什么要采用.NET编写的受控组件优点在于 1. 它不需要进行客户端注册。相对于ActiveX的一个大优点。 2. 比ActiveX安全性高。在.NET Common Language Runtime的控制之下运行 3. 编写方便。我喜欢C#和Visual Studio .NET。 4. 有很强大的打印控制功能。利用.NET framework类库。 5. 直接支持XML技术。 6. 和IE兼容性高。同为Microsoft公司产品。 另外需要注意一点就是在.NET framework sp1和sp2中默认的安全级别是不能直接运行受控组件的但是在.NET framework 1.1 beta中又改了回来可以直接运行了。 服务器端您则可以采用现有的服务器系统和数据库不需要新添加任何新硬件设备和新的.NET服务器管理人员他们往往是些要求拿高薪的家伙。 服务器的工作流程为 1. 接受客户端的标准XML模版查询。 2. 需要根据查询要求将数据库数据格式转换成标准的XML数据格式。 3. 将XML数据通过80端口发送出去。 可行性分析 由于现在的大部分数据库都支持XML格式的数据查询和转换如SQL Server 2000Oracle 9iIBM DB2等大型关系型数据库。只需要通过简单的设置就能直接进行XML数据转换工作。如果数据库不能支持直接XML数据转换也可以籍由一些服务器端脚本程序进行脚本转换工作比如JSPASPPHP等等。 客户端也不需要任何特殊的设置工作仅需要安装一个大小为21M的.NET framework分发包然后直接打开网页就可以进行工作。也没有操作系统限制从windows 98到windows xp都能很好的支持。 伸缩性和安全性 伸缩性 由于采用的是XML标准数据格式作为中间数据交换因此本解决方案具有非常好伸缩性例如客户端的.NET控件可以采用JAVA APPLET、ACTIVX或者是VBVC等编写的客户端应用程序直接替换。服务器也可以任意选择采用IIS或APACHE等WEB服务器。数据库也可以采用任意一种数据库。包括SQL ServerOracle或者是Access等。这点上文已经谈到过因为文章的长一点并不会使送给我的T恤大一号这里再强调一遍只是为了加深读者对XML的跨平台性的认识。 安全性 由于采用的是普通WEB服务器传送数据因此可以直接采用SSL安全套接字等已经成熟的WEB加密技术。同时还可以对XML进行数据算法加密在客户端再进行解密保证了传输的安全性。 由于采用的是80端口不需要再另外新增加专用端口减少了安全漏洞的可能性同时还能方便的穿过双方的的网络防火墙等保护设备。 方案设计图 格式定义 为了能自己控制打印的格式我们定义了下列的格式标签其中在命名上参考了HTML的命名办法所以基本上熟悉HTML的都能一看就能明白标签的具体含义。如果您觉得这些标签的表达能力还不够强您还可以自己定义一些更多更精确的格式标签。 主要标签说明 text文本字符串 属性 x打印输出的X坐标 y打印输出的Y坐标 fontname字体 fontsize字体大小 fontcolor颜色 b是否为粗体 i是否为斜体 u是否有下划线 table表 属性 x打印输出的x坐标 y打印输出的y坐标 border边框粗细 bordercolor边框颜色 maxlines每页最大行数 tr行 属性 height高度 td列 属性 width宽度 align对齐方式 fontname字体 fontsize字体大小 fontcolor字体颜色 b是否粗体 i是否斜体 u是否下划线 bgcolor:背景颜色 next下一页 tablehead表头 tablebody表项 tablefoot表底 page页设置 PrintWard横/纵向打印 PageType纸张类型 PageLeft左边距 PageRight右边距 PageTop上边距 PageBottom下边距 标签应用示例 root
pagesetting
Landscapetrue/Landscape
paperkindA4/paperkind
paperwidth210/paperwidth
paperheight297/paperheight
pageleft0/pageleft
pageright0/pageright
pagetop0/pagetop
pagebottom0/pagebottom
/pagesetting
reporttable
text x450 y40 fontname黑体 fontsize24 fontcolorBlack
btrue ifalse utrue最新成交合同信息/text
text x70 y100 fontname宋体 fontsize12 fontcolorBlack
btrue ifalse utrue制表时间2002年0月10日/text
text x910 y100 fontname宋体 fontsize12 fontcolorBlack
btrue ifalse utrue单位元/text
table x65 y130 border 1 bordercolorBlack maxlines28
tablehead
tr height25
td width90 aligncenter fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite合同号/td
td width90 aligncenter fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite产品名称/td
td width50 aligncenter fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite成交量/td
td width50 aligncenter fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite成交价/td
td width50 aligncenter fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite成交金额/td
td width50 aligncenter fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite挂单量/td
td width50 aligncenter fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite起始价/td
td width330 aligncenter fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite卖方/td
td width330 aligncenter fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite买方/td
/tr
/tablehead
tablebody
tr height25
td width100 alignleft fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite20021010015/td
td width100 alignleft fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhiteCNR/td
td width70 alignleft fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite93/td
td width70 alignleft fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite6680/td
td width70 alignleft fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite621240/td
td width70 alignleft fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite93/td
td width70 alignleft fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite6680/td
td width200 alignleft fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite湖北省国营新星拖拉机厂/td
td width200 alignleft fontname宋体 fontsize12 fontcolorBlack
btrue ifalse ufalse bgcolorWhite中化国际贸易股份有限公司/td
/tr
……….
/tablebody
tablefoot
/tablefoot
/table
/reporttable
/root注意事项 a) 如果采用服务器脚本动态生成XML文档时发送内容类型应该设置为text/xml普通html页面为text/html字符编码应该为UTF-8否则会出现编码错误问题。 b) 应该严格按照XML规定的格式来生成文件否则XML解析器将不会予以解析。 2. 客户端 可以采用任意应用程序来读取服务器端生成的XML文件如果采用VB、DELPHI等桌面应用软件开发工具则可以使用MSXML的COM解析器。推荐采用.NET内部已经集成了XML解析器直接就可以通过使用.NET类库调用。既可以做成桌面应用程序形式通过远程调用也可以嵌入到IE浏览器中直接在网页中运行。 效果示例图 打印预览 注意事项 1. 如果采用.NET客户端必须先安装.NET framework1.0运行环境下载地址为http://download.microsoft.com/download/.NETframesdk/Redist/1.0/W98NT42KMeXP/EN-US/dotnetredist.exe 2. 如果采用嵌入到网页中的形式那么本程序需要编译成一个控件形式一个扩展名为dll的文件然后在网页中插入以下标记 object idprint classidhttp:print.dll#Print.UserControl1 Width728 Height460/object 将控件嵌入到一个静态或动态网页中。然后将该控件文件拷贝到和该网页相同的目录中标记中Print.dll为生成的控件文件名Print.UserControl1为该控件的命名空间NAMESPACE。软件原理 该软件的原理其实很简单就是要方便的解析出定义好的XML格式标记解读出文件中标记的参数定义最后将这些信息还原成打印机输出的图形格式。 为了能表达出复杂的报表样式我们需要定义一些标记在这些标记中附加上具体的样式信息作用类似HTML的标签而我们的解析程序就相当于IE浏览器所不同的是IE将图形输出到屏幕而我们是将图形输出到打印机由于打印机相对于显示屏的特殊性例如分页因此我们不能直接采用网页浏览器的标签解析功能来打印需要自己来做一个满足需要的打印浏览器。 针对大多数报表的功能需要我只定义了两种格式标签文本text和表格(table)它们的具体属性定义和另外一些设置性的标签定义请参考《利》文这里再补充一幅结构图帮助读者理解。如下所示 结构设计 为了描述所有的样式标记我先定义了一个抽象基类PrintElement它拥有一个虚拟方法Draw然后对应表格和文本从PrintElement派生出两个子类分别是Table和Text我还创建了一个Parser类用来解析不同的样式标记和创建对应的对象它拥有一个静态的方法CreateElement用来根据不同的格式标签创建出对应的对象。结构图如下所示 读过《设计模式》的读者一定已经看出来了这种设计应用了设计模式中的一个非常著名的模式Abstract Factory。这里使用该模式的好处就是让标签对象和解析器都独立出来降低了系统的耦合度有利于今后在需要的时候可以很容易的增加其它的格式标签下文将会举一个实例和方便的更换不同的用户界面图中Client表示Windows应用程序或者是网页插件。 代码实现 首先创建一个Windows控件库的新项目在项目名称处写入RemotePrint如下图所示 然后把新建项目中的那个默认的UserControl1类,它的构造函数名和文件名都改成PrintControl。再将它的背景颜色设置为白色添加三个按纽并将它们的Enable属性都设置为falseAnchor属性设置为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)
{
//表格坐标
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;
//表头
foreach(XmlNode tr in table[tablehead].ChildNodes)
{
trheight int.Parse(tr.Attributes[height].InnerText);
DrawTR(x, y, tr, pen, g);
y trheight;
}
//表项
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;
//表底
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);
}
//画表格行
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;
}
}
//画单元格
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;
//设置字体样式
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();
//设置对齐方式
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方法的实现。 最后我们还需要一个解析器用来解析顶层的标签和生成相应的对象它在此模式中的作用就是一个工厂类负责生产出用户需要的产品。代码如下 using System;
using System.Xml;
namespace RemotePrint
{
public class Parser
{
public Parser()
{
}
public static PrintElement CreateElement(XmlNode element)
{
PrintElement printElement null;
switch(element.Name)
{
case text:
printElement new Text(element);
break;
case table:
printElement new Table(element);
break;
default:
printElement new PrintElement();
break;
}
return printElement;
}
}
}好了核心的解析和标签的具体打印方法已经完成了现在我们回到PrintControl中编写一些代码来测试我们的成果。 首先需要引用两个要用到的名称空间 using System.Xml; using System.Drawing.Printing; 然后在打印之前需要根据XML文件中的pagesetting标签来设置一下打印机的页面所以我们先写一个方法来设置打印机。在PrintControl类中增加一个私有的方法 private void SettingPrinter(XmlNode ps)
{
//打印方向纵/横
this.printDocument1.DefaultPageSettings.Landscape bool.Parse(ps[landscape].InnerText);
//设置纸张类型
string papername ps[paperkind].InnerText;
bool fitpaper false;
//获取打印机支持的所有纸张类型
foreach(PaperSize size in this.printDocument1.PrinterSettings.PaperSizes)
{
if(papername size.PaperName)//看该打印机是否有我们需要的纸张类型
{
this.printDocument1.DefaultPageSettings.PaperSize size;
fitpaper true;
}
}
if(!fitpaper)
{
//假如没有我们需要的标准类型,则使用自定义的尺寸
this.printDocument1.DefaultPageSettings.PaperSize
new PaperSize(Custom, int.Parse(ps[paperwidth].InnerText),
int.Parse(ps[paperheight].InnerText));
}
}接下来我们类中添加一个XmlDocument的对象和一个静态变量计算页码 private XmlDocument doc new XmlDocument(); public static int Pages 1; 然后再控件的Load事件中为该对象加载XML报表数据代码如下 private void PrintControl_Load(object sender, System.EventArgs e)
{
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(Exception ex)
{
this.label1.Text 出现错误 ex.Message;
}
}请注意我们这里只是装入了一个本地的测试数据文件该文件的编写请参考《利》文其实完全可以改成装载网络上任何地方的静态或者动态的XML文件例如以上的doc.Load(http://localhost/report.xml)可以改写成 doc.Load(http://www.anywhere.com/report.xml); doc.Load(http://www.anywhere.com/report.asp); doc.Load(http://www.anywhere.com/report.jspdatexxx); 等等只要装载的数据是符合我们规定的XML数据文档就可以。 然后在控件的构造函数中加入打印事件的委托 public PrintControl() { InitializeComponent(); this.printDocument1.PrintPage new PrintPageEventHandler(this.pd_PrintPage); } 该委托方法的代码如下 private void pd_PrintPage(object sender, PrintPageEventArgs ev)
{
Graphics g ev.Graphics;
bool HasMorePages false;
PrintElement printElement null;
foreach(XmlNode node in doc[root][reporttable].ChildNodes)
{
printElement Parser.CreateElement(node);//调用解析器生成相应的对象
try
{
HasMorePages printElement.Draw(g);//是否需要分页
}
catch(Exception ex)
{
this.label1.Text ex.Message;
}
}
//在页底中间输出页码
Font font new Font(黑体, 12.0f);
Brush brush new SolidBrush(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事件代码分别如下 //页面设置
private void button1_Click(object sender, System.EventArgs e)
{
this.pageSetupDialog1.ShowDialog();
this.printDocument1.DefaultPageSettings this.pageSetupDialog1.PageSettings;
}
//打印预览
private void button2_Click(object sender, System.EventArgs e)
{
try
{
this.printPreviewDialog1.ShowDialog();
}
catch(Exception ex)
{
this.label1.Text ex.Message;
}
}
//打印
private void button3_Click(object sender, System.EventArgs e)
{
try
{
this.printDocument1.Print();
}
catch(Exception ex)
{
this.label1.Text ex.Message;
}
}好了我们的打印控件到这里就全部做完了选择生成一个Release的版本然后到工程目录下将生成的PrintControl.dll文件拷贝到IIS的虚拟根目录下然后新建一个remoteprint.htm的HTML格式文件在合适的地方加上object idprint classidhttp:RemotePrint.dll#RemotePrint.PrintControl Width100% Height60 /object为了更加形象和美观还可以将需要打印的数据做成网页形式放在上面如果需要获取的XML是动态数据源则可以采用asp等动态脚本来生成该网页表格如果需要获取的XML是一个静态的文本则可以采用XSLT直接将XML文件转换成网页表格。 打开浏览器输入http://localhost/remoteprint.htm如果您已经跟我一样事先做好了一个XML报表数据文件的话您就可以看到下图所示的效果 请注意该图示例中的所有数据均为笔者随意虚拟网页中的表格数据和打印数据并非来自同一数据源也没有刻意去对等仅仅只是为了演示一下效果因此网页显示报表跟打印预览中的报表有一些出入是正常的。在实际应用中可以让网页显示数据跟打印输出数据完全一致。 方案扩充 有一部分读者在来信中问到如何打印一些特殊形态的图表《利》文中已经提到采用本方案可以非常方便的定义出自己所需要的标签在理论上可以打印出任何样式的特殊图表。因此本文打算详细介绍一下增加自己定义的标签扩充打印格式的具体过程。 先假设我们的客户看了打印效果后基本上满意但是还有觉得一点不足如果需要打印一些图表怎么办例如折线图、K线图、饼状图、柱状图等等。使用我们现有的标签就不行了所以我们首先要扩充我们的标签库让它的表达能力更加强。在这里我将只打算让我们的打印控件学会画简单的折线图希望读者能举一反三创造出其它各种各样的打印效果。 最基本的折线图是由X坐标轴、Y坐标轴和一系列点连接成的线构成的因此我定义了以下几种标签 1. linechart跟tabletext标签一样为样式根标签。 属性无 2 coordinate坐标。 属性无 3 xcoordinateX轴坐标线 属性 # x起点X坐标值 # y起点Y坐标值 # length长度值 # stroke粗细 # color颜色 # arrow是否有箭头 4 ycoordinateY轴坐标线 属性同xcoordinate。 5scale刻度线 标签内容显示在刻度边的文字 属性 # length距离起点长度值 # height刻度线高度 # width刻度线宽度 # color颜色 # fontsize字体大小 6chart图表根 属性无 7lines线段 属性值 # stroke粗细 # color颜色 8 point点 属性值 # xX坐标值 # yY坐标值 # radius半径 # color颜色 其结构图如下所示 下面是一段用刚才定义的标签制作的XML折线图示例 linechart coordinate xcoordinate x200 y600 length800 stroke2 colorBlack arrowtrue scale length100 height10 width1 colorBlack fontsize9100/scale scale length200 height10 width1 colorBlack fontsize9200/scale scale length300 height10 width1 colorBlack fontsize9300/scale scale length400 height10 width1 colorBlack fontsize9400/scale scale length500 height10 width1 colorBlack fontsize9500/scale scale length600 height10 width1 colorBlack fontsize9600/scale scale length700 height10 width1 colorBlack fontsize9700/scale /xcoordinate ycoordinate x200 y600 length-400 stroke2 colorBlack arrowtrue scale length-100 height10 width1 colorBlack fontsize9100/scale scale length-200 height10 width1 colorBlack fontsize9200/scale scale length-300 height10 width1 colorBlack fontsize9300/scale /ycoordinate /coordinate chart lines stroke1 colorBlue point x200 y600 radius5 colorBlack/ point x300 y300 radius5 colorBlack/ point x400 y400 radius5 colorBlack/ point x500 y500 radius5 colorBlack/ point x600 y300 radius5 colorBlack/ point x700 y300 radius5 colorBlack/ point x800 y600 radius5 colorBlack/ point x900 y500 radius5 colorBlack/ /lines lines stroke1 colorRed point x200 y400 radius5 colorBlack/ point x300 y500 radius5 colorBlack/ point x400 y600 radius5 colorBlack/ point x500 y300 radius5 colorBlack/ point x600 y400 radius5 colorBlack/ point x700 y400 radius5 colorBlack/ point x800 y500 radius5 colorBlack/ point x900 y300 radius5 colorBlack/ /lines /chart /linechart 完成了标签的定义下一步就要来修改我们的程序让他能读懂这些标签。 首先我们先给工程增加一个LineChart的新类跟Table,Text类一样它也是继承自PrintElement类同样重载了Draw虚方法。代码如下 using System;
using System.Xml;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace RemotePrint
{
public class LineChart : PrintElement
{
private XmlNode chart;
public LineChart(XmlNode Chart)
{
chart Chart;
}
public override bool Draw(Graphics g)
{
DrawCoordinate(g, chart[coordinate]);//画坐标轴
DrawChart(g, chart[chart]);
return false;
}
private void DrawCoordinate(Graphics g, XmlNode coo)
{
DrawXCoor(g, coo[xcoordinate]);//画X坐标
DrawYCoor(g, coo[ycoordinate]);//画Y坐标
}
private void DrawXCoor(Graphics g, XmlNode xcoo)
{
int x int.Parse(xcoo.Attributes[x].InnerText);
int y int.Parse(xcoo.Attributes[y].InnerText);
int length int.Parse(xcoo.Attributes[length].InnerText);
bool arrow bool.Parse(xcoo.Attributes[arrow].InnerText);
int stroke int.Parse(xcoo.Attributes[stroke].InnerText);
Color color Color.FromName(xcoo.Attributes[color].InnerText);
Pen pen new Pen(color, (float)stroke);
if(arrow)//是否有箭头
{
AdjustableArrowCap Arrow new AdjustableArrowCap(
(float)(stroke * 1.5 1.5),
(float)(stroke * 1.5 2), true);
pen.CustomEndCap Arrow;
}
g.DrawLine(pen, x, y, x length, y);//画坐标
//画刻度
foreach(XmlNode scale in xcoo.ChildNodes)
{
int len int.Parse(scale.Attributes[length].InnerText);
int height int.Parse(scale.Attributes[height].InnerText);
int width int.Parse(scale.Attributes[width].InnerText);
int fontsize int.Parse(scale.Attributes[fontsize].InnerText);
Color clr Color.FromName(scale.Attributes[color].InnerText);
string name scale.InnerText;
Pen p new Pen(clr, (float)width);
g.DrawLine(p, x len, y, x len, y - height);
Font font new Font(Arial, (float)fontsize);
g.DrawString(
name, font, new SolidBrush(clr),
(float)(x len - 10), (float)(y 10));
}
}
private void DrawYCoor(Graphics g, XmlNode ycoo)
{
int x int.Parse(ycoo.Attributes[x].InnerText);
int y int.Parse(ycoo.Attributes[y].InnerText);
int length int.Parse(ycoo.Attributes[length].InnerText);
bool arrow bool.Parse(ycoo.Attributes[arrow].InnerText);
int stroke int.Parse(ycoo.Attributes[stroke].InnerText);
Color color Color.FromName(ycoo.Attributes[color].InnerText);
Pen pen new Pen(color, (float)stroke);
if(arrow)//是否有箭头
{
AdjustableArrowCap Arrow new AdjustableArrowCap(
(float)(stroke * 1.5 2),
(float)(stroke * 1.5 3),
true);
pen.CustomEndCap Arrow;
}
g.DrawLine(pen, x, y, x, y length);//画坐标
//画刻度
foreach(XmlNode scale in ycoo.ChildNodes)
{
int len int.Parse(scale.Attributes[length].InnerText);
int height int.Parse(scale.Attributes[height].InnerText);
int width int.Parse(scale.Attributes[width].InnerText);
int fontsize int.Parse(scale.Attributes[fontsize].InnerText);
Color clr Color.FromName(scale.Attributes[color].InnerText);
string name scale.InnerText;
Pen p new Pen(clr, (float)width);
g.DrawLine(p, x, y len, x height, y len);
Font font new Font(Arial, (float)fontsize);
StringFormat sf new StringFormat();
sf.Alignment StringAlignment.Far;
RectangleF rect new RectangleF(
(float)(x - 100),
(float)(y len - 25),
90f,
50f);
sf.LineAlignment StringAlignment.Center;
g.DrawString(name, font, new SolidBrush(clr), rect, sf);
}
}
private void DrawChart(Graphics g, XmlNode chart)
{
foreach(XmlNode lines in chart.ChildNodes)
{
DrawLines(g, lines);
}
}
private void DrawLines(Graphics g, XmlNode lines)
{
int Stroke int.Parse(lines.Attributes[stroke].InnerText);
Point[] points new Point[lines.ChildNodes.Count];
Color linecolor Color.FromName(lines.Attributes[color].InnerText);
for(int i 0; i lines.ChildNodes.Count; i)
{
XmlNode node lines.ChildNodes[i];
points[i] new Point(
int.Parse(node.Attributes[x].InnerText),
int.Parse(node.Attributes[y].InnerText));
int Radius int.Parse(node.Attributes[radius].InnerText);
Color pointcolor Color.FromName(node.Attributes[color].InnerText);
if(Radius ! 0)//画点
{
g.FillEllipse(new SolidBrush(pointcolor),
points[i].X - Radius,
points[i].Y - Radius,
Radius * 2,
Radius * 2);
}
}
Pen pen new Pen(linecolor);
g.DrawLines(pen, points);//画线
}
}
}然后为Parser类的CreateElement方法增加一个小case代码如下 switch(element.Name)
{
case text:
printElement new Text(element);
break;
case table:
printElement new Table(element);
break;
case linechart://新增加的linechart
printElement new LineChart(element);
break;
default:
printElement new PrintElement();
break;
}将原来的XML文件中的table标签和其子标签都替换成刚才写的那段linechart然后编译程序运行后效果如下所示 现在我们的打印控件就能打印折线图了由于我们采用了Abstract Factory的设计模式将报表的打印和格式的解析分开使得本程序有着非常方便的扩充能力如果需要再增加一种新形式的图表那么需要定义出标签写一个解析类再到Paser中为这个类增加一个case就搞定了PrintControl内部的代码一行都不需要改写。 总结 以上就是如何制作打印控件的详细介绍基本上解答了读者来信中的大部分问题另外还有几个被问得很多的问题这里再集中解答一下 Q这种方案是否一定需要客户端装有.Net Framework A是肯定的这也是算是本方案一个缺陷。不过我可以肯定在不远的将来微软一定会将.Net Framework以升级或者是补丁的形式安装到我们的大多数Windows甚至是Linux操作系统当中。那时便不会有现在的这个遗憾存在。 Q我采用Winform应用程序的形式那么是不是存在着一个部署的问题例如我增加了一种新的图表格式那么是否所有的打印客户端都需要升级到新的版本 A是的不过理论上可以采用.Net Remoting的设计来避免这个问题因为Graphics类也是从System.MarshalByRefObject继承下来的因此同样可以通过Remoting序列化这样我们就可以把解析类Table,Text,Chart等和厂类(Paser)都放到服务器端通过Remoting提供远程调用方法而只把打印控制PrintControl放到客户端那么当我们新增加图表的时候就可以不需要对客户端进行任何升级。 Q打开网页控件不会运行只显示一个白框怎么办 A这个是因为你安装了.Net Framework SP1或者SP2它们默认的安全策略是不允许控件运行的这时需要进行以下修改打开Microsoft .NET Framework Wizards在程序里有也可以在管理工具里面找到它点击调整.NET安全性如下图所示 再将Internet区域的安全级别设置为完全信任如下图所示 转载于:https://www.cnblogs.com/foxhawk/archive/2006/06/18/428903.html