線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:1045
推到 Plurk!
推到 Facebook!

Inside the VCL: From Messages to Events Part I

 
conundrum
尊榮會員


發表:893
回覆:1272
積分:643
註冊:2004-01-06

發送簡訊給我
#1 引用回覆 回覆 發表時間:2004-02-19 20:52:16 IP:61.64.xxx.xxx 未訂閱
http://bcbcaq.freeservers.com/MessagesToEvents.html     

Inside the VCL: From Messages to Events Part I

Most introductory C courses fortify the ideas of syntax, object-oriented design, coding styles, and a plethora of other topics that serve to form a solid foundation for the language itself. Indeed, such a fortification is neccessary to mastering any progamming language. However, being an apt C programmer is far from being a strong Windows developer. It isn’t until one embarks upon the journey of implementing an event-driven program that a true appreciation of the idea of a window-based environment can be gained. Indeed, Windows itself is based upon a message or “signal” oriented framework that lends to a better understanding of the system as a whole. These signals form the foundation of Windows’ multi-tasking capabilities. The fact that C Builder encapsulates most the API leads many to believe that there is some type magic going on behind the scene in the VCL. In fact, although it is sometimes a subtle point, a typical VCL application contains all the necessary underlying API work. The level of abstraction exhibited by the VCL presents us with an intuitive, easily maintanable framework, of which the Rapid Application Development (RAD) theme is based. Nonetheless, a true understanding of the VCL cannot be appreciated without some knowledge of the API itself. The purpose of this article is to introduce the basic constructs of a VCL application from an API point of view. Mastery of the Windows API is surely not a prerequisite here. As such, many of the presentations of this first article will be preceeded by a high-level introduction of the underlying API methods. The intent here is introduce the inner workings of the TApplication and TWinControl classes from an API point of view; a look “under the hood” if you will. This, in turn, will allow a common foundation of terminology and notation from which subsequent presentations can be based. I. An Event-Driven Implementation Recall, the basic “Hello World!” program which simply outputs this, almost ubiquitous, single line of text. For completeness, this is provided in Listing 1.0.
int main(int argc, char *argv[])    {        std::cout << "Hello World!" << std::endl;        return 0;    }     
Common knowledge tells us that this is clearly not an event-driven program. That is, a certain event does not trigger a certain action at any given time. Imagine now, a primitive attempt to extend this simple program toward an event-driven implementation as in Listing 1.1.
int main(int argc, char* argv[])    {        while (getch() != 'Q')        {            std::cout << "Hello World!" << std::endl;        }        return 0;    }     
 
Here, we see that the program will simply output the phrase, “Hello World!”, for every keystroke, until the user enters the letter “Q”. While this is far from a de facto event-driven program, it lends to the idea of an “action / reaction” implementation. That is, for every user action, there is some reaction. Sometimes this reaction is simply the feedback of textual information as in our simple program above. Sometimes the reaction will be the output of a printed page of text. Sometimes the reaction will be as complicated as a change in the direction of an animated figure in a graphically intense 3-D game. Perhaps even the output will be nothing at all. The idea here is that the user implicitly defines the reactions or “path” of the program through his or her own actions. As mentioned, Windows is an event-driven, message based environment. Clicking the Start Button, for example, will cause the Start Menu to appear. Like before, this is an example of an “action / reaction” implementation, which happens to be the standard behavior of most Windows programs. As we will shortly see, the actual main processing in any Windows program resembles the form of our simple “Hello World!” extension. To begin to understand the fundamental connection between a text-based C application and an event-driven Windows application, let us first examine a typical application message loop, commonly referred to as the main message “pump” as provided in Listing 1.2.
WINAPI APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,        LPTSTR lpCmdLine, int nCmdShow)    {        //        //  ...        //             MSG Msg;        while (GetMessage(&Msg, NULL, 0, 0))        {            TranslateMessage(&Msg);            DispatchMessage(&Msg);        }        return 0;    }     
The first thing to notice from Listing 1.2 is that the familiar main function of Listing 1.1 is replaced by its Windows equivalent, WinMain. From within the implementation of this WinMain function, we can begin to see how a Windows application can be based upon events. Just as the getch function of Listing 1.1 served to retrieve a character from the keyboard, the GetMessage API function is designed to retrieve a message. These messages originate from the system – the keyboard driver, mouse driver, and other device drivers. In fact, the implementation of our WinMain function consists of nothing more than what may at first seem like an infinite while loop. This latter statement would certainly be accurate if the GetMessage function were to always return true. In fact, the GetMessage function returns true for all messages except WM_QUIT. The purpose of this function is to simply retrieve a message from the application’s message queue (filled by Windows). Again, this is working in much the same way as the getch function from our “Hello World!” example. The TranslateMessage API function serves to interpret keyboard input messages. Specifically, this function converts virtual-key code messages (WM_KEYDOWN, WM_KEYUP) into the more useful (WM_CHAR) character value message. It the then the job of the DispatchMessage API function to dispatch or "send" the message to the correct window. Specifically, this information is sent to a special callback function known as a window procedure. The DispatchMessage function serves a role analogous to that of the cout statement of Listing1.1, except that DispatchMessage directs a message to a window procedure instead of directing text to an iostream. The message retrieval/transmission cycle continuously repeats until the WM_QUIT message is received (i.e., the GetMessage function returns false, breaking the while loop, or “terminating the pump”). The information that the GetMessage function retrieves is placed in a MSG structure. The six data members of this structure serve to inform an application of a particular event.
 typedef struct tagMSG {        HWND   hwnd;             UINT   message;         WPARAM wParam;         LPARAM lParam;         DWORD  time;         POINT  pt;     } MSG;    
The hwnd data member contains the handle of the window that the message is intended for. Again, this is used by the DispatchMessage API function to determine to which window's window procedure to send the mesasge. The message data member contains the actual message constant (e.g., WM_QUIT). The wParam and lParam members are 32-bit message parameters that may contain useful information about the specifics of the message. For example, the wParam member of the WM_QUIT message contains an integer value identifying the “exit code”. The time data member identifies the value of the system clock when the message was posted, while the pt member contains the location of the mouse cursor at this time II. Communication via Messages As mentioned earlier, the window procedure of a window receives any messages that are sent to it. Most of these messages will be sent by Windows itself, either directly to the window procedure, or indirectly through our main message pump. Messages that pass through the message pump are called queued messages, and are commonly referred to as “posted” messages. These typically include messages resulting from user input via the mouse or keyboard. Painting messages, such as WM_PAINT, are also queued, as are system timer (WM_TIMER) and termination (WM_QUIT) messages. Most other messages are sent directly to a window’s window procedure. These latter messages are called non-queued messages. To get a better understanding of window messages, let us consider a simple VCL application consisting of a single form with a TButton control and a TEdit control parented to it, as depicted in Figure 1.1. The code for this example is provided in Listing 1.3. //---------------------------------------------------------------------- #include #pragma hdrstop #include "Unit1.h" //---------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //---------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //---------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject* Sender) { Edit1->Text = "Hello World!"; } //---------------------------------------------------------------------- In fact, our entire implementation consists of nothing more than a single event handler for the TButton::OnClick event, in which we simply maniuplate the TEdit::Text property. We see here that when the button is clicked, the edit control’s text is changed to the value “Hello World!”. This is another example of an “action / reaction” implementation. The action being the user clicking the button control, while the reaction is a change in the edit control’s text. While this simple program is far from being a sophisticated application, we get first hand evidence here of the level of abstraction presented by the VCL. Namely, unlike in a raw API program, we did not have to send any window messages to the edit control itself, nor did we have to tap into the button control’s window procedure and respond to the WM_LBUTTONUP message. Further, we did not have to code the standard windows message pump. How then can this program operate in an event driven fashion? To see how this is accomplished, we need to examine our project’s source code as provided in Listing 1.4.
 //---------------------------------------------------------------------- 
#include     #pragma hdrstop         //----------------------------------------------------------------------    USEFORM("Unit1.cpp", Form1);    USERES("Project1.res");    //----------------------------------------------------------------------         WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)    {        try        {             Application->Initialize();             Application->CreateForm(__classid(TForm1), &Form1);             Application->Run();        }        catch (Exception &exception)        {             Application->ShowException(&exception);        }        return 0;    }    //----------------------------------------------------------------------
Inside the VCL: From Messages to Events Part I By Damon Chandler Most introductory C courses fortify the ideas of syntax, object-oriented design, coding styles, and a plethora of other topics that serve to form a solid foundation for the language itself. Indeed, such a fortification is neccessary to mastering any progamming language. However, being an apt C programmer is far from being a strong Windows developer. It isn’t until one embarks upon the journey of implementing an event-driven program that a true appreciation of the idea of a window-based environment can be gained. Indeed, Windows itself is based upon a message or “signal” oriented framework that lends to a better understanding of the system as a whole. These signals form the foundation of Windows’ multi-tasking capabilities. The fact that C Builder encapsulates most the API leads many to believe that there is some type magic going on behind the scene in the VCL. In fact, although it is sometimes a subtle point, a typical VCL application contains all the necessary underlying API work. The level of abstraction exhibited by the VCL presents us with an intuitive, easily maintanable framework, of which the Rapid Application Development (RAD) theme is based. Nonetheless, a true understanding of the VCL cannot be appreciated without some knowledge of the API itself. The purpose of this article is to introduce the basic constructs of a VCL application from an API point of view. Mastery of the Windows API is surely not a prerequisite here. As such, many of the presentations of this first article will be preceeded by a high-level introduction of the underlying API methods. The intent here is introduce the inner workings of the TApplication and TWinControl classes from an API point of view; a look “under the hood” if you will. This, in turn, will allow a common foundation of terminology and notation from which subsequent presentations can be based. I. An Event-Driven Implementation Recall, the basic “Hello World!” program which simply outputs this, almost ubiquitous, single line of text. For completeness, this is provided in Listing 1.0. int main(int argc, char *argv[]) { std::cout << "Hello World!" << std::endl; return 0; } Listing 1.0 Common knowledge tells us that this is clearly not an event-driven program. That is, a certain event does not trigger a certain action at any given time. Imagine now, a primitive attempt to extend this simple program toward an event-driven implementation as in Listing 1.1. int main(int argc, char* argv[]) { while (getch() != 'Q') { std::cout << "Hello World!" << std::endl; } return 0; } Listing 1.1 Here, we see that the program will simply output the phrase, “Hello World!”, for every keystroke, until the user enters the letter “Q”. While this is far from a de facto event-driven program, it lends to the idea of an “action / reaction” implementation. That is, for every user action, there is some reaction. Sometimes this reaction is simply the feedback of textual information as in our simple program above. Sometimes the reaction will be the output of a printed page of text. Sometimes the reaction will be as complicated as a change in the direction of an animated figure in a graphically intense 3-D game. Perhaps even the output will be nothing at all. The idea here is that the user implicitly defines the reactions or “path” of the program through his or her own actions. As mentioned, Windows is an event-driven, message based environment. Clicking the Start Button, for example, will cause the Start Menu to appear. Like before, this is an example of an “action / reaction” implementation, which happens to be the standard behavior of most Windows programs. As we will shortly see, the actual main processing in any Windows program resembles the form of our simple “Hello World!” extension. To begin to understand the fundamental connection between a text-based C application and an event-driven Windows application, let us first examine a typical application message loop, commonly referred to as the main message “pump” as provided in Listing 1.2. WINAPI APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // // ... // MSG Msg; while (GetMessage(&Msg, NULL, 0, 0)) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return 0; } Listing 1.2 The first thing to notice from Listing 1.2 is that the familiar main function of Listing 1.1 is replaced by its Windows equivalent, WinMain. From within the implementation of this WinMain function, we can begin to see how a Windows application can be based upon events. Just as the getch function of Listing 1.1 served to retrieve a character from the keyboard, the GetMessage API function is designed to retrieve a message. These messages originate from the system – the keyboard driver, mouse driver, and other device drivers. In fact, the implementation of our WinMain function consists of nothing more than what may at first seem like an infinite while loop. This latter statement would certainly be accurate if the GetMessage function were to always return true. In fact, the GetMessage function returns true for all messages except WM_QUIT. The purpose of this function is to simply retrieve a message from the application’s message queue (filled by Windows). Again, this is working in much the same way as the getch function from our “Hello World!” example. The TranslateMessage API function serves to interpret keyboard input messages. Specifically, this function converts virtual-key code messages (WM_KEYDOWN, WM_KEYUP) into the more useful (WM_CHAR) character value message. It the then the job of the DispatchMessage API function to dispatch or "send" the message to the correct window. Specifically, this information is sent to a special callback function known as a window procedure. The DispatchMessage function serves a role analogous to that of the cout statement of Listing1.1, except that DispatchMessage directs a message to a window procedure instead of directing text to an iostream. The message retrieval/transmission cycle continuously repeats until the WM_QUIT message is received (i.e., the GetMessage function returns false, breaking the while loop, or “terminating the pump”). The information that the GetMessage function retrieves is placed in a MSG structure. The six data members of this structure serve to inform an application of a particular event. typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG; The hwnd data member contains the handle of the window that the message is intended for. Again, this is used by the DispatchMessage API function to determine to which window's window procedure to send the mesasge. The message data member contains the actual message constant (e.g., WM_QUIT). The wParam and lParam members are 32-bit message parameters that may contain useful information about the specifics of the message. For example, the wParam member of the WM_QUIT message contains an integer value identifying the “exit code”. The time data member identifies the value of the system clock when the message was posted, while the pt member contains the location of the mouse cursor at this time. II. Communication via Messages As mentioned earlier, the window procedure of a window receives any messages that are sent to it. Most of these messages will be sent by Windows itself, either directly to the window procedure, or indirectly through our main message pump. Messages that pass through the message pump are called queued messages, and are commonly referred to as “posted” messages. These typically include messages resulting from user input via the mouse or keyboard. Painting messages, such as WM_PAINT, are also queued, as are system timer (WM_TIMER) and termination (WM_QUIT) messages. Most other messages are sent directly to a window’s window procedure. These latter messages are called non-queued messages. To get a better understanding of window messages, let us consider a simple VCL application consisting of a single form with a TButton control and a TEdit control parented to it, as depicted in Figure 1.1. The code for this example is provided in Listing 1.3. //---------------------------------------------------------------------- #include #pragma hdrstop #include "Unit1.h" //---------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //---------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //---------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject* Sender) { Edit1->Text = "Hello World!"; } //---------------------------------------------------------------------- Listing 1.3 In fact, our entire implementation consists of nothing more than a single event handler for the TButton::OnClick event, in which we simply maniuplate the TEdit::Text property. We see here that when the button is clicked, the edit control’s text is changed to the value “Hello World!”. This is another example of an “action / reaction” implementation. The action being the user clicking the button control, while the reaction is a change in the edit control’s text. While this simple program is far from being a sophisticated application, we get first hand evidence here of the level of abstraction presented by the VCL. Namely, unlike in a raw API program, we did not have to send any window messages to the edit control itself, nor did we have to tap into the button control’s window procedure and respond to the WM_LBUTTONUP message. Further, we did not have to code the standard windows message pump. How then can this program operate in an event driven fashion? To see how this is accomplished, we need to examine our project’s source code as provided in Listing 1.4. //---------------------------------------------------------------------- #include #pragma hdrstop //---------------------------------------------------------------------- USEFORM("Unit1.cpp", Form1); USERES("Project1.res"); //---------------------------------------------------------------------- WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->Run(); } catch (Exception &exception) { Application->ShowException(&exception); } return 0; } //---------------------------------------------------------------------- Listing 1.4 Notice that our project’s source code contains the aforementioned WinMain function. Specifically, this is the exact same WinMain function as in Listing 1.2. The main difference here is that the entire message loop, window creation, and exception handling mechanisms are encapsulated by the TApplication and TWinControl classes. In fact, the standard Windows message loop is entered via the TApplication::Run member function. We can begin to see here that a VCL application is indeed a standard Windows application consisting of a WinMain function and a message loop. Now that we have convinced ourselves that the standard message pump is indeed present, let us return to our TForm1 class implementation and examine how the TWinControl class relieves the hassle of dealing with messages, including those dispatched from the Application object. In Listing 1.3 our button’s OnClick event handler consists of a single line of code, which simply changes the edit control’s text via the TWinControl::Text property. Yet, in an API implementation, to actually change the text of any windowed control, the WM_SETTEXT message is used, as demonstrated in Listing 1.5.
void __fastcall TForm1::Button1Click(TObject* Sender)    {        // Edit1->Text = "Hello World!";        SendMessage(Edit1->Handle, WM_SETTEXT, 0,                    reinterpret_cast("Hello World!"));    }
 
Here we use the SendMessage API function to directly send the edit control’s window procedure the WM_SETTEXT message. Not only is this code hard to maintain, it is less concise and not as type-safe as the TWinControl::Text Property. The latter simply encapsulates the SendMessage call via the TWinControl::SetText member function. In fact, the TWinControl class allows manipulation of many window attributes through the use of its member functions. There are very few occassions where we will need to explicitly use the SendMessage API function. We now know that changing a TWinControl’s Text property will indirectly send the WM_SETTEXT message to the control itself. However, we have yet to examine the messages associated with our button’s OnClick event. Specifically, let us explore the messages that are sent to the button when it is clicked, and examine how these messages relate to our Button1Click event handler. As previously stated, messages that are directed to a window are sent to a special function called a window procedure. For example, when we used the SendMessage API function, in Listing 1.5, we sent our edit control’s window procedure the WM_SETTEXT message. In turn, the edit control handled this message by changing its text. In much the same way, when our button is clicked we indirectly handled the WM_LBUTTONUP message through our button’s OnClick event handler. The difference here is that Windows itself, indirectly sent our Button the WM_LBUTTONUP message. The TWinControl class handled this message by testing the location of the mouse cursor, then appropriately calling our Button1Click member function. In general, when the left mouse button is depressed while the mouse cursor is within the bounds of any enabled windowed control within our application, Windows sends the WM_LBUTTONDOWN message to our message pump. The DispatchMessage function within our message loop then directs this message to the control’s window procedure. Similarly, if the left mouse button is then released, the control is indirectly sent the WM_LBUTTONUP message. These two messages are examples of queued messages, and are commonly used to define a mouse “click”. Typically, these messages are explicitly handled in the control’s window procedure as demonstrated in Listing 1.6.
LRESULT CALLBACK ButtonWndProc(        HWND hwnd,        // handle of window        UINT uMsg,        // message identifier        WPARAM wParam,    // "word" message parameter        LPARAM lParam)    // "long" message parameter    {                switch (uMsg)         {             case WM_LBUTTONDOWN:             {                SetCapture(hwnd);                break;            }                  case WM_LBUTTONUP:             {                ReleaseCapture();                SendMessage(hwndEdit, WM_SETTEXT, 0,                            reinterpret_cast("Hello World!"));                break;            }             default:                 return DefWindowProc(hwnd, uMsg, wParam, lParam);         }         return 0;     }          
The OnClick event, however, saves us the hassle of implementing a window procedure as that of Listing 1.6. This is especially useful when a control need only respond to a single user event. We will examine the specifics of the TButton and TEdit classes in subsequent articles. III. Window Procedures and VCL Events In the previous section, we discussed queued and non-queued messages, and how specific messages can be used to invoke certain changes. For example, we used to the WM_SETTEXT message to change the text entry in our edit control. Similarly, we could have used the WM_SETFONT message to change the font of this text. We also implemented an OnClick event handler for a TButton control, and examined how this event related to the underlying window messages WM_LBUTTONDOWN and WM_LBUTTONUP. Let us now investigate, in-depth, how these events are triggered and how we can manipulate the fashion in which the messages that trigger these events are handled. In this section and the next (Message Handling), we will discover how user-defined event handlers are triggered in response to window messages. Recall, our simple program of Listing 1.3 consisting of a single form with a TEdit control and a TButton control parented to it. Focussing on the TButton control, let us inspect the various events published in the Object Inspector, as depicted in Figure 1.2. Since we already know that the WM_LBUTTONUP message fires the OnClick event, let us examine the OnEnter and OnExit events. When a windowed control receives keyboard or “input” focus, its window procedure is sent the WM_SETFOCUS message. For example, notice that our TButton oftentimes displays a selection rectangle that surrounds its client area, as depicted in Figure 1.1. This rectangle serves to indicate that the button has keyboard focus, and is rendered in response to the WM_SETFOCUS message. When the button loses keyboard focus (i.e., another windowed control receives keyboard focus), the button is redrawn without the selection rectangle. This latter task is performed in response to the WM_KILLFOCUS message. For TWinControl descendants, both the OnExit and OnEnter events are triggered by the WM_SETFOCUS message. While this may seem counterintuitive, we will see in later articles how this functionality is accomplished by TCustomForm::SetFocusedControl member function. For now, it is sufficient to understand that we can provide a handler for the button’s OnEnter event to perform special processing when the button receives keyboard focus. Similarly, we can use the OnExit event to perform processing when the button loses keyboard focus. The OnKeyDown, OnKeyPress, and OnKeyUp events work in much the same way as the OnEnter and OnExit events. Namely, they are fired in response to a certain window message. In this case, the messages are WM_KEYDOWN, WM_CHAR, and WM_KEYUP, respectively. The WM_KEYDOWN message is sent to the window procedure of a window that has input focus when a non-system key is pressed. For example, pressing the letter ‘A’ when our button has keyboard focus will cause Windows to send our message pump the WM_KEYDOWN message (remember, this is a keyboard message, and is thus a queued message). The DispatchMessage function within our message loop will then send the WM_KEYDOWN message to our button’s window procedure. The OnKeyDown event is fired in response to this message. In the same fashion, the WM_CHAR and WM_KEYUP messages are responsible for the OnKeyPress and OnKeyUp events, respectively. While the OnKeyDown, OnKeyPress, and OnKeyUp events correspond to keyboard input, the OnMouseDown, OnMouseMove, and OnMouseUp events are fired in response to mouse input. OnMouseDown is fired in response to the WM_LBUTTONDOWN, WM_MBUTTONDOWN, or WM_RBUTTONDOWN messages. OnMouseMove is fired in response to the WM_MOUSEMOVE message. OnMouseUp is fired in response to the WM_LBUTTONUP, WM_MBUTTONUP, or WM_RBUTTONUP messages. Similarly, the OnDblClick event is fired in response to the WM_LBUTTONDBLCLK message. While we have discussed which of our button’s events correspond to which window messages, we still have yet to investigate how exactly these events are triggered. Since the messages are sent to the window procedure of our button, we first need to examine the TWinControl::WndProc member function. This latter function is simply a member function that is called from the TWinControl::MainWndProc member function. As mentioned, all messages that are sent to a window are ultimately sent to its window procedure, which is simply a non-member callback function that defines how the window responds to certain messages. We saw in Listing 1.6 an example of a window procedure that defines how a control responds to the WM_LBUTTONDOWN and WM_LBUTTON
系統時間:2024-06-27 2:56:21
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!