TApplication与主消息循环
生活随笔
收集整理的這篇文章主要介紹了
TApplication与主消息循环
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Windows應用程序的每一個窗口都有一個大的消息循環以及一個窗口函數(WndProc)用以分發和處理消息。VCL作為一個Framework,當然會將這些東西隱藏起來,而重新提供一種易用的、易理解的虛擬機制給程序員。
那么VCL是如何做到的呢?
本節就來解答這個問題。
只要代碼單元中包含了Forms.pas,就會得到一個對象——Application。利用它可以幫助我們完成許多工作。例如要退出應用程序,可以使用
Application.Terminate();
Application對象是VCL提供的,在Forms.pas中可以看到如下這個定義:
var
Application: TApplication;
當創建一個默認的應用程序時,會自動得到以下幾行代碼:
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
這幾行代碼很簡潔地展示了TApplication的功能、初始化、創建必要的窗體、運行……
但是,這幾行代碼具體做了什么幕后操作呢?Application.Run之后,程序流程走向了哪里?
1.脫離VCL的Windows程序
??? 在此,給出一個用純Pascal所編寫的十分簡單的Windows應用程序,以演示標準Windows程序是如何被建立及運行的。
program?WindowDemo;
uses?Windows,?Messages;
//?窗口函數,窗口接到消息時被Windows所調用
function?WindowProc(hwnd?:?HWND;?uMsg?:?Cardinal;?wParam?:?WPARAM;
lParam?:?LPARAM)?:?LResult;?stdcall;
begin
Result?:=?0;
case?uMsg?of
//?關閉窗口消息,當用戶關閉窗口后,通知主消息循環結束程序
WM_CLOSE?:?PostMessage(hwnd,?WM_QUIT,?0,?0);
//?鼠標左鍵按下消息
WM_LBUTTONDOWN?:?MessageBox(hwnd,?'Hello!',?'和您打個招呼',
MB_ICONINFORMATION);
else
//?其他消息做默認處理
Result?:=?DefWindowProc(hWnd,?uMsg,?wParam,?lParam);
end;
end;
var
wndcls?:?WNDCLASS;?//?窗口類的記錄(結構)類型
hWnd?:?THandle;
Msg?:?tagMSG;?//?消息類型
begin
wndcls.style?:=?CS_DBLCLKS;?//?允許窗口接受鼠標雙擊
wndcls.lpfnWndProc?:=?@WindowProc;?//?為窗口類指定窗口函數
wndcls.cbClsExtra?:=?0;
wndcls.cbWndExtra?:=?0;
wndcls.hInstance?:=?hInstance;
wndcls.hIcon?:=?0;
wndcls.hCursor?:=?LoadCursor(hInstance,?'IDC_ARROW');
wndcls.hbrBackground?:=?COLOR_WINDOWFRAME;
wndcls.lpszMenuName?:=?nil;
wndcls.lpszClassName?:=?'WindowClassDemo';?//?窗口類名稱
//?注冊窗口類
if?RegisterClass(wndcls)?=?0?then
Exit;
//?創建窗口
hWnd?:=?CreateWindow(
'WindowClassDemo',?//?窗口類名稱
'WindowDemo',?//?窗口名稱
WS_BORDER?or?WS_CAPTION?or?WS_SYSMENU,?//?窗口類型
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT),
0,
0,
hInstance,
nil
);
if?hWnd?=?0?then
Exit;
//?顯示窗口
ShowWindow(hWnd,?SW_SHOWNORMAL);
UpdateWindow(hWnd);
//?創建主消息循環,處理消息隊列中的消息并分發
//?直至收到WM_QUIT消息,退出主消息循環,并結束程序
//?WM_QUIT消息由PostMessage()函數發送
while?GetMessage(Msg,?hWnd,?0,?0)?do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end.
該程序沒有使用VCL,它所做的事情就是顯示一個窗口。當在窗口上單擊鼠標右鍵時,會彈出一個友好的對話框向您問好。如果從來不曾了解過這些,那么建議您實際運行一下光盤上的這個程序,對其多一些感性認識。
就是這樣一個簡單的程序,演示了標準Windows程序的流程:
(1)從入口函數WinMain開始。
(2)注冊窗口類及窗口函數(Window Procedure)。
(3)創建并顯示窗口。
(4)進入主消息循環,從消息隊列中獲取并分發消息。
(5)消息被分發后,由Windows操作系統調用窗口函數,由窗口函數對消息進行 處理。
在Object Pascal中看不到所謂的“WinMain”函數。不過,其實整個program的begin處就是Windows程序的入口。
注冊窗口類通過系統API函數RegisterClass()來完成,它向Windows系統注冊一個窗口的類型。
注冊窗口類型完成后,就可以創建這個類型的窗口實例。創建出一個真正的窗口可通過API函數CreateWindow()來實現。
創建出的窗口實例通過API函數ShowWindow()來使得它顯示在屏幕上。
當這一切都完成后,窗口開始進入一個while循環以處理各種消息,直至API函數GetMessage()返回0才退出程序。循環中,程序需要從主線程的消息隊列中取出各種消息,并將它分發給系統,然后由Windows系統調用窗口的窗口函數(WndProc),以完成窗口對消息的響應處理。
???????? TApplication除了定義一個應用程序的特性及行為外,另一個重要的使命就是封裝以上的那些令人討厭的、繁瑣的步驟。
2.Application對象的本質
注意:Application是一個0*0大小的不可見窗口!并且這個窗口是windows應用程序的主窗口,delphi應用程序的主窗體是這個窗口的子窗口,因此會以一個消息循環接受窗口消息并且加以分派和處理。TApplication類封裝了創建秘密窗口和消息循環的程序代碼。所有的事情發生在全局對象Application對象被創建之時。
?? TApplication的構造函數中:
constructor?TApplication.Create(AOwner:?TComponent)
var
??
??if?not?IsLibrary?then?CreateHandle;
??
end;
? 構造函數會調用CreateHandle方法(非常重要的函數)。查看該方法源代碼可知,該方法的任務正是注冊窗口類,并創建一個窗口實例。
procedure?TApplication.CreateHandle;
var
??TempClass:?TWndClass;
??SysMenu:?HMenu;
begin
??if?not?FHandleCreated?and?not?IsConsole?then
??begin
????FObjectInstance?:=?Classes.MakeObjectInstance(WndProc);
????//?如果窗口類不存在,則注冊窗口類
????if?not?GetClassInfo(HInstance,?WindowClass.lpszClassName,?TempClass)?then
????begin
??????WindowClass.hInstance?:=?HInstance;
??????if?Windows.RegisterClass(WindowClass)?=?0?then
????????raise?EOutOfResources.Create(SWindowClass);
????end;
???//?創建窗口,長度和寬度都是0,位置在屏幕中央,返回的句柄FHandle
???//?也就是Tapplication.Handle的值
????FHandle?:=?CreateWindow(WindowClass.lpszClassName,?PChar(FTitle),
??????WS_POPUP?or?WS_CAPTION?or?WS_CLIPSIBLINGS?or?WS_SYSMENU
??????or?WS_MINIMIZEBOX,
??????GetSystemMetrics(SM_CXSCREEN)?div?2,
??????GetSystemMetrics(SM_CYSCREEN)?div?2,
??????0,?0,?0,?0,?HInstance,?nil);
????FTitle?:=?'';
????FHandleCreated?:=?True;
???//?調用SetWindowLong設置窗口的窗口函數(WndProc)
????SetWindowLong(FHandle,?GWL_WNDPROC,?Longint(FObjectInstance));
????if?NewStyleControls?then
????begin
??????SendMessage(FHandle,?WM_SETICON,?1,?GetIconHandle);
??????SetClassLong(FHandle,?GCL_HICON,?GetIconHandle);
????end;
????SysMenu?:=?GetSystemMenu(FHandle,?False);
????DeleteMenu(SysMenu,?SC_MAXIMIZE,?MF_BYCOMMAND);
????DeleteMenu(SysMenu,?SC_SIZE,?MF_BYCOMMAND);
????if?NewStyleControls?then?DeleteMenu(SysMenu,?SC_MOVE,?MF_BYCOMMAND);
??end;
end;
對照一下此前使用純API編寫的窗口程序,就會發現一些它們的相似之處。在CreateHandle()中,可以看到熟悉的RegisterClass()、CreateWindow()等API函數的調用。比較特別的是,CreateHandle()中通過API函數SetWindowLong()來設置窗口的窗口函數:
SetWindowLong(FHandle, GWL_WNDPROC, Longint(FObjectInstance));
此時,SetWindowLong()的第3個參數為窗口函數實例的地址,其中FObjectInstance是由CreateHandle()的第1行代碼
FObjectInstance := Classes.MakeObjectInstance(WndProc);
所創建的實例的指針,而WndProc()則成了真正的窗口函數。
????????? TApplication本身有一個private成員FMainForm,它指向程序員所定義的主窗體,并在TApplication.CreateForm方法中判斷并賦值:
procedure?TApplication.CreateForm(InstanceClass:?TComponentClass;?var?Reference);
var
??Instance:?TComponent;
begin
??Instance?:=?TComponent(InstanceClass.NewInstance);
??TComponent(Reference)?:=?Instance;
??try
????Instance.Create(Self);
??except
????TComponent(Reference)?:=?nil;
????raise;
??end;
???//?第一個創建的窗體實例就是MainForm
??if?(FMainForm?=?nil)?and?(Instance?is?TForm)?then
??begin
????TForm(Instance).HandleNeeded;
????FMainForm?:=?TForm(Instance);
??end;
end;
因此,Delphi為每個應用程序自動生成的代碼中就有對CreateForm的調用,如:
Application.CreateForm(TForm1, Form1);
值得注意的是,如果有一系列的多個CreateForm的調用,則第一個調用CreateForm被創建的窗體,就是整個Application的MainForm。
3.TApplication創建主消息循環
????? 在TApplication的CreateHandle方法中可以看到,SetWindowLong()的調用將TApplication.WndProc設置成了那個0×0大小窗口的窗口函數。
也就是說,在TApplication的構造函數中主要完成了兩件事情:注冊窗口類及窗口函數,創建Application窗口實例。TApplication類的Run方法中:
procedure?TApplication.Run;
begin
??FRunning?:=?True;
??try
????AddExitProc(DoneApplication);
????if?FMainForm?<>?nil?then
????begin
??????case?CmdShow?of
????????SW_SHOWMINNOACTIVE:?FMainForm.FWindowState?:=?wsMinimized;
????????SW_SHOWMAXIMIZED:?MainForm.WindowState?:=?wsMaximized;
??????end;
??????if?FShowMainForm?then
????????if?FMainForm.FWindowState?=?wsMinimized?then
??????????Minimize?else
??????????FMainForm.Visible?:=?True;
??????repeat
????????try
??????????HandleMessage;
????????except
??????????HandleException(Self);
????????end;
??????until?Terminated;
????end;
??finally
????FRunning?:=?False;
??end;
end;
是的,這就是主消息循環??瓷先ニ坪鯖]有取消息、分發消息的過程,其實它們都被包含在HandleMessage()方法中了。HandleMessage()方法其實是對ProcessMessage()方法的調用,而在ProcessMessage()中就可以看到取消息、分發消息的動作了。
procedure?TApplication.HandleMessage;
var
??Msg:?TMsg;
begin
??if?not?ProcessMessage(Msg)?then?Idle(Msg);
end;
function?TApplication.ProcessMessage(var?Msg:?TMsg):?Boolean;
var
??Handled:?Boolean;
begin
??Result?:=?False;
??//?取消息
??if?PeekMessage(Msg,?0,?0,?0,?PM_REMOVE)?then
??begin
????Result?:=?True;
????if?Msg.Message?<>?WM_QUIT?then
????begin
??????Handled?:=?False;
??????if?Assigned(FOnMessage)?then?FOnMessage(Msg,?Handled);
??????if?not?IsHintMsg(Msg)?and?not?Handled?and?not?IsMDIMsg(Msg)?and
????????not?IsKeyMsg(Msg)?and?not?IsDlgMsg(Msg)?then
??????begin
????????//?熟悉的分發消息過程
????????TranslateMessage(Msg);
????????DispatchMessage(Msg);
??????end;
????end
????else
??????//?如果取到的消息為WM_QUIT,則將Fterminate設為真
???????//?以通知主消息循環退出
???????//?這和WindowDemo程序中判斷GetMessage()函數返回值是否為0等效
???????//?因為GetMessage()函數取出的消息如果是WM_QUIT,它的返回值為0
??????FTerminate?:=?True;
??end;
end;
4.窗口函數(WndProc)處理消息
窗口函數是一個回調函數,它被Windows系統所調用,其參數會被給出消息編號、消息參數等信息,以便進行處理。
典型的窗口函數中會包含一個大的case分支,以處理不同的消息。TApplication.CreateHandle()的代碼時提到過,CreateHandle()將Application窗口的窗口函數設置為WndProc()。那么,現在就來看一下這個WndProc:
procedure?TApplication.WndProc(var?Message:?TMessage);
type?//?函數內嵌定義的類型,只限函數內部使用
??TInitTestLibrary?=?function(Size:?DWord;?PAutoClassInfo:?Pointer):?Boolean;?stdcall;
var
??I:?Integer;
??SaveFocus,?TopWindow:?HWnd;
??InitTestLibrary:?TInitTestLibrary;
??//?內嵌函數,默認的消息處理
???//?調用Windows的API函數DefWindowProc
??procedure?Default;
??begin
????with?Message?do
??????Result?:=?DefWindowProc(FHandle,?Msg,?WParam,?LParam);
??end;
??procedure?DrawAppIcon;
??var
????DC:?HDC;
????PS:?TPaintStruct;
??begin
????with?Message?do
????begin
??????DC?:=?BeginPaint(FHandle,?PS);
??????DrawIcon(DC,?0,?0,?GetIconHandle);
??????EndPaint(FHandle,?PS);
????end;
??end;
begin
??try
????Message.Result?:=?0;
????for?I?:=?0?to?FWindowHooks.Count?-?1?do
??????if?TWindowHook(FWindowHooks[I]^)(Message)?then?Exit;
????CheckIniChange(Message);
????with?Message?do
??????//?開始龐大的case分支,對不同的消息做出不同的處理
??????case?Msg?of
????????WM_SYSCOMMAND:
??????????case?WParam?and?$FFF0?of
????????????SC_MINIMIZE:?Minimize;
????????????SC_RESTORE:?Restore;
??????????else
????????????Default;
??????????end;
????????WM_CLOSE:
??????????if?MainForm?<>?nil?then?MainForm.Close;
????????WM_PAINT:
??????????if?IsIconic(FHandle)?then?DrawAppIcon?else?Default;
????????WM_ERASEBKGND:
??????????begin
????????????Message.Msg?:=?WM_ICONERASEBKGND;
????????????Default;
??????????end;
????????WM_QUERYDRAGICON:
??????????Result?:=?GetIconHandle;
????????WM_SETFOCUS:
??????????begin
????????????PostMessage(FHandle,?CM_ENTER,?0,?0);
????????????Default;
??????????end;
????????WM_ACTIVATEAPP:
??????????begin
????????????Default;
????????????FActive?:=?TWMActivateApp(Message).Active;
????????????if?TWMActivateApp(Message).Active?then
????????????begin
??????????????RestoreTopMosts;
??????????????PostMessage(FHandle,?CM_ACTIVATE,?0,?0)
????????????end
????????????else
????????????begin
??????????????NormalizeTopMosts;
??????????????PostMessage(FHandle,?CM_DEACTIVATE,?0,?0);
????????????end;
??????????end;
????????WM_ENABLE:
??????????if?TWMEnable(Message).Enabled?then
??????????begin
????????????RestoreTopMosts;
????????????if?FWindowList?<>?nil?then
????????????begin
??????????????EnableTaskWindows(FWindowList);
??????????????FWindowList?:=?nil;
????????????end;
????????????Default;
??????????end?else
??????????begin
????????????Default;
????????????if?FWindowList?=?nil?then
??????????????FWindowList?:=?DisableTaskWindows(Handle);
????????????NormalizeAllTopMosts;
??????????end;
????????WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:
??????????Result?:=?SendMessage(LParam,?CN_BASE?+?Msg,?WParam,?LParam);
????????WM_ENDSESSION:?if?TWMEndSession(Message).EndSession?then?FTerminate?:=?True;
????????WM_COPYDATA:
??????????if?(PCopyDataStruct(Message.lParam)^.dwData?=?DWORD($DE534454))?and
????????????(FAllowTesting)?then
????????????if?FTestLib?=?0?then
????????????begin
??????????????FTestLib?:=?SafeLoadLibrary('vcltest3.dll');
??????????????if?FTestLib?<>?0?then
??????????????begin
????????????????Result?:=?0;
????????????????@InitTestLibrary?:=?GetProcAddress(FTestLib,?'RegisterAutomation');
????????????????if?@InitTestLibrary?<>?nil?then
??????????????????InitTestLibrary(PCopyDataStruct(Message.lParam)^.cbData,
????????????????????PCopyDataStruct(Message.lParam)^.lpData);
??????????????end
??????????????else
??????????????begin
????????????????Result?:=?GetLastError;
????????????????FTestLib?:=?0;
??????????????end;
????????????end
????????????else
??????????????Result?:=?0;
????????CM_ACTIONEXECUTE,?CM_ACTIONUPDATE:
??????????Message.Result?:=?Ord(DispatchAction(Message.Msg,?TBasicAction(Message.LParam)));
????????CM_APPKEYDOWN:
??????????if?IsShortCut(TWMKey(Message))?then?Result?:=?1;
????????CM_APPSYSCOMMAND:
??????????if?MainForm?<>?nil?then
????????????with?MainForm?do
??????????????if?(Handle?<>?0)?and?IsWindowEnabled(Handle)?and
????????????????IsWindowVisible(Handle)?then
??????????????begin
????????????????FocusMessages?:=?False;
????????????????SaveFocus?:=?GetFocus;
????????????????Windows.SetFocus(Handle);
????????????????Perform(WM_SYSCOMMAND,?WParam,?LParam);
????????????????Windows.SetFocus(SaveFocus);
????????????????FocusMessages?:=?True;
????????????????Result?:=?1;
??????????????end;
????????CM_ACTIVATE:
??????????if?Assigned(FOnActivate)?then?FOnActivate(Self);
????????CM_DEACTIVATE:
??????????if?Assigned(FOnDeactivate)?then?FOnDeactivate(Self);
????????CM_ENTER:
??????????if?not?IsIconic(FHandle)?and?(GetFocus?=?FHandle)?then
??????????begin
????????????TopWindow?:=?FindTopMostWindow(0);
????????????if?TopWindow?<>?0?then?Windows.SetFocus(TopWindow);
??????????end;
????????WM_HELP,???//?MessageBox(?MB_HELP)
????????CM_INVOKEHELP:?InvokeHelp(WParam,?LParam);
????????CM_WINDOWHOOK:
??????????if?wParam?=?0?then
????????????HookMainWindow(TWindowHook(Pointer(LParam)^))?else
????????????UnhookMainWindow(TWindowHook(Pointer(LParam)^));
????????CM_DIALOGHANDLE:
??????????if?wParam?=?1?then
????????????Result?:=?FDialogHandle
??????????else
????????????FDialogHandle?:=?lParam;
????????WM_SETTINGCHANGE:
??????????begin
????????????Mouse.SettingChanged(wParam);
????????????SettingChange(TWMSettingChange(Message));
????????????Default;
??????????end;
????????WM_FONTCHANGE:
??????????begin
????????????Screen.ResetFonts;
????????????Default;
??????????end;
????????WM_NULL:
??????????CheckSynchronize;??
??????else
????????Default;
??????end;
??except
????HandleException(Self);
??end;
end;
整個WndProc()方法,基本上只包含了一個龐大的case分支,其中給出了每個消息的處理代碼,“WM_”打頭的為Windows定義的窗口消息,“CM_”打頭的為VCL庫自定義的消息。
需要注意的是,這里給出WndProc是屬于TApplication的,也就是那個0×0大小的Application窗口的窗口函數,而每個Form另外都有自己的窗口函數。
那么VCL是如何做到的呢?
本節就來解答這個問題。
只要代碼單元中包含了Forms.pas,就會得到一個對象——Application。利用它可以幫助我們完成許多工作。例如要退出應用程序,可以使用
Application.Terminate();
Application對象是VCL提供的,在Forms.pas中可以看到如下這個定義:
var
Application: TApplication;
當創建一個默認的應用程序時,會自動得到以下幾行代碼:
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
這幾行代碼很簡潔地展示了TApplication的功能、初始化、創建必要的窗體、運行……
但是,這幾行代碼具體做了什么幕后操作呢?Application.Run之后,程序流程走向了哪里?
1.脫離VCL的Windows程序
??? 在此,給出一個用純Pascal所編寫的十分簡單的Windows應用程序,以演示標準Windows程序是如何被建立及運行的。
program?WindowDemo;
uses?Windows,?Messages;
//?窗口函數,窗口接到消息時被Windows所調用
function?WindowProc(hwnd?:?HWND;?uMsg?:?Cardinal;?wParam?:?WPARAM;
lParam?:?LPARAM)?:?LResult;?stdcall;
begin
Result?:=?0;
case?uMsg?of
//?關閉窗口消息,當用戶關閉窗口后,通知主消息循環結束程序
WM_CLOSE?:?PostMessage(hwnd,?WM_QUIT,?0,?0);
//?鼠標左鍵按下消息
WM_LBUTTONDOWN?:?MessageBox(hwnd,?'Hello!',?'和您打個招呼',
MB_ICONINFORMATION);
else
//?其他消息做默認處理
Result?:=?DefWindowProc(hWnd,?uMsg,?wParam,?lParam);
end;
end;
var
wndcls?:?WNDCLASS;?//?窗口類的記錄(結構)類型
hWnd?:?THandle;
Msg?:?tagMSG;?//?消息類型
begin
wndcls.style?:=?CS_DBLCLKS;?//?允許窗口接受鼠標雙擊
wndcls.lpfnWndProc?:=?@WindowProc;?//?為窗口類指定窗口函數
wndcls.cbClsExtra?:=?0;
wndcls.cbWndExtra?:=?0;
wndcls.hInstance?:=?hInstance;
wndcls.hIcon?:=?0;
wndcls.hCursor?:=?LoadCursor(hInstance,?'IDC_ARROW');
wndcls.hbrBackground?:=?COLOR_WINDOWFRAME;
wndcls.lpszMenuName?:=?nil;
wndcls.lpszClassName?:=?'WindowClassDemo';?//?窗口類名稱
//?注冊窗口類
if?RegisterClass(wndcls)?=?0?then
Exit;
//?創建窗口
hWnd?:=?CreateWindow(
'WindowClassDemo',?//?窗口類名稱
'WindowDemo',?//?窗口名稱
WS_BORDER?or?WS_CAPTION?or?WS_SYSMENU,?//?窗口類型
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT),
0,
0,
hInstance,
nil
);
if?hWnd?=?0?then
Exit;
//?顯示窗口
ShowWindow(hWnd,?SW_SHOWNORMAL);
UpdateWindow(hWnd);
//?創建主消息循環,處理消息隊列中的消息并分發
//?直至收到WM_QUIT消息,退出主消息循環,并結束程序
//?WM_QUIT消息由PostMessage()函數發送
while?GetMessage(Msg,?hWnd,?0,?0)?do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end.
該程序沒有使用VCL,它所做的事情就是顯示一個窗口。當在窗口上單擊鼠標右鍵時,會彈出一個友好的對話框向您問好。如果從來不曾了解過這些,那么建議您實際運行一下光盤上的這個程序,對其多一些感性認識。
就是這樣一個簡單的程序,演示了標準Windows程序的流程:
(1)從入口函數WinMain開始。
(2)注冊窗口類及窗口函數(Window Procedure)。
(3)創建并顯示窗口。
(4)進入主消息循環,從消息隊列中獲取并分發消息。
(5)消息被分發后,由Windows操作系統調用窗口函數,由窗口函數對消息進行 處理。
在Object Pascal中看不到所謂的“WinMain”函數。不過,其實整個program的begin處就是Windows程序的入口。
注冊窗口類通過系統API函數RegisterClass()來完成,它向Windows系統注冊一個窗口的類型。
注冊窗口類型完成后,就可以創建這個類型的窗口實例。創建出一個真正的窗口可通過API函數CreateWindow()來實現。
創建出的窗口實例通過API函數ShowWindow()來使得它顯示在屏幕上。
當這一切都完成后,窗口開始進入一個while循環以處理各種消息,直至API函數GetMessage()返回0才退出程序。循環中,程序需要從主線程的消息隊列中取出各種消息,并將它分發給系統,然后由Windows系統調用窗口的窗口函數(WndProc),以完成窗口對消息的響應處理。
???????? TApplication除了定義一個應用程序的特性及行為外,另一個重要的使命就是封裝以上的那些令人討厭的、繁瑣的步驟。
2.Application對象的本質
注意:Application是一個0*0大小的不可見窗口!并且這個窗口是windows應用程序的主窗口,delphi應用程序的主窗體是這個窗口的子窗口,因此會以一個消息循環接受窗口消息并且加以分派和處理。TApplication類封裝了創建秘密窗口和消息循環的程序代碼。所有的事情發生在全局對象Application對象被創建之時。
?? TApplication的構造函數中:
constructor?TApplication.Create(AOwner:?TComponent)
var
??
??if?not?IsLibrary?then?CreateHandle;
??
end;
? 構造函數會調用CreateHandle方法(非常重要的函數)。查看該方法源代碼可知,該方法的任務正是注冊窗口類,并創建一個窗口實例。
procedure?TApplication.CreateHandle;
var
??TempClass:?TWndClass;
??SysMenu:?HMenu;
begin
??if?not?FHandleCreated?and?not?IsConsole?then
??begin
????FObjectInstance?:=?Classes.MakeObjectInstance(WndProc);
????//?如果窗口類不存在,則注冊窗口類
????if?not?GetClassInfo(HInstance,?WindowClass.lpszClassName,?TempClass)?then
????begin
??????WindowClass.hInstance?:=?HInstance;
??????if?Windows.RegisterClass(WindowClass)?=?0?then
????????raise?EOutOfResources.Create(SWindowClass);
????end;
???//?創建窗口,長度和寬度都是0,位置在屏幕中央,返回的句柄FHandle
???//?也就是Tapplication.Handle的值
????FHandle?:=?CreateWindow(WindowClass.lpszClassName,?PChar(FTitle),
??????WS_POPUP?or?WS_CAPTION?or?WS_CLIPSIBLINGS?or?WS_SYSMENU
??????or?WS_MINIMIZEBOX,
??????GetSystemMetrics(SM_CXSCREEN)?div?2,
??????GetSystemMetrics(SM_CYSCREEN)?div?2,
??????0,?0,?0,?0,?HInstance,?nil);
????FTitle?:=?'';
????FHandleCreated?:=?True;
???//?調用SetWindowLong設置窗口的窗口函數(WndProc)
????SetWindowLong(FHandle,?GWL_WNDPROC,?Longint(FObjectInstance));
????if?NewStyleControls?then
????begin
??????SendMessage(FHandle,?WM_SETICON,?1,?GetIconHandle);
??????SetClassLong(FHandle,?GCL_HICON,?GetIconHandle);
????end;
????SysMenu?:=?GetSystemMenu(FHandle,?False);
????DeleteMenu(SysMenu,?SC_MAXIMIZE,?MF_BYCOMMAND);
????DeleteMenu(SysMenu,?SC_SIZE,?MF_BYCOMMAND);
????if?NewStyleControls?then?DeleteMenu(SysMenu,?SC_MOVE,?MF_BYCOMMAND);
??end;
end;
對照一下此前使用純API編寫的窗口程序,就會發現一些它們的相似之處。在CreateHandle()中,可以看到熟悉的RegisterClass()、CreateWindow()等API函數的調用。比較特別的是,CreateHandle()中通過API函數SetWindowLong()來設置窗口的窗口函數:
SetWindowLong(FHandle, GWL_WNDPROC, Longint(FObjectInstance));
此時,SetWindowLong()的第3個參數為窗口函數實例的地址,其中FObjectInstance是由CreateHandle()的第1行代碼
FObjectInstance := Classes.MakeObjectInstance(WndProc);
所創建的實例的指針,而WndProc()則成了真正的窗口函數。
????????? TApplication本身有一個private成員FMainForm,它指向程序員所定義的主窗體,并在TApplication.CreateForm方法中判斷并賦值:
procedure?TApplication.CreateForm(InstanceClass:?TComponentClass;?var?Reference);
var
??Instance:?TComponent;
begin
??Instance?:=?TComponent(InstanceClass.NewInstance);
??TComponent(Reference)?:=?Instance;
??try
????Instance.Create(Self);
??except
????TComponent(Reference)?:=?nil;
????raise;
??end;
???//?第一個創建的窗體實例就是MainForm
??if?(FMainForm?=?nil)?and?(Instance?is?TForm)?then
??begin
????TForm(Instance).HandleNeeded;
????FMainForm?:=?TForm(Instance);
??end;
end;
因此,Delphi為每個應用程序自動生成的代碼中就有對CreateForm的調用,如:
Application.CreateForm(TForm1, Form1);
值得注意的是,如果有一系列的多個CreateForm的調用,則第一個調用CreateForm被創建的窗體,就是整個Application的MainForm。
3.TApplication創建主消息循環
????? 在TApplication的CreateHandle方法中可以看到,SetWindowLong()的調用將TApplication.WndProc設置成了那個0×0大小窗口的窗口函數。
也就是說,在TApplication的構造函數中主要完成了兩件事情:注冊窗口類及窗口函數,創建Application窗口實例。TApplication類的Run方法中:
procedure?TApplication.Run;
begin
??FRunning?:=?True;
??try
????AddExitProc(DoneApplication);
????if?FMainForm?<>?nil?then
????begin
??????case?CmdShow?of
????????SW_SHOWMINNOACTIVE:?FMainForm.FWindowState?:=?wsMinimized;
????????SW_SHOWMAXIMIZED:?MainForm.WindowState?:=?wsMaximized;
??????end;
??????if?FShowMainForm?then
????????if?FMainForm.FWindowState?=?wsMinimized?then
??????????Minimize?else
??????????FMainForm.Visible?:=?True;
??????repeat
????????try
??????????HandleMessage;
????????except
??????????HandleException(Self);
????????end;
??????until?Terminated;
????end;
??finally
????FRunning?:=?False;
??end;
end;
是的,這就是主消息循環??瓷先ニ坪鯖]有取消息、分發消息的過程,其實它們都被包含在HandleMessage()方法中了。HandleMessage()方法其實是對ProcessMessage()方法的調用,而在ProcessMessage()中就可以看到取消息、分發消息的動作了。
procedure?TApplication.HandleMessage;
var
??Msg:?TMsg;
begin
??if?not?ProcessMessage(Msg)?then?Idle(Msg);
end;
function?TApplication.ProcessMessage(var?Msg:?TMsg):?Boolean;
var
??Handled:?Boolean;
begin
??Result?:=?False;
??//?取消息
??if?PeekMessage(Msg,?0,?0,?0,?PM_REMOVE)?then
??begin
????Result?:=?True;
????if?Msg.Message?<>?WM_QUIT?then
????begin
??????Handled?:=?False;
??????if?Assigned(FOnMessage)?then?FOnMessage(Msg,?Handled);
??????if?not?IsHintMsg(Msg)?and?not?Handled?and?not?IsMDIMsg(Msg)?and
????????not?IsKeyMsg(Msg)?and?not?IsDlgMsg(Msg)?then
??????begin
????????//?熟悉的分發消息過程
????????TranslateMessage(Msg);
????????DispatchMessage(Msg);
??????end;
????end
????else
??????//?如果取到的消息為WM_QUIT,則將Fterminate設為真
???????//?以通知主消息循環退出
???????//?這和WindowDemo程序中判斷GetMessage()函數返回值是否為0等效
???????//?因為GetMessage()函數取出的消息如果是WM_QUIT,它的返回值為0
??????FTerminate?:=?True;
??end;
end;
4.窗口函數(WndProc)處理消息
窗口函數是一個回調函數,它被Windows系統所調用,其參數會被給出消息編號、消息參數等信息,以便進行處理。
典型的窗口函數中會包含一個大的case分支,以處理不同的消息。TApplication.CreateHandle()的代碼時提到過,CreateHandle()將Application窗口的窗口函數設置為WndProc()。那么,現在就來看一下這個WndProc:
procedure?TApplication.WndProc(var?Message:?TMessage);
type?//?函數內嵌定義的類型,只限函數內部使用
??TInitTestLibrary?=?function(Size:?DWord;?PAutoClassInfo:?Pointer):?Boolean;?stdcall;
var
??I:?Integer;
??SaveFocus,?TopWindow:?HWnd;
??InitTestLibrary:?TInitTestLibrary;
??//?內嵌函數,默認的消息處理
???//?調用Windows的API函數DefWindowProc
??procedure?Default;
??begin
????with?Message?do
??????Result?:=?DefWindowProc(FHandle,?Msg,?WParam,?LParam);
??end;
??procedure?DrawAppIcon;
??var
????DC:?HDC;
????PS:?TPaintStruct;
??begin
????with?Message?do
????begin
??????DC?:=?BeginPaint(FHandle,?PS);
??????DrawIcon(DC,?0,?0,?GetIconHandle);
??????EndPaint(FHandle,?PS);
????end;
??end;
begin
??try
????Message.Result?:=?0;
????for?I?:=?0?to?FWindowHooks.Count?-?1?do
??????if?TWindowHook(FWindowHooks[I]^)(Message)?then?Exit;
????CheckIniChange(Message);
????with?Message?do
??????//?開始龐大的case分支,對不同的消息做出不同的處理
??????case?Msg?of
????????WM_SYSCOMMAND:
??????????case?WParam?and?$FFF0?of
????????????SC_MINIMIZE:?Minimize;
????????????SC_RESTORE:?Restore;
??????????else
????????????Default;
??????????end;
????????WM_CLOSE:
??????????if?MainForm?<>?nil?then?MainForm.Close;
????????WM_PAINT:
??????????if?IsIconic(FHandle)?then?DrawAppIcon?else?Default;
????????WM_ERASEBKGND:
??????????begin
????????????Message.Msg?:=?WM_ICONERASEBKGND;
????????????Default;
??????????end;
????????WM_QUERYDRAGICON:
??????????Result?:=?GetIconHandle;
????????WM_SETFOCUS:
??????????begin
????????????PostMessage(FHandle,?CM_ENTER,?0,?0);
????????????Default;
??????????end;
????????WM_ACTIVATEAPP:
??????????begin
????????????Default;
????????????FActive?:=?TWMActivateApp(Message).Active;
????????????if?TWMActivateApp(Message).Active?then
????????????begin
??????????????RestoreTopMosts;
??????????????PostMessage(FHandle,?CM_ACTIVATE,?0,?0)
????????????end
????????????else
????????????begin
??????????????NormalizeTopMosts;
??????????????PostMessage(FHandle,?CM_DEACTIVATE,?0,?0);
????????????end;
??????????end;
????????WM_ENABLE:
??????????if?TWMEnable(Message).Enabled?then
??????????begin
????????????RestoreTopMosts;
????????????if?FWindowList?<>?nil?then
????????????begin
??????????????EnableTaskWindows(FWindowList);
??????????????FWindowList?:=?nil;
????????????end;
????????????Default;
??????????end?else
??????????begin
????????????Default;
????????????if?FWindowList?=?nil?then
??????????????FWindowList?:=?DisableTaskWindows(Handle);
????????????NormalizeAllTopMosts;
??????????end;
????????WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:
??????????Result?:=?SendMessage(LParam,?CN_BASE?+?Msg,?WParam,?LParam);
????????WM_ENDSESSION:?if?TWMEndSession(Message).EndSession?then?FTerminate?:=?True;
????????WM_COPYDATA:
??????????if?(PCopyDataStruct(Message.lParam)^.dwData?=?DWORD($DE534454))?and
????????????(FAllowTesting)?then
????????????if?FTestLib?=?0?then
????????????begin
??????????????FTestLib?:=?SafeLoadLibrary('vcltest3.dll');
??????????????if?FTestLib?<>?0?then
??????????????begin
????????????????Result?:=?0;
????????????????@InitTestLibrary?:=?GetProcAddress(FTestLib,?'RegisterAutomation');
????????????????if?@InitTestLibrary?<>?nil?then
??????????????????InitTestLibrary(PCopyDataStruct(Message.lParam)^.cbData,
????????????????????PCopyDataStruct(Message.lParam)^.lpData);
??????????????end
??????????????else
??????????????begin
????????????????Result?:=?GetLastError;
????????????????FTestLib?:=?0;
??????????????end;
????????????end
????????????else
??????????????Result?:=?0;
????????CM_ACTIONEXECUTE,?CM_ACTIONUPDATE:
??????????Message.Result?:=?Ord(DispatchAction(Message.Msg,?TBasicAction(Message.LParam)));
????????CM_APPKEYDOWN:
??????????if?IsShortCut(TWMKey(Message))?then?Result?:=?1;
????????CM_APPSYSCOMMAND:
??????????if?MainForm?<>?nil?then
????????????with?MainForm?do
??????????????if?(Handle?<>?0)?and?IsWindowEnabled(Handle)?and
????????????????IsWindowVisible(Handle)?then
??????????????begin
????????????????FocusMessages?:=?False;
????????????????SaveFocus?:=?GetFocus;
????????????????Windows.SetFocus(Handle);
????????????????Perform(WM_SYSCOMMAND,?WParam,?LParam);
????????????????Windows.SetFocus(SaveFocus);
????????????????FocusMessages?:=?True;
????????????????Result?:=?1;
??????????????end;
????????CM_ACTIVATE:
??????????if?Assigned(FOnActivate)?then?FOnActivate(Self);
????????CM_DEACTIVATE:
??????????if?Assigned(FOnDeactivate)?then?FOnDeactivate(Self);
????????CM_ENTER:
??????????if?not?IsIconic(FHandle)?and?(GetFocus?=?FHandle)?then
??????????begin
????????????TopWindow?:=?FindTopMostWindow(0);
????????????if?TopWindow?<>?0?then?Windows.SetFocus(TopWindow);
??????????end;
????????WM_HELP,???//?MessageBox(?MB_HELP)
????????CM_INVOKEHELP:?InvokeHelp(WParam,?LParam);
????????CM_WINDOWHOOK:
??????????if?wParam?=?0?then
????????????HookMainWindow(TWindowHook(Pointer(LParam)^))?else
????????????UnhookMainWindow(TWindowHook(Pointer(LParam)^));
????????CM_DIALOGHANDLE:
??????????if?wParam?=?1?then
????????????Result?:=?FDialogHandle
??????????else
????????????FDialogHandle?:=?lParam;
????????WM_SETTINGCHANGE:
??????????begin
????????????Mouse.SettingChanged(wParam);
????????????SettingChange(TWMSettingChange(Message));
????????????Default;
??????????end;
????????WM_FONTCHANGE:
??????????begin
????????????Screen.ResetFonts;
????????????Default;
??????????end;
????????WM_NULL:
??????????CheckSynchronize;??
??????else
????????Default;
??????end;
??except
????HandleException(Self);
??end;
end;
整個WndProc()方法,基本上只包含了一個龐大的case分支,其中給出了每個消息的處理代碼,“WM_”打頭的為Windows定義的窗口消息,“CM_”打頭的為VCL庫自定義的消息。
需要注意的是,這里給出WndProc是屬于TApplication的,也就是那個0×0大小的Application窗口的窗口函數,而每個Form另外都有自己的窗口函數。
轉載于:https://www.cnblogs.com/sideandside/archive/2007/05/09/740309.html
總結
以上是生活随笔為你收集整理的TApplication与主消息循环的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 成绩管理系统
- 下一篇: .NET打包工具怎么注册 .dll文件?