那么如何打印一些特殊形态的图表,前文中已经提到,采用本方案可以非常方便的定义出自己所需要的标签,在理论上可以打印出任何样式的特殊图表。因此本文打算详细介绍一下增加自己定义的标签扩充打印格式的具体过程。
先假设我们的客户看了打印效果后基本上满意,但是还有觉得一点不足,如果需要打印一些图表怎么办?例如折线图、K线图、饼状图、柱状图等等。使用我们现有的标签就不行了,所以我们首先要扩充我们的标签库,让它的表达能力更加强。在这里,我将只打算让我们的打印控件学会画简单的折线图,希望读者能举一反三,创造出其它各种各样的打印效果。
最基本的折线图是由X坐标轴、Y坐标轴和一系列点连接成的线构成的,因此,我定义了以下几种标签:
1. linechart:跟table,text标签一样,为样式根标签。
属性:无
2. coordinate:坐标。
属性:无
3. xcoordinate:X轴坐标线
属性:
# x:起点X坐标值
# y:起点Y坐标值
# length:长度值
# stroke:粗细
# color:颜色
# arrow:是否有箭头
4. ycoordinate:Y轴坐标线
属性:同xcoordinate。
5.scale:刻度线
标签内容:显示在刻度边的文字
属性:
# length:距离起点长度值
# height:刻度线高度
# width:刻度线宽度
# color:颜色
# fontsize:字体大小
6.chart:图表根
属性:无
7.lines:线段
属性值:
# stroke:粗细
# color:颜色
8. point:点
属性值:
# x:X坐标值
# y:Y坐标值
# radius:半径
# color:颜色
其结构图如下所示:
下面是一段用刚才定义的标签制作的XML折线图示例:
<linechart> <coordinate> <xcoordinate x="200" y="600" length="800" stroke="2" color="Black" arrow="true"> <scale length="100" height="10" width="1" color="Black" fontsize="9">100</scale> <scale length="200" height="10" width="1" color="Black" fontsize="9">200</scale> <scale length="300" height="10" width="1" color="Black" fontsize="9">300</scale> <scale length="400" height="10" width="1" color="Black" fontsize="9">400</scale> <scale length="500" height="10" width="1" color="Black" fontsize="9">500</scale> <scale length="600" height="10" width="1" color="Black" fontsize="9">600</scale> <scale length="700" height="10" width="1" color="Black" fontsize="9">700</scale> </xcoordinate> <ycoordinate x="200" y="600" length="-400" stroke="2" color="Black" arrow="true"> <scale length="-100" height="10" width="1" color="Black" fontsize="9">100</scale> <scale length="-200" height="10" width="1" color="Black" fontsize="9">200</scale> <scale length="-300" height="10" width="1" color="Black" fontsize="9">300</scale> </ycoordinate> </coordinate> <chart> <lines stroke="1" color="Blue"> <point x="200" y="600" radius="5" color="Black"/> <point x="300" y="300" radius="5" color="Black"/> <point x="400" y="400" radius="5" color="Black"/> <point x="500" y="500" radius="5" color="Black"/> <point x="600" y="300" radius="5" color="Black"/> <point x="700" y="300" radius="5" color="Black"/> <point x="800" y="600" radius="5" color="Black"/> <point x="900" y="500" radius="5" color="Black"/> </lines> <lines stroke="1" color="Red"> <point x="200" y="400" radius="5" color="Black"/> <point x="300" y="500" radius="5" color="Black"/> <point x="400" y="600" radius="5" color="Black"/> <point x="500" y="300" radius="5" color="Black"/> <point x="600" y="400" radius="5" color="Black"/> <point x="700" y="400" radius="5" color="Black"/> <point x="800" y="500" radius="5" color="Black"/> <point x="900" y="300" radius="5" color="Black"/> </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);//画坐标 file://画刻度 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);//画坐标 file://画刻度 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内部的代码一行都不需要改写。