C#實現無物理邊距 可打印區域的繪圖\打印 z

常常在開發實際的應用程序中,須要用到圖形繪製和打印程序。如何實現完整的精確打印和繪圖是須要注意許多細節地方的。最近在遇到打印問題的時候,仔細研究一陣,總結這篇博文,寫得有點雜亂,看文要還請費點神。
html

基本功能:窗體繪圖與鼠標交互程序員

 打印預覽與打印輸出數據庫

開發平臺:VisualStudio 2010 (C#編程

1繪圖座標系統api

1.1繪圖系統座標轉換(屏幕窗口/打印機)瀏覽器

   繪圖程序涉及到多種座標系統,整體上可分爲三個座標系:世界座標系、頁面座標系以及設備座標系。想要將圖形圖像會知道最終的設備上,中間須要作各類座標轉換,下面將詳細介紹繪圖系統中的座標轉換關係緩存

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種映射方式

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轉換便可。兩種方法都是主動計算轉換的像素,也可換成簡便的開發平臺自帶的繪圖系統,即設置GraphicsPageUnit屬性爲毫米便可。

設置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_ScaleZoomXv_f_ScaleZoomY。此時存在四種屏幕顯示方式,以下表所示。

1.2屏幕座標系

在屏幕窗體上繪圖的座標系原點在左上角位置:

Origin = ( 0, 0)(Pixel)

邏輯座標和設別座標都基於該Origin點進行計算,具體的繪製轉換計算關係以下圖所示:

   其中須要注意設置的是邏輯座標,根據打印機選擇的紙張,設定頁面座標系,同時考慮了打印機物理邊距和打印紙頁面邊距,真正用於座標轉換的,只有PlotArea區域的尺寸大小。

1.3打印座標系

在實際的物理打印中,常常出現之前問題:

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*11690.01 inch,而真正可打印的區域爲v_PrintDocument.DefaultPageSettings.PrintableArea提供的綠色座標系區域,此時應該把PrintableArea做爲真正的打印計算座標系。

Solution知道物理邊距影響因素後,打印問題的處理一般有兩種方案:

方案一:廣泛採用的講繪圖內容進行位移

一般的作法是將可打印區域進行Offset平移,平移量爲打印機物理邊距(HardMarginX / HardMarginY),這樣結合默認的打印紙張的頁面邊距(MarginLeft /MarginRight / MarginTop / MarginBottom)也能按照預覽時的繪圖如出一轍的打印出來。

這種作法比較簡單,只須要進行位移一下Graphics便可。可是注意,此時的頁面邊距不能爲0,不然任然會出現打印繪圖缺失現象,物理邊距是不可避免的。要避免這種狀況就須要判斷頁面邊距的值,不能小於物理邊距的值才行。

方案二:能夠從新定義頁面座標,對頁面尺寸進行裁剪

方案一在計算了左邊和上邊的不可打印區域,可是沒有計算右邊和下邊的不可打印區域。這樣位移和繪圖的時候,就須要考慮是否打印出來的圖正好在正中等問題。要精確控制就是將可打印區域裁剪到實際物理可打印的區域,如上圖所示的綠色區域。

設定OriginAtMargins = false使得打印機的繪圖Graphics的座標原點在有效打印區域的邊界上,即座標原點爲物理邊距上,而不是在頁面邊距上。因而,咱們繪製的時候,X座標0100,其實是從HardMarginX+0HardMarginX+100,在計算頁面座標的時候,考慮物理邊距進行排除後,就能實如今可打印區域內(排除了物理不可打印區域)進行精確的繪圖。

可是也要注意,在屏幕繪圖和打印預覽的時候,須要將座標進行向下向右平移物理尺寸,使得繪圖效果與打印效果一致。同時,針對不一樣的Dpi形成的偏差,避免的辦法是定義單位是mm,而後採用LPtoDP進行轉換,在轉換的過程當中,會用到GraphicsDpi屬性進行計算,屏幕Dpi和打印機Dpi不一樣,可是獲得最後的繪圖效果將會統一,實現所見所得。

 

物理邊距的偏差

至於物理邊距問題,有可能打印出來的實際紙張上的物理邊距跟程序獲取的邊距有偏差,緣由多是打印機自身有關,也可能跟紙張在紙盒中放置有偏移有關。但這都算是不可避免的偏差了,也不知道如何校訂,咱們只須要實現實際繪圖內容打印完整,且繪製內容的精肯定位繪製便可!

?當紙張方向改變的時候,程序獲取到的HardMarginXHardMarginY會有所改變,不知是何緣由啊?

LandScapefalse的時候:HardMarginX = 23HardMarginY = 16

LandScapetrue的時候:HardMarginX = 19HardMarginY = 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

http://www.tech-archive.net/Archive/DotNet/microsoft.public.dotnet.framework.drawing/2005-08/msg00311.html

(4)使用.Net 下的打印控件進行預覽和打印時的模型初探

http://blog.csdn.net/windcoder/article/details/8178096

 

1.4繪圖操做流程

  繪圖工做開始後,須要進行一系列的繪圖參數設定,其中繪圖座標系的設定最爲重要。大概可分爲如下幾個步驟:

Step 1:設定世界座標系。給定須要繪製的世界座標區域大小,至關於繪圖的視野範圍;

Step 2:設定頁面座標系。即紙張大小,至關於邏輯座標系。這點在打印機的繪製程序中尤其重要,同時爲了實現所見即所得的繪圖思路,此處的窗體繪圖跟打印繪圖應該有良好的對應關係,因此設定的紙張應該一致。

Step 3:設定繪圖對象Graphics,能夠是打印機繪圖,也能夠是屏幕窗口繪圖。

Step 4:設定設備座標系。給定繪圖設備的實際區域,如屏幕窗口大小、打印機的實際可打印區域等。

Step 5:輸入繪圖數據。

Step 6:繪圖輸出

簡單的繪製流程圖以下:

1.5交互操做中屏幕座標系與打印座標系的轉換

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;

1.6提升繪圖效率

   如今通常的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=trueC#窗體的屬性,設置了此屬性估計系統自己會起用無效區的內存位圖緩存,而不須要程序員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>只繪製變化的部分。

1.7異或做圖法響應鼠標實時繪製

   異或做圖法不一樣於普通的繪圖方法中的刷新須要按照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);

  其中的RGB三個份量的排列順序是BGR

.NET中經過數據類型Color表示顏色,該類有一個函數FromArgb(int,int,int),能夠經過輸入RGB三個值獲得一個Color類型的顏色。同時也有一個ToArgb()函數,獲得一個32位的整數值,

32ARGB值的字節順序爲AARRGGBB。由AA表示的最高有效字節(MSB)alpha份量值。分別由RRGGBB表示的第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

說明:
設置繪圖模式。繪圖模式指出筆與被填充對象的顏色是怎樣同顯示錶面的顏色組合的。繪圖模式只用於光柵設備,不用於矢量設備。繪圖模式是雙重的光柵操做代碼,表明了兩個變量全部可能的布爾組合,分別使用ANDORXOR(異或)和NOT運算符。 

(7)字符串繪製,可否異或?(答曰:不能)

字符串輸出繪製不能採用異或的方式進行擦除更新,那麼須要實時的動態位置顯示信息的時候如何解決?目前看到三種方案:

方案一:利用局部更新文字造成的位圖方法(參考CSDN論壇,忘了地址)

如何經過兩次繪製的方式從屏幕上擦除文字

OnDraw
 CDC mem;
 CBitmap memmap;
 mem.CreateCompatibleDC(pDC);
 memmap.CreateCompatibleBitmap(m_pDC,1000,1000);
 CBitmap *memoldmap=mem.SelectObject(&memmap);
 /*
在這裏畫你的字,對mem的```
 */
 pDC->BitBlt(0,0,1000,1000,&mem,0,0,SRCCOPY); 
 mem.SelectObject(memoldmap);
 memmap.DeleteObject();

重載BOOL CXXXView::OnEraseBkgnd(CDC* pDC) 
直接return TRUE;

 

方案二:利用文字的點陣圖輸出

在背景上輸出和擦除文字

http://eyinlu.blog.163.com/blog/static/242321612011627921975/

在背景上輸出文字,而且能夠不留痕跡的擦除。
微軟提供的 CDC 函數, TextOut  DrawText 不支持界面像素的異或運算操做,因此沒法從背景中擦除輸出的文字。要達到這種目的,只好使用支持像素的異或運算的圖形函數了。
有兩種方式能夠選擇:
 1    輸出輪廓路徑,而後指定畫刷填充封閉的路徑區域。
 2    獲得文字的點陣圖,使用向屏幕花點的方式輸出文字。
下面給出第二種方式的實現代碼。
調用兩次下面的函數能夠擦除輸出的文字,還能夠顯示按任意角度旋轉的文字。
注意:傳入的設備上下文變量 pDC ,其中的 ROP2 屬性應該指定爲 R2_XORPEN  R2_NOTXORPEN 
void  CDicfexView :: DrawVectorText ( CDC * pDC , CString & strText , CPoint  pntPosition , int nAngle ) 
 { 
      if ( strText . IsEmpty ()) 
         return ; 
   
      CClientDC dc ( this ); 
   
      // 確認要按文本輸出的座標映射模式
      if ( pDC -> GetMapMode () != MM_TEXT ) 
      { 
          // 轉換到原座標映射
          dc . SetROP2 ( pDC ->  GetROP2 () ); 
   
          pDC -> LPtoDP ( &  pntPosition ); 
          pDC = & dc ; 
      } 
   
      DWORD dwSize ; 
         
      MAT2 stM2 ; 
      GLYPHMETRICS stGM  ;         
      TEXTMETRIC stTM ; 
   
      // 建立字體
     CFont font ; 
      LOGFONT stFont ; 
      memset (& stFont ,0, sizeof ( stFont )); 
      // 設置字體結構的屬性;
      stFont . lfHeight =12; 
      stFont . lfWeight = FW_NORMAL ; 
      stFont . lfClipPrecision = CLIP_LH_ANGLES ; 
      strcpy (( char *) stFont . lfFaceName ,  "Courier New" ); 
      stFont . lfEscapement =  nAngle * 10; 
      stFont . lfOrientation =  nAngle * 10; 
   
      font . CreateFontIndirect ( & stFont ); 
   
      CFont * pOldFont = pDC -> SelectObject (&  font ); 
     pDC -> SelectObject (& font ); 
      pDC -> GetTextMetrics (& stTM ); 
   
      // 座標變換矩陣 , 但這種轉換時 , 因爲精度舍入的緣由 , 取得的字體點陣的質量不好 , 
      //   stFont.lfEscapement 變量賦值 , 彷佛微軟作過優化 , 取得字體點陣勉強能說的過去 , 
      /*double nEscapement = nAngle * 3.1415926 / 180.0; 
     stM2.eM11 = FloatToFixed(cos(nEscapement)); 
      stM2.eM12 = FloatToFixed(sin(nEscapement)); 
      stM2.eM21 = FloatToFixed(-sin(nEscapement)); 
      stM2.eM22 = FloatToFixed(cos(nEscapement));*/ 
   
      stM2 . eM11 = FloatToFixed (1.0); 
      stM2 . eM12 = FloatToFixed (0.0); 
      stM2 . eM21 = FloatToFixed (0.0); 
      stM2 . eM22 = FloatToFixed (1.0); 
   
      int nChar = 0;  
     int nSx = pntPosition . x ; 
     int nSy = pntPosition . y ; 
   
      int nFontSpace = 0; 
   
      int nCx = 0; 
     int nCy = 0; 
   
     for ( int i = 0; i < strText . GetLength (); i ++) 
     { 
         if ( strText . GetAt ( i ) >=  0) 
             nChar =  strText . GetAt ( i ); 
         else 
         { 
                // 寬字節
             int th =  strText . GetAt ( i ); 
             int tl =  strText . GetAt ( i + 1); 
             nChar = ((  th & 0x00ff)<<8) + ( tl & 0x00ff); 
             i ++; 
         } 
    
          // 獲得字體輪廓信息的尺寸
          dwSize = pDC -> GetGlyphOutline  ( nChar , GGO_BITMAP ,& stGM , 0L , NULL ,& stM2 ); 
          // 定義緩衝區
          BYTE * pBuffer = new BYTE [  dwSize ]; 
          memset ( pBuffer , 0, dwSize  ); 
          // 取得字體輪廓
          pDC -> GetGlyphOutline (  nChar , GGO_BITMAP , & stGM , dwSize , pBuffer , & stM2 ); 
      
          int nStride = dwSize / stGM  . gmBlackBoxY ; 
   
          // 輪廓數據
          OUTLINETEXTMETRIC     stOtm ;  
          memset ( & stOtm , 0,  sizeof ( stOtm ) );  
          stOtm . otmSize = sizeof (  stOtm );  
          pDC ->  GetOutlineTextMetrics ( sizeof ( stOtm ), & stOtm ); 
   
          //x 的偏移量
          int nXOffset = stGM .  gmptGlyphOrigin . x ;     
          //y 的偏移量     字的頂部         - y 方向原點  都是相對於  baseline  
          int nYOffset = stOtm .  otmAscent - stGM . gmptGlyphOrigin . y ;    
          
          for ( int y =0; y < stGM  . gmBlackBoxY ; ++ y ) 
          { 
                for ( int x =0; x < nStride ; ++ x ) 
                { 
                     for ( int k =0; k < 8; ++ k ) 
                     { 
                         if ( ( pBuffer [ y * nStride + x ] >> (7- k ) ) & 1 ) 
                         { 
                              int nX = nCx + 8 * x + k + nXOffset ; 
                              int nY = nCy + y + nYOffset ; 
   
                              pDC -> SetPixel ( nSx + nX , nSy + nY , RGB (255,200,200) ); 
                         } 
                     } 
                } 
          } 
   
          // 設置下一個字符的位置
          nCx += stGM . gmCellIncX ; 
         nCx += nFontSpace ; 
         nCy += stGM . gmCellIncY ; 
   
          delete [] pBuffer ; 
     }   
     pDC -> SelectObject ( pOldFont ); 
 }

方案三:直接用Label控件顯示信息,讓Label跟隨鼠標移動顯示文字內容

直接修改Label控件LeftTop屬性,更新其Text屬性內容,而後改變控件位置,實現實時顯示。默認狀況下,控件不支持透明背景色。在屬性框裏設置background屬性爲transparent。同時修改Visible屬性進行顯示和隱藏。

2數據管理

繪圖內容數據管理按照數據庫方式的拓撲結構進行管理,而單個對象則採用面向對象方式進行存儲和管理操做。

3窗體繪圖顯示設計

工具欄主要操做項:

4繪圖打印

4.1公制與英制單位的換算問題

   利用PageSetupDialog對話框設置紙張的類型、頁邊距等信息後,再次進入頁面設置的對話框,發現裏面的頁邊距所有改變了,再進入又改變了,這是爲何呢?

其實緣由很簡單,單位的不一樣形成了這個現象。咱們能夠再看看上圖中頁邊距一項明確的註明了單位採用的是毫米,說明在頁面設置對話框中使用的是公制長度計量單位,而在.net中採用的是英制的計量單位。英制中長度的基本計量單位是英寸,公制中長度的基本計量單位是釐米,打印時默認的長度單位爲 1/100英寸。所以假設咱們在頁面設置對話框中設置上部邊距爲10mm(以下左圖),.net把它轉換成了英制單位,數值是1/2.54 * 100=391/100英寸,(1英寸約等於2.54釐米,1釐米=10毫米)因此,這時上部頁邊距的數值變成了39,當你再次打開頁面設置對話框時,系統將認爲上部頁邊距是391/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)的頁邊距數值,利用 PrinterUnitConvertConvert方法均可以轉換,在上例中,PrinterUnit.Display是指1/100英寸的單位, PrinterUnit.TenthsOfAMillimeter是指1/100毫米的單位,這樣就能夠將英制單位轉換爲公制單位。

  固然咱們也能夠本身編寫代碼進行轉換,但請注意,轉換時英制的單位是1/100英寸,轉換後要以毫米爲單位。

注意:轉換時只須對紙張的頁邊距進行轉換,紙張自己的寬度和高度在你選擇一種紙張類型的時候,它已經自動幫你轉換成英制單位了,千萬不要多此一舉 以上咱們介紹瞭如何利用PageSetupDialog對話框設置頁面、公制與英制單位的換算,已經爲打印程序的編寫創建了一個良好的基礎。接下來,咱們就來介紹如何實現具體的特殊打印功能。

.NET的升級版本也能夠一句話就解決問題:

// 打印頁面設置

       publicPageSetupDialogv_PrintPageSetDlg = newPageSetupDialog();

// 英制單位轉換爲公制單位

     v_PrintPageSetDlg.EnableMetric = true;

結果以下:

可是注意:此時顯示的25.4mm,實際上獲取Margins屬性的時候,仍然是1001/100英寸,所以存在0.254的係數關係。

4.2繪圖單位轉換問題

這裏須要統一打印時的度量單位,打印文檔的DefaultPageSettings中的參數都是以1/100英寸爲單位,而窗口界面繪圖中的尺寸獲取(widthHeight)是以像素爲單位,而中文操做系統中以毫米爲單位(如打印設置頁面),在打印時如何統一單位是必需要進行的。

而程序設定的座標轉換是基於世界座標(單位:m)、邏輯座標(單位:mm)以及設備座標(單位:pixel),所以須要將C#提供的英制單位1/100英寸轉換爲mm,而後最終轉換爲像素繪圖座標。

// mmPixel

       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);

4.3關於物理頁邊距

相同的打印繪圖內容,若是是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向左向上平移的,因此有空白的地方出如今預覽圖像的右邊和下邊。

打印的時候出現物理邊界的影響,致使繪圖能完整打印出來,以下圖所示:

4.4偏差?繪製問題

// 繪製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,已解決

4.5打印結果

4.5.1頁面設置

測試採用普通的A4紙張,並設置爲橫向,以下圖所示:

 

4.5.2打印預覽

查看打印閱覽,跟繪圖內容是否相同,測試結果一致。


4.5.3打印成果圖

選用兩種打印成果(虛擬打印 + 物理打印),以下圖所示:

(1)AdobePDF虛擬打印機,無物理邊距打印


2)實際物理打印機HP LaserJet P4515打印機,物理邊距 (4.8, 4.1)mm,之前兩張圖是不一樣時期打印,圖紙上的物理邊距線存在打印不徹底現象,這就是常常出現的打印不徹底現象,能夠看出兩張圖紙的上和頂部邊距不一樣,這是人工裝載紙盒的時候,可能存在必定誤差,形成打印不徹底等各類現象。可是兩張圖都保證了頁面邊距徹底的效果,都是有25.4mm的默認邊距,實現真正的可打印區域的徹底打印功能。




http://blog.sciencenet.cn/blog-244606-747345.html 
相關文章
相關標籤/搜索