1. 程式人生 > >windows程式設計(8):對映模式

windows程式設計(8):對映模式

什麼是對映模式呢?為了說清楚這個概念,我們先介紹兩個名詞:“視窗”、“視口”。

視口是基於裝置座標的。對於顯示器,就是畫素的,也就是你看到的。而視窗是基於邏輯座標的,虛擬的,也是你寫程式時使用的。而且與你當前拿到的裝置描述表有關,一般通過beginPaint拿到的都是客戶區;而使用getDC拿到的則是通常意義下的視窗:客戶區+選單欄+工具欄+標題欄等等。

而視窗到視口的座標對映,就是對映模式。用數學公式表述為:

視窗到視口:

xViewport = (xWindow-xWinOrg)*xViewExt/xWinExt+xViewOrg

yViewport = (yWindow-yWinOrg)*yViewExt/yWinExt+yViewOrg

其中 xViewExt表示的是視口的橫座標範圍,xWinExt表示的是視窗的橫座標範圍,通常我們關心的不是它們的具體值,而是二者的比例。

windows提供了8種對映模式

對映方式

邏輯單位

增加值

x值

y值

MM_TEXT

圖素

MM_LOMETRIC

0.1 mm

MM_HIMETRIC

0.01 mm

MM_LOENGLISH

0.01 in.

MM_HIENGLISH

0.001 in.

MM_TWIPS

1/1440 in.

MM_ISOTROPIC

任意(x = y)

可選

可選

MM_ANISOTROPIC

任意(x != y)

可選

可選

這8種模式通常分為3大類:MM_TEXT、“度量”對映方式、“自作主張”的對映方式。下面對他們一一進行介紹:

MM_TEXT
之所以稱為MM_TEXT,是因為這個方式在預設情況下與我們看書的方式相同:原點在左上角,從左向右,從上到下。

對於MM_TEXT對映方式,內定的原點和範圍如下所示:

視窗原點:(0, 0) 可以改變

視口原點:(0, 0) 可以改變

視窗範圍:(1, 1) 不可改變

視口範圍:(1, 1) 不可改變

這意味著視窗座標與邏輯座標之間沒有比例變換,只有原點設定引起的平移變換。所以它也稱為稱為“全約束”的對映方式。你在視窗位置中+1,則在螢幕上移動一個畫素。

你可以通過SetViewportOrgEx和SetWindowOrgEx,用來改變視口和視窗的原點,但一般我們只改變一個就能達到滿意的效果,兩個同時改變則容易產生混亂。

通過GetViewportOrgEx和GetWindowOrgEx來獲取目前視埠和視窗的原點。

case WM_PAINT:
		hdc = BeginPaint(hwnd,&ps);

		
		//畫一條直線
		MoveToEx	(hdc,0,			cyClient/2,NULL);
		LineTo		(hdc,cxClient,	cyClient/2);
		//繪製正弦曲線
		for(int i = 0;  i< NUM;i++)
		{
			//把x軸等分成1000份
			apt[i].x = i * cxClient / NUM;
			apt[i].y = (int) (cyClient / 2 * (1-sin(TWOPI * i /NUM)));
			LineTo(hdc,apt[i].x,apt[i].y);
			//Sleep(10);
			
		}

		

		EndPaint(hwnd,&ps);
		return 0;

我們可以改變視口的原點:

	case WM_PAINT:
		hdc = BeginPaint(hwnd,&ps);
		//設定視口原點		
		SetViewportOrgEx(hdc,cxClient/2,cyClient/2,NULL);


		
		//畫一條直線
		MoveToEx	(hdc,0,			cyClient/2,NULL);
		LineTo		(hdc,cxClient,	cyClient/2);
		//繪製正弦曲線
		for(int i = 0;  i< NUM;i++)
		{
			//把x軸等分成1000份
			apt[i].x = i * cxClient / NUM;
			apt[i].y = (int) (cyClient / 2 * (1-sin(TWOPI * i /NUM)));
			LineTo(hdc,apt[i].x,apt[i].y);
			//Sleep(10);
			
		}

		

		EndPaint(hwnd,&ps);
		return 0;


可以預見到,將視口的原點設成了客戶區的中間,而我在畫圖時,第一條直線是從原點下面半個客戶區的範圍畫的,所以直線剛好看不到。而正弦曲線也只能看到一半。

通過更改視口的原點,還有一個重要的應用,就是做出字幕“滾動”的效果:

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	//字元的寬度,大寫字母寬度,字元高度
	static int    cxChar, cxCaps, cyChar, iBegin;
	//視窗大小
	static int cxClient, cyClient ;

	//滾動條位置
	static int iVertPos,iHorzPos,iPaintBeg,iPaintEnd;
	HDC hdc;
	//該變數用於索引sysmets.h中定義的結構體陣列sysmetrics[]的每個元素
	int i;
	//輸出文字的位置
	int x,y;
	//繪圖結構
	PAINTSTRUCT ps;

	//字串
	TCHAR szBuffer [10];
	//字型資訊結構
	TEXTMETRIC  tm;
	switch(message)
	{
	case WM_CREATE:
		SetTimer(hwnd,ID_TIMER,20,NULL);
		hdc = GetDC(hwnd);
		//取得內定系統字型的文字大小,存在放在tm裡
		GetTextMetrics (hdc, &tm);
		//平均字元寬
		cxChar = tm.tmAveCharWidth ;
		//大寫字母的平均寬度
		cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
		//字元總高度:高度+行間距
		cyChar = tm.tmHeight + tm.tmExternalLeading ;

		ReleaseDC(hwnd,hdc);

		return 0;
	case WM_TIMER:
		iBegin++;
		InvalidateRect(hwnd,NULL,TRUE);

	case WM_SIZE:
		cxClient = LOWORD (lParam) ;        
		cyClient = HIWORD (lParam) ;   
		return 0; 
		
	case WM_PAINT:
		hdc = BeginPaint (hwnd, &ps) ;
		//不斷地改變原點
		SetWindowOrgEx(hdc,0,iBegin,NULL);

		for(i = 0; i<NUMLINES;i++)
		{
			TextOut(hdc,0,cyChar*i,sysmetrics[i].szLabel,lstrlen(sysmetrics[i].szLabel));
			TextOut(hdc,22 * cxChar,cyChar*i,sysmetrics[i].szDesc,lstrlen(sysmetrics[i].szDesc));
			SetTextAlign(hdc,TA_RIGHT | TA_TOP);
			TextOut(hdc,22*cxCaps + 40 * cxChar,cyChar * i,szBuffer,wsprintf(szBuffer,TEXT("%5d"),GetSystemMetrics(sysmetrics[i].Index)));
			SetTextAlign(hdc,TA_LEFT | TA_TOP);
		}
		
		EndPaint (hwnd, &ps) ;
		return 0;
		

	case  WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc (hwnd, message, wParam, lParam) ;

}

“度量”對映方式

具體包含一下5種:

映像方式

邏輯單位

英寸

毫米

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

之所以成為“度量”,因為它們使用的是實際的尺寸。

 通常我們感興趣的是視窗和視口的轉換因子。

在MM_LOMETRIC模式下,xViewExt/xWinExt表示每水平方向上每0.01英寸中的畫素數,這是系統的一個值。在Win7系統中,控制面板->顯示->設定自定義文字大小中,當選擇為100%時,每英寸有96個畫素。所以比例為:96/100。可以看出,轉換因子是不變的。

有一點需要特別注意,就是這幾種模式的座標系相當於把笛卡爾座標系的原點搬到了左上角,意味著你看到的y軸都是負的。比如:

SetMapMode (hdc, MM_LOENGLISH) ;
        
TextOut (hdc, 100, -100, "Hello", 5)


將把文字顯示在距離顯示區域左邊和上邊各一英寸的地方。


“自作主張的”的對映方式

剩下對映方式只有MM_ISOTROPIC和MM_ANISOTROPIC。它們可以改變視口到視窗的轉換因子。
MM_ISOTROPIC表示各向同性,表明水平和垂直方向的轉換因子是相同的。這意味著,如果你在客戶區上畫一個矩形,隨著客戶區的變大變小,矩形也會變大變小,而且矩形的長寬比是不變的。
MM_ANISOTROPIC表示各向異性,表明水平和垂直方向的轉換因子是不同的。我們下面具體討論一下這兩種方式:

MM_ISOTROPIC
當您剛開始將映像方式設定為MM_ISOTROPIC時,Windows使用與MM_LOMETRIC同樣的視窗和視埠範圍。(注意y軸是負的)

您可以用所期望的邏輯視窗的邏輯尺寸作為SetWindowExtEx的引數,用客戶區的實際寬和高作為SetViewportExtEx的引數。Windows在調整這些範圍時,必須讓邏輯視窗適應實際視窗,這就有可能導致客戶區的一段落到了邏輯視窗的外面。所以必須在呼叫SetViewportExtEx之前呼叫SetWindowExtEx。

MM_ANISOTROPIC

在MM_ISOTROPIC映像方式下設定視窗和視埠範圍時,Windows會調整範圍,以便兩條軸上的邏輯單位具有相同的實際尺度。在MM_ANISOTROPIC對映方式下,Windows不對您所設定的值進行調整,這就是說,MM_ANISOTROPIC不需要維持正確的縱橫比。

 這裡舉一個例子:

#define LOGWIDE 4000
#define LOGHIGH 3000

	case WM_PAINT:
		hdc = BeginPaint(hwnd,&ps);

		//設定對映模式
		
		SetMapMode(hdc,MM_ANISOTROPIC);
		//SetWindowExtEx(hdc,1,1,NULL);		
		//SetViewportExtEx(hdc,1,-1,NULL);
		SetWindowExtEx(hdc,LOGWIDE,LOGHIGH,NULL);

		SetViewportExtEx(hdc,cxClient,-cyClient,NULL);

		SetViewportOrgEx(hdc,0,cyClient /2,NULL);
		//畫一條直線
		MoveToEx	(hdc,0,			0,NULL);
		LineTo		(hdc,LOGWIDE,	0);
		MoveToEx	(hdc,0,			0,NULL);
		//繪製正弦曲線
		//for(int i = 0;  i< NUM;i++)
		for( i = 0;  i< LOGWIDE;i++)
		{
			//把x軸等分成1000份
			//apt[i].x = i * cxClient / NUM;
			apt[i].x = i;
			//apt[i].y = (int) (cyClient / 2 * (sin(TWOPI * i /NUM)));
			apt[i].y = (int) (LOGHIGH / 2 * sin(TWOPI * i /LOGWIDE));
			LineTo(hdc,apt[i].x,apt[i].y);
			//Sleep(10);
			
		}		

		EndPaint(hwnd,&ps);
		return 0;


視窗範圍為(4000,,3000),而視口範圍(1,1).這是什麼意思呢?我們可以從兩個方面理解:

1.比例關係:4000:客戶區大小(比如800)意味著每5個邏輯單位對映為1個畫素

2.在邏輯座標下選一個範圍(寬*高),裝置座標下也有一個範圍(客戶區的寬*高),視窗範圍裡的東西嚴格按比例對映到視口。這個理解方法也是我們通常使用的。

想到這裡,我覺得微軟設計的這個視窗是很好用的。舉一個例子,比如股市軟體。我們可以儲存每一天的資訊,然後當我們需要分析哪一段時間之後,我們可以選定一個視窗範圍,這個範圍內的資料是我們需要的那一段,然後把它展示出來。這時我不要考慮具體是怎麼展示的,系統會通過對映模式自動幫你把視窗的內容轉化到視口上顯示出來。