·TextOut函數
TextOut函數的作用是使用系統當前選擇的字體、背景顏色以及正文顏色將一個字符串輸出到指定位置, 其函數的原型如下:
BOOL TextOut( HDC hdc, //設備環境句柄 int nXStart, //字符串開始輸出的x坐標 int nYStart, // 字符串開始輸出的y坐標 LPCTSTR lpString, //需要輸出的字符串 int cbString // 字符串的長度 );
當函數調用成功時返回一個非零的值, 調用失敗時, 返回值為0。
嘗試下用TextOut函數在屏幕上輸出些字符串, 輸出一個4列3行的學生信息。 定義一個結構體, 成員為姓名, 年齡, 住址和聯系電話, 相關的代碼:
#include<windows.h> LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; //聲明窗口過程函數 //待輸出的信息 struct { TCHAR *szName ; TCHAR *szAge ; TCHAR *szAddr ; TCHAR *szTel ; }STU[] = { TEXT("李??"), TEXT("23"), TEXT("上海"), TEXT("123456"), TEXT("王??"), TEXT("18"), TEXT("安徽"), TEXT("654321"), TEXT("孫??"), TEXT("21"), TEXT("浙江"), TEXT("987654") }; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) { static TCHAR szAppName[] = TEXT("PrintText") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass; //窗口類成員屬性 wndclass.lpfnWndProc = WndProc ; wndclass.lpszClassName = szAppName ; wndclass.hInstance = hInstance ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH) ; wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ) ; wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ) ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.lpszMenuName = NULL ; //注冊窗口類 if( !RegisterClass( &wndclass ) ) { MessageBox( NULL, TEXT("錯誤, 窗口注冊失敗!"), TEXT("錯誤"), MB_OK | MB_ICONERROR ) ; return 0 ; } //創建窗口 hwnd = CreateWindow(szAppName, TEXT("TextOut用法示例"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); //顯示窗口 ShowWindow( hwnd, iCmdShow ) ; 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 ; int y = 0 ; //記錄x、y方向坐標 int i ; switch(message) { case WM_PAINT: //處理WM_PAINT消息 hdc = BeginPaint( hwnd, &ps ) ; //輸出信息標題文字 TextOut( hdc, 0, y, TEXT("姓名:"), lstrlen(TEXT("姓名:")) ) ; TextOut( hdc, 0 + 200, y, TEXT("年齡:"), lstrlen(TEXT("年齡:")) ) ; TextOut( hdc, 0 + 400, y, TEXT("住址:"), lstrlen(TEXT("住址:")) ) ; TextOut( hdc, 0 + 600, y, TEXT("聯系電話:"), lstrlen(TEXT("聯系電話:")) ) ; y += 30 ; //y方向坐標+25 //輸出信息 for( i = 0; i < 3; i++ ) { TextOut( hdc, 0, y, STU[i].szName, lstrlen(STU[i].szName) ) ; //(0, 0)處輸出姓名 TextOut( hdc, 0 + 200, y, STU[i].szAge, lstrlen(STU[i].szAge) ) ; //(200, y)處輸出年齡 TextOut( hdc, 0 + 400, y, STU[i].szAddr, lstrlen(STU[i].szAddr) ) ; //(400, y)處輸出住址 TextOut( hdc, 0 + 600, y, STU[i].szTel, lstrlen(STU[i].szTel) ) ; //(600, y)處輸出聯系電話 y += 25 ; //y方向坐標+25, 相當於換一行 } EndPaint( hwnd, &ps ) ; return 0 ; case WM_DESTROY: //處理WM_DESTROY消息 PostQuitMessage(0) ; return 0 ; } return DefWindowProc( hwnd, message, wParam, lParam ) ; }
運行的效果如下:
理清代碼思路:
包含頭文件 -> 聲明窗口過程函數 -> 定義WinMain函數 -> 聲明相關變量 -> 為wndclass窗口類成員賦值 -> 注冊窗口類 -> 建立窗口獲取窗口句柄 -> 顯示窗口 -> 獲取、翻譯、分發消息 -> 定義窗口過程函數。
•關於TextOut中的坐標
在沒有接觸過任何GUI程序設計的情況下, 通常對窗口中的坐標不太了解, 與平時常見的坐標系感覺不太一樣, 在一個窗口的客戶區中, ( 0, 0 )坐標即為窗口客戶區的左上角, 沿着豎直向下方向為y坐標, 水平向右方向為x坐標, 用圖示來表示如下:
在上面的例子中, 看起來一起都很好, 4列3行顯示的整整齊齊, 這是因為我們對坐標的不斷調整才使其顯示成這樣的效果, 在這篇文章的開頭部分已經說明了, TextOut函數是使用系統當前選擇的字體、背景顏色以及正文顏色將一個字符串輸出到指定位置。
要知道, 系統字體是可以根據用戶的需求進行調節, 用戶可以選擇將用戶字體調大或者調小, 當字體調大時我們就應該考慮到兩行文字之間的間距問題, 如果調的很大, 而我們在程序中使用的y方向坐標每行之間的間隔留的過小, 那么兩行文字就會擠在一起造成無法辨認, 當用戶把系統字體調小時如果y方向間距留的過大則影響顯示效果, 那么, 如何才能讓顯示效果達到最好呢?
·獲取系統字體信息
要使顯示的文字能夠根據用戶的屏幕大小以及分辨率自動調整顯示位置以及字體間的間距, 我們就要知道系統字體的字符的高度以及寬度, 還要知道一句話的總寬度, 防止這句話被輸出到窗口外部, 當一行文字因過長輸出的窗口外部, 多出的文字就會被截掉, 這可不是我們想要的結果。
獲取系統當前字體信息的函數為GetTextMetrics, 該函數的原型如下:
BOOL GetTextMetrics( HDC hdc, // 設備環境句柄 LPTEXTMETRIC lptm // 指向一個TEXTMETRIC結構的指針, 該結構用於存放字體信息。 );
如果函數調用成功, 返回值為非零, 如果函數調用失敗, 返回值是0。
關於TEXTMETRIC結構:
TEXTMETRIC結構的成員為當前字體的各種信息, 該結構定義在WINGDI.H頭文件中, 定義如下:
typedef struct tagTEXTMETRIC { LONG tmHeight; //字符高度 LONG tmAscent; //字符上部高度(基線以上) LONG tmDescent; //字符下部高度(基線以下) LONG tmInternalLeading, //由tmHeight定義的字符高度的頂部空間數目 LONG tmExternalLeading, //夾在兩行之間的空間數目 LONG tmAveCharWidth, //平均字符寬度 LONG tmMaxCharWidth, //最寬字符的寬度 LONG tmWeight; //字體的粗細輕重程度 LONG tmOverhang, //加入某些拼接字體上的附加高度 LONG tmDigitizedAspectX, //字體設計所針對的設備水平方向 LONG tmDigitizedAspectY, //字體設計所針對的設備垂直方向 BCHAR tmFirstChar; //為字體定義的第一個字符 BCHAR tmLastChar; //為字體定義的最后一個字符 BCHAR tmDefaultChar; //字體中所沒有字符的替代字符 BCHAR tmBreakChar; //用於拆字的字符 BYTE tmItalic, //字體為斜體時非零 BYTE tmUnderlined, //字體為下划線時非零 BYTE tmStruckOut, //字體被刪去時非零 BYTE tmPitchAndFamily, //字體間距(低4位)和族(高4位) BYTE tmCharSet; //字體的字符集 } TEXTMETRIC;
可以看出, 在TEXTMETRIC結構中共有20位成員, 不過暫時我們只關心前7個成員, 這7個成員為:
LONG tmHeight; //字符高度 LONG tmAscent; //字符上部高度(基線以上) LONG tmDescent; //字符下部高度(基線以下) LONG tmInternalLeading, //由tmHeight定義的字符高度的頂部空間數目 LONG tmExternalLeading, //夾在兩行之間的空間數目 LONG tmAveCharWidth, //平均字符寬度 LONG tmMaxCharWidth, //最寬字符的寬度
在《Windows程序設計》第五版一書中, 用圖示來表示出了字符縱向尺寸的4個值:
其中tmAscent是指字符上部高度(基線Baseline以上), tmDescent為字符下部高度(基線以下), tmHeight的值為tmAscent與tmDescent的和, 表示整個字符的高度, tmInternalLeading為內部間距, 通常用來顯示字母上部的重音符號。
除此之外, TEXTMETRICT結構中的tmExternalLeading用來表示字體設計者建議在兩行文本之間留出的空間大小;
tmAveCharWidth表示小寫字符的加權平均寬度, 大寫字符的加權平均寬度通常按小寫字符的加權平均寬度的1.5倍來計算。
tmMaxCharWidth為字體中最寬字符的寬度。
·使用系統字體信息
在使用系統字體信息之前我們首先要獲取系統字體的信息, 那么, 在何時獲取呢? 通常的做法就是在收到WM_CREATE消息時, WM_CREATE是窗口過程函數接收到的第一條消息, 在這時獲取系統字體消息再好不過了。
首先定義一個TEXTMETRIC結構的對象:
TEXTMETRIC tm ;
然后再獲取設備環境句柄, 並調用GetTextMetrics函數:
hdc = GetDC(hwnd) ; GetTextMetrics( hdc, &tm ) ; ReleaseDC( hwnd, hdc ) ;
這樣, tm中就有了我們需要的信息了。
下面我們嘗試將文章開始部分的代碼簡單的更改為根據系統字體信息的方式進行輸出, 僅改動窗口過程函數部分:
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { HDC hdc ; PAINTSTRUCT ps ; TEXTMETRIC tm ; //記錄系統字體信息 static int cxChar, cyChar ; //系統字體的平均寬度、高度 int y = 0 ; //記錄x、y方向坐標 int i ; switch(message) { case WM_CREATE: //處理WM_CREATE消息 hdc = GetDC( hwnd ) ; //獲取設備環境句柄 GetTextMetrics( hdc, &tm ) ; //獲取系統字體信息 ReleaseDC( hwnd, hdc ) ; //設備環境句柄使用完畢, 釋放 cxChar = tm.tmAveCharWidth ; //得到字體平均寬度 cyChar = tm.tmHeight + tm.tmExternalLeading ; //字體高度, 總高度tmHeight + 兩行文字之間的建議間距大小tmExternalLeading return 0 ; case WM_PAINT: //處理WM_PAINT消息 hdc = BeginPaint( hwnd, &ps ) ; //輸出信息標題文字 TextOut( hdc, 0, y, TEXT("姓名:"), lstrlen(TEXT("姓名:")) ) ; TextOut( hdc, 0 + 200, y, TEXT("年齡:"), lstrlen(TEXT("年齡:")) ) ; TextOut( hdc, 0 + 400, y, TEXT("住址:"), lstrlen(TEXT("住址:")) ) ; TextOut( hdc, 0 + 600, y, TEXT("聯系電話:"), lstrlen(TEXT("聯系電話:")) ) ; y += cyChar ; //y方向增加一個字體高度的單位 //輸出信息 for( i = 0; i < 3; i++ ) { TextOut( hdc, 0, y, STU[i].szName, lstrlen(STU[i].szName) ) ; //(0, 0)處輸出姓名 TextOut( hdc, 0 + 200, y, STU[i].szAge, lstrlen(STU[i].szAge) ) ; //(200, y)處輸出年齡 TextOut( hdc, 0 + 400, y, STU[i].szAddr, lstrlen(STU[i].szAddr) ) ; //(400, y)處輸出住址 TextOut( hdc, 0 + 600, y, STU[i].szTel, lstrlen(STU[i].szTel) ) ; //(600, y)處輸出聯系電話 y += cyChar ; //y方向坐標cyChar, 相當於換一行 } EndPaint( hwnd, &ps ) ; return 0 ; case WM_DESTROY: //處理WM_DESTROY消息 PostQuitMessage(0) ; return 0 ; } return DefWindowProc( hwnd, message, wParam, lParam ) ; }
修改后的代碼運行效果如下:
一些問題:
1>. 上面的代碼中, 我們雖說獲取了字符的平均寬度, 但是我們並沒有去使用它;
2>. 修改后的代碼僅僅是解決了多行文本的顯示問題, 並沒有解決當文本過長時的輸出越界問題;
3>. 當行數過多時, 多出的行數也會被截斷不予顯示。
解決思路:
解決水平方向越界問題: 利用平均字體的寬度計算何時將會越界並進行相應的換行處理;
解決水平方向越界問題: 使用滾動條, 滾動條的使用將在下次的學習中介紹。