雞啄米在上一節(jié)中講了CFont字體類,本節(jié)主要講解文本輸出的方法和實(shí)例。
文本輸出過程
在文本輸出到設(shè)備以前,我們需要確定字體、字體顏色和輸出的文本內(nèi)容等信息。Windows窗口的客戶區(qū)由應(yīng)用程序管理,所以我們還要在應(yīng)用程序中控制輸出文本的格式,例如后續(xù)字符的位置、換行等格式。
由此,文本的輸出過程大致包括確定字體信息、格式化文本和執(zhí)行輸出操作三個(gè)步驟。下面分別講解。
1、確定字體信息
文本在輸出以前應(yīng)該先確定字體信息,或者是當(dāng)前正在使用的字體,或者是自定義的字體,之后就可以根據(jù)確定的字體來顯示文本或者利用字體信息來設(shè)定文本的格式了,例如,我們可以根據(jù)當(dāng)前字體的字符高度來確定下一行字符在什么位置輸出。
自定義字體可以通過CFont類的創(chuàng)建字體的幾個(gè)成員函數(shù)完成。獲取當(dāng)前選擇字體的信息可以使用API函數(shù)GetTextMetrics實(shí)現(xiàn),此函數(shù)的原型如下:
BOOL GetTextMetrics(__in HDC hdc,__out LPTEXTMETRIC lptm);
參數(shù)hdc為設(shè)備上下文的句柄;參數(shù)lptm是指向TEXTMETRIC結(jié)構(gòu)體變量的指針,此結(jié)構(gòu)體變量用于接收字體信息。TEXTMETRIC結(jié)構(gòu)體的定義如下:
- typedef struct tagTEXTMETRIC {
- LONG tmHeight; // 字符高度
- LONG tmAscent; // 字符基線以上的高度
- LONG tmDescent; // 字符基線以下的高度
- LONG tmInternalLeading; // 由tmHeight成員指定的字符高度頂部的空間
- LONG tmExternalLeading; // 行間距
- LONG tmAveCharWidth; // 字符的平均寬度
- LONG tmMaxCharWidth; // 字符的最大寬度
- LONG tmWeight; // 字符的粗度
- LONG tmOverhang; // 合成字體間附加的寬度
- LONG tmDigitizedAspectX; // 為輸出設(shè)備設(shè)計(jì)的x軸尺寸
- LONG tmDigitizedAspectY; // 為輸出設(shè)備設(shè)計(jì)的y軸尺寸
- TCHAR tmFirstChar; // 字體中第一個(gè)字符值
- TCHAR tmLastChar; // 字體中最后一個(gè)字符值
- TCHAR tmDefaultChar; // 替換字體中沒有的字符
- TCHAR tmBreakChar; // 作為分隔符的字符
- BYTE tmItalic; // 非0則表示字體為斜體
- BYTE tmUnderlined; // 非0則表示字體有下劃線
- BYTE tmStruckOut; // 非0則表示字符帶有刪除線
- BYTE tmPitchAndFamily;// 字體間距和字體族
- BYTE tmCharSet; // 字符集
- } TEXTMETRIC, *PTEXTMETRIC;
2、格式化文本
格式化文本一般包括兩種,一種是確定文本行中后續(xù)文本的位置,另一種是確定換行時(shí)下一行文本的位置。
確定后續(xù)文本的位置
一般我們可以先獲取當(dāng)前字符串的寬度,根據(jù)此寬度確定文本行中后續(xù)文本的位置。當(dāng)前字符串的寬度可以通過API函數(shù)GetTextExtentPoint32獲得。GetTextExtentPoint32函數(shù)的原型如下:
BOOL GetTextExtentPoint32(__in HDC hdc,__in LPCTSTR lpString,__in int c,__out LPSIZE lpSize);
參數(shù)hdc為設(shè)備上下文的句柄;參數(shù)lpString為指向文本字符串緩存的指針,此字符串不是必須以結(jié)束符結(jié)尾的,因?yàn)閰?shù)c指定了長(zhǎng)度;參數(shù)c為lpString指向的字符串的長(zhǎng)度;參數(shù)lpSize為指向SIZE結(jié)構(gòu)體變量的指針,此SIZE結(jié)構(gòu)體變量用于接收字符串的寬度和高度信息。SIZE結(jié)構(gòu)體定義如下:
- typedef struct tagSIZE {
- LONG cx; // 寬度
- LONG cy; // 高度
- } SIZE, *PSIZE;
已知本字符串的起始水平坐標(biāo)和寬度,兩者相加即是后續(xù)文本的起始坐標(biāo)。
確定換行時(shí)下一行文本的位置
由GetTextMetrics函數(shù)獲取了當(dāng)前字體的信息并存入TEXTMETRIC結(jié)構(gòu)體后,通過計(jì)算當(dāng)前文本行的垂直坐標(biāo)、當(dāng)前字體的高度和行間距之和,就可以得到換行時(shí)下一行的垂直坐標(biāo)。
3、執(zhí)行文本輸出操作
最后,通過API函數(shù)TextOut執(zhí)行文本輸出操作。TextOut函數(shù)的原型如下:
BOOL TextOut(__in HDC hdc,__in int nXStart,__in int nYStart,__in LPCTSTR lpString,__in int cbString);
參數(shù)hdc為設(shè)備上下文的句柄;參數(shù)nXStart為起始點(diǎn)x坐標(biāo);參數(shù)nYStart為起始點(diǎn)y坐標(biāo);參數(shù)lpString為要輸出的文本字符串;參數(shù)cbString為字符串中要輸出的字符的數(shù)量。
當(dāng)然也可以使用設(shè)備上下文類CDC的成員函數(shù)TextOut來輸出,CDC::TextOut函數(shù)的兩種重載形式如下:
virtual BOOL TextOut(int x,int y,LPCTSTR lpszString,int nCount);
BOOL TextOut(int x,int y,const CString& str);
參數(shù)x指定文本起始點(diǎn)的x坐標(biāo);參數(shù)y指定文本起始點(diǎn)的y坐標(biāo);參數(shù)lpszString為要輸出的文本字符串;參數(shù)nCount指定字符串中的字節(jié)個(gè)數(shù);參數(shù)str為包含要輸出的字符的CString對(duì)象。
字體和文本輸出的應(yīng)用實(shí)例
雞啄米下面給大家演示一個(gè)簡(jiǎn)單的關(guān)于字體和文本輸出的實(shí)例。功能就是實(shí)現(xiàn)兩個(gè)字符串分別在水平方向和垂直方向上定時(shí)滾動(dòng)。實(shí)現(xiàn)步驟如下:
1、創(chuàng)建一個(gè)基于對(duì)話框的MFC工程,名字設(shè)置為“Example48”。
2、在自動(dòng)生成的對(duì)話框模板IDD_EXAMPLE48_DIALOG中,刪除“TODO: Place dialog controls here.”靜態(tài)文本框。
3、在Example48Dlg.h文件中為CExample48類添加成員變量:
- int m_nTextX; // 水平滾動(dòng)文本的起始點(diǎn)的x坐標(biāo)
- int m_nTextY; // 垂直滾動(dòng)文本的起始點(diǎn)的y坐標(biāo)
- CFont m_newFont; // 新字體
- CFont *m_pOldFont; // 選擇新字體之前的字體
4、在CExample48Dlg類的構(gòu)造函數(shù)中,初始化新添加的成員變量:
- CExample48Dlg::CExample48Dlg(CWnd* pParent /*=NULL*/)
- : CDialogEx(CExample48Dlg::IDD, pParent)
- {
- m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
- m_nTextX = 260;
- m_nTextY = 10;
- m_pOldFont = NULL;
- }
5、在CExample48Dlg對(duì)話框初始化函數(shù)中,創(chuàng)建新的字體,并開啟定時(shí)器:
- BOOL CExample48Dlg::OnInitDialog()
- {
- CDialogEx::OnInitDialog();
- // Add "About..." menu item to system menu.
- // IDM_ABOUTBOX must be in the system command range.
- ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
- ASSERT(IDM_ABOUTBOX < 0xF000);
- CMenu* pSysMenu = GetSystemMenu(FALSE);
- if (pSysMenu != NULL)
- {
- BOOL bNameValid;
- CString strAboutMenu;
- bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
- ASSERT(bNameValid);
- if (!strAboutMenu.IsEmpty())
- {
- pSysMenu->AppendMenu(MF_SEPARATOR);
- pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
- }
- }
- // Set the icon for this dialog. The framework does this automatically
- // when the application's main window is not a dialog
- SetIcon(m_hIcon, TRUE); // Set big icon
- SetIcon(m_hIcon, FALSE); // Set small icon
- // TODO: Add extra initialization here
- // 創(chuàng)建一種新的字體(18點(diǎn),隸書)
- m_newFont.CreatePointFont(180, _T("隸書"));
- // 設(shè)置定時(shí)器,定時(shí)時(shí)間為200ms
- SetTimer(1,200,NULL);
- return TRUE; // return TRUE unless you set the focus to a control
- }
6、修改CExample48Dlg::OnPaint()函數(shù),如果窗口沒有最小化就在指定的位置輸出文本,即在OnPaint函數(shù)中if(IsIconic())對(duì)應(yīng)的else大括號(hào)內(nèi)添加相應(yīng)代碼。CExample48Dlg::OnPaint()函數(shù)修改如下:
- void CExample48Dlg::OnPaint()
- {
- if (IsIconic())
- {
- CPaintDC dc(this); // device context for painting
- SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
- // Center icon in client rectangle
- int cxIcon = GetSystemMetrics(SM_CXICON);
- int cyIcon = GetSystemMetrics(SM_CYICON);
- CRect rect;
- GetClientRect(&rect);
- int x = (rect.Width() - cxIcon + 1) / 2;
- int y = (rect.Height() - cyIcon + 1) / 2;
- // Draw the icon
- dc.DrawIcon(x, y, m_hIcon);
- }
- else
- {
- CPaintDC dc(this); // device context for painting
- // 設(shè)置m_newFont對(duì)象的字體為當(dāng)前字體,并將之前的字體指針保存到m_pOldFont
- m_pOldFont = (CFont*)dc.SelectObject(&m_newFont);
- // 設(shè)置
- dc.SetBkMode(TRANSPARENT); //設(shè)置背景為透明!
- // 設(shè)置文本顏色為紅色
- dc.SetTextColor(RGB(255,0,0));
- // 在指定位置輸出文本
- dc.TextOut(m_nTextX,10,_T("歡迎來到雞啄米!"));
- // 設(shè)置文本顏色為綠色
- dc.SetTextColor(RGB(0,255,0));
- // 在指定位置輸出文本
- dc.TextOut(10,m_nTextY,_T("謝謝關(guān)注www.jizhuomi.com"));
- // 恢復(fù)以前的字體
- dc.SelectObject(m_pOldFont);
- CDialogEx::OnPaint();
- }
- }
7、在Class View類視圖中找到CExample48Dlg,右鍵點(diǎn)Properties,顯示出其屬性頁(yè),在屬性頁(yè)工具欄上點(diǎn)擊Messages按鈕,找到WM_TIMER消息,添加消息響應(yīng)函數(shù)CExample48Dlg::OnTimer(UINT_PTR nIDEvent),并在此函數(shù)中修改兩個(gè)文本輸出的坐標(biāo)位置。
- void CExample48Dlg::OnTimer(UINT_PTR nIDEvent)
- {
- // TODO: Add your message handler code here and/or call default
- LOGFONT logFont;
- // 獲取m_newFont字體的LOGFONT結(jié)構(gòu)
- m_newFont.GetLogFont(&logFont);
- // 將m_nTextX的值減5
- m_nTextX -= 5;
- // 如果m_nTextX小于10,則文本“歡迎來到雞啄米”回到起始位置
- if (m_nTextX < 10)
- m_nTextX = 260;
- // 將m_nTextY的值加一個(gè)字符高度
- m_nTextY += abs(logFont.lfHeight);
- // 如果m_nTextY大于260,則文本“謝謝關(guān)注www.jizhuomi.com”回到起始位置
- if (m_nTextY >260)
- m_nTextY = 10;
- // 使窗口客戶區(qū)無效,之后就會(huì)重繪
- Invalidate();
- CDialogEx::OnTimer(nIDEvent);
- }
到這一步,兩個(gè)文本就可以分別在水平和垂直方向滾動(dòng)了。雞啄米再簡(jiǎn)單解釋下這個(gè)過程:程序剛啟動(dòng)時(shí),會(huì)調(diào)用OnPaint函數(shù),在初始位置繪出兩個(gè)文本,然后每次到了定時(shí)器的定時(shí)時(shí)間后,會(huì)執(zhí)行OnTimer函數(shù),修改兩個(gè)文本的坐標(biāo)值,并通過Invalidate使窗口重繪,又會(huì)重新調(diào)用OnPaint函數(shù)繪制兩個(gè)文本。這樣通過定時(shí)修改坐標(biāo)值就實(shí)現(xiàn)了兩個(gè)文本的滾動(dòng)效果。
8、運(yùn)行程序,最終的效果如下圖:

好了,本節(jié)就講到這里了,最后的實(shí)例大家可以自己豐富下它的功能,看看效果。雞啄米謝謝大家的支持。