宋華 小題大做?GDI 繪製可滾動視窗

2021-05-23 07:30:31 字數 4349 閱讀 4874

gdi+繪製可滾動視窗,對如此簡單的應用做文章是不是小題大做?

的確,通常情況下,我們新建web或windows專案,在窗體中拖入需要使用的控制項。這時,如果文件(應用程式處理的任何資料,本文指需要繪圖的資料)太大,螢幕當前工作區不能完全顯示,應用程式就會新增滾動塊(可能需要明確設定窗體的autoscroll屬性為true)。這種應用下,我們實際上只使用了windows標準控制項,.net執行環境和基類處理程式就能很好地完成這些操作。

為了說明這種情形,我們建立乙個c# windows專案teststandardcontorls,在方法initializecomponent 中新增**行(直接往窗體中拖入listbox控制項也產生這些**):

private void initializecomponent()

同時,設定窗體的autoscroll屬性為true:

public form1()

**很簡單,呼叫乙個尺寸大於初始窗體的listbox控制項(當然,也可以通過拉伸操作改變窗體大小)。編譯並執行應用程式,窗體正確顯示listbox控制項並新增滾動條(這是由於listbox 控制項區間大於窗體工作區間),拖動滾動條能正確顯示listbox控制項各部分。

包含gdi+繪圖的窗體滾動

上面例子只使用了windows系統標準控制項,而下面我們將面對另一種情形。這時,我們要不使用或不僅僅使用windows標準控制項(這樣做有很多理由,或許因為標準控制項並不能獲得使用者介面需要的靈活性),要在螢幕上自行繪圖。很明顯,這是使用gdi+繪圖的領域(gdi+繪圖包括許多相關的知識,這裡不展開講解,你可以參見專門的文章),這一領域下的文件管理與windows對標準控制項的管理有很大的不同。

1、滾動管理差異

gdi+下的文件管理與windows對標準控制項的管理有很大的不同。尤其地,應用程式不能自行管理視窗的滾動,除非你在**中進行了相應的程式設計。也就是說,我們需要幫助窗體form例項確定何時以及如何滾動。

同樣地,為了說明這種情形,我們新建c# windows專案testgdiplus。為實現定製繪圖,我們過載paint事件如下:

protected override void onpaint(painteventargs e)

//我們也設定窗體初值為矩形區間300*300:

private void initializecomponent()

**很簡單,以螢幕上點(0,0)為左上角起點,以長400、寬350的外接矩形繪製了乙個填充橢圓。從**上分析,當前視窗(300*300)小於文件尺寸(400*350),應用程式初始化時應該產生滾動條,執行後改變窗體尺寸也可能產生滾動條。但編譯執行應用程式卻發現事實並非如此,這兩種情況下滾動條都不會出現。即便我們顯式設定窗體autoscroll屬性為true(this.autoscroll=true),並且無論怎樣拉伸以改變窗體尺寸,滾動條也不會出現。

2、為包含gdi+繪圖的窗體新增滾動條

在這裡,沒有滾動條的原因是窗體不知道是否需要滾動條-它不知道我們自行產生的繪圖區域有多大。也就是說,系統不能自動管理這些繪圖區域,除非它明確當前繪圖區域的大小符合滾動條出現的條件。那麼,如何確定繪圖區域的大小呢?通常,繪圖區域與文件區域相一致,而在我們的示例中,文件區域是從文件的左上角(或者是在進行任何滾動前的客戶區域左上角)開始向下延伸,其大小應足以包含整個文件的矩形區間。從**中繪製橢圓的語句可以得出,這裡的文件區域應是矩形區間(400,350)。它也就是我們需要的繪圖區域。

確定繪圖區域後,告訴窗體文件有多大就很容易了。form例項的autoscrollminsize屬性用於設定自動滾動的最小尺寸,實際使用中給它賦予繪圖區域,窗體就能夠知道文件的大小並管理滾動條的出現。下述**用於設定當前的文件區域為(400*350),這意味著當窗體小於size(400,350)所表示的矩形區間時,系統將產生滾動條(或橫向或縱向):

public form1()

(需要指出的是,這裡之所以使用autoscrollminsize設定自動滾動的最小尺寸,是因為從**中能夠確定文件區域的大小,進而可確定相應螢幕區域的大小。並且,在執行應用程式的過程中,這個文件區域的大小是不會改變的。這種情況下,你能夠使用確定的數值表示滾動尺寸(象上面的**那樣),這也是很普遍的情況。但是,如果應用程式執行並不能確定文件區域的大小,如顯示檔案的內容的操作,或者執行某些改變螢幕區域的操作,就需要在**中動態設定autoscrollminsize屬性值。)

之後,重新編譯並執行應用程式,得到正確顯示圖形的螢幕。窗體正確設定了滾動條位置及大小,以指定文件正確顯示的比例。試著在執行樣例時改變(拉伸或縮放窗體而不是使用滾動條)視窗的大小,可以發現滾動條會正確響應,甚至如果使視窗變得足夠大,不再需要滾動條時,滾動條就會消失。

3、全域性變形-全域性座標到頁面座標的轉換

然而,使用一下上面產生的應用程式滾動條,向下滾動它,錯誤出現了:窗體不能正確顯示文件!

可見,僅僅設定autoscrollminsize是不夠的。

實際上,出錯原因是我們沒有在onpaint()過載方法**中考慮滾動條的位置。看看前面繪製橢圓的**,它告訴graphics例項使用視窗客戶區域左上角(0,0)為起點繪製乙個橢圓。graphics例項在預設情況下把座標解釋為相對於客戶視窗(系統初始化時客戶視窗的左上角與文件的左上角是一致的),它不知道滾動條的情況,而滾動條的使用改變了文件左上角的起點位置,但**卻沒有為滾動條的位置相應調整座標,這樣,錯誤出現了。

如果你理解.net中的座標系統及其轉換,你就能清楚錯誤的原因所在。

· .net座標系統

gdi+ 使用三個座標空間:全域性、頁面和裝置。與這裡有關的是全域性座標和頁面座標。其中,全域性座標(也叫世界座標)是指要測量的點距離文件區域左上角的位置。而頁面座標是指要測量的點距離客戶區域左上角的位置。

從以上定義不難看出,頁面座標空間的原點總是螢幕工作區的左上角點,這是不會變化的。而全域性座標左上角點取決於當前文件的位置。通常,使用graphics物件的draw系列方法繪圖預設在全域性座標空間中進行,在系統初始化時,全域性座標空間的原點也在螢幕工作區的左上角,因此頁面座標與全域性座標相同,這時不需要轉換也能正確繪圖(這就是上面的示例第一次啟動總能正確顯示窗體的原因所在)。但是,當使用滾動條時,窗體中文件的位置將發生改變,使文件左上角點不再與客戶區域左上角點重合。這時,為正確顯示文件,就需要進行轉換,使之總是對應於客戶區域的左上角點。顯然,這裡需要將全域性座標轉換為頁面座標。.net中,把全域性座標對映到頁面座標的變形稱為"全域性變形"。

· 實施全域性變形

從數學變換的角度來看,如果我們知道窗體滾動的距離,全域性變形就很簡單。form例項的autoscrollposition屬性正是用於獲取或設定自動滾動定位的位置,通過它就能獲得窗體滾動的距離。下面的語句用以獲取滾動在x、y方向的偏移量:

int xoffset=this.autoscrollposition.x;//獲取x軸方向偏移距離

int yoffset=this.autoscrollposition.y;//獲取y軸方向偏移距離

這兩個值就是全域性座標點轉換為對應頁面座標點的橫、縱偏移量。把它們與全域性空間下的座標點進行簡單的向量加運算,就可以變換為頁面空間座標。graphics 類提供了方法translatetransform來執行這個計算,我們給它傳送水平和垂直座標,表示客戶區域左上角點相對於文件左上角點的座標分量,然後graphics裝置考慮客戶區域相對於文件區域的位置,處理這些座標。以下語句實現了這一過程:

mygraphics.translatetransform(xoffset,yoffset);

示例**

綜合上述,修改testgdiplust專案檔案onpaint方法如下:

protected override void onpaint(painteventargs e)

作為對照,這裡也提供另一種實現方式,它通過自行計算而不是借助於translatetransform方法實現座標變換,這是這兩種方式唯一的區別。

protected override void onpaint(painteventargs e)

編譯並執行,應用程式正確顯示螢幕圖形,改變窗體尺寸使出現滾動條並拖動,文件各部分正確顯示。

其它

需要指出的是,單就視窗滾動而言,以上**是完全可行的了。但實際工作中還需要考慮繪圖的效率,這時,以上**還應該加以完善。

首先,應該判斷文件的剪下區域以確定只在需要的時候進行圖形繪製;

其次,應該將繪圖輔助物件pen、brush等宣告為類成員變數以減少初始化次數。

(責任編輯

sunny