VC++例說Windows窗口、視口以及GDI映射模式

 

  |舉報|字號 訂閱

在Windows應用程序中,只要進行繪圖,就要使用GDI座標系統。Windows提供了幾種映射方式,每一種映射都對應着一種座標系。例如,繪製圖形時,必須給出圖形各個點在客戶區的位置,其位置用x 和y兩個座標表示,x表示橫座標,y表示縱座標。在所有的GDI繪製函數中,這些座標使用的是一種“邏輯單位”。當GDI函數將結果輸出送到某個物理設備上時,Windows將邏輯座標轉換成設備座標(如屏幕或打印機的像素點)。本文討論了圖形環境中的各個映射模式,包括它們是什麼,怎麼工作的,以及它們真正的含義。
一、窗口、視口以及映射模式基本概念  
強調一個網上和教科書上沒有將清楚但是至關重要的概念:窗口和視口其實是同一塊矩形區域,兩者座標系的原點是同一個點。窗口和視口的區別僅僅是單位不同。窗口和視口都不是指顯示屏或打印機上的區域,我們看到顯示屏上的物體實際上是顯示在“設備環境”上的,由於視口(也就是窗口)區域與設備環境的左手座標系第一象限xoy平面有交集,我們才能看到視口中的物體。窗口的變換和視口的變換的目的是一樣的,都是爲了將物體顯示在設備環境中,只不過由於視口中使用像素作爲單位也就是顯示屏的設備座標,所以往往在視口中進行調整dc比較直觀一些。

(一)邏輯座標。邏輯座標即是世界座標系下的座標。邏輯座標與設備無關,客觀世界中的場景可以由世界窗口或視口中進行描述,在窗口中進行描述時使用世界座標系中的座標單位也即邏輯座標,在視口中進行描述使用的是設備座標。 
(二)設備座標。圖形輸出時,Windows將GDI函數中指定的邏輯座標映射爲設備座標。在屏幕顯示的設備座標系統中,單位以像素點爲準,水平值從左到右增大(正方向向右),垂直值從上到下增大(正方向向下)。注意設備座標系的原點永遠不會移動,它們僅僅與物理設備有關。

        設備空間的範圍實際顯示設備上的矩形區域,通常有三種範圍:窗口的客戶區(使用BeginPaint 或 GetDC 獲取)全窗口(使用GetWindowDC 獲取)全屏幕(使用GetDc(0)/ CreateDC 獲取);還有一些特殊的設備空間,如內存設備空間(使用CreateCompatibleDC獲取),打印機設備空間,元文件設備空間即(CreateMetaFile)Windows中包括以下3種設備座標,以滿足各種不同需要:   
  1、客戶區域座標,包括應用程序的客戶區域,客戶區域的左上角爲(0, 0)。 
  2、屏幕座標,包括整個屏幕,屏幕的左上角爲(0, 0)。屏幕座標用在WM_MOVE消息中(對於非子窗口)以及下面的Windows 函數中:CreateWindow 和MoveWindow(都對於非子窗口)、GetMessage、GetCursorPos、GetWindowRect、WindowFromPoint 和SetBrushOrg 中。用函數ClientToScreen和ScreenToClient可以將客戶區域座標轉換成屏幕區域座標,或反之。
  3、全窗口座標,包括一個程序的整個窗口,包括標題條、菜單、滾動條和窗口框,窗口的左上角爲(0,0)。使用GetWindowDC得到的窗口設備環境,可以將邏輯單位轉換成窗口”座標。


(三)映射模式。映射方式定義了Windows如何將GDI函數中指定的邏輯座標映射爲設備座標。Windows爲了程序員方便,允許程序員在一個假想的空間上(世界座標系中)繪製圖形,這就是邏輯空間,但是最終這些圖形還是需要顯示到真實的屏幕或打印機,這就是設備空間。此時就會出現一個問題,即如何將邏輯空間中的圖形通過怎樣的關聯適當的顯示在屏幕上呢?Windows給出了答案—映射模式,Windows內定了8種映射模式,其中有常用的6種固定的模式(MM_TEXT、MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH、MM_TWIPS)把邏輯單位及數軸的方向都確定好了,還有2種允許自行定義邏輯單位及數軸方向。這兩種模式的區別在於MM_ISOTROPIC要求必須橫縱兩軸的邏輯單位必須是相等的,而MM_ANISOTROPIC是絕對的自定義,沒有任何限制。設備座標系始終是左手座標系且以顯示區域的左上角的(0,0)點作爲座標原點,水平向右爲x軸的正方向,垂直向下爲y軸的正方向。窗口座標系(就是視口座標系)是可以自定義的座標系,其座標軸和原點都不固定,dc繪圖始終是在窗口座標系中進行。在MM_TEXT映射模式下,窗口座標系的x軸向右,y軸向下和設備環境的座標系方向相同,在沒有移動窗口座標系的原點時,窗口座標系的原點也在設備點(0,0)處。在窗口座標系的第一象限繪圖,圖像會顯示在設備環境中。這開始給我一種錯覺:視口就是設備環境的顯示區域。 需要明確在MM_TEXT的映射模式下,視口座標系(畫筆)的x軸與設備環境座標系(顯示屏)的x軸平行都是水平向右爲正,視口座標系(畫筆)的y軸與設備環境座標系(顯示屏)的y軸平行都是垂直向下爲正。此時將dc從世界座標系的原點(0,0)移動到(X,Y)後,dc繪製的圖形會直接顯示的設備環境(顯示屏)上。

在MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH、MM_TWIPS的映射模式下,窗口座標系的x軸向右,y軸向上。所以在窗口的第一象限繪圖一般圖像不會顯示在設備環境上(如窗口的客戶區),因爲此時視口(窗口)的第一象限與設備環境的第一象限的交集爲空。

映射模式

邏輯單位

增加值

x

y

MM_TEXT

像素

MM_LOMETRIC

0.01 cm

MM_HIMETRIC

0.001 cm

MM_LOENGLISH

0.01 in.

MM_HIENGLISH

0.001 in.

MM_TWIPS

1/1440 in.

MM_ISOTROPIC

任意(x = y)

可選

可選

MM_ANISOTROPIC

任意(x != y)

可選

可選

如果您將顯示器的像素尺寸設定爲1024×768,下表就是Windows NT報告的視口和窗口範圍的值。

映像方式

視口範圍(x,y)

窗口範圍(x,y)

MM_LOMETRIC (1024, -768) (3,200, 2,400)
MM_HIMETRIC (1024, -768) (32,000, 24,000)
MM_LOENGLISH (1024, -768) (1,260, 945)
MM_HIENGLISH (1024, -768) (12,598, 9,449)
MM_TWIPS (1024, -768) (18,142, 13,606)

映像方式

邏輯單位

英寸

毫米

MM_LOENGLISH 0.01 in. 0.01 0.254
MM_LOMETRIC 0.1 mm. 0.00394 0.1
MM_HIENGLISH 0.001 in. 0.001 0.0254
MM_TWIPS 1/1400 in. 0.000694 0.0176
MM_HIMETRIC 0.01 mm. 0.000394 0.01

/*--------------------------插入GetDeviceCaps的使用說明Begin-----------------------------

//所有像素數
int pagecx=dc.GetDeviceCaps(HORZRES);
int pagecy=dc.GetDeviceCaps(VERTRES);

//即每英寸點數
short cxInch = dc.GetDeviceCaps(LOGPIXELSX);
short cyInch = dc.GetDeviceCaps(LOGPIXELSY);

// 計算一個設備單位等於多少0.1mm
double scaleX = 254.0 / (double)GetDeviceCaps(dc.m_hAttribDC,LOGPIXELSX);
double scaleY = 254.0 / (double)GetDeviceCaps(dc.m_hAttribDC, LOGPIXELSY);

說明:
主要用到的參數見例子中的:HORZRES,VERTRES,LOGPIXELSX,LOGPIXELSY.總的來說是爲了方便控制打印或重畫時的控制,如爲了定製打印時,一般依據的是物理的長度,而不是像素,而DC一般是用像素的映射模式,所以需要一下轉換,上面這個函數就爲這種轉換設計的.

以上三者的關係通常滿足:HORZSIZE = 25.4 * HORZRES/LOGPIXELSX
    HORZSIZE爲屏幕水平尺寸(定爲度量尺寸,以mm計),HORZRES爲水平的像素總數(定爲像素大小,平時所說的屏幕分辨率,但在這不這麼稱呼。這裏,分辨率定爲“每英寸的像素數”),LOGPIXELSX爲邏輯像素(假設的每英寸的像素數,並不是剛纔所說的實際的“分辨率”)。因此HORZSIZE也稱爲邏輯寬度。
    當我們選擇“顯示”屬性裏的大字體時,LOGPIXELSX(通常分爲96dpi與120dpi)變大了,這樣假設原來的字體爲10磅,則原來的字體橫向所佔像素(實際所佔的像素數)爲10*(1/72)*LOGPIXELSX,現在LOGPIXELSX變大了,則字體所佔像素也大了,因此看起來字體大了。如果HORZRES不變的話,則HORZSIZE應該變小。然後這是和Windows有關的,在16位OS中,HORZSIZE值是固定的。
    在XP系統上驗證了一下,發現HORZSIZE值與LOGPIXELSX的值也是不變的,如果改變HORZRES的話,則HORZSIZE會發生相應變化,但LOGPIXELSX不變,一直是96。

/*--------------------------插入GetDeviceCaps的使用說明End-----------------------------

這些窗口範圍表示包含顯示器全部寬度和高度的邏輯單位元數值。320毫米寬的屏幕(MM_HIMETRIC下屏幕寬度的邏輯單位爲0.01mm,共32000個間隔,故1024像素實際中對應32000*0.01=320mm)也爲1260 MM_LOENGLISH單位或12.6英寸(320除以25.4毫米/英寸)。
首先要明確圖形只能在設備環境的座標系中顯示。其次要注意視口範圍中,視口座標系的y軸前面的負號表示改變了畫筆dc移動時y軸的方向。對於這五種映像方式,視口範圍相當於顯示屏沿着x軸向屏幕上方摺疊後位於實際顯示屏上方的矩形區域,想象一下當前顯示屏上面放置了一個同樣的大小的虛擬顯示屏,dc的y值隨dc的上升而增加,dc向上移動然後然後繪製圖形將顯示在虛擬屏幕中,這個事實有一個有趣的結果。此時要想在設備環境的顯示區域顯示任何東西,必須使用負的y值才能將畫筆移動到真實的顯示屏上。VC++例說Windows窗口、視口以及GDI映射模式 - 阿英 - Mr.Right

例如下面的程序代碼:
void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 dc.SetMapMode(MM_LOMETRIC);

 //dc.TextOut(400,400,"Hello"); //看不到屏幕上的Hello,此時dc畫到顯示屏上方的虛擬顯示屏上了。
 dc.TextOut(400,-400,"Hello"); //可以看出屏幕上的Hello
}

爲了使自己保持頭腦清醒,您可能想避免這樣做。

1)一種解決辦法是將邏輯的(0,0)點設爲顯示區域的左下角,您可以通過呼叫SetViewportOrgEx (hdc, 0, cyClient, NULL) ;(假設cyClient是以像素爲單位的顯示區域的高度)。此時的座標系是直角座標系的右上象限。這相當於將視口的原點從設備環境的左上角移動到左下角,將當前顯示屏正上方那個虛擬顯示屏向下移動,覆蓋掉當前的顯示屏區域,此時視口的區域和設備環境顯示區域重合,所以視口中的物體能顯示到計算機屏幕上。例如下面的程序代碼:
void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 CRect Recto;
 GetClientRect(&Recto);
 dc.SetMapMode(MM_LOMETRIC);
 dc.SetViewportOrg(0,Recto.Height());
 dc.TextOut(400,400,"Hello"); //結果與題設的結果相同!
}
2)另一種方法是將邏輯(0,0)點設爲顯示區域的中心:
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;代碼如下

void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 CRect Recto;
 GetClientRect(&Recto);
 dc.SetMapMode(MM_LOMETRIC);
 dc.SetViewportOrg(Recto.Width()/2,Recto.Height()/2);
 dc.TextOut(400,400,"Hello");
}   現在,我們有了一個真正的4象限笛卡爾座標系,在x軸和y軸上有相等的按英寸、毫米或twip計算的邏輯單位。
3)您還可以使用SetWindowOrgEx函數來改變邏輯(0,0)點,但是這稍微困難一些,因爲SetWindowOrgEx的參數必須使用邏輯單位,先要將(cxClient,cyClient)用DPtoLP函數轉換爲邏輯座標。假設變量pt是型態爲POINT的結構,下面的代碼將邏輯(0,0)點改變到顯示區域的中央:
pt.x = cxClient ;
pt.y = cyClient ;
DptoLP (hdc, &pt, 1) ;
SetWindowOrgEx (hdc, -pt.x / 2, -pt.y / 2, NULL) ;代碼如下

void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 CRect Recto;
 CPoint pt;
 GetClientRect(&Recto);
 dc.SetMapMode(MM_LOMETRIC);
 pt.x = Recto.Width()/2;
 pt.y = Recto.Height()/2;
 DPtoLP(dc,&pt,1);
 dc.SetWindowOrg(-pt.x,-pt.y);
 dc.TextOut(400,400,"Hello"); //結果與2)相同
}

二、MM_ISOTROPIC和MM_ANISOTROPIC映射模式

 1)Isotropic的意思是“同方向性”。 如果想要在使用任意的軸時都保證兩個軸上的邏輯單位相同,邏輯座標系中的圓形在設備環境中現實爲圓形,則MM_ISOTROPIC映射方式就是理想的映射方式。這時具有相同邏輯寬度和高度的矩形顯示爲正方形,具有相同邏輯寬度和高度的橢圓顯示爲圓。當您剛開始將映射方式設定爲MM_ISOTROPIC時,Windows使用與MM_LOMETRIC同樣的窗口和視口範圍(但是,不要對此有所依賴)。區別在於,您現在可以呼叫SetWindowExt和SetViewportExt來根據自己的偏好改變範圍了,然後,Windows將調整範圍的值,以便兩條軸上的邏輯單位有相同的實際距離。

SetWindowExt(int Lwidth, int Lheight) //參數的單位爲邏輯單位(Logical);
SetViewportExt(int Pwidth, int Pheight) //參數的單位爲像素(Pixel); 
以x軸爲例(y軸類似),邏輯座標系中的x軸的單位刻度=| Pwidth | / | Lwidth |表示x軸上一個邏輯單位等於多少個像素。比如我們先通過GetDeviceCap(LOGPIXELSX)獲得在我們的顯示器上每英寸等於多少個像素,設爲p,然後我們將它賦給Pwidth,將Lwidth賦成2,即Pwidth / Lwidth=p / 2。那麼此時邏輯座標系x軸上的單位刻度就是p / 2個像素;又由於p個像素是代表一個英寸的,所以此時的邏輯座標系x軸上的單位刻度同時也是半個英寸。還有一點要注意的是,如果Lwidth與Pwidth同號,邏輯座標的x軸方向與設備座標系中的x軸方向相同,否則相反。
此外,當使用MM_ISOTROPIC模式時,如果通過計算window與viewport範圍的比值得到兩個方向的單位刻度值不同,那麼將會以較小的那個爲準

 

例如,假設您想要一個「傳統的」單象限虛擬座標系,其中(0,0)在顯示區域的左下角,寬度和高度的範圍都是從0到2000,並且希望x和y軸的單位具有同樣的實際尺寸。以下就是所需的程序:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 2000, 2000, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;
如果其後用GetWindowExtEx和GetViewportExtEx函數獲得了窗口和視端口的範圍,可以發現,它們並不是先前指定的值。Windows將根據顯示設備的縱橫比來調整範圍,以便兩條軸上的邏輯單位表示相同的實際尺寸。如果顯示區域的寬度大於高度(以實際尺寸爲準),Windows將調整x的範圍,以便邏輯窗口比顯示區域視口窄。這樣,邏輯窗口將放置在顯示區域的左邊。代碼如下:

void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 CRect Recto;
 GetClientRect(&Recto);
  dc.SetMapMode(MM_ISOTROPIC);
  dc.SetWindowExt(2000,2000); //現實中的邏輯座標系的x軸有2000個單位,y軸也有2000個單位。
  dc.SetViewportExt(Recto.Width(),-Recto.Height()); // Lwidth與Pwidth異號,所以邏輯座標系的y軸垂直向上
 dc.SetViewportOrg(0,Recto.Height());//將視口原點區域下移到設備環境的左下方,此時窗口中的物體可以顯示到設備環境上(顯示屏)

  dc.Ellipse(-2000,-2000,2000,2000);
 dc.MoveTo(0,0);
 dc.LineTo(2000,2000);

}

前面給出的程序代碼等價的用SetWindowOrg改爲:
SetMapMode (MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 2000, 2000, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetWindowOrgEx (hdc, 0, 2000, NULL) ;

在呼叫SetWindowOrgEx中,我們將邏輯點(0, 2000)映像爲設備點(0,0)。程序代碼如下

void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 CRect Recto;
 GetClientRect(&Recto);
  dc.SetMapMode(MM_ISOTROPIC);
  dc.SetWindowExt(2000,2000); //現實中的邏輯座標系的x軸有500個單位,y軸也有500個單位。
  dc.SetViewportExt(Recto.Width(),-Recto.Height()); // Lwidth與Pwidth異號,所以邏輯座標系的y軸垂直向上,與設備環境的y軸相反
  dc.SetWindowOrg(0,2000); //將邏輯點(0, 2000)映射爲設備點(0,0),邏輯點(0,0)恰好在設備點(0,cyClient)
  dc.Ellipse(-2000,-2000,2000,2000);
  dc.MoveTo(0,0);
  dc.LineTo(2000,2000);
}

您也許想要使用一個四象限的笛卡爾座標系,四個方向的座標尺度可以任意指定,(0,0) 必須居於顯示區域的中央。如果您想要每條軸的範圍從0到1000,則可以使用以下程序代碼:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 1000, 1000, NULL) ;
SetViewportExtEx (hdc, cxClient / 2, -cyClient / 2, NULL) ;
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;

void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 CRect Recto;
 GetClientRect(&Recto);
  dc.SetMapMode(MM_ISOTROPIC);
  dc.SetWindowExt(2000,2000); //現實中的邏輯座標系的x軸有2000個單位,y軸也有2000個單位。
  dc.SetViewportExt(Recto.Width(),-Recto.Height()); // Lwidth與Pwidth異號,所以邏輯座標系的y軸垂直向上,與設備環境的y軸相反
  dc.SetViewportOrg(Recto.Width()/2,Recto.Height()/2);
  dc.Ellipse(-1000,-1000,1000,1000);
  dc.MoveTo(0,0);
  dc.LineTo(2000,2000);
}

在MM_ISOTROPIC映像方式下,可以使邏輯單位大於像素。例如,假設您想要一種映像方式,使點(0,0)顯示在屏幕的左上角,y的值向下增長(和MM_TEXT相似),但是邏輯座標單位爲1/16英寸。以下是一種方法:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 16, 16, NULL) ;
SetViewportExtEx (hdc, GetDeviceCaps (hdc, LOGPIXELSX),
GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;
SetWindowExtEx函數的參數指出了每一英寸中邏輯單位數。SetViewportExtEx函數的參數指出了每一英寸中實際單位數(像素)。
然而,這種方法與Windows NT中的度量映像方式不一致。這些映射方式使用顯示器的像素大小和公制大小。要與度量映像方式保持一致,可以這樣做:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 160 * GetDeviceCaps (hdc, HORZSIZE) / 254,
160 * GetDeviceCaps (hdc, VERTSIZE) / 254, NULL) ;
SetViewportExtEx (hdc, GetDeviceCaps (hdc, HORZRES),
GetDeviceCaps (hdc, VERTRES), NULL) ;

在這個程序代碼中,視埠範圍設定爲按像素計算的整個屏幕的大小,窗口範圍則必須設定爲以1/16英寸爲單位的整個屏幕的大小。GetDeviceCaps以HORZRES和VERTRES爲參數,傳回以毫米爲單位的設備尺寸。如果我們使用浮點數,將把毫米數除以25.4,轉換爲英寸,然後,再乘以16以轉換爲l/16英寸。但是,由於我們使用的是整數,所以先乘以160,再除以254。
當然,這種座標系會使邏輯單位大於實際單位。在設備上輸出的所有東西都將映像爲按1/16英寸增量的座標值。當然,這樣就不能畫兩條間隔l/32英寸的水平直線,因爲這樣將需要小數邏輯座標。

2)在MM_ANISOTROPIC映射方式下,Windows不對您所設定的值進行調整,這就是說,MM_ANISOTROPIC不需要維持正確的縱橫比。使用MM_ANISOTROPIC的一種方法是對顯示區域使用任意座標,就像我們對MM_ISOTROPIC所做的一樣。下面的程序代碼將點(0,0)設定爲顯示區域的左下角,x軸和y軸都從0到2000:
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 2000, 2000, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;
 在MM_ISOTROPIC方式下,相似的程序代碼導致顯示區域的一部分在軸的範圍之外。但是對於MM_ANISOTROPIC,不論其尺度多大,顯示區域的右上角總是(2000, 2000)。如果顯示區域不是正方形的,則邏輯x和y的單位具有不同的實際尺度。代碼如下:
void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 CRect Recto;
 GetClientRect(&Recto);
 dc.SetMapMode(MM_ANISOTROPIC); //和MM_ISOTROPIC進行對比便清楚
 dc.SetWindowExt(2000,2000); //現實中的邏輯座標系的x軸有2000個單位,y軸也有2000個單位。
 dc.SetViewportExt(Recto.Width(),-Recto.Height()); // Lwidth與Pwidth異號,所以邏輯座標系的y軸垂直向上
 dc.SetViewportOrg(0,Recto.Height());//將視口原點區域下移到設備環境的左下方,此時窗口中的物體可以顯示到設備環境上(顯示屏)
 dc.Ellipse(-2000,-2000,2000,2000);
 dc.MoveTo(0,0);
 dc.LineTo(2000,2000);
}

 另一種使用MM_ANISOTROPIC的方法是將x和y軸的單位固定,但其值不相等。例如,如果有一個只顯示文字的程序,您可能想根據單個字符的高度和寬度設定一種粗刻度的座標:
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 1, 1, NULL) ;
SetViewportExtEx (hdc, cxChar, cyChar, NULL) ;
當然,這裏假設cxChar和cyChar分別是那種字體的字符寬度和高度。現在,您可以按字符行和列指定座標。下面的敘述在距離顯示區域左邊三個字符,上邊二個字符處顯示文字:
TextOut (hdc, 3, 2, TEXT ("Hello"), 5) ;

三、窗口、視口以及映射模式的意義

MFC缺省地使用MM_TEXT映射模式,一個邏輯單位等於設備中的一個象素。它是實現“所見即所得”的基礎。其實大部分情況下使用像素進行工作是很合適的,不需要使用除了MM_TEXT方式外的任何映射方式。但是如果你需要進行類似CAD這樣的繪圖時,需要以英寸或者釐米尺寸顯示圖像,就需要使用其他映射方式,以方便編程。因爲只要你把映射方式確定後,你只需要在邏輯空間使用邏輯單位繪製圖像就好了,無需擔心怎樣顯示在真實的屏幕或打印機上,這些繁瑣的工作Windows會做好的。

這時可能會有人問,要是使用cm作爲邏輯單位,Windows是如何確定1cm的真實長度呢?我悄悄地告訴你,“Windows 它根本不知道!”,別驚訝,聽我接着說,Windows它確實不知道,但是Windows知道像素,這個很重要,它會以像素爲依據計算得出1cm的長度(不一定是真實長度)。當程序需要繪製了一個10cm×10cm的矩形時,如果需要顯示在打印機上,那麼Windows首先通過GetDeviceCaps獲取打印機相關信息如每英寸顯示320像素(點),而1英寸≈2.54釐米,那麼Windows就可以計算出10cm其實就是(320/2.54)*101259.8點。10cm×10cm矩形=1259.8×1259.8點矩形,打印機上完美的顯示了10cm×10cm的矩形沒有任何問題,10cm的長度絕對正確,不信你可以用尺子量!

打印機上可以這樣做,但是顯示器上是不行的,此時Windows給出了一個辦法,當然它也是根據人眼的視覺經驗,即讓顯示器每英寸顯示96點(正常字體),此時96/2.5437.8點,人眼看着很好,但是1cm的長度可能是不真實的。這些不重要,重要的是打印出來絕對正確就好了。 

四、VC++中窗口、視口以及映射模式的應用舉例

例1. 驗證世界窗口的原點和視口的原點位於同一個點,初始時它們與設備座標系的原點重合均在客戶區的左上角。

void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // 繪圖的設備上下文
 CRect Recto;
 CPen PenBlue;
 PenBlue.CreatePen(PS_SOLID, 1, RGB(0, 12, 255));

 //dc.SetViewportOrg(100,100);  // Step 1: 僅加上這一句後可以看出,視口原點的下移和客戶區的下移

 //dc.SetWindowOrg(-100,-100); // Setp 2: 將Step 1註釋掉,加上此句可以得到和Step 1相同的結果
 dc.SelectObject(&PenBlue);
 dc.Ellipse(-100, -100, 100, 100);  //圓心位於屏幕的左上角,僅僅只有圓的四分之一部分(270度到360度的部分)顯示在屏幕上。

 dc.MoveTo(0,0);
 dc.LineTo(100,100);
 CPen PenBlack; 
 PenBlack.CreatePen(PS_SOLID, 1, BLACK_PEN);
 dc.SelectObject(&PenBlack);

 GetClientRect(&Recto); // 得到客戶區域的尺寸,我以前沒看出客戶區的左上角就是視口的原點,而誤以爲是設備座標的原點了。
 dc.MoveTo(Recto.Width() / 2, 0); //dc的移動是參考視口原點畫筆的相對移動,注意這裏視口的原點(也是窗口原點)並沒有移動
 dc.LineTo(Recto.Width() / 2, Recto.Height());
 dc.MoveTo(0, Recto.Height() / 2);
 dc.LineTo(Recto.Width(), Recto.Height() / 2);
}

例2. 驗證世界窗口的原點和視口的原點位於同一個點,默認MM_TEXT映射模式下x軸水平向右,y軸垂直向下。其他固定映射模式的使用

void CDemoView::OnPaint() 
{
CPaintDC dc(this); // device context for painting

 dc.SetMapMode(MM_TEXT); //讀者可以將MM_TEXT換爲MM_LOMETRIC等其他的固定映射模式

 dc.SetViewportOrg(380, 220);
 // Use a red pen
 CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
 dc.SelectObject(PenRed);
 // A circle whose center is at the origin (0, 0)
 dc.Ellipse(-100, -100, 100, 100);
 // Use a blue pen
 CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
 dc.SelectObject(PenBlue);
 // Horizontal axis
 dc.MoveTo(-380, 0);
 dc.LineTo(380, 0);
 // Vertical axis
 dc.MoveTo(0, -220);
 dc.LineTo(0, 220);
 // An orange pen
 CPen PenOrange(PS_SOLID, 1, RGB(255, 128, 0));
 dc.SelectObject(PenOrange);
 // A diagonal line at 45 degrees
 dc.MoveTo(0, 0);
 dc.LineTo(120, 120); //直線沒有在笛卡爾座標系的45度位置,而是位於笛卡爾座標系統的第四象限
}

 例3. 自定義映射模式MM_ISOTROPIC的使用

  void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 dc.SetMapMode(MM_ISOTROPIC);
 dc.SetWindowExt(10240,7680);
 dc.SetViewportExt(1024,768);
 dc.Ellipse(-100,-100,100,100); //以客戶區左上角(0,0)畫出一個半徑爲10pixels的圓
}其本質就是,X方向,每個邏輯單位有1024/10240個象素,Y方向每個邏輯單位有768/7680個象素。因此以下代碼有相同的作用。

void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 dc.SetMapMode(MM_ISOTROPIC);
 dc.SetWindowExt(102400,76800);
 dc.SetViewportExt(10240,7680);
 dc.Ellipse(-100,-100,100,100);
}

例4. 自定義映射模式MM_ANISOTROPIC的使用以左上角爲原點,X軸和Y軸爲1000的座標

void CDemoView::OnDraw(CDC* pDC)
{
 CDemoDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);
 // TODO: add draw code for native data here
 CRect rect;
 GetClientRect(&rect);
 pDC->SetMapMode(MM_ANISOTROPIC);
 //pDC->SetViewportOrg(0,0); //可以註釋掉
 pDC->SetViewportExt(rect.right,rect.bottom);
 pDC->SetWindowOrg(0,0);
 pDC->SetWindowExt(1000,1000);
 
 pDC->MoveTo(50,50);
 pDC->LineTo(50,950);
 pDC->LineTo(950,950);
 pDC->LineTo(50,50);
}

代碼分析:
1. GetClientRect(&rect); 取得客戶區矩形區域,將其存放在rect中
2. 用pDC->SetMapMode(MM_ANISOTROPIC); 設置映射模式
3. 通過pDC->SetViewportOrg(0,0);設置邏輯座標的原點。
4. 通過pDC->SetViewportExt(rect.right,rect.bottom);和 
pDC->SetWindowExt(1000,1000);來確定邏輯座標下和設備座標下的尺寸對應關係
5. 在MM_ANISOTROPIC模式下,X軸單位和Y軸單位可以不相同
6. 座標方向的確定方法是如果邏輯窗範圍和視口範圍符號相同,則邏輯座標的方向和視口的方向相同,即X軸向右爲正,Y軸向下爲正。
7. 如果將顯示模式改爲MM_ISOTROPIC,那麼X軸單位和Y軸單位一定相同,感興趣的讀者可以自己使一下。

例5.繪製點狀網格

void CDemoView::OnPaint() 
{
 CPaintDC dc(this); // device context for painting 
 CRect Recto;
 GetClientRect(&Recto);
 CBrush bgBrush(BLACK_BRUSH); 
 
 dc.SelectObject(bgBrush);
 dc.Rectangle(Recto); 
 for(int x = 0; x < Recto.Width(); x += 20)
 { 
  for(int y = 0; y < Recto.Height(); y += 20)
  { 
   dc.SetPixel(x, y, RGB(255, 255, 255));
  } 
 }
}注意屏幕閃的厲害時,可以添加如下代碼

BOOL CDemoView::OnEraseBkgnd(CDC* pDC) 
{
 return TRUE;
}

閱讀(1727)| 評論(0)
|     
推薦轉載

最近讀者

登錄後,您可以在此留下足跡。
           

評論

點擊登錄|暱稱:
 
 

網易公司版權所有 ©1997-2014


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章