如需改變標題(Caption)時,使之換行可以用c++轉義符\n
vc++中不能改變單個控件的字體,可以使用位圖改變
應用程序的一般組成:應用程序對象和窗口對象。其中沒個MFC程序必須有一個應用程序對象,負責啟動和終止應用程序。
文檔類:用于管理應用程序的數據
視圖類:用于顯示應用程序的數據
組件:專用的,自包含的窗口對象,它們通常是構成用戶界面的元素。也稱為控件。
組件必須包含在其他的窗口中,把該窗口成為斧窗口。所以控件也叫做子窗口控件。
MFC的六種嵌入窗口家族:
CStatic--用于顯示文本或圖標控件
CButton--按鈕、復選框、單選按鈕、組框
CListBox--顯示項目滾動列表的控件
CComboBox--顯示可縮回的項目列表的控件
CScrollBar--水平或垂直滾動條
CEdit--單行或多行文本編輯控件
資源:特殊形式的只讀數據,由一個叫做資源編譯器的程序將之聯編到可執行程序中。
資源的兩種基本形式:
二進制--圖形資源,包括圖標、光標和位圖
文本--結構化的資源,包括對話框、菜單、字符串表和加速鍵表。
二進制資源存儲在一個獨立的文件中,文本資源存儲在普通的ASCII文本文件中,該文件描述了每個元素的結構。該ASSCII文件稱為資源描述文件--通常與你的項目同名,其擴展名為.rc
DECLARE_MESSAGE_MAP()該宏告訴VisualC++這個類將響應Windows消息,該入口以及在實現文件中的相應映射入口,是用于建立消息映射的高級宏系統的部分。消息映射保證Windows消息被交給正確的成員函數。
#include "stdafx.h"是所有MFC程序都要用到的標準應用程序框架頭文件。它引入用語標準MFC組件、大多數通用擴展以及Internet Explorer4常用控件的定義。
正式版和調試版的轉換:使用Build|Set Active Configuration.
構造函數和InitInstance()函數:
構造函數是在對象創建是調用的。InitInstance()是在WinMain()調用時重載的。所以夠找函數是在WinMain()被調用前調用的。此時,很多MFC系統還沒有完成自身的初始化,只有在主應用程序對象被構造完畢,WinMain()才會被調用。
在一個API應用程序中,WinMain()函數有三個任務,它必須完成:
1、注冊一個新的主窗口類
2、創建一個窗口的實例并顯示它
3、運行消息循環
InitInstance()函數為MFC的WinMain()函數執行以上內容的第二項內容。
關于CWinApp
MFC中CWinApp類定義的關鍵幾個虛函數或可重載函數
1、InitInstance()肯定是要被重載的
2、Run()掃描消息并處理消息。
3、OnIdlc(),當Run()在沒有消息要處理事就要調用該函數。OnIdle()可利用該機會執行后臺任務,否則該后臺任務會降低系統反應速度。
4、ExitInstance(),當一個應用程序結束時由Run()進行調用。
基于CDialog的窗口是以局部變量的形式在棧中創建的,而基于CWnd的窗口是以動態變量的形式在自由存儲器(freestore)中創建的 。
故有對于基于CDialog類:
CFourUpdlg dlg;
m_pMainWnd=&dlg;
對于基于CWnd的類:
m_pMainWnd=new CMainWindow;
CFourUpDlg::CFourUpDlg(CWnd* pParent /*=NULL*/)
: CDialog(CFourUpDlg::IDD, pParent)
構造函數中CFourUpDlg::IDD的 IDD是個枚舉量(定義在頭文件中),包含對話框模板的資源ID(包含在資源描述中)。表示CDialog構造函數從對話框模板中讀出信息,然后構造一個窗口,在構造過程使用的是由模板給出的規范。為了創建對話框窗口(并且所有的控件都包含在對話框模板中),CDialog構造函數使用CWnd::Create()或CWnd::CreateEx()函數,對主對話框調用該函數一次并對它碰到的每個控件都調用一次。
CWnd:Create()函數
原型:
virtual BOOL Create(LPCTSTR lpszClassName,LPCTSTR lpszWindowName,DWORD dwStyle,const RECT & rect,UINT nID,CCreateContext * pContext=NULL);
在Windows中,每個窗口都分成兩大部分:非客戶區(包括標題條,窗口邊界)和客戶區。
其中Windows負責非客戶區的繪制
每當Windows想繪制一個窗口的客戶區時,Windows便向該窗口發送WM_PAINT消息,通常由OnPaint()成員函數。
OnPaint()大致分為:
1.獲取畫布或繪制平面,在Windows中,使用的繪制平面稱為設備環境(device context,DC).
2.建立環境,包括收集所有需要的畫筆和畫刷,并測量工作平面的大小,以便能夠正確地在改平面上對其圖案.
3.用Windows圖形庫GDI中地函數繪制窗口
只可以在OnPaint()中創建CPaintDC對象.
需要在其他地方繪制時用CClientDC
調用CWnd::Invalidate()可以重畫整個窗口.
畫筆:用來畫線或圖形邊框的顏色
刷子:用于填充圖形或繪制窗口背景的顏色
庫存對象(stock object):Windows提供的幾種嵌入式的畫筆和刷子.可以通過SelectStockObject()選擇.
定時器創建:SetTimer(),它有三個函數:
1.定時器ID--區別不同的定時器實例.
2.定時器間隔--最大分辨率55毫秒
3.定時器回調函數--特殊的回調函數的地址,該函數用于處理定時器消息.如果值為NULL,Windows將向你通知WM_TIMER消息.
在基于對話框的應用程序中,OnInitDialog()函數是最佳的創建定時器的地方.如果創建成功,SetTimer()函數返回的定時器ID于所使用的函數相同.
刪除定時器:
KillTimer()向之傳遞構造定時器所使用的ID.定時器是個受限的全局資源.當用完定時器后,調KillTimer()是個必須的過程.通常可以相應WM_DESTORY消息時處理KillTimer()函數
定時器的相應:
當計數器溢出時,產生一個WM_TIMER消息,通常在OnTimer()處理WM_TIMER消息
創建畫筆:
第一種:使用構造函數,如:CPen greePen(PS_SOLID,10,RGB(0,255,0));
第二種:兩步法
CPen greenPen;
greePen.CreatePen(PS_SOLID,10,RGB(0,255,0));
第三中:創建LOGPEN結構的實例來構造
LOGPEN lp;
lp.lopnstyle=PS_SDLID;
lp.lopnWidth=10;
lp.lopnColor=RGB(0,255,0);
Cpen greenPen;
GreenPen.CreatePenIndirect(&lp);
fopen
打開文件或者 URL。
語法: int fopen(string filename, string mode);
返回值: 整數
函數種類: 文件存取
說明: 本函數可用來打開本地或者遠端的文件。若參數 filename 為 "http://......" 則本函數利用 HTTP 1.0 協議與服務器連接,文件指針則指到服務器返回文件的起始處。若參數 filename 為 "ftp://......." 則本函數會與服務器連接,文件指針指到指定的文件處。若 FTP 服務器沒有支持被動模式 (passive mode ftp) 則返回失敗值。打開的 FTP 文件可以是讀取或寫入其中之一,但不能讀或寫二種同時使用。其它的情形,本函數打開本地的文件,文件的指針則指向打開的文件。若開文件失敗,則返回 false 值。
字符串參數 mode 可以是下列的情形:
- 'r' 開文件方式為只讀,文件指針指到開始處。
- 'r+' 開文件方式為可讀寫,文件指針指到開始處。
- 'w' 開文件方式為寫入,文件指針指到開始處,并將原文件的長度設為 0。若文件不存在,則建立新文件。
- 'w+' 開文件方式為可讀寫,文件指針指到開始處,并將原文件的長度設為 0。若文件不存在,則建立新文件。
- 'a' 開文件方式為寫入,文件指針指到文件最后。若文件不存在,則建立新文件。
- 'a+' 開文件方式為可讀寫,文件指針指到文件最后。若文件不存在,則建立新文件。
- 'b' 若操作系統的文字及二進位文件不同,則可以用此參數,UNIX 系統不需要使用本參數。
第一行為 UNIX 系統使用;第二行是 Windows 系列系統的用法;第三、四行則為 URL 的使用范例。
<? $fp = fopen("/home/rasmus/file.txt", "r"); $fp = fopen("c:\\mydata\\info.txt", "r"); $fp = fopen("http://www.php.net/", "r"); $fp = fopen("ftp://user:password@my.com/", "w"); ?>
fclose() popen() fsockopen()
|
---- 方法一:調用CWinApp類的成員函數SetDialogBkColor來實現。
---- 其中函數的第一個參數指定了背景顏色,第二個參數指定了文本顏色。下面的例子是將應用程序對話框設置為藍色背景和紅色文本,步驟如下:
---- ① 新建一個基于Dialog的MFC AppWizard應用程序ExampleDlg。
---- ② 在CExampleDlgApp ::InitInstance()中添加如下代碼:
BOOL CExampleDlgApp: : InitInstance ( )
{
...
CExampleDlgDlg dlg;
m_pMainWnd = &dlg;
//先于DoModal()調用,將對話框設置為藍色背景、紅色文本
SetDialogBkColor(RGB(0,0,255),RGB(255,0,0));
int nResponse = dlg.DoModal();
...
}
---- 編譯并運行,此時對話框的背景色和文本色已發生了改變。值得注意的是:在調用DoModal()之前必須先調用SetDialogBkColor,且此方法是將改變應用程序中所有的對話框顏色,并不能針對某一個指定的對話框。
---- 方法二:重載OnPaint(),即WM_PAINT消息。有關代碼如下(以上例工程為準):
void CExampleDlgDlg::OnPaint()
{
if (IsIconic())
...
else
{
CRect rect;
CPaintDC dc(this);
GetClientRect(rect);
dc.FillSolidRect(rect,RGB(0,255,0)); //設置為綠色背景
CDialog::OnPaint();
}
---- 方法三:重載OnCtlColor (CDC* pDC, CWnd* pWnd, UINT nCtlColor),即WM_CTLCOLOR消息。具體步驟如下(以上例工程為準):
---- ①在CExampleDlgDlg的頭文件中,添加一CBrush的成員變量:
class CExampleDlgDlg : public CDialog
{
...
protected:
CBrush m_brush;
...
};
---- ②在OnInitDialog()函數中添加如下代碼:
BOOL CExampleDlgDlg::OnInitDialog()
{
...
// TODO: Add extra initialization here
m_brush.CreateSolidBrush(RGB(0, 255, 0)); // 生成一綠色刷子
...
}
---- ③利用ClassWizard重載OnCtlColor(...),即WM_CTLCOLOR消息:
HBRUSH CExampleDlgDlg::OnCtlColor
(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
/*
** 這里不必編寫任何代碼!
**下行代碼要注釋掉
** HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
*/
return m_brush; //返加綠色刷子
}
---- 方法四:還是重載OnCtlColor (CDC* pDC, CWnd* pWnd, UINT nCtlColor),即WM_CTLCOLOR消息。具體步驟如下(以上例工程為準):
---- 步驟①、②同上方法三中的步驟①、②。
---- 步驟③利用ClassWizard重載OnCtlColor(...)(即WM_CTLCOLOR消息)時則有些不同:
HBRUSH CExampleDlgDlg::OnCtlColor
(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
//在這加一條是否為對話框的判斷語句
if(nCtlColor ==CTLCOLOR_DLG)
return m_brush; //返加綠色刷子
return hbr;
}
---- 編譯并運行即可。
- MFC和Win32
- MFC Object和Windows Object的關系
MFC中最重要的封裝是對Win32 API的封裝,因此,理解Windows Object和MFC Object (C++對象,一個C++類的實例)之間的關系是理解MFC的關鍵之一。所謂Windows Object(Windows對象)是Win32下用句柄表示的Windows操作系統對象;所謂MFC Object (MFC對象)是C++對象,是一個C++類的實例,這里(本書范圍內)MFC Object是有特定含義的,指封裝Windows Object的C++ Object,并非指任意的C++ Object。
MFC Object 和Windows Object是不一樣的,但兩者緊密聯系。以窗口對象為例:
一個MFC窗口對象是一個C++ CWnd類(或派生類)的實例,是程序直接創建的。在程序執行中它隨著窗口類構造函數的調用而生成,隨著析構函數的調用而消失。而Windows窗口則是Windows系統的一個內部數據結構的實例,由一個“窗口句柄”標識,Windows系統創建它并給它分配系統資源。Windows窗口在MFC窗口對象創建之后,由CWnd類的Create成員函數創建,“窗口句柄”保存在窗口對象的m_hWnd成員變量中。Windows窗口可以被一個程序銷毀,也可以被用戶的動作銷毀。MFC窗口對象和Windows窗口對象的關系如圖2-1所示。其他的Windows Object和對應的MFC Object也有類似的關系。
下面,對MFC Object和Windows Object作一個比較。有些論斷對設備描述表(MFC類是CDC,句柄是HDC)可能不適用,但具體涉及到時會指出。
- 從數據結構上比較
MFC Object是相應C++類的實例,這些類是MFC或者程序員定義的;
Windows Object是Windows系統的內部結構,通過一個句柄來引用;
MFC給這些類定義了一個成員變量來保存MFC Object對應的Windows Object的句柄。對于設備描述表CDC類,將保存兩個HDC句柄。
- 從層次上講比較
MFC Object是高層的,Windows Object是低層的;
MFC Object封裝了Windows Object的大部分或全部功能,MFC Object的使用者不需要直接應用Windows Object的HANDLE(句柄)使用Win32 API,代替它的是引用相應的MFC Object的成員函數。
- 從創建上比較
MFC Object通過構造函數由程序直接創建;Windows Object由相應的SDK函數創建。
MFC中,使用這些MFC Object,一般分兩步:
首先,創建一個MFC Object,或者在STACK中創建,或者在HEAP中創建,這時,MFC Object的句柄實例變量為空,或者說不是一個有效的句柄。
然后,調用MFC Object的成員函數創建相應的Windows Object,MFC的句柄變量存儲一個有效句柄。
CDC(設備描述表類)的創建有所不同,在后面的2.3節會具體說明CDC及其派生類的創建和使用。
當然,可以在MFC Object的構造函數中創建相應的Windows對象,MFC的GDI類就是如此實現的,但從實質上講,MFC Object的創建和Windows Object的創建是兩回事。
- 從轉換上比較
可以從一個MFC Object得到對應的Windows Object的句柄;一般使用MFC Object的成員函數GetSafeHandle得到對應的句柄。
可以從一個已存在的Windows Object創建一個對應的MFC Object; 一般使用MFC Object的成員函數Attach或者FromHandle來創建,前者得到一個永久性對象,后者得到的可能是一個臨時對象。
- 從使用范圍上比較
MFC Object對系統的其他進程來說是不可見、不可用的;而Windows Object一旦創建,其句柄是整個Windows系統全局的。一些句柄可以被其他進程使用。典型地,一個進程可以獲得另一進程的窗口句柄,并給該窗口發送消息。
對同一個進程的線程來說,只可以使用本線程創建的MFC Object,不能使用其他線程的MFC Object。
- 從銷毀上比較
MFC Object隨著析構函數的調用而消失;但Windows Object必須由相應的Windows系統函數銷毀。
設備描述表CDC類的對象有所不同,它對應的HDC句柄對象可能不是被銷毀,而是被釋放。
當然,可以在MFC Object的析構函數中完成Windows Object的銷毀,MFC Object的GDI類等就是如此實現的,但是,應該看到:兩者的銷毀是不同的。
每類Windows Object都有對應的MFC Object,下面用表格的形式列出它們之間的對應關系,如表2-1所示:
表2-1 MFC Object和Windows Object的對應關系
描述
|
Windows句柄
|
MFC Object
|
窗口
|
HWND
|
CWnd and CWnd-derived classes
|
設備上下文
|
HDC
|
CDC and CDC-derived classes
|
菜單
|
HMENU
|
CMenu
|
筆
|
HPEN
|
CGdiObject類,CPen和CPen-derived classes
|
刷子
|
HBRUSH
|
CGdiObject類,CBrush和CBrush-derived classes
|
字體
|
HFONT
|
CGdiObject類,CFont和CFont-derived classes
|
位圖
|
HBITMAP
|
CGdiObject類,CBitmap和CBitmap-derived classes
|
調色板
|
HPALETTE
|
CGdiObject類,CPalette和CPalette-derived classes
|
區域
|
HRGN
|
CGdiObject類,CRgn和CRgn-derived classes
|
圖像列表
|
HimageLIST
|
CimageList和CimageList-derived classes
|
套接字
|
SOCKET
|
CSocket,CAsynSocket及其派生類
|
表2-1中的OBJECT分以下幾類:
Windows對象,
設備上下文對象,
GDI對象(BITMAP,BRUSH,FONT,PALETTE,PEN,RGN),
菜單,
圖像列表,
網絡套接字接口。
從廣義上來看,文檔對象和文件可以看作一對MFC Object和Windows Object,分別用CDocument類和文件句柄描述。
后續幾節分別對前四類作一個簡明扼要的論述。
- Windows Object
用SDK的Win32 API編寫各種Windows應用程序,有其共同的規律:首先是編寫WinMain函數,編寫處理消息和事件的窗口過程WndProc,在WinMain里頭注冊窗口(Register Window),創建窗口,然后開始應用程序的消息循環。
MFC應用程序也不例外,因為MFC是一個建立在SDK API基礎上的編程框架。對程序員來說所不同的是:一般情況下,MFC框架自動完成了Windows登記、創建等工作。
下面,簡要介紹MFC Window對Windows Window的封裝。
- Windows的注冊
一個應用程序在創建某個類型的窗口前,必須首先注冊該“窗口類”(Windows Class)。注意,這里不是C++類的類。Register Window把窗口過程、窗口類型以及其他類型信息和要登記的窗口類關聯起來。
- “窗口類”的數據結構
“窗口類”是Windows系統的數據結構,可以把它理解為Windows系統的類型定義,而Windows窗口則是相應“窗口類”的實例。Windows使用一個結構來描述“窗口類”,其定義如下:
typedef struct _WNDCLASSEX {
UINT cbSize; //該結構的字節數
UINT style; //窗口類的風格
WNDPROC lpfnWndProc; //窗口過程
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance; //該窗口類的窗口過程所屬的應用實例
HICON hIcon; //該窗口類所用的像標
HCURSOR hCursor; //該窗口類所用的光標
HBRUSH hbrBackground; //該窗口類所用的背景刷
LPCTSTR lpszMenuName; //該窗口類所用的菜單資源
LPCTSTR lpszClassName; //該窗口類的名稱
HICON hIconSm; //該窗口類所用的小像標
} WNDCLASSEX;
從“窗口類”的定義可以看出,它包含了一個窗口的重要信息,如窗口風格、窗口過程、顯示和繪制窗口所需要的信息,等等。關于窗口過程,將在后面消息映射等有關章節作詳細論述。
Windows系統在初始化時,會注冊(Register)一些全局的“窗口類”,例如通用控制窗口類。應用程序在創建自己的窗口時,首先必須注冊自己的窗口類。在MFC環境下,有幾種方法可以用來注冊“窗口類”,下面分別予以討論。
- 調用AfxRegisterClass注冊
AfxRegisterClass函數是MFC全局函數。AfxRegisterClass的函數原型:
BOOL AFXAPI AfxRegisterClass(WNDCLASS *lpWndClass);
參數lpWndClass是指向WNDCLASS結構的指針,表示一個“窗口類”。
首先,AfxRegisterClass檢查希望注冊的“窗口類”是否已經注冊,如果是則表示已注冊,返回TRUE,否則,繼續處理。
接著,調用::RegisterClass(lpWndClass)注冊窗口類;
然后,如果當前模塊是DLL模塊,則把注冊“窗口類”的名字加入到模塊狀態的域m_szUnregisterList中。該域是一個固定長度的緩沖區,依次存放模塊注冊的“窗口類”的名字(每個名字是以“\n\0”結尾的字符串)。之所以這樣做,是為了DLL退出時能自動取消(Unregister)它注冊的窗口類。至于模塊狀態將在后面第9章詳細的討論。
最后,返回TRUE表示成功注冊。
- 調用AfxRegisterWndClass注冊
AfxRegisterWndClass函數也是MFC全局函數。AfxRegisterWndClass的函數原型:
LPCTSTR AFXAPI AfxRegisterWndClass(UINT nClassStyle,
HCURSOR hCursor, HBRUSH hbrBackground, HICON hIcon)
參數1指定窗口類風格;
參數2、3、4分別指定該窗口類使用的光標、背景刷、像標的句柄,缺省值是0。
此函數根據窗口類屬性動態地產生窗口類的名字,然后,判斷是否該類已經注冊,是則返回窗口類名;否則用指定窗口類的屬性(窗口過程指定為缺省窗口過程),調用AfxRegisterCalss注冊窗口類,返回類名。
動態產生的窗口類名字由以下幾部分組成(包括冒號分隔符):
如果參數2、3、4全部為NULL,則由三部分組成。
“Afx”+“:”+模塊實例句柄”+“:”+“窗口類風格”
否則,由六部分組成:
“Afx”+“:”+模塊實例句柄+“:”+“窗口類風格”+“:”+光標句柄+“:”+背景刷句柄+“:”+像標句柄。比如:“Afx:400000:b:13de:6:32cf”。
該函數在MFC注冊主邊框或者文檔邊框“窗口類”時被調用。具體怎樣用在5.3.3.3節會指出。
- 隱含的使用MFC預定義的的窗口類
MFC4.0以前的版本提供了一些預定義的窗口類,4.0以后不再預定義這些窗口類。但是,MFC仍然沿用了這些窗口類,例如:
用于子窗口的“AfxWnd”;
用于邊框窗口(SDI主窗口或MDI子窗口)或視的“AfxFrameOrView”;
用于MDI主窗口的“AfxMDIFrame”;
用于標準控制條的“AfxControlBar”。
這些類的名字就 是“AfxWnd”、“AfxFrameOrView”、“AfxMdiFrame”、 “AfxControlBar”加上前綴和后綴(用來標識版本號或是否調試版等)。它們使用標準應用程序像標、標準文檔像標、標準光標等標準資源。為了使用這些“窗口類”,MFC會在適當的時候注冊這些類:或者要創建該類的窗口時,或者創建應用程序的主窗口時,等等。
MFC內部使用了函數
BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)
來幫助注冊上述原MFC版本的預定義“窗口類”。參數fClass區分了那些預定義窗口的類型。根據不同的類型,使用不同的窗口類風格、窗口類名字等填充WndClass的域,然后調用AfxRegisterClass注冊窗口類。并且注冊成功之后,通過模塊狀態的m_fRegisteredClasses記錄該窗口類已經注冊,這樣該模塊在再次需要注冊這些窗口類之前可以查一下m_fRegisteredClasses,如果已經注冊就不必浪費時間了。為此,MFC內部使用宏
AfxDeferRegisterClass(short fClass)
來注冊“窗口類”,如果m_fRegisteredClasses記錄了注冊的窗口類,返回TRUE,否則,調用AfxEndDeferRegisterClass注冊。
注冊這些窗口類的例子:
MFC在加載邊框窗口時,會自動地注冊“AfxFrameOrView”窗口類。在創建視時,就會使用該“窗口類”創建視窗口。當然,如果創建視窗口時,該“窗口類”還沒有注冊,MFC將先注冊它然后使用它創建視窗口。
不過,MFC并不使用”AfxMDIFrame”來創建MDI主窗口,因為在加載主窗口時一般都指定了主窗口的資源,MFC使用指定的像標注冊新的MDI主窗口類(通過函數AfxRegisterWndClass完成,因此“窗口類”的名字是動態產生的)。
MDI子窗口類似于上述MDI主窗口的處理。
在MFC創建控制窗口時,如工具欄窗口,如果“AfxControlBar”類還沒有注冊,則注冊它。注冊過程很簡單,就是調用::InitCommonControl加載通用控制動態連接庫。
- 調用::RegisterWndClass。
直接調用Win32的窗口注冊函數::RegisterWndClass注冊“窗口類”,這樣做有一個缺點:如果是DLL模塊,這樣注冊的“窗口類”在程序退出時不會自動的被取消注冊(Unregister)。所以必須記得在DLL模塊退出時取消它所注冊的窗口類。
- 子類化
子類化(Subclass)一個“窗口類”,可自動地得到它的“窗口類”屬性。
- MFC窗口類CWnd
在Windows系統里,一個窗口的屬性分兩個地方存放:一部分放在“窗口類”里頭,如上所述的在注冊窗口時指定;另一部分放在Windows Object本身,如:窗口的尺寸,窗口的位置(X,Y軸),窗口的Z軸順序,窗口的狀態(ACTIVE,MINIMIZED,MAXMIZED,RESTORED…),和其他窗口的關系(父窗口,子窗口…),窗口是否可以接收鍵盤或鼠標消息,等等。
為了表達所有這些窗口的共性,MFC設計了一個窗口基類CWnd。有一點非常重要,那就是CWnd提供了一個標準而通用的MFC窗口過程,MFC下所有的窗口都使用這個窗口過程。至于通用的窗口過程卻能為各個窗口實現不同的操作,那就是MFC消息映射機制的奧秘和作用了。這些,將在后面有關章節詳細論述。
CWnd提供了一系列成員函數,或者是對Win32相關函數的封裝,或者是CWnd新設計的一些函數。這些函數大致如下。
(1)窗口創建函數
這里主要討論函數Create和CreateEx。它們封裝了Win32窗口創建函數::CreateWindowEx。Create的原型如下:
BOOL CWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext)
Create是一個虛擬函數,用來創建子窗口(不能創建桌面窗口和POP UP窗口)。CWnd的基類可以覆蓋該函數,例如邊框窗口類等覆蓋了該函數以實現邊框窗口的創建,視類則使用它來創建視窗口。
Create調用了成員函數CreateEx。CWnd::CreateEx的原型如下:
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
CreateEx有11個參數,它將調用::CreateWindowEx完成窗口的創建,這11個參數對應地傳遞給::CreateWindowEx。參數指定了窗口擴展風格、“窗口類”、窗口名、窗口大小和位置、父窗口句柄、窗口菜單和窗口創建參數。
CreateEx的處理流程將在后面4.4.1節討論窗口過程時分析。
窗口創建時發送WM_CREATE消息,消息參數lParam指向一個CreateStruct結構的變量,該結構有11個域,其描述見后面4.4.1節對窗口過程的分析,Windows使用和CreateEx參數一樣的內容填充該變量。
(2)窗口銷毀函數
例如:
DestroyWindow函數 銷毀窗口
PostNcDestroy( ),銷毀窗口后調用,虛擬函數
(3)用于設定、獲取、改變窗口屬性的函數,例如:
SetWindowText(CString tiltle) 設置窗口標題
GetWindowText() 得到窗口標題
SetIcon(HICON hIcon, BOOL bBigIcon);設置窗口像標
GetIcon( BOOL bBigIcon ) ;得到窗口像標
GetDlgItem( int nID);得到窗口類指定ID的控制子窗口
GetDC(); 得到窗口的設備上下文
SetMenu(CMenu *pMenu); 設置窗口菜單
GetMenu();得到窗口菜單
…
(4)用于完成窗口動作的函數
用于更新窗口,滾動窗口,等等。一部分成員函數設計成或可重載(Overloaded)函數,或虛擬(Overridden)函數,或MFC消息處理函數。這些函數或者實現了一部分功能,或者僅僅是一個空函數。如:
SendMessage( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );
給窗口發送發送消息,立即調用方式
PostMessage(( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );
給窗口發送消息,放進消息隊列
…
MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );
移動窗口到指定位置
ShowWindow(BOOL );顯示窗口,使之可見或不可見
….
virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam ); 窗口過程,虛擬函數
virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );處理命令消息
…
OnCreate( LPCREATESTRUCT lpCreateStruct );MFC窗口消息處理函數,窗口創建時由MFC框架調用
OnClose();MFC窗口消息處理函數,窗口創建時由MFC框架調用
…
CWnd的導出類是類型更具體、功能更完善的窗口類,它們繼承了CWnd的屬性和方法,并提供了新的成員函數(消息處理函數、虛擬函數、等等)。
常用的窗口類及其層次關系見圖1-1。
- 在MFC下創建一個窗口對象
MFC下創建一個窗口對象分兩步,首先創建MFC窗口對象,然后創建對應的Windows窗口。在內存使用上,MFC窗口對象可以在棧或者堆(使用new創建)中創建。具體表述如下:
- 創建MFC窗口對象。通過定義一個CWnd或其派生類的實例變量或者動態創建一個MFC窗口的實例,前者在棧空間創建一個MFC窗口對象,后者在堆空間創建一個MFC窗口對象。
- 調用相應的窗口創建函數,創建Windows窗口對象。
例如:在前面提到的AppWizard產生的源碼中,有CMainFrame(派生于CMDIFrame(SDI)或者CMDIFrameWnd(MDI))類。它有兩個成員變量定義如下:
CToolBar m_wndToolBar;
CStatusBar m_wndStatusBar;
當創建CMainFrame類對象時,上面兩個MFC Object也被構造。
CMainFrame還有一個成員函數
OnCreate(LPCREATESTRUCT lpCreateStruct),
它的實現包含如下一段代碼,調用CToolBar和CStatusBar的成員函數Create來創建上述兩個MFC對象對應的工具欄HWND窗口和狀態欄HWND窗口:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
…
if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
…
}
關于工具欄、狀態欄將在后續有關章節作詳細討論。
在MFC中,還提供了一種動態創建技術。動態創建的過程實際上也如上所述分兩步,只不過MFC使用這個技術是由框架自動地完成整個過程的。通常框架窗口、文檔框架窗口、視使用了動態創建。介于MFC的結構,CFrameWnd和CView及其派生類的實例即使不使用動態創建,也要用new在堆中分配。理由見窗口的銷毀(2.2.5節)。
至于動態創建技術,將在下一章具體討論。
在Windows窗口的創建過程中,將發送一些消息,如:
在創建了窗口的非客戶區(Nonclient area)之后,發送消息WM_NCCREATE;
在創建了窗口的客戶區(client area)之后,發送消息WM_CREATE;
窗口的窗口過程在窗口顯示之前收到這兩個消息。
如果是子窗口,在發送了上述兩個消息之后,還給父窗口發送WM_PARENATNOTIFY消息。其他類或風格的窗口可能發送更多的消息,具體參見SDK開發文檔。
- MFC窗口的使用
MFC提供了大量的窗口類,其功能和用途各異。程序員應該選擇哪些類來使用,以及怎么使用他們呢?
直接使用MFC提供的窗口類或者先從MFC窗口類派生一個新的C++類然后使用它,這些在通常情況下都不需要程序員提供窗口注冊的代碼。是否需要派生新的C++類,視MFC已有的窗口類是否能滿足使用要求而定。派生的C++類繼承了基類的特性并改變或擴展了它的功能,例如增加或者改變對消息、事件的特殊處理等。
主要使用或繼承以下一些MFC窗口類(其層次關系圖見圖1-1):
框架類CFrameWnd,CMdiFrameWnd;
文檔框架CMdiChildWnd;
視圖CView和CView派生的有特殊功能的視圖如:列表CListView,編輯CEditView,樹形列表CTreeView,支持RTF的CRichEditView,基于對話框的視CFormView等等。
對話框CDialog。
通常,都要從這些類派生應用程序的框架窗口和視窗口或者對話框。
工具條CToolBar
狀態條CStatusBar
其他各類控制窗口,如列表框CList,編輯框CEdit,組合框CComboBox,按鈕Cbutton等。
通常,直接使用這些類。
- 在MFC下窗口的銷毀
窗口對象使用完畢,應該銷毀。在MFC下,一個窗口對象的銷毀包括HWND窗口對象的銷毀和MFC窗口對象的銷毀。一般情況下,MFC編程框架自動地處理了這些。
(1)對CFrameWnd和CView的派生類
這些窗口的關閉導致銷毀窗口的函數DestroyWindow被調用。銷毀Windows窗口時,MFC框架調用的最后一個成員函數是OnNcDestroy函數,該函數負責Windows清理工作,并在最后調用虛擬成員函數PostNcDestroy。CFrameWnd和CView的PostNcDestroy調用delete this刪除自身這個MFC窗口對象。
所以,對這些窗口,如前所述,應在堆(Heap)中分配,而且,不要對這些對象使用delete操作。
(2)對Windows Control窗口
在它們的析構函數中,將調用DestroyWidnow來銷毀窗口。如果在棧中分配這樣的窗口對象,則在超出作用范圍的時候,隨著析構函數的調用,MFC窗口對象和它的Windows window對象都被銷毀。如果在堆(Heap)中分配,則顯式調用delete操作符,導致析構函數的調用和窗口的銷毀。
所以,這種類型的窗口應盡可能在棧中分配,避免用額外的代碼來銷毀窗口。如前所述的CMainFrame的成員變量m_wndStatusBar和m_wndToolBar就是這樣的例子。
(3)對于程序員直接從CWnd派生的窗口
程序員可以在派生類中實現上述兩種機制之一,然后,在相應的規范下使用。
后面章節將詳細的討論應用程序退出時關閉、清理窗口的過程。
- 設備描述表
- 設備描述表概述
當一個應用程序使用GDI函數時,必須先裝入特定的設備驅動程序,然后為繪制窗口準備設備描述表,比如指定線的寬度和顏色、刷子的樣式和顏色、字體、剪裁區域等等。不像其他Win32結構,設備描述表不能被直接訪問,只能通過系列Win32函數來間接地操作。
如同Windows“窗口類”一樣,設備描述表也是一種Windows數據結構,用來描述繪制窗口所需要的信息。它定義了一個坐標映射模式、一組GDI圖形對象及其屬性。這些GDI對象包括用于畫線的筆,繪圖、填圖的刷子,位圖,調色板,剪裁區域,及路徑(Path)。
表2-2列出了設備描述表的結構和各項缺省值,表2-3列出了設備描述表的類型,表2-4顯示設備描述表的類型。
表2-2 設備描述表的結構
屬性
|
缺省值
|
Background color
|
Background color setting from Windows Control Panel (typically, white)
|
Background mode
|
OPAQUE
|
Bitmap
|
None
|
Brush
|
WHITE_BRUSH
|
Brush origin
|
(0,0)
|
Clipping region
|
Entire window or client area with the update region clipped, as appropriate. Child and pop-up windows in the client area may also be clipped
|
Palette
|
DEFAULT_PALETTE
|
Current pen position
|
(0,0)
|
Device origin
|
Upper left corner of the window or the client area
|
Drawing mode
|
R2_COPYPEN
|
Font
|
SYSTEM_FONT (SYSTEM_FIXED_FONT for applications written to run with Windows versions 3.0 and earlier)
|
Intercharacter spacing
|
0
|
Mapping mode
|
MM_TEXT
|
Pen
|
BLACK_PEN
|
Polygon-fill mode
|
ALTERNATE
|
Stretch mode
|
BLACKONWHITE
|
Text color
|
Text color setting from Control Panel (typically, black)
|
Viewport extent
|
(1,1)
|
Viewport origin
|
(0,0)
|
Window extent
|
(1,1)
|
Window origin
|
(0,0)
|
表2-3 設備描述表的分類
Display
|
顯示設備描述表,提供對視頻顯示設備上的繪制操作的支持
|
Printer
|
打印設備描述表,提供對打印機、繪圖儀設備上的繪制操作的支持
|
Memory
|
內存設備描述表,提供對位圖操作的支持
|
Information
|
信息設備描述表,提供對操作設備信息獲取的支持
|
表2-3中的顯示設備描述表又分三種類型,如表2-4所示。
表2-4 顯示設備描述表的分類
名稱
|
特點
|
功能
|
Class Device
Contexts
|
提供對Win16的向后兼容
|
|
Common
Device
Contexts
|
在Windows系統的高速緩沖區,數量有限
|
Applicaion獲取設備描述表時,Windows用缺省值初始化該設備描述表,Application使用它完成繪制操作,然后釋放
|
Private
Device
Contexts
|
沒有數量限制,用完不需釋放一次獲取,多次使用
|
多次使用過程中,每次設備描述表屬性的任何修改或變化都會被保存,以支持快速繪制
|
(1)使用設備描述表的步驟
要使用設備描述表,一般有如下步驟:
- 獲取或者創建設備描述表;
- 必要的話,改變設備描述表的屬性;
- 使用設備描述表完成繪制操作;
- 釋放或刪除設備描述表。
Common設備描述表通過::GetDC,::GetDCEx,::BeginPaint來獲得一個設備描述表,用畢,用::ReleaseDC或::EndPaint釋放設備描述表;
Printer設備描述表通過::CreateDC創建設備描述表,用::DeleteDC刪除設備描述表。
Memory設備描述表通過::CreateCompatibleDC創建設備描述表,用::DeleteDC刪除。
Information設備描述表通過::CreateIC創建設備描述表,用::DeleteDC刪除。
(2)改變設備描述表屬性的途徑
要改變設備描述表的屬性,可通過以下途徑:
用::SelectObject選入新的除調色板以外的GDI Object到設備描述表中;
對于調色板,使用::SelectPalette函數選入邏輯調色板,并使用::RealizePalette把邏輯調色板的入口映射到物理調色板中。
用其他API函數改變其他屬性,如::SetMapMode改變映射模式。
- 設備描述表在MFC中的實現
MFC提供了CDC類作為設備描述表類的基類,它封裝了Windows的HDC設備描述表對象和相關函數。
- CDC類
CDC類包含了各種類型的Windows設備描述表的全部功能,封裝了所有的Win32 GDI 函數和設備描述表相關的SDK函數。在MFC下,使用CDC的成員函數來完成所有的窗口繪制工作。
CDC 類的結構示意圖2-2所示。
CDC類有兩個成員變量:m_hDC,m_hAttribDC,它們都是Windows設備描述表句柄。CDC的成員函數作輸出操作時,使用m_Hdc;要獲取設備描述表的屬性時,使用m_hAttribDC。
在創建一個CDC類實例時,缺省的m_hDC等于m_hAttribDC。如果需要的話,程序員可以分別指定它們。例如,MFC框架實現CMetaFileDC類時,就是如此:CMetaFileDC從物理設備上讀取設備信息,輸出則送到元文件(metafile)上,所以m_hDC和m_hAttribDC是不同的,各司其責。還有一個類似的例子:打印預覽的實現,一個代表打印機模擬輸出,一個代表屏幕顯示。
CDC封裝::SelectObject(HDC hdc,HGDIOBJECT hgdiobject)函數時,采用了重載技術,即它針對不同的GDI對象,提供了名同而參數不同的成員函數:
SelectObject(CPen *pen)用于選入筆;
SelectObject(CBitmap* pBitmap)用于選入位圖;
SelectObject(CRgn *pRgn)用于選入剪裁區域;
SelectObject(CBrush *pBrush)用于選入刷子;
SelectObject(CFont *pFont)用于選入字體;
至于調色板,使用SelectPalette(CPalette *pPalette,BOOL bForceBackground )選入調色板到設備描述表,使用RealizePalletter()實現邏輯調色板到物理調色板的映射。
- 從CDC派生出功能更具體的設備描述表
從CDC 派生出四個功能更具體的設備描述表類。層次如圖2-3所示。

下面,分別討論派生出的四種設備描述表。
代表窗口客戶區的設備描述表。其構造函數CClientDC(CWnd *pWin)通過::GetDC獲取指定窗口的客戶區的設備描述表HDC,并且使用成員函數Attach把它和CClientDC對象捆綁在一起;其析構函數使用成員函數Detach把設備描述表句柄HDC分離出來,并調用::ReleaseDC釋放設備描述表HDC。
僅僅用于響應WM_PAINT消息時繪制窗口,因為它的構造函數調用了::BeginPaint獲取設備描述表HDC,并且使用成員函數Attach把它和CPaintDC對象捆綁在一起;析構函數使用成員函數Detach把設備描述表句柄HDC分離出來,并調用::EndPaint釋放設備描述表HDC,而::BeginPaint和::EndPaint僅僅在響應WM_PAINT時使用。
用于生成元文件。
代表整個窗口區(包括非客戶區)的設備描述表。其構造函數CWindowDC(CWnd *pWin)通過::GetWindowDC獲取指定窗口的客戶區的設備描述表HDC,并使用Attach把它和CWindowDC對象捆綁在一起;其析構函數使用Detach把設備描述表HDC分離出來,調用::ReleaseDC釋放設備描述表HDC。
- MFC設備描述表類的使用
- 使用CPaintDC、CClientDC、CWindowDC的方法
首先,定義一個這些類的實例變量,通常在棧中定義。然后,使用它。
例如,MFC中CView對WM_PAINT消息的實現方法如下:
void CView::OnPaint()
{
// standard paint routine
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}
在棧中定義了CPaintDC類型的變量dc,隨著構造函數的調用獲取了設備描述表;設備描述表使用完畢,超出其有效范圍就被自動地清除,隨著析構函數的調用,其獲取的設備描述表被釋放。
如果希望在堆中創建,例如
CPaintDC *pDC;
pDC = new CPaintDC(this)
則在使用完畢時,用delete刪除pDC:
delete pDC;
- 直接使用CDC
需要注意的是:在生成CDC對象的時候,并不像它的派生類那樣,在構造函數里獲取相應的Windows設備描述表。最好不要使用::GetDC等函數來獲取一個設備描述表,而是創建一個設備描述表。其構造函數如下:
CDC::CDC()
{
m_hDC = NULL;
m_hAttribDC = NULL;
m_bPrinting = FALSE;
}
其析構函數如下:
CDC::~CDC()
{
if (m_hDC != NULL)
::DeleteDC(Detach());
}
在CDC析構函數中,如果設備描述表句柄不空,則調用DeleteDC刪除它。這是直接使用CDC時最好創建Windows設備描述表的理由。如果設備描述表不是創建的,則應該在析構函數被調用前分離出設備描述表句柄并用::RealeaseDC釋放它,釋放后m_hDC為空,則在析構函數調用時不會執行::DeleteDC。當然,不用擔心CDC的派生類的析構函數調用CDC的析構函數,因為CDC::~CDC()不是虛擬析構函數。
直接使用CDC的例子是內存設備上下文,例如:
CDC dcMem; //聲明一個CDC對象
dcMem.CreateCompatibleDC(&dc); //創建設備描述表
pbmOld = dcMem.SelectObject(&m_bmBall);//更改設備描述表屬性
…//作一些繪制操作
dcMem.SelectObject(pbmOld);//恢復設備描述表的屬性
dcMem.DeleteDC(); //可以不調用,而讓析構函數去刪除設備描述表
- GDI對象
在討論設備描述表時,已經多次涉及到GDI對象。這里,需強調一下:GDI對象要選入Windows 設備描述表后才能使用;用畢,要恢復設備描述表的原GDI對象,并刪除該GDI對象。
一般按如下步驟使用GDI對象:
Create or get a GDI OBJECT hNewGdi;
hOldGdi = ::SelectObject(hdc, hNewGdi)
……
::SelectObject(hdc, hOldGdi)
::DeleteObject(hNewGdi)
先創建或得到一個GDI對象,然后把它選入設備描述表并保存它原來的GDI對象;用畢恢復設備描述表原來的GDI對象并刪除新創建的GDI對象。
需要指出的是,如果hNewGdi是一個Stock GDI對象,可以不刪除(刪除也可以)。通過
HGDIOBJ GetStockObject(
int fnObject // type of stock object
);
來獲取Stock GDI對象。
- MFC GDI對象
MFC用一些類封裝了Windows GDI對象和相關函數,層次結構如圖2-4所示:
CGdiObject封裝了Windows GDI Object共有的特性。其派生類在繼承的基礎上,主要封裝了各類GDI的創建函數以及和具體GDI對象相關的操作。
CGdiObject的構造函數僅僅讓m_hObject為空。如果m_hObject不空,其析構函數將刪除對應的Windows GDI對象。MFC GDI對象和Windows GDI對象的關系如圖2-5所示。
- 使用MFC GDI類的使用
首先創建GDI對象,可分一步或兩步創建。一步創建就是構造MFC對象和Windows GDI對象一步完成;兩步創建則先構造MFC對象,接著創建Windows GDI對象。然后,把新創建的GDI對象選進設備描述表,取代原GDI對象并保存。最后,恢復原GDI對象。例如:
void CMyView::OnDraw(CDC *pDC)
{
CPen penBlack; //構造MFC CPen對象
if (penBlack.CreatePen(PS_SOLID, RGB(0, 0, 0)))
{
CPen *pOldPen = pDC->SelectObject(&penBlack)); //選進設備表,保存原筆
…
pDC->SelectObject(pOldPen); //恢復原筆
}else
{
…
}
}
和在SDK下有一點不同的是:這里沒有DeleteObject。因為執行完OnDraw后,棧中的penBlack被銷毀,它的析構函數被調用,導致DeleteObject的調用。
還有一點要說明:
pDC->SelectObject(&penBlack)返回了一個CPen *指針,也就是說,它根據原來PEN的句柄創建了一個MFC CPen對象。這個對象是否需要刪除呢?不必要,因為它是一個臨時對象,MFC框架會自動地刪除它。當然,在本函數執行完畢把控制權返回給主消息循環之前,該對象是有效的。
關于臨時對象及MFC處理它們的內部機制,將在后續章節詳細討論。
至此,Windows編程的核心概念:窗口、GDI界面(設備描述表、GDI對象等)已經陳述清楚,特別揭示了MFC對這些概念的封裝機制,并簡明講述了與這些Windows Object對應的MFC類的使用方法。還有其他Windows概念,可以參見SDK開發文檔。在MFC的實現上,基本上僅僅是對和這些概念相關的Win32函數的封裝。如果明白了MFC的窗口、GDI界面的封裝機制,其他就不難了。
系統理解Win32 API和MFC(下)
作者: 溫昱
作者主頁: lcspace.diy.163.com
系統理解Win32 API和MFC(上)
二、MFC的概念模型
前面我們研究了WIN32 API的“領域模型”,對它有較全面的認識。下面,對MFC概念模型的研究,我們把重點放在對app framework的研究上。
app framework中的message響應/傳遞機制是最重要的。而Hook機制和Message響應/傳遞機制是密切相關的,后者以前者為基礎。
1. Hook機制
也許有些程序員只知道hook機制可以編寫很“牛”的應用,孰不知MFC本身也是依靠hook機制的。
從圖中看到,每個hook擁有一個指針隊列,每個指針指向一個稱為的HookProc函數,HookProc將在合適的時機被OS調用執行。hook是分不同種類的,其實正是hook的種類決定了它什么時機被OS調用執行。提示,可以看一下“訂閱-發布”設計模式以助理解。
2 MFC中Message響應函數的安裝
2.1 回憶API中Message響應函數的安裝
API中Message響應函數的安裝,是由CreateWindow()實現的,它將window與一個windowClass聯系起來,而后者中記錄了Message響應函數的指針。
至于細節,看一下如何用Win32 SDK或Win16 SDK寫程序就清楚了,其中 DefWindowProc()是API函數,負責提供缺省的消息處理,所以,程序員只需要handle需要特殊處理的消息。
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{
WNDCLASS wndclass;
...
wndclass.lpfnWndProc =WndProc;
wndclass.lpszClassName = szWindowClass;
...
RegisterClass(&wndclass);
hWnd = CreateWindow( szWindowClass, ...);
...
}
LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
...
return;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}
2.2 MFC中Message響應函數的安裝
MFC中Message響應函數的安裝顯然更復雜,是在CWnd::CreateEx()被調用時完成的,其中還用到了Hook機制。
我們可以先猜一下MFC是怎么做的。MFC支持massage map,使得對消息的響應份散到多個message handler函數中,而不是API開發是那種集中式的消息處理函數;所以,想必會有專門的代碼來負責“檢索message map table然后調用message handle”。message map是為了支持程序員處理他關心的特殊message的,那么缺省的message處理邏輯在哪里呢?答案是MFC創建window obj時是用的“預定義的窗口類”,自然已經有了缺省的message處理函數。
從圖中看到,CWnd有成員變量m_pfnSuper、成員變量m_hWnd、成員函數OnWndMsg()和成員函數DefWindowProc()。Wnd::OnWndMsg()負責“在message map中定義的message handle”能否處理到來的message,如果處理了要返回true;CWnd::DefWindowProc()負責對message缺省處理。
執行過程是,首先CWnd::CreateEx()被調用,window obj和window class被相應建立,此時window class的WindowProc字段存儲了預定義的缺省處理函數的地址;由于有hook在監聽窗口創建消息,所以注冊的hookProc()會被調用執行,它將classWindow數據結構的WindowProc字段備份到CWnd::m_pfnSuper,再用SetWindowLong()改寫classWindow數據結構的WindowProc字段為::AfxWndProc()的地址。當任何一個message到達時,::AfxWndProc()被調用,至于它的邏輯,聰明的你一定猜到了,先調用Wnd::OnWndMsg(),如果返回值為false,還要調用CWnd::DefWindowProc(),CWnd::m_pfnSuper指向的缺省處理邏輯,也會在CWnd::DefWindowProc()中被調用。
提示,上面其實有多態情況發生。比如你可以在搜一下pWnd->WindowProc(nMsg, wParam, lParam); 另外,OnWndMsg和DefWindowProc都是CWnd類的虛擬函數。
要是覺得不太好理解,最好在VC++里創建一個project實際跟蹤一下,下面是我跟蹤時調用棧映象的截圖。

3. SubClass機制

從圖中看到,SubClass機制以CWnd自身的m_pfnSuper為基礎,和“MFC中Message響應函數的安裝”很象。
4.frame work中的主要相關類
frame work中的主要相關類 就是 message route的候選人,正是它們的OnCmdMsg()共同完成了message route,形成了chain of responsability模式。
5. frame work中的chain of responsability模式
下圖是一個對象樹,注意消息會在縱向和橫向兩個方向傳播。

消息在縱向方向上的傳遞,是在“上溯父類的massge map表”,MFC的message map完全是為了代替虛函數而采取的手段,而和message route無關。
消息在橫向方向上的傳遞,才是message route,才是chain of responsability模式,由多個相關類的OnCmdMsg()共同完成。
三、 總結
從上面的討論不難發現,MFC中用到了不少設計模式,如上面提到的chain of responsability模式、composite模式和“訂閱-發布”模式。上面的討論不僅有助于程序員全面掌握Win32 API和MFC,對architect設計architecture也有很大幫助。
系統理解Win32 API和MFC(上)
作者: 溫昱
作者主頁: lcspace.diy.163.com
Win32 API是微軟的操作系統Windows提供給開發人員的編程接口,它決定了我們開發的Windows應用程序的能力。MFC是微軟為開發人員提供的類庫,在某種意義上是對Win32 API的封裝。本文試圖從全局角度對Win32 API和MFC進行理解──給出二者的概念模型。
本文使用UML描述概念模型。Win32 API本不是面向對象的,我用面向對象的觀點去理解它,無非是想表達其全局。
本文參考了MSDN、相關書籍和網上的一些資料,在此一并感謝。
一、Win32 API的概念模型Win32 API的object有3種:user obj,gdi obj,kernel obj。但是,如果一點不考慮OS本身的支持,就會在有些問題上疑惑,因此,我這里把“operation system負責將中斷封裝成message”加上。
1、user obj、gdi obj、kernel obj、system 4者的關系 由于是kernel obj部分負責將另外3者聯系起來,因此我們在下圖中直接深入到kernel obj部分內部。
從圖中看到,在內存中運行的,除了“負責將中斷封裝成message”的system支持部分,還有另外3類object:kernel obj、user obj和gdi obj,每個obj都有一個句柄handle與之對應。其中,gdi obj建立了待開發的Windows 應用和外部輸出設備的聯系,kernel obj中的file建立了內存和永久存儲設備的聯系。具體說,內存中的file從可以從硬盤上來,如果這個file是可執行文件,它將生成module,module運行起來就是process,process可以包含多條thread,而thread的運行映象最終還是來自于file。thread是kernel obj中最重要的一個,因為消息隊列就是thread擁有的,只有thread才能夠接受message。對gdi obj、urser obj和file的操作,也是發生在thread中的。所以書都講,process至少擁有一個thread。
2、展開“system負責將中斷封裝成message”部分下面展開“system負責將中斷封裝成message”部分,盡早解除對“message到底是怎么形成的”的困惑。
3、展開“gdi obj”部分
開發人員可以通過gdi obj將app的信息反饋給User。
從圖中看到,gdi obj有8種,其中7種為:bmp,brush,pen,region,font,palette,path。另一種比較特殊的是DC,它可以被理解為一種容器,程序員通過調用SelectPallette()將pallte放入容器,通過調用BeginPath()和EndPath()將path放入容器,其它5種gdi obj,是通過調用SelectObject()放入容器的。DC又具體分為4種,其中DisplayDC就是最常用的用來支持我們“畫Window”的DC。 另外,如果覺得不好理解,請參考composite設計模式。
4、展開user obj部分
4.1 第1次迭代
window在Windows應用開發中占有重要地位。
從圖中看到,window可分為3種:desktop,top-level window,child window。所有window被OS組織成tree,有專門的數據結構來管理。desktop就是樹根,desktop的子節點是top-level window,top-level window的子節點是child window,child window仍然可以有子節點,同樣歸屬于child window。tree數據結構中還記錄了4種重要信息,是4種指針:parent指針、child指針、brother指針、owner指針。這樣,從任何一個window就能很容易地找到其它window了。
好了,暫且得到 window = desktop + topLevel + child 的結論,看看全局先。畢竟,一步到位有時候并不好。

從圖中看到,window確實占有重要地位。從邏輯是講,thread是window的擁有者;但是,所有window一起決定了屏幕看起來是上面樣子,何況點擊任何一個window都會使window得相互覆蓋關系發生變化,對所用window進行統一管理是必須的,所以OS又不得不統一用window tree來管理window,反映復雜的window關系。每個window都必須有一個且只能有一個客戶區,還可能有一個title bar。
再來看看CreateWindow()函數的interface spec透露了哪些信息。

從圖中看到,CreateWindow()負責為window建立與窗口類的聯系。每個window都有一個窗口類與之對應,而一個窗口類可以對應多個window。窗口類中記錄了窗口函數和菜單等資源信息,而由file生成的module正是窗口函數和資源的老家。
4.2 第2次迭代
考察消息種類。

從圖中看到,每個message都是發送給某個window的。注意,msg可由SYS代碼產生,也可以由API函數產生。
進一步考察window,深入topLevel和child。

從圖中看到,OVERLAPPED風格的window是top-level window的一種,而另一種POPUP風格的window從本質上(行為上)是特殊的一種OVERLAPPED風格的window,雖然我們從coding的角度常常不這么認為。
還是不好,因為當我們調用CreateWindow() API函數時,明明感覺CHILD、OVERLAPPED、POPUP是“window style”。我再畫一張圖。
從圖中看到,control必須是CHILD風格的,dialog必須是POPUP風格的,而一般性的window卻可以是任意風格的。
4.3 第3次迭代
總結user obj:

CreateDialog()函數示意:
從圖中看到,CreateDialog()和CreateWindow()最大的區別就是,它有對話框模板支持方便地定制dialog界面。注意,Dialog是特殊的window,窗口類它一定也是有的。
1.檢測
程序中的括號是否匹配
把光標移動到需要檢測的括號(如大括號{}、方括號[]、圓括號()和尖括號<>)前面,鍵入快捷鍵“Ctrl+]”。如果括號匹配正確,光標就跳到匹配的括號處,否則光標不移動,并且機箱喇叭還會發出一聲警告聲。
2.查看一個宏(或變量、函數)的宏定義
把光標移動到你想知道的一個宏上,就比如說最常見的DECLARE_MAP_MESSAGE上按一下F12(或右鍵菜單中的Go To Defition Of …),如果沒有建立Browse files,會出現提示對話框,確定,然后就會跳到定義那些東西的地方。
相當可喜的是,它也可以看到Microsoft定義的系統宏,非常good.
3.格式化一段亂七八糟的源代碼
選中那段源代碼,按ATL+F8。
4.在編輯狀態下發現成員變量或函數不能顯示
刪除該項目擴展名為.ncb文件,重新打開該項目。
5.如何整理ClassView視圖中大量的類
可以在classview 視圖中右鍵新建文件夾(new folder),再把具有相近性質的類拖到對應的文件夾中,使整個視圖看上去清晰明了.
6.定位預處理指定
在源文件中定位光標到對稱的#if, #endif,使用Ctrl+K.
7.如何添加系統中Lib到當前項目
在Project | Settings | Link | Object/library modules:輸入Lib名稱,不同的Lib之間用空格格開.
8.如何添加系統中的頭文件(.h)到當前項目.
#include ,告訴編譯到VC系統目錄去找;使用#include "FileName.h",告訴編譯在當前目錄找.
9.如何在Studio使用匯編調試
在WorkBench的Debugger狀態下按CTRL+F7.
10.怎樣處理ClassZiard找不到的系統消息
如果要在ClassWizard中處理WM_NCHITTEST等系統消息,請在ClassWizard中Class Info頁中將Message filter改為Window就有了.
11.如何干凈的刪除一個類
先從Workspace中的FileView中刪除對應的.h和.cpp文件,再關閉項目,從實際的文件夾中刪除對應的.h和.cpp文件與.clw文件。
12.在Studio中快速切換兩個文件
有時,我們需要在最近使用的兩個文件中快速切換,換Ctrl+F6。這在兩個文件不相今的時候就有用的.
13.取得源程序預處理后的結果:
在Studio里,可以在->PROJECT-> SETTINGS->C/C++->Project Options中,在最后加上 /P /EP這兩個編譯開關即可做到"只進行預處理".就可以了。編譯以后就可以在源程序目錄中發現“文件名.I ”的文本文件。這就是預處理后的結果。
(注意注:區分大小定,請用大定/P)
14.在Debug模式中查看WINAPI調用后的返回值:
很簡單,且實用:在watch中加入@hr,err。在CSDN的文檔中心有一篇講得更細,請參考。
15.產生指定源程序文件的匯編代碼:
從IDE菜單的Project->Setting打開項目設置,按如下文件做:
1.先在左邊選擇指定文件,可以多選。
2. 在右邊的C++屬性頁中,在category中選擇List Files,接著在下面的List Files Type中選擇Assembly and source code(或選擇其它),最后在List File Name中輸入在個C/C++源文件產生的相應的匯編代碼的文件。
3.編譯整個工程。
16.手工編譯純資源成dll:
Rc.exe /v data.rc
Cvtres.exe /machine:ix86 data.res
Link /SUBSYSTEM:WINDOWS /DLL /NOENTRY data.res ;編譯成DLL文件
這種方式創建的DLL是最小的,比起你用Win 32 Dynamic Libray等產生的更小。
17:怎樣快速生成一個與現有項目除了項目名外完全相同的新項目?
利用File菜單下生成新項目中的Custom AppWizard ,選擇 An existing Project ,然后選擇現有項目的項目文件名(*.dsp)Finish,編譯后就生成一個可以生成與現有項目相同但可以重新取名的項目的AppWizard。你可以象用MFC AppWizard一樣用它。如果不想用了,可以在VC 安裝目錄下Common\MSDev98\Template目錄中刪除該Wizard中.awx和 .pdb文件。
18:如果想把整個項目拷貝到軟盤,那些文件可以刪掉?
除了項目文件夾中debug文件夾可以刪除外,.ncb,.clw,.opt 等文件也可以刪除,這些文件Rebuilt all后可以重新生成。
附:VC項目文件說明
.dsp 項目參數配置文件,這個文件太重要,重點保護對象。.
.dsw 工作區文件,重要性一般,因為它信息不我,容易恢復。
以下文件在項目中是可丟棄的,有些文件刪除后,VC會自動生成的。
.clw ClassWizard信息文件,實際上是INI文件的格式,有興趣可以研究一下.有時候ClassWizard出問題,手工修改CLW文件可以解決.如果此文件不存在的話,每次用ClassWizard的時候繪提示你是否重建.
.ncb 無編譯瀏覽文件(no compile browser)。當自動完成功能出問題時可以刪除此文件。build后會自動生成。
.opt 工程關于開發環境的參數文件。如工具條位置等信息;(可丟棄)
.aps (AppStudio File),資源輔助文件,二進制格式,一般不用去管他.
.plg 是編譯信息文件,編譯時的error和warning信息文件(實際上是一個html文件),一般用處不大.在Tools->Options里面有個選項可以控制這個文件的生成.
.hpj (Help Project)是生成幫助文件的工程,用microsfot Help Compiler可以處理.
.mdp (Microsoft DevStudio Project)是舊版本的項目文件,如果要打開此文件的話,會提示你是否轉換成新的DSP格式.
.bsc 是用于瀏覽項目信息的,如果用Source Brower的話就必須有這個文件.如果不用這個功能的話,可以在Project Options里面去掉Generate Browse Info File,可以加快編譯速度.
.map 是執行文件的映像信息紀錄文件,除非對系統底層非常熟悉,這個文件一般用不著.
.pch (Pre-Compiled File)是預編譯文件,可以加快編譯速度,但是文件非常大.
.pdb (Program Database)記錄了程序有關的一些數據和調試信息,在調試的時候可能有用.
.exp 只有在編譯DLL的時候才會生成,記錄了DLL文件中的一些信息.一般也沒什么用.
VC 中的定時
VC中提供了很多關于時間操作的函數,編寫程序時我們可以跟據定時的不同精度要求選擇不同的時間函數來完成定時和計時操作。
方式一:VC中的WM_TIMER消息映射能進行簡單的時間控制。首先調用函數SetTimer()設置定時間隔,如SetTimer(0,200,NULL)即為設置200ms的時間間隔。然后在應用程序中增加定時響應函數 OnTimer(),并在該函數中添加響應的處理語句,用來完成到達定時時間的操作。這種定時方法非常簡單,可以實現一定的定時功能,但其定時功能如同Sleep()函數的延時功能一樣,精度非常低,最小計時精度僅為18ms。CPU占用低,且定時器消息在多任務操作系統中的優先級很低,不能得到及時響應,往往不能滿足實時控制環境下的應用。只可以用來實現諸如位圖的動態顯示等對定時精度要求不高的情況。
方式二:VC中使用sleep()函數實現延時,它的單位是ms,如延時2秒,用sleep(2000)。精度非常低,最小計時精度僅為30ms,用sleep函數的不利處在于延時期間不能處理其他的消息,如果時間太長,就好象死機一樣,CPU占用率非常高,只能用于要求不高的延時程序中。
方式三:利用COleDateTime類和COleDateTimeSpan類結合WINDOWS的消息處理過程來實現秒級延時。以下是實現2秒的延時代碼:
COleDateTime start_time = COleDateTime::GetCurrentTime();
COleDateTimeSpan end_time= COleDateTime::GetCurrentTime()-start_time;
while(end_time.GetTotalSeconds()< 2) //實現延時2秒
{
MSG msg;
GetMessage(&msg,NULL,0,0);
TranslateMessage(&msg);
DispatchMessage(&msg);
//以上四行是實現在延時或定時期間能處理其他的消息,
//雖然這樣可以降低CPU的占有率,
//但降低了延時或定時精度,實際應用中可以去掉。
end_time = COleDateTime::GetCurrentTime()-start_time;
}//這樣在延時的時候我們也能夠處理其他的消息。
方式四:在精度要求較高的情況下,VC中可以利用GetTickCount()函數,該函數的返回值是 DWORD型,表示以ms為單位的計算機啟動后經歷的時間間隔。精度比WM_TIMER消息映射高,在較短的定時中其計時誤差為15ms,在較長的定時中其計時誤差較低,如果定時時間太長,就好象死機一樣,CPU占用率非常高,只能用于要求不高的延時程序中。下列代碼可以實現50ms的精確定時:
DWORD dwStart = GetTickCount();
DWORD dwEnd = dwStart;
do
{
dwEnd = GetTickCount() - dwStart;
}while(dwEnd <50);
為使GetTickCount()函數在延時或定時期間能處理其他的消息,可以把代碼改為:
DWORD dwStart = GetTickCount();
DWORD dwEnd = dwStart;
do
{
MSG msg;
GetMessage(&msg,NULL,0,0);
TranslateMessage(&msg);
DispatchMessage(&msg);
dwEnd = GetTickCount()-dwStart;
}while(dwEnd <50);
雖然這樣可以降低CPU的占有率,并在延時或定時期間也能處理其他的消息,但降低了延時或定時精度。
方式五:與GetTickCount()函數類似的多媒體定時器函數DWORD timeGetTime(void),該函數定時精度為ms級,返回從Windows啟動開始經過的毫秒數。微軟公司在其多媒體Windows中提供了精確定時器的底層API持,利用多媒體定時器可以很精確地讀出系統的當前時間,并且能在非常精確的時間間隔內完成一個事件、函數或過程的調用。不同之處在于調用DWORD timeGetTime(void) 函數之前必須將 Winmm.lib 和 Mmsystem.h 添加到工程中,否則在編譯時提示DWORD timeGetTime(void)函數未定義。由于使用該函數是通過查詢的方式進行定時控制的,所以,應該建立定時循環來進行定時事件的控制。
方式六:使用多媒體定時器timeSetEvent()函數,該函數定時精度為ms級。利用該函數可以實現周期性的函數調用。函數的原型如下:
MMRESULT timeSetEvent( UINT uDelay,
UINT uResolution,
LPTIMECALLBACK lpTimeProc,
WORD dwUser,
UINT fuEvent )
該函數設置一個定時回調事件,此事件可以是一個一次性事件或周期性事件。事件一旦被激活,便調用指定的回調函數,成功后返回事件的標識符代碼,否則返回NULL。函數的參數說明如下:
uDelay:以毫秒指定事件的周期。
Uresolution:以毫秒指定延時的精度,數值越小定時器事件分辨率越高。缺省值為1ms。
LpTimeProc:指向一個回調函數。
DwUser:存放用戶提供的回調數據。
FuEvent:指定定時器事件類型:
TIME_ONESHOT:uDelay毫秒后只產生一次事件
TIME_PERIODIC :每隔uDelay毫秒周期性地產生事件。
具體應用時,可以通過調用timeSetEvent()函數,將需要周期性執行的任務定義在LpTimeProc回調函數中(如:定時采樣、控制等),從而完成所需處理的事件。需要注意的是,任務處理的時間不能大于周期間隔時間。另外,在定時器使用完畢后,應及時調用timeKillEvent()將之釋放。
方式七:對于精確度要求更高的定時操作,則應該使用QueryPerformanceFrequency()和 QueryPerformanceCounter()函數。這兩個函數是VC提供的僅供Windows 95及其后續版本使用的精確時間函數,并要求計算機從硬件上支持精確定時器。
QueryPerformanceFrequency()函數和QueryPerformanceCounter()函數的原型如下:
BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
BOOL QueryPerformanceCounter(LARGE_INTEGER *lpCount);
數據類型ARGE_INTEGER既可以是一個8字節長的整型數,也可以是兩個4字節長的整型數的聯合結構,其具體用法根據編譯器是否支持64位而定。該類型的定義如下:
typedef union _LARGE_INTEGER
{
struct
{
DWORD LowPart ;// 4字節整型數
LONG HighPart;// 4字節整型數
};
LONGLONG QuadPart ;// 8字節整型數
}LARGE_INTEGER ;
在進行定時之前,先調用QueryPerformanceFrequency()函數獲得機器內部定時器的時鐘頻率,然后在需要嚴格定時的事件發生之前和發生之后分別調用QueryPerformanceCounter()函數,利用兩次獲得的計數之差及時鐘頻率,計算出事件經歷的精確時間。下列代碼實現1ms的精確定時:
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;// 獲得計數器的時鐘頻率
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;// 獲得初始值
do
{
QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;//獲得中止值
dfMinus = (double)(QPart2-QPart1);
dfTim = dfMinus / dfFreq;// 獲得對應的時間值,單位為秒
}while(dfTim<0.001);
VC定時器 SetTimer 怎么用阿
[此問題的推薦答案]
SetTimer函數的用法
1 )用WM_TIMER來設置定時器
先請看SetTimer這個API函數的原型
UINT_PTR SetTimer(
HWND hWnd, // 窗口句柄
UINT_PTR nIDEvent, // 定時器ID,多個定時器時,可以通過該ID判斷是哪個定時器
UINT uElapse, // 時間間隔,單位為毫秒
TIMERPROC lpTimerFunc // 回調函數
);
例如
SetTimer(m_hWnd,1,1000,NULL); //一個1秒觸發一次的定時器
在MFC程序中SetTimer被封裝在CWnd類中,調用就不用指定窗口句柄了
于是SetTimer函數的原型變為:
UINT SetTimer(UINT nIDEvent,UINT nElapse,void(CALLBACK EXPORT *lpfnTimer)(HWND,UINT ,YINT ,DWORD))
當使用SetTimer函數的時候,就會生成一個計時器。函數中nIDEvent指的是計時器的標識,也就是名字。nElapse指的是時間間隔,
也就是每隔多長時間觸發一次事件。第三個參數是一個回調函數,在這個函數里,放入你想要做的事情的代碼,你可以將它設定為NULL,
也就是使用系統默認的回調函數,系統默認認的是onTime函數。這個函數怎么生成的呢?你需要在需要計時器的類的生成onTime函數:
在ClassWizard里,選擇需要計時器的類,添加WM_TIME消息映射,就自動生成onTime函數了。然后在函數里添加代碼,讓代碼實現功能。
每隔一段時間就會自動執行一次。
例:
SetTimer(1,1000,NULL);
1:計時器的名稱;
1000:時間間隔,單位是毫秒;
NULL:使用onTime函數。
當不需要計時器的時候調用KillTimer(nIDEvent);
例如:KillTimer(1);
2) 調用回調函數
此方法首先寫一個如下格式的回調函數
void CALLBACK TimerProc(HWND hWnd,UINT nMsg,UINT nTimerid,DWORD dwTime);
然后再用SetTimer(1,100,TimerProc)函數來建一個定時器,第三個參數就是回調函數地址。
二. 或許你會問,如果我要加入兩個或者兩個以上的 timer怎么辦?
繼續用SetTimer函數吧,上次的timer的ID是1,這次可以是2,3,4。。。。
SetTimer(2,1000,NULL);
SetTimer(3,500,NULL);
嗯,WINDOWS會協調他們的。當然onTimer函數體也要發生變化,要在函數體內添加每一個timer的處理代碼:
onTimer(nIDEvent)
{
switch(nIDEvent)
{
case 1:........;
break;
case 2:.......;
break;
case 3:......;
break;
}
}
本貼來自ZDNetChina中文社區 http://bbs.zdnet.com.cn ,本貼地址:http://bbs.zdnet.com.cn/viewthread.php?tid=313294
VC定時器 SetTimer 怎么用阿
Timer事件,即定時器事件,是在游戲編程中,經常使用的一個事件。借助它可以產生定時執行動作的效果。這篇文章,就和大家一起探討一下如何使用SetTimer()函數。
1、SetTimer定義在那里?
SetTimer表示的是定義個定時器。根據定義指定的窗口,在指定的窗口(CWnd)中實現OnTimer事件,這樣,就可以相應事件了。
SetTimer有兩個函數。一個是全局的函數::SetTimer()
UINT SetTimer(
HWND hWnd, // handle of window for timer messages
UINT nIDEvent, // timer identifier
UINT uElapse, // time-out value
TIMERPROC lpTimerFunc // address of timer procedure
);
其中hWnd 是指向CWnd的指針,即處理Timer事件的窗口類。說道窗口類(CWnd),我們有必要來看一下CWnd的繼承情況:CWnd有以下子類:CFrameWnd,CDialog,CView,CControlBar等類。這也意味這些類中都可以定義SetTimer事件。
同時,SetTimer()在CWnd中也有定義,即SetTimer()是CWnd的一個成員函數。CWnd的子類可以調用該函數,來設置觸發器。
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );
參數含義:
nIDEvent:是指設置這個定時器的iD,即身份標志,這樣在OnTimer()事件中,才能根據不同的定時器,來做不同的事件響應。這個ID是一個無符號的整型。
nElapse
是指時間延遲。單位是毫秒。這意味著,每隔nElapse毫秒系統調用一次Ontimer()。
void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD)
Specifies the address of the application-supplied TimerProc callback function that processes the WM_TIMER messages. If this parameter is NULL, the WM_TIMER messages are placed in the application’s message queue and handled by the CWnd object。
意思是,指定應用程序提供的TimerProc回調函數的地址,來處里這個Timer事件。如果是NULL,處理這個Timer事件的定義這個Timer的CWnd對象。他將WM_TIMER消息傳遞給這個對象,通過實現這個對象的OnTimer()事件來處理這個Timer事件。
所以,一般情況下,我們將這個值設為NULL,有設置該定時器的對象中的OnTimer()函數來處理這個事件。
同樣的,我們再看看KillTimer()和OnTimer()的定義:
KillTimer同SetTimer()一樣,他也有兩個,一個是全局的::KillTimer(),另一個是CWnd的一個函數。他的聲明如下:
//全局函數
BOOL KillTimer(
HWND hWnd, // handle of window that installed timer
UINT uIDEvent // timer identifier
);
//CWnd函數
BOOL KillTimer( int nIDEvent );
這兩個函數表示的意思是將iD為nIDEVENT的定時器移走。使其不再作用。其用法如同SetTimer()一樣。
再看看OnTimer()
CWnd::OnTimer
afx_msg void OnTimer( UINT nIDEvent );
ontimer()是響應CWnd對象產生的WM_Timer消息。nIDEvent表示要響應TIMER事件的ID。
二、Timer事件的使用:
由以上的分析,我們應該很清楚,如何來使用Timer事件。假定我們在視圖上畫一個漸變的動畫。我們首先在菜單欄上添加一個菜單項,給這個菜單添加命令響應:
pView->SetTimer(1,1000,NULL);//pView是視圖類的指針,這里是在視圖類當中設置一個定時器。
添加完畢,再給視圖類添加一個WM_Timer事件的相應。在OnTimer()函數中編寫漢書,進行相應。
如此,就能做出動畫。
本貼來自ZDNetChina中文社區 http://bbs.zdnet.com.cn ,本貼地址:http://bbs.zdnet.com.cn/viewthread.php?tid=313294
在開發過程中經常需要獲得程序當前的運行目錄,這時就可以使用GetModuleFileName函數
DWORD WINAPI GetModuleFileName(
HMODULE hModule,
LPTSTR lpFileName,
DWORD nSize
);
hModule:要獲取文件名的模塊名柄,null表示當前模塊
lpFileName:輸出參數,存放取得的文件名
nSize:lpFileName參數的長度
例
void FileName()
{
TCHAR lpFileName[MAX_PATH];
::GetModuleFileName(null, lpFileName, MAX_PATH);
SetDlgItemText(IDC_TEXTBOX, lpFileName);
}
//==============================================================================
//==============================================================================
在開發軟件的過程里,經常需要把數據保存到當前執行文件路徑下面,或者讀取當前執行文件路徑下的一些配置信息。這時就需要從當前模塊里獲取所在的目錄路徑,以便進行固定的位置操作文件。要解決這個需求,就需要調用API函數GetModuleFileName來獲取模塊所在的路徑。
函數GetModuleFileName聲明如下:
WINBASEAPI
DWORD
WINAPI
GetModuleFileNameA(
__in_opt HMODULE hModule,
__out_ecount_part(nSize, return + 1) LPCH lpFilename,
__in DWORD nSize
);
WINBASEAPI
DWORD
WINAPI
GetModuleFileNameW(
__in_opt HMODULE hModule,
__out_ecount_part(nSize, return + 1) LPWCH lpFilename,
__in DWORD nSize
);
#ifdef UNICODE
#define GetModuleFileName GetModuleFileNameW
#else
#define GetModuleFileName GetModuleFileNameA
#endif // !UNICODE
hModule是模塊的句柄,或者設置為NULL表示當前模塊。
lpFilename是保存路徑的緩沖區。
nSize是緩沖區的大小。
調用函數的例子如下:
#001 //獲取當前程序所在路徑。
#002 //蔡軍生 2007/12/05 QQ:9073204 深圳
#003 void TestGetExePath(void)
#004 {
#005 //
#006 const int nBufSize = 512;
#007 TCHAR chBuf[nBufSize];
#008 ZeroMemory(chBuf,nBufSize);
#009
#010 //獲取當前執行文件的路徑。
#011 if (GetModuleFileName(NULL,chBuf,nBufSize))
#012 {
#013 //輸出帶文件名稱路徑。
#014 OutputDebugString(chBuf);
#015 OutputDebugString(_T("\r\n"));
#016
#017 //獲取文件路徑。
#018 TCHAR* lpStrPath = chBuf;
#019 PathRemoveFileSpec(lpStrPath);
#020 OutputDebugString(lpStrPath);
#021 OutputDebugString(_T("\r\n"));
#022 }
#023
#024 }
輸出的結果如下:
g:\work\windows_api\wincpp2\debug\WinCpp.exe
g:\work\windows_api\wincpp2\debug