研究:窗口映射
/**********************************************************************
*
* 這一篇文是以前寫的,但現在看來好像還是沒有透徹理解。一塊內容真的需要反反復復好幾遍才能理解。
*
************************************************************************/
?
對于我們來說,都學過笛卡爾坐標系,無論畫什么圖都首先考慮用笛卡爾坐標系來描述,比如說,我想在x=100,y=200的地方畫一個點,我的腦中想像的是這樣的:
可是,一旦把【想像】的東西畫在程序的客戶區中,就有變化了,如果想得到同樣的點,前提是程序客戶區的坐標系和這一樣,也是笛卡爾坐標系,方向也相同,x軸向右為正,y軸向上為正。
一個點可能未必那么明顯,假設想讓別人看到三個點的圖像,(這三個點的圖像是想像的,也是想讓別人看到原樣的圖像),比如:
三個點的坐標,假設為(100,200),(200,300),(300,400),(不改變設備任何屬性)用API的設點函數,代碼:
(圈住的就是點),由這個圖可以看出,走形了,已經不是想讓別人看到的那個圖。這是為什么呢?其實就是因為客戶區的坐標系和習慣用的笛卡爾坐標系不同了。客戶區的坐標系是下面這樣的:
這樣一來,就會導致出現上面“走形”的圖像。矛盾就出現在這兒,我們習慣于笛卡爾坐標系來描述一幅圖,而windows用的是這種坐標系,接下來會考慮怎樣把windows的這種坐標系轉個方向變成笛卡爾坐標系,適應自己的習慣!
這里就出現了映射模式,在默認情況下,客戶區的坐標參考系為上圖所示,名稱為MM_TEXT,X軸向右為正,Y軸向下為正,除此之外,還有其它幾種映射模式。
現在,將客戶區的坐標系改成笛卡爾坐標系,SetMapMode(hdc,MM_LOMETRIC),單位為0.1mm。得到下面這種樣式:
這樣就得到了自己習慣的坐標系,接下來,會出現這樣一個問題,假如說,我想畫一個(類似)正弦的弧線,下圖所示,我想從X的負坐標開始(這些都是習慣),可是上圖所示的坐標系的原點是在客戶區的左上角,還是不太習慣,能不能把這個坐標原點移到中間,把上面和左邊都留出一定的空隙,這樣看起來更適應習慣。
于是,又出現一個函數,可以將這個原點進行移動。
如上圖所示,把坐標原點從客戶區左上角移到客戶區的(100,150)的位置,經過截圖軟件測距,觀察得到的確實是x=100,y=150,這兩個值是設備單位,也就是像素。
代碼:
這里采用的函數是:SetViewportOrgEx(),經過上面兩步,首先將坐標系改變,接著再將坐標原點移動,目的只有一個:得到一個適應自己習慣的坐標系。
有了這個坐標系之后,就可以在上面進行操作,但是這個坐標系的邏輯單位是0.1mm,假如畫一條線,代碼寫100,其實是1厘米。
//這一段尤其重要,當轉換映射模式后,在繪圖函數中,它默認的是當前映射模式下的單位,比如,當前是MM_LOMETRIC,則畫線函數中采用的400,是以0.1mm為單位,而不是以像素為單位
打算從坐標原點開始畫兩條直線,已經具有了笛卡爾坐標系,這樣【想像】的是什么,畫出來的就是原樣的外觀。
MoveToEx(hdc,0,0,NULL); LineTo(hdc,300,0); MoveToEx(hdc,0,0,NULL);LineTo(hdc,0,400);//y軸400
一個完整的示例:
代碼(注意:用win32應用程序創建一個典型的"hello world"模板。)
?
// 正弦.cpp : Defines the entry point for the application. // #include "stdafx.h" #include "resource.h" #include "math.h" #define PI 3.1415 #define MAX_LOADSTRING 100// Global Variables: HINSTANCE hInst; // current instance TCHAR szTitle[MAX_LOADSTRING]; // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; // The title bar text// Foward declarations of functions included in this code module: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) {// TODO: Place code here. MSG msg;HACCEL hAccelTable;// Initialize global strings LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadString(hInstance, IDC_MY, szWindowClass, MAX_LOADSTRING);MyRegisterClass(hInstance);// Perform application initialization:if (!InitInstance (hInstance, nCmdShow)) {return FALSE;}hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_MY);// Main message loop:while (GetMessage(&msg, NULL, 0, 0)) {if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {TranslateMessage(&msg);DispatchMessage(&msg);}}return msg.wParam; } // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // // COMMENTS: // // This function and its usage is only necessary if you want this code // to be compatible with Win32 systems prior to the 'RegisterClassEx' // function that was added to Windows 95. It is important to call this function // so that the application will get 'well formed' small icons associated // with it. // ATOM MyRegisterClass(HINSTANCE hInstance) {WNDCLASSEX wcex;wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc = (WNDPROC)WndProc;wcex.cbClsExtra = 0;wcex.cbWndExtra = 0;wcex.hInstance = hInstance;wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_MY);wcex.hCursor = LoadCursor(NULL, IDC_ARROW);wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName = (LPCSTR)IDC_MY;wcex.lpszClassName = szWindowClass;wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);return RegisterClassEx(&wcex); } // // FUNCTION: InitInstance(HANDLE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) {HWND hWnd;hInst = hInstance; // Store instance handle in our global variablehWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return TRUE; } // // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {int wmId, wmEvent;PAINTSTRUCT ps;HDC hdc;TCHAR szHello[MAX_LOADSTRING];LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);static HPEN hpen;double y,r;int x;switch (message) {case WM_CREATE:hpen=CreatePen(PS_SOLID,6,RGB(0,0,0));return 0;case WM_COMMAND:wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections:switch (wmId){case IDM_ABOUT:DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}break;case WM_PAINT:hdc = BeginPaint(hWnd, &ps);SelectObject(hdc,hpen);SetMapMode(hdc,MM_LOMETRIC);//映射模式改變SetViewportOrgEx(hdc,100,150,NULL); //視區原點改變 for(x=-60;x<600;x++){r=x/((double)60*2)*PI;y=sin(r)*2*60;MoveToEx(hdc,(int)x,(int)y,NULL);LineTo(hdc,(int)x,(int)y);}MoveToEx(hdc,-240,0,NULL);//橫向線LineTo(hdc,680,0);MoveToEx(hdc,0,400,NULL);//縱向線LineTo(hdc,0,-380);EndPaint(hWnd, &ps);break;case WM_DESTROY:DeleteObject(hpen);PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0; }// Mesage handler for about box. LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {switch (message){case WM_INITDIALOG:return TRUE;case WM_COMMAND:if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {EndDialog(hDlg, LOWORD(wParam));return TRUE;}break;}return FALSE; }繼續新的問題:
考慮這樣一種情況,在MM_LOMETRIC的映射模式下(不改變坐標原點),我想從坐標原點處開始畫一條線直接到達窗口的最底端。如圖所示(現在研究左邊的一條線):
這條線尾的特點是,X不變(固定一個值,比如360),而y也就是客戶區的高度可以自由伸縮。
要實現這個功能,首先要獲取窗口的高度,采用GetClientRect函數。但問題出在這了,這個函數返回的是客戶區的矩形區域,它是設備坐標還是邏輯坐標呢?書上的意思是,它返回的是設備坐標,而設備坐標是像素。我這個畫線程序里面的360是邏輯坐標(0.1mm為單位),不匹配,所以要將設備坐標轉換為邏輯坐標。
這就算是DPtoLP函數的一個來歷。
這樣一來,兩者就匹配了。
LineTo(hdc,360,pt.y);但是,在這里又出現一個問題,MM_LOMETRIC的坐標系和笛卡爾坐標系一樣,現在LineTo(hdc,360,pt.y),這個pt.y看起來像是一個正數,x和y都為正數的點怎么會落在用戶區中呢?如果一個東西只有一個解釋,那就是唯一的解釋,那就是這個pt.y只能是個負數才合理。驗證輸出pt.y發現它確實是個負數。
DPtoLP將用戶區的高度進行了兩個功能的轉換,第一,根據坐標方向,轉換正負值,第二,將設備高轉換為邏輯高。
整理一下這個過程,假設用戶區沒有做任何映射轉換,就是MM_TEXT,x向右為正,y向下為正。通過GetClientRect函數得到的高度是像素為單位。
接著,轉換用戶區的映射方式,變成MM_LOMETRIC,通過GetClientRect函數獲取高度,則依然是像素,說明,GetClientRect是以設備坐標為單位(像素)返回用戶區高度。
既然都為正,那就說明問題出在DPtoLP這個函數頭上,它的工作要取決于當前的映射方式,當它發現是MM_LOMETRIC的映射模式時,就將這個高度轉換為負值的以0.1mm為單位的邏輯坐標。這樣,一切就合理了。根據后文一個粗略的近似計算:16像素約等于5.6mm,我這個程序的窗口約694像素,兩者一換算:2429(0.1mm為單位),而程序輸出經過DPtoLP轉換后的高度為:-2444,幾乎相等,這樣就證明了。
得到下面兩條結論:
一、GetClientRect返回的是用戶區的矩形高度(設備坐標,像素為單位),和方向無關,總是用設備單位。
二、DPtoLP函數會根據當前的映射方式將設備坐標轉換為邏輯坐標,正如上面所示,不僅改單位,還改方向。
代碼示例:
?
//--------------------DPtoLP函數的研究例子---------------// #include <windows.h> #include "stdio.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; void paint(); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int nCmd) {static TCHAR szAppName[] = TEXT ("HelloWin") ;HWND hwnd ; //窗口句柄MSG msg ; //消息結構WNDCLASS wndclass ; //窗口類結構wndclass.style = CS_HREDRAW | CS_VREDRAW ;wndclass.lpfnWndProc = WndProc ;wndclass.cbClsExtra = 0 ;wndclass.cbWndExtra = 0 ;wndclass.hInstance = hInstance ;wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;//加載圖標供程序使用wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; //加載鼠標指針供程序使用wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;//獲取一個圖形對象,在這個例子中,是獲取繪制窗口背景的刷子wndclass.lpszMenuName = NULL ;wndclass.lpszClassName = szAppName ;if (!RegisterClass (&wndclass))//為程序窗口注冊窗口類 {return 0 ;}//根據窗口類創建一個窗口hwnd = CreateWindow (szAppName, TEXT ("一個簡單的Win32程序"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, SW_SHOWMAXIMIZED) ; //在屏幕上顯示窗口UpdateWindow (hwnd) ; //指示窗口刷新自身while (GetMessage (&msg, NULL, 0, 0)) //從消息隊列中獲取消息 {TranslateMessage (&msg) ; //轉換某些鍵盤消息DispatchMessage (&msg) ; //將消息發送給窗口過程 }return msg.wParam ; }LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {HDC hdc ;PAINTSTRUCT ps ;RECT rect ;POINT pt;char a[20],b[20],c[20];switch (message){ case WM_PAINT:hdc = BeginPaint (hwnd, &ps) ; //開始窗口繪制GetClientRect (hwnd, &rect) ;//轉換映射模式前,默認為MM_TEXTpt.y=rect.bottom;//返回用戶區高度sprintf(a,"MM_TEXT's height:%d",pt.y);TextOut(hdc,350,0,a,strlen(a));//轉換映射模式為MM_LOMETRIC SetMapMode(hdc,MM_LOMETRIC);GetClientRect (hwnd, &rect) ; //獲取窗口客戶區的尺寸pt.y=rect.bottom;//高度pt.x=rect.right;//寬度 sprintf(b,"MM_LOMETRIC's height:%d",pt.y);//沒有被DPtoLP轉換前TextOut(hdc,0,0,b,strlen(b));DPtoLP(hdc,(LPPOINT)&pt,2);sprintf(c,"MM_LOMETRIC's height:%d",pt.y);//被DPtoLP轉換后TextOut(hdc,0,-250,c,strlen(c));LineTo(hdc,360,pt.y);MoveToEx(hdc,360,pt.y,NULL);LineTo(hdc,pt.x,-90);EndPaint (hwnd, &ps) ; //結束窗口繪制return 0 ;case WM_DESTROY:PostQuitMessage (0) ; //在消息隊列中插入一條“退出”消息return 0 ;}return DefWindowProc (hwnd, message, wParam, lParam);//執行默認的消息處理 }題目:以毫米為單位在用戶區中輸出(上下相鄰)兩行字符串,如何解?
如果打印兩行字符串,需要在行間保持(一定的)距離才不會重疊或者行距太大。
第一個問題,如何知道字體的高度呢?根據文本顯示的研究,字體的高度存放在TEXTMETRIC這個結構中,通過創建DC得到這個高度,接著的問題是,如果按照默認映射模式,也即MM_TEXT模式,得到的是映射模式的邏輯單位,換句話說,假如不改變映射模式,得出的高度為16(象素)。
16是象素,但是現在需要以毫米為單位,顯然這個16不符合要求,需要把映射模式改為MM_LOMETRIC,于是得到邏輯單位以0.1mm為單位,再去取TEXTMETRIC中的高度,得到56。
計算:
同樣的字體,象素為單位=16,0.1mm為單位=56,于是16象素=5.6mm。
通過計算得知,在以MM_LOMETRIC的映射模式下,字體的高度為56(即5.6毫米),問題已經基本得到解決。
在MM_LOMETRIC的映射模式下,x向右,y向上, 假如第一行字串起點為:(100,-100),則第二行字串的起點為(100,-100-56)。
同樣,如果轉換為MM_TEXT模式,在相同的地方輸出,則第一行字串轉換為(28,28),可以覆蓋,稍有點誤差。
代碼如下:
?
轉載于:https://www.cnblogs.com/tinaluo/p/5389636.html
總結
- 上一篇: pip 使用总结
- 下一篇: 五:二叉树中和为某一直的路径