常常在开发实际的应用程序中,须要用到图形绘制和打印程序。如何实现完整的精确打印和绘图是须要注意许多细节地方的。最近在遇到打印问题的时候,仔细研究一阵,总结这篇博文,写得有点杂乱,看文要还请费点神。
html
基本功能:窗体绘图与鼠标交互程序员
打印预览与打印输出数据库
开发平台:VisualStudio 2010 (C#)编程
绘图程序涉及到多种坐标系统,整体上可分为三个坐标系:世界坐标系、页面坐标系以及设备坐标系。想要将图形图像会知道最终的设备上,中间须要作各类坐标转换,下面将详细介绍绘图系统中的坐标转换关系缓存
1、世界坐标ide
实际的绘图区域,如港口码头的2000米长度的范围、测井3000米的测量深度等。一般的实际应用中若是用到有打印这样的精确绘制功能,则还须要注意由世界坐标映射到逻辑坐标的比例,有两种方式:函数
(1)根据绘图窗口大小动态的计算映射比例,通常绘图都是这样,这种方式可让用户更加方便的阅览全局绘图;工具
(2)设定映射比例为一个定值,计算出相应的转换坐标,如测井中绘图的MD 1:200这样的参数,这样绘图区域大小是不变的,超出窗口位置要设定滚动条拖动显示。
一般还须要根据须要,把以上两种方式都实现出来,供选择使用,如Adobe PDF浏览器既具备整页缩放功能,也有按比例缩放这样的功能。
2、页面坐标
绘图页面整体设计大小,如默认选择A4的纸张(210mm * 297mm),此时系统创建基于所选纸张大小区域做为逻辑坐标范围。
3、设备坐标
(1)屏幕窗口:根据具体绘图区域获取,如屏幕窗口绘图的客户区ClientRectangle等,单位是pixel。
(2)打印机:绘图区域也能够是打印机的实际绘制区域,但此时要注意单位,VS.NET默认的是单位1/100 inch,即VC++编程用到映射模式中的的MM_LOENGLISH映射方式,逻辑单位为0.01 inch(以下表中颜色标识项所示)。
Windows定义了的8种映射方式
映射方式 |
逻辑单位 |
X 轴增加 |
Y 轴增加 |
毫米 |
MM_TEXT |
像素点 |
右 |
下 |
与设备有关 |
MM_LOMETRIC |
0. 1mm |
右 |
上 |
0.1 |
MM_HIMETRIC |
0. 01mm |
右 |
上 |
0.01 |
MM_LOENGLISH |
0. 01英寸 |
右 |
上 |
0.254 |
MM_HIENGLISH |
0.001英寸 |
右 |
上 |
0.0254 |
MM_TWIPS |
1/1440英寸 |
右 |
上 |
0.0176 |
MM_ISOTROPIC |
任意(x=y) |
可选 |
可选 |
可设 |
MM_ANISOTROPIC |
任意(x!=y) |
可选 |
可选 |
可设 |
注:MM_TWIPS常常在打印机上,单位是1/20磅(1磅=1/72英寸)。
三个坐标系转换关系以下图所示:
因为打印机的分辨率参数常常是600Dpi或者更高的1200Dpi等数值,远比屏幕的96Dpi或者120Dpi数值大得多,为了保证能有效的实现所见即所得效果,即屏幕窗口绘图跟实际打印结果一致,须要处理不一样分辨率问题。此处有两种解决方案:一种是计算打印机和屏幕的分辨率比例,而后屏幕绘图的结果,在向打印机上绘图时候进行比例缩放计算,这种方法是又世界坐标直接映射到像素在作计算;另外一种则是基于页面坐标绘图,全部绘图坐标单位设置映射到页面坐标(mm单位),随后进行的绘图就直接计算LPtoDP转换便可。两种方法都是主动计算转换的像素,也可换成简便的开发平台自带的绘图系统,即设置Graphics的PageUnit属性为毫米便可。
设置Graphics.PageUnit为默认的Pixel单位,此后全部绘图单位都是基于像素单位,此处设备坐标大小,即多少像素是程序计算。为了方便计算页面坐标同设备坐标之间的转换,能够创建函数处理,相似MFC中的逻辑坐标与设备坐标之间的转换函数LPtoDP,如上图所示的②坐标转换过程。
设定窗口到视口的绘图单位为像素单位,两种坐标系设定以下:
(1)屏幕绘图:
Graphics g = panel1.CreateGraphics();
g.PageUnit= GraphicsUnit.Pixel;
(2)打印机绘图:
PrintPageEventArgse
Graphics g = e.Graphics;
g.PageUnit= GraphicsUnit.Pixel;
4、坐标转换差别测试2013/11/17
坐标轴绘图测试代码(注:边框线是用m_WPtoDP_X绘制的,而坐标轴是用两种方法进行绘制,由此看出两种绘图方式存在的差别)
for (float fX = 0; fX <= WorldSize.Width;fX += v_f_MarkLong)
(1)测试由世界坐标到页面坐标转换,再由页面坐标到设备坐标转换,并加上缩放系数最后获得窗口范围内的坐标值绘图以下所示。
v_f_TempX = m_WPtoLP_X(fX);
v_f_TempX = m_LPtoDP_X(v_f_TempX);
v_f_TempX *= v_f_ScaleZoomX;
左边为小窗体时的首尾绘图,存在明显偏差;右边为窗体放大后的首尾绘图,偏差明显减少,但仍然存在必定偏差。
2)测试直接由世界坐标转换到设备坐标窗口绘图,结果以下:
v_f_TempX = m_WPtoDP_X(fX);
实际效果证实窗体缩放对偏差大小基本没有影响,基本没有偏差。
结论:直接由世界坐标到设备坐标映射减小了坐标转换的计算次数,有效避免了转换将计算过程当中的偏差累积,效果较好。其他的坐标转换则能够在须要的时候,直接使用,避免屡次叠加使用。可是在实际使用过程当中,仍是使用了第一种方法(全部绘图都用相同方法,保证了绘图的一致性,不存在差别),这样避免了老是设定设备坐标(DP)的区域参数,在窗体缩放大小等变化的时候,减小计算量。
5、绘图显示缩放问题
进一步采用(1)的绘制方法注意的问题,即采用页面坐标到设备坐标映射比例绘图,此时基于页面坐标的区域进行绘图,会形成与设备坐标之间的绘图区域部匹配问题,例如A3纸张大小的区间须要的设备区域是2000*3000, 而实际的绘图窗口大小事800*600,所以会出现绘图显示不彻底的状况,因此再次引进缩放系数v_f_ScaleZoomX和v_f_ScaleZoomY。此时存在四种屏幕显示方式,以下表所示。
在屏幕窗体上绘图的坐标系原点在左上角位置:
Origin = ( 0, 0)(Pixel)
逻辑坐标和设别坐标都基于该Origin点进行计算,具体的绘制转换计算关系以下图所示:
其中须要注意设置的是逻辑坐标,根据打印机选择的纸张,设定页面坐标系,同时考虑了打印机物理边距和打印纸页面边距,真正用于坐标转换的,只有PlotArea区域的尺寸大小。
在实际的物理打印中,常常出现之前问题:
Problem:因为实际的物理打印机可能存在物理边距问题,使得实际的物理打印坐标存在偏移(PrintOffset),所以在获取页面边距后,须要进一步考虑物理边距,才能更好的计算实际的打印区域。
Analysis:分析打印系统的坐标系统成为必要,上述问题中明显出现的坐标平移状况,同时致使右边和下边出现了打印不彻底的状况。最后得出C#的提供的平台中,打印的坐标原点是须要设定的,因为存在打印机的物理边距问题,实际物理打印坐标原点分两种状况:
(1)原点在物理边距线上,Origin = (HardMaeginX, HardMarginY )(1/100inch)
(2)原点在页面边距线上,Origin = (MaeginLeft, MarginTop )(1/100inch)
打印文档类关于打印坐标原点位置属性的MSDN解释:
public bool OriginAtMargins { get; set; }
---trueif the graphics origin starts at the page margins;对应于(2)
---falseif the graphics origin is at the top-left corner of the printable page. 对应于(1)
具体坐标以下图所示:打印纸的整体尺寸为黑色坐标系(827*1169)0.01 inch,而真正可打印的区域为v_PrintDocument.DefaultPageSettings.PrintableArea提供的绿色坐标系区域,此时应该把PrintableArea做为真正的打印计算坐标系。
Solution:知道物理边距影响因素后,打印问题的处理一般有两种方案:
方案一:广泛采用的讲绘图内容进行位移
一般的作法是将可打印区域进行Offset平移,平移量为打印机物理边距(HardMarginX / HardMarginY),这样结合默认的打印纸张的页面边距(MarginLeft /MarginRight / MarginTop / MarginBottom)也能按照预览时的绘图如出一辙的打印出来。
这种作法比较简单,只须要进行位移一下Graphics便可。可是注意,此时的页面边距不能为0,不然任然会出现打印绘图缺失现象,物理边距是不可避免的。要避免这种状况就须要判断页面边距的值,不能小于物理边距的值才行。
方案二:能够从新定义页面坐标,对页面尺寸进行裁剪
方案一在计算了左边和上边的不可打印区域,可是没有计算右边和下边的不可打印区域。这样位移和绘图的时候,就须要考虑是否打印出来的图正好在正中等问题。要精确控制就是将可打印区域裁剪到实际物理可打印的区域,如上图所示的绿色区域。
设定OriginAtMargins = false使得打印机的绘图Graphics的坐标原点在有效打印区域的边界上,即坐标原点为物理边距上,而不是在页面边距上。因而,咱们绘制的时候,X坐标0到100,其实是从HardMarginX+0到HardMarginX+100,在计算页面坐标的时候,考虑物理边距进行排除后,就能实如今可打印区域内(排除了物理不可打印区域)进行精确的绘图。
可是也要注意,在屏幕绘图和打印预览的时候,须要将坐标进行向下向右平移物理尺寸,使得绘图效果与打印效果一致。同时,针对不一样的Dpi形成的偏差,避免的办法是定义单位是mm,而后采用LPtoDP进行转换,在转换的过程当中,会用到Graphics的Dpi属性进行计算,屏幕Dpi和打印机Dpi不一样,可是获得最后的绘图效果将会统一,实现所见所得。
物理边距的偏差
至于物理边距问题,有可能打印出来的实际纸张上的物理边距跟程序获取的边距有偏差,缘由多是打印机自身有关,也可能跟纸张在纸盒中放置有偏移有关。但这都算是不可避免的偏差了,也不知道如何校订,咱们只须要实现实际绘图内容打印完整,且绘制内容的精肯定位绘制便可!
?当纸张方向改变的时候,程序获取到的HardMarginX和HardMarginY会有所改变,不知是何缘由啊? LandScape为false的时候:HardMarginX = 23,HardMarginY = 16 LandScape为true的时候:HardMarginX = 19,HardMarginY = 16 但愿有网友能解决这问题的麻烦留个言指导一二哦。 |
其余关于物理边距讨论的一部分连接
(1) How to Find the ActualPrintable area
http://stackoverflow.com/questions/8761633/how-to-find-the-actual-printable-area-printdocument
(2) I am Loss in Printing Margins
http://bytes.com/topic/c-sharp/answers/275603-im-loss-printing-margins
(3)How to print in full page withoutmargins
(4)使用.Net 下的打印控件进行预览和打印时的模型初探
http://blog.csdn.net/windcoder/article/details/8178096
绘图工做开始后,须要进行一系列的绘图参数设定,其中绘图坐标系的设定最为重要。大概可分为如下几个步骤:
Step 1:设定世界坐标系。给定须要绘制的世界坐标区域大小,至关于绘图的视野范围;
Step 2:设定页面坐标系。即纸张大小,至关于逻辑坐标系。这点在打印机的绘制程序中尤其重要,同时为了实现所见即所得的绘图思路,此处的窗体绘图跟打印绘图应该有良好的对应关系,因此设定的纸张应该一致。
Step 3:设定绘图对象Graphics,能够是打印机绘图,也能够是屏幕窗口绘图。
Step 4:设定设备坐标系。给定绘图设备的实际区域,如屏幕窗口大小、打印机的实际可打印区域等。
Step 5:输入绘图数据。
Step 6:绘图输出
简单的绘制流程图以下:
(1)鼠标屏幕拾取转换,讲鼠标坐标值(Pixel)转换为页面坐标值(mm),须要考虑滚动条位置、物理边距和屏幕显示缩放系数等,页面坐标(mm)转换为世界坐标(M)直接调用函数便可。伪代码以下:
// 须要考虑滚动条位置
Graphics g = panel1.CreateGraphics();
int i_PosHor = panel1.HorizontalScroll.Value;
int i_PosVer =panel1.VerticalScroll.Value;
int i_OffsetX = (int)(m_LPtoDP_X(v_f_LogicHardMarginX, g) /v_f_ScaleZoomX) - i_PosHor;
int i_OffsetY = (int)(m_LPtoDP_Y(v_f_LogicHardMarginY, g) /v_f_ScaleZoomY) - i_PosVer;
float v_f_LogicMousePosDownX =m_DPtoLP_X(e.X + i_PosHor, g) * v_f_ScaleZoomX - v_f_LogicHardMarginX;
float v_f_LogicMousePosDownY =m_DPtoLP_Y(e.Y + i_PosVer, g) * v_f_ScaleZoomY - v_f_LogicHardMarginY;
转换为世界坐标:
float v_f_WorldMousePosDownX= m_LPtoWP_X(v_f_LogicMousePosDownX);
(2)世界坐标转换为页面坐标和设备坐标伪代码:
Graphics g = panel1.CreateGraphics();
float v_f_WorldX = 2000;
float v_f_LogicTempX = m_WPtoLP_X(fX);
float v_f_DeviceTempX =m_LPtoDP_X(v_f_TempX, g) / v_f_ScaleZoomX;
如今通常的GDI和GDI+绘图都没有问题,关键是提升绘图的效率,防止绘图刷新时的闪烁问题,在此参考了两篇高质量的网文以下:
(1)使用bitblt提升GDI+绘图的效率(转)
http://www.cnblogs.com/carekee/articles/2178308.html
引用(略)
(2)绘图效率完整解决方案——三种手段提升GDI/GDI+绘图效率
http://www.cnblogs.com/fyhui/archive/2011/06/09/2076298.html
如今的cpu飞快,其实数学计算通常很快,cpu大部分时间是在处理绘图,而绘图有三种境界:1>每次重绘总体Invalidate(); 2>每次局部绘制Invalidate(Rect); 3>有选择的局部绘制。 不能说,必定是第三种方式好,得视状况,境界高程序确定就复杂,若是对效率要求不高或者绘图量小固然直接用第一种方式。然而,稍微专业点的绘图程序,第一第二种方式确定知足不了要求,必须选用第三种方式。而第三种方式的手段多样,也得根据实际状况拿相应的解决之道。这里讲解通常的三种手段,他们能够联合使用。 1. 缓存——Bitmap或者DoubleBuffer。缓存就是先把绘制的图形绘制到一张内存位图上,而后在一次性的贴位图,他能够提升绘图速度,也能避免闪烁。DoubleBuffer=true是C#窗体的属性,设置了此属性估计系统自己会起用无效区的内存位图缓存,而不须要程序员Bitmap处理。 2. 合理利用无效区域。无效区域就是系统保存当前变化须要重绘的区域,能够在OnPaint()中,e.ClipRectangle直接得到,也能够经过其余方式得到。Windows系统只会重绘无效区域内的绘图信息,然而咱们用户的绘制代码通常是绘制整个区域的,不少时候无效区域只是一小部分区域,虽然执行了全部的绘图代码,可是Windows系统只会从新更新无效区域内的绘图。这里有两个利用点:1>用户请求重绘时,只请求重绘指定区域的,而不是整个区域,如Invalidate(Rect);2>在用户绘图代码Graphics g; g.DrawLine\g.DrawString\g.FillRectangle...前,先判断绘图的内容是否在无效区域,若是不是就不直接g.Draw...绘图代码。 3. 直接贴图。通常绘图或者重绘是Windows根据无效区域绘制的,若是在鼠标移动时须要重绘经过Windows系统处理Paint消息,有时知足不了要求,好比①鼠标移动绘制十字测量线就得用异或线而不是Paint消息,又好比②鼠标移动绘制跟随的信息提示框须要频繁擦除上次覆盖的背景,又好比③台球滚动时台球与球桌背景的关系。相似的这些问题如何解决?首先确定不能利用Windows原来的绘图机制。其中一种解决方式是,不断的帧间变化区域贴内存位图——②中的信息框每次鼠标位置变化时能够从新g.Draw...或者贴早生成的信息框内存位图,②中被信息框覆盖的背景应该把原本的大背景截取此须要擦除区域的位置大小位图贴回来就是擦除背景了。因为每次大背景发生变化时,都应会从新生成大背景内存位图,因此能够是变化的背景。 这三种方式能够一块儿使用,应该能够解决中等的绘图项目的效率问题。中大型的绘图,必须记住两点1>只绘制电脑屏幕能显示的部分;2>只绘制变化的部分。 |
异或做图法不一样于普通的绘图方法中的刷新须要按照Invalidate的区域所有从新绘制,它只是采用覆盖绘制的方式,实现了擦除原有轨迹来达到刷新绘制,这样就极大的提升了绘图效率,避免没必要要的区域性重绘耗费资源成本,也能十分有效的避免由于小区域或线型实时绘制等任务形成的整个绘图窗体闪烁问题。须要注意的是,异或做图法跟普通的画法感受最大的别扭就是须要“连续”的两次绘图,而后这两次绘图结果根据用户设定的异或方式,进行异或计算,得出擦除、反色或覆盖等结果。
其实现方式很简单,直接调用GDI的相关函数进行设定便可,下图为已实现异或填充绘图。
文章已经很长了,不想一一列举,如下是一些有用的参考连接:
(1)<基础的异或做图方法>VC橡皮筋绘图技术的实现(异或模式绘图)
http://xvdongming001.blog.163.com/blog/static/739891892008613516138/
(2)<C#实现异或做图方法>C#调用GDI实现.NET中XOR、AND和OR模式的贴图(填充不规则图形)
http://blog.163.com/xuanmingzhiyou@yeah/blog/static/14247767620116201178195/
(3)<C#实现异或做图方法> C#中利用GDI做图解决异或问题
http://www.tp5u.com/winForm/1794.html
校订:这篇文章中公布了一个异或法绘制直线的方法,可是其中关于MoveToEx的GDI库调用函数存在问题,须要增长ref关键字引用,
[DllImport("gdi32.dll")]
private static extern bool MoveToEx(IntPtr hDC, int x, int y, ref POINTAPI lpPoint);
调用函数:MoveToEx(hDC, 10, 10, ref ptsOld);
具体参考地址:
GDI32.DLL API函数MoveToEx 在C#2.0中的调用问题
http://bbs.csdn.net/topics/90040089
(4)<C#使用其余GDI方法接口定义>在C#中使用GDI的简单总结
http://www.cnblogs.com/canson/archive/2011/07/09/2101862.html
(5)C#Color 和 VC++COLORREF 转化
http://blog.csdn.net/whchina/article/details/2639389
http://responsibility.blog.sohu.com/86726377.html
若是使用MFC与.NET混合编程,就会遇到这个问题,经过MFC编写的控件,由.NET调用,则控件中背景色的设置,须要颜色的转换。
COLORREF类型颜色的值COLORREFcr=RGB(123,200,12);
其中的R、G、B三个份量的排列顺序是BGR。
.NET中经过数据类型Color表示颜色,该类有一个函数FromArgb(int,int,int),能够经过输入RGB三个值获得一个Color类型的颜色。同时也有一个ToArgb()函数,获得一个32位的整数值,
32位ARGB值的字节顺序为AARRGGBB。由AA表示的最高有效字节(MSB)是alpha份量值。分别由RR、GG和BB表示的第2、第三和第四个字节分别为红色、绿色和蓝色颜色份量
Color到COLORREF |
COLORREF到Color |
uint GetCustomColor(Color color) { int nColor = color.ToArgb(); int blue = nColor & 255; int green = nColor >> 8 & 255; int red = nColor >> 16 & 255; return Convert.ToUInt32( blue << 16 | green << 8 | red); } |
Color GetArgbColor(int color) { int blue = color & 255; int green = color >> 8 & 255; int red = color >> 16 & 255 ; return Color.FromArgb(blue, green, red); } 或者直接经过下面的代码: Color.FromArgb(nColorRef&255, nColorRef>>8&255,nColorRef>>16&255); |
注:(1)注意COLORREF中颜色的排列是BGR,红色份量在最后面;(2)上面的代码使用C#编写。 |
|
最后还有.NET自带的函数:ColorTranslator |
(6)异或绘图模式设置的Index值
绘图模式(drawing mode)指前景色的混合方式,它决定新画图的笔和刷的颜色(pbCol)如何与原有图的颜色(scCol)相结合而获得结果像素色(pixel)。
可以使用CDC类的成员函数SetROP2 (ROP = Raster OPeration光栅操做)来设置绘图模式:
其中,R2_COPYPEN(覆盖)为缺省绘图模式,R2_XORPEN(异或)较经常使用。
CDC::SetROP2
int SetROP2(int nDrawMode);
返回值:绘图模式的前一次取值。能够取联机文档“Windows SDK”中提供的任意值。
参 数: nDrawMode 指定新的绘制模式,能够为下列值之一:
绘制模式 |
定义说明 |
索引值 |
R2_BLACK |
像素始终为黑色 |
1 |
R2_WHITE |
像素始终为白色 |
16 |
R2_NOP |
像素保持不变 |
11 |
R2_NOT |
像素为屏幕颜色的反色 |
6 |
R2_COPYPEN |
像素为笔的颜色 |
13 |
R2_NOTCOPYPEN |
像素为笔颜色的反色 |
4 |
R2_MERGEPENNOT |
像素为笔颜色或屏幕颜色反色 |
14 |
R2_MASKPENNOT |
像素为笔颜色与屏幕颜色反色 |
5 |
R2_MERGENOTPEN |
像素为笔颜色反色或屏幕颜色 |
12 |
R2_MASKNOTPEN |
像素为笔颜色反色与屏幕颜色 |
3 |
R2_MERGEPEN |
像素为笔颜色或屏幕颜色 |
15 |
R2_NOTMERGEPEN |
R2_MERGEPEN的反色 |
2 |
R2_MASKPEN |
像素为笔颜色与屏幕颜色 |
9 |
R2_NOTMASKPEN |
R2_MASKPEN的反色 |
8 |
R2_XORPEN |
像素为笔颜色异或屏幕颜色。连续异或两次会变为原来颜色 |
7 |
R2_NOTXORPEN |
R2_XORPEN的反色(同或) |
10 |
说明:
设置绘图模式。绘图模式指出笔与被填充对象的颜色是怎样同显示表面的颜色组合的。绘图模式只用于光栅设备,不用于矢量设备。绘图模式是双重的光栅操做代码,表明了两个变量全部可能的布尔组合,分别使用AND、OR、XOR(异或)和NOT运算符。
(7)字符串绘制,可否异或?(答曰:不能)
字符串输出绘制不能采用异或的方式进行擦除更新,那么须要实时的动态位置显示信息的时候如何解决?目前看到三种方案:
方案一:利用局部更新文字造成的位图方法(参考CSDN论坛,忘了地址)
如何经过两次绘制的方式从屏幕上擦除文字 OnDraw里
|
方案二:利用文字的点阵图输出
在背景上输出和擦除文字 http://eyinlu.blog.163.com/blog/static/242321612011627921975/ 在背景上输出文字,而且能够不留痕迹的擦除。 |
方案三:直接用Label控件显示信息,让Label跟随鼠标移动显示文字内容
直接修改Label控件的Left和Top属性,更新其Text属性内容,而后改变控件位置,实现实时显示。默认状况下,控件不支持透明背景色。在属性框里设置background属性为transparent。同时修改Visible属性进行显示和隐藏。
绘图内容数据管理按照数据库方式的拓扑结构进行管理,而单个对象则采用面向对象方式进行存储和管理操做。
工具栏主要操做项:
利用PageSetupDialog对话框设置纸张的类型、页边距等信息后,再次进入页面设置的对话框,发现里面的页边距所有改变了,再进入又改变了,这是为何呢?
其实缘由很简单,单位的不一样形成了这个现象。咱们能够再看看上图中“页边距”一项明确的注明了单位采用的是“毫米”,说明在页面设置对话框中使用的是公制长度计量单位,而在.net中采用的是英制的计量单位。英制中长度的基本计量单位是英寸,公制中长度的基本计量单位是厘米,打印时默认的长度单位为 1/100英寸。所以假设咱们在页面设置对话框中设置上部边距为10mm(以下左图),但.net把它转换成了英制单位,数值是1/2.54 * 100=39个1/100英寸,(1英寸约等于2.54厘米,1厘米=10毫米)因此,这时上部页边距的数值变成了39,当你再次打开页面设置对话框时,系统将认为上部页边距是39个1/100厘米,也就是3.9毫米(以下右图),按下“肯定”按钮后,.net将再次对页边距进行转换,这时上部边距就约为15个 1/100英寸,这样结果固然与咱们设置的相差甚远。
知道了缘由,解决问题就很好办了。其实微软也考虑到了这个问题,提供了一个用于单位转换的类PrinterUnitConvert,以下所示:
If(System.Globalization.RegionInfo.CurrentRegion.IsMetric) Then
'若是使用的是公制单位
'将英制单位的数据转换成公制单位的数据
psd.PageSettings.Margins= PrinterUnitConvert.Convert (psd.PageSettings.Margins, PrinterUnit.Display,PrinterUnit.TenthsOfAMillimeter)
EndIf
pap.DefaultPageSettings= psd.PageSettings
Margins属性中保存的页面的上(Top)、下(Bottom)、左(Left)、右(Right)的页边距数值,利用 PrinterUnitConvert的Convert方法均可以转换,在上例中,PrinterUnit.Display是指1/100英寸的单位, PrinterUnit.TenthsOfAMillimeter是指1/100毫米的单位,这样就能够将英制单位转换为公制单位。
固然咱们也能够本身编写代码进行转换,但请注意,转换时英制的单位是1/100英寸,转换后要以毫米为单位。
注意:转换时只须对纸张的页边距进行转换,纸张自己的宽度和高度在你选择一种纸张类型的时候,它已经自动帮你转换成英制单位了,千万不要多此一举。 以上咱们介绍了如何利用PageSetupDialog对话框设置页面、公制与英制单位的换算,已经为打印程序的编写创建了一个良好的基础。接下来,咱们就来介绍如何实现具体的特殊打印功能。
.NET的升级版本也能够一句话就解决问题:
// 打印页面设置
publicPageSetupDialogv_PrintPageSetDlg = newPageSetupDialog();
// 英制单位转换为公制单位
v_PrintPageSetDlg.EnableMetric = true;
结果以下:
可是注意:此时显示的25.4mm,实际上获取Margins属性的时候,仍然是100个1/100英寸,所以存在0.254的系数关系。
这里须要统一打印时的度量单位,打印文档的DefaultPageSettings中的参数都是以1/100英寸为单位,而窗口界面绘图中的尺寸获取(width和Height)是以像素为单位,而中文操做系统中以毫米为单位(如打印设置页面),在打印时如何统一单位是必需要进行的。
而程序设定的坐标转换是基于世界坐标(单位:m)、逻辑坐标(单位:mm)以及设备坐标(单位:pixel),所以须要将C#提供的英制单位1/100英寸转换为mm,而后最终转换为像素绘图坐标。
// mm转Pixel
publicfloat m_LPtoDP_X(floatv_f_Logicmm_X)
{
floatd_X = (float)((v_f_Logicmm_X / 25.4) *v_Graphic.DpiX);
returnd_X;
}
NOTE:在绘图时基于像素的时候,Graphics的绘图单位也必需要设定为Pixel,尤为是打印机事件参数的e.Graphics。
e.Graphics.PageUnit = GraphicsUnit.Pixel;
获取打印机相关信息后,将1/100英寸转换为像素:
float f_DeviceMargin_Left =m_LPtoDP_X(v_PrintDocument.DefaultPageSettings.Margins.Left * 0.254f);
float f_DeviceMargin_Top =m_LPtoDP_Y(v_PrintDocument.DefaultPageSettings.Margins.Top *0.254f);
相同的打印绘图内容,若是是PDF虚拟打印机,则是严格的根据页面边距打印出来;而物理打印机出现误差。如前面所阐述的打印坐标系原点,若是设定为true,怎会出现如下问题:
// 打印文档
publicPrintDocument v_PrintDocument = newPrintDocument();
// 边距
v_PrintDocument.OriginAtMargins = true;
利用Adobe PDF虚拟打印机打印出来的PDF文档,而后在PDF文档中打印出来的页边距是准确的,标准的默认25.4mm页面边距,以下图所示:
若选择实际的物理打印机,此时的打印机存在物理边距,错误打印结果以下:
考虑到物理边距之后,打印事件参数(PrintPageEventArgs e)的绘图区域e.MarginBounds 须要进行向左和向上的偏移处理。
NOTE:打印机绘图用MarginBounds,而不是直接用PaperSize,那样计算起来很麻烦
classPrinterBounds
{
[DllImport("gdi32.dll")]
privatestaticexternInt32 GetDeviceCaps(IntPtrhdc, Int32 capindex);
privateconstintPHYSICALOFFSETX = 112;
privateconstintPHYSICALOFFSETY = 113;
publicreadonlyRectangleBounds;
publicreadonlyintHardMarginLeft;
publicreadonlyintHardMarginTop;
publicPrinterBounds(PrintPageEventArgs e)
{
IntPtrhDC = e.Graphics.GetHdc();
HardMarginLeft =GetDeviceCaps(hDC, PHYSICALOFFSETX);
HardMarginTop = GetDeviceCaps(hDC,PHYSICALOFFSETY);
e.Graphics.ReleaseHdc(hDC);
HardMarginLeft = (int)(HardMarginLeft * 100.0 / e.Graphics.DpiX);
HardMarginTop = (int)(HardMarginTop * 100.0 / e.Graphics.DpiY);
Bounds = e.MarginBounds;
Bounds.Offset(-HardMarginLeft, -HardMarginTop);
}
}
在.NET之后的版本中,也能够直接用打印文档类的获取物理边界属性
DefaultPageSettings.HardMarginX, DefaultPageSettings.HardMarginY
而后进行打印的时候,就基本不会存在边距相差太大的状况,不过仍是存在1点几个毫米的偏差。
(1)页面边距25.4mm时预览结果:
打印结果:左图为打印图纸左侧,右图为打印图纸右侧。
(2)边距为0mm时预览,因为计算绘图区域的时候,有物理边界存在,致使获得的绘图区域是通过了Offset向左向上平移的,因此有空白的地方出如今预览图像的右边和下边。
打印的时候出现物理边界的影响,致使绘图能完整打印出来,以下图所示:
// 绘制0边距位置
Graphics.DrawLine(Pens.Red, 0, 0, m_LPtoDP_X(v_PrintDocument.DefaultPageSettings.PaperSize.Width* 0.254f), 0);
Graphics.DrawLine(Pens.Blue, 0, 0, 0,m_LPtoDP_Y(v_PrintDocument.DefaultPageSettings.PaperSize.Height * 0.254f));
用Adobe PDF虚拟打印结果以下:在设置页边距为0的时候,能够准确的打印到0位置(如左边距),可是若是存在页边距的状况,打印0位置会出现误差(如上边距),不知道为何?
Solve:不是偏差,而是打印机绘图原点坐标问题2013/11/24,已解决
测试采用普通的A4纸张,并设置为横向,以下图所示:
查看打印阅览,跟绘图内容是否相同,测试结果一致。
选用两种打印成果(虚拟打印 + 物理打印),以下图所示:
(1)AdobePDF虚拟打印机,无物理边距打印
(2)实际物理打印机HP LaserJet P4515打印机,物理边距 (4.8, 4.1)mm,之前两张图是不一样时期打印,图纸上的物理边距线存在打印不彻底现象,这就是常常出现的打印不彻底现象,能够看出两张图纸的上和顶部边距不一样,这是人工装载纸盒的时候,可能存在必定误差,形成打印不彻底等各类现象。可是两张图都保证了页面边距彻底的效果,都是有25.4mm的默认边距,实现真正的可打印区域的彻底打印功能。