天天看點

windows消息處理(強烈推薦,收藏)

由于看了一下,比較好了解,暫時先放到這裡,待有空再翻譯。隻是在每節後大緻介紹一下講的内容。

感覺寫的比較全,無論從消息的原理還是從MFC操作上來說,值得一看,我也在此做個收藏。

(一)

說明:以下首先對消息進行介紹,然後在消息進行中,使用類向導建立消息循環,這個操作是在vc6.0(或者之下版本)操作的。

Perhaps one of the most important means of communication in windows is Messages. The traditional program starts at your<code>main()</code> function, moves down line-by-line in your code, and eventually exits. The Windows concept is different. The way you program in windows is by responding to events. These events are called messages.

Messages can signal many events, caused by the user, the operating system, or another program. An event could be caused by a mousemove, a key-press, or by your window getting resized. There are 2 kinds of messages: a window message, or a thread message. Since Threads are an advanced issue, I'll refer only to window messages.

In general, a message must be sent to a window. All the messages sent to you are stored in a Message Queue, a place in the memory which stores message which are transferred between applications.

the way you retrieve messages from the Message Queue is by creating a Message Loop. A Message Loop is a loop that checks for messages in the Message Queue. once a message is received, the Message Loop dispatches the message by calling a Message Handler, a function designed to help the Message Loop at processing the message.

The Message Loop will end when a <code>WM_QUIT</code> message is received, signaling the application to end. This message could be sent because the user selected Exit from your File menu, clicked on the close button (the X small button in the upper right corner of your window), or pressed Alt+F4. Windows has default Message Handlers for almost all the messages, giving your window the default window behavior. In fact, all the standard controls are simply windows with Message handlers. Take a Button for example. When it gets a <code>WM_PAINT</code>message it will draw the button. When you Left-click the button, it gets a<code>WM_LBUTTONDOWN</code> message, and it draws the pressed-button. When you let go of the mouse button it receives a<code>WM_LBUTTONUP</code> message, and respectively draws the button.

Windows defines many different message types (which are stored as UINTs). They usually begin with the letters "WM" and an underscore, as in<code>WM_CHAR</code> and<code>WM_SIZE</code>. The names of the message are usually a good indicator of what they represent.<code>WM_SIZE</code> for sizing messages,<code>WM_CHAR</code> for character entry messages and so on. The naming convention in MFC for message handler functions is to take away the "WM_" and replace it with "On", so the message handler for<code>WM_SIZE</code> is usually called<code>OnSize</code>.

A message comes with 2 parameters that give you more information about the event. Each parameter is a 32-bit value: lParam and wParam. For example:<code>WM_MOUSEMOVE</code> will give you the mouse coordinates in one paramter, and in the other some flags indicating the state of the ALT, Shift, CTRL and mouse buttons.

A Message may also return a value which allows you to send data back to the the sending program. For example, the<code>WM_QUERYENDSESSION</code> message sent by windows before the computer is shutdown, expects you to return a Boolean value. If your application can terminate conveniently, it should return TRUE; otherwise, it should return FALSE. Other message such as the<code>WM_CTLCOLOR</code> messages expect you to return an<code>HBRUSH</code>.

Note: In the rest of the tutorial I will focus on MFC for simplicity reasons. All the information above applies to both SDK programs, and MFC programs.

Fortunately, MFC will give all the code needed for the message loop, One of the<code>CWinApp</code> member functions called by WinMain—Run—provides the message loop that pumps messages to the application's window. The only thing you need to do so you can receive messages is to create Message Handlers, and inform MFC of them. So, how do you create a Message Handler? Once you have an MFC C++ class that encapsulates a window, you can easily use ClassWizard to create Message Handlers.

Press Ctrl + W to start the ClassWizard, or right click the Add button and select ClassWizard from the context menu. Open ClassWizard, select Message Maps tab. In Class name select the name of your C++ class. on Object IDs select either the ID of a menu item (for messages caused by the user interacting with a menu), the ID of a control (for messages caused by the user interacting with a control), or the first option to handle messages other messages. Choose the message from the Messages list,<code>WM_SIZE</code> for example, and Click on Add Function. Click OK, then click Edit Code. ClassWizard will write a new empty function (<code>OnSize</code> for example) with the proper prototype in the class header. The code generated should look similar to this:

That's it, now you can handle messages. If you want to handle a message and then let the default message handler handle the message, you should call the base class member function that corresponds with the message. Take the following<code>WM_CLOSE</code> Message Handler as an example:

If you want windows to get a shot at the message, you should call the base class member function OnClose:

You could use this behavior to screen-out events. For example, a program that prompts the user if he is sure that he wants to close the window:

Besides receiving messages, you will often find your self sending messages. You might want to send messages to communicate between to windows in your program, or to communicate between different programs. In order to send a message you need a pointer to a c++ window class. This can be retrieved using various functions, including <code>CWnd::FindWindow, GetDlgItem(), GetParent(),</code> and more. The<code>CWnd</code> class has a<code>SendMessage()</code> member function which allows you to send messages to it's window. For example, Let’s say you have a CWnd pointer to the Calculator, and you want to close it. What you should do is send a <code>WM_CLOSE</code>message, which will notify the Calculator that it should close. You can use the following code. In order to get a pointer to Calculator, I use the static<code>CWnd::FindWindow()</code> function and pass the title of the window, which in our case is "Calculator".

(二)

Whenever your window receives a message, MFC will call a member function of your class. But how does MFC know what function to call?

MFC uses a technique called Message Maps. A Message Map is a table that associates messages with functions. When you receive a message, MFC will go through your Message Map and search for a corresponding Message Handler. I have showed in Part 1 how you add a Message Handler to the Message Map by using ClassWizard, but what really happens code-wise?

MFC uses a large set of rather complicated macros that add the Message Map to your classes. When you use ClassWizard to create a Message Handler, it will first add the function to your class, and add the corresponding macro to your Message Map. For example, examine the following ClassWizard generated<code>WM_CLOSE</code> handler:

By adding a <code>DECLARE_MESSAGE_MAP</code> statement to the class declaration, MFC adds the required code to declare the message map. The<code>BEGIN_MESSAGE_MAP</code> tells MFC where the Message Map begins, and identifies your class and it's base class. The reason it needs the base class is because Message Handlers are passed through c++ inheritance, just like any other function.<code>END_MESSAGE_MAP</code> obviously, tells MFC where the Message Map ends. In between these two macros is where your declare the Message Map entry for your Message Handler. MFC has many predefined macros, which associate messages with your member function. Take the the<code>ON_WM_CLOSE</code>macro as an example: It associates the<code>WM_CLOSE</code> message with your<code>OnClose()</code> member function. The macro takes no parameters since it always expects a function called<code>OnClose()</code> which is prototyped as<code>afx_msg void OnClose()</code>. This method gives you 2 advantages:

It is easy to keep track of Message Handlers and the messages they handle

MFC screens out any irrelevant and will break up lParam and wParam to parameters relevant to the message.

Also the return value is simplified, and the Message Handler is prototyped according to the message. For example: If the value should always be zero, MFC simplifies the process and allows you to declare the function as a<code>void</code>, and MFC will be responsible for returning 0. To find the name of the message handler that correlates with a given Message Handler macro you should look it up in the MFC documentation.

There are some messages that ClassWizard doesn't support, but you can manualy add your message handler by adding the function and Message Map macro as described above. If you add message-map entries manually, you may not be able to edit them with ClassWizard later. If you add them outside the bracketing comments <code>//{{AFX_MSG_MAP(classname)</code> and<code>//}}AFX_MSG_MAP</code>, ClassWizard cannot edit them at all. Note that by the same token ClassWizard will not touch any entries you add outside the comments, so feel free to add messages outside the comments if you do not want them to be modified. Messages that are not recognized by ClassWizard, such as message-map ranges, must be added outside the comments.

Sometimes you will find yourself trying to handle a message that ClassWizard doesn't support, and it doesn't have a Message Map macro. MFC has a generic macro just for this kind of situation<code>ON_MESSAGE</code>.<code>ON_MESSAGE</code>allows you to handle any message that exists. The prototype of Message Handlers that use<code>ON_MESSAGE</code> is

where <code>OnMessage</code> is the name of your handler function. The<code>ON_MESSAGE</code> macro takes 2 parameters: The address of the handler, and the message it should handle. For example: The following statement Maps<code>WM_GETTEXTLENGTH</code> to<code>OnGetTextLength()</code>:

<code>OnGetTextLength</code> is prototyped as

Sometimes, you will need to communicate between 2 windows in your application or between 2 windows from different applications. An easy way to do this is by using User-defined messages. The name "User-defined" can be confusing at first; you define a User-defined message and not the user of your program. I have stated in Part 1 that messages are identified by numbers, and that Windows predefines standard messages. The way of using predefined messages is to simply use a number. To make sure that you don't conflict with the system defined messages you should use a number in the range of <code>WM_APP</code> through 0xBFFF:

Handling a user-defined message is done with the <code>ON_MESSAGE</code> macro:

The <code>RegisterWindowMessage</code> function is used to define a new window message that is guaranteed to be unique throughout the system. The macro<code>ON_REGISTERED_MESSAGE</code> is used to handle these messages. This macro accepts a name of a<code>UINT</code> variable that contains the registered Windows message ID. For example:

The range of user defined messages using this approach will be in the range 0xC000 to 0xFFFF. And you send it using the regular<code>SendMessage()</code> method:

(三)

說明 :這部分才是消息處理的底層部分,前面MFC隻是在這部分之上包了一層,是以你在那層看不到消息處理的本質。

This article assumes you are familiar with creating a window in an SDK program. The Dialog part assumes you are familiar with creating modal and modeless dialog in a SDK program.

Handling messages in SDK applications is a totally different process than MFC. No ClassWizard or macros to help you. No<code>CWinApp</code> to implement the Message Loop for you. It's all up to you.

Window "classes" in traditional programming for Windows define the characteristics of a "class" (not a C++ class) from which any number of windows can be created. This kind of class is a template or model for creating windows. In Windows, every window has a Window Class that defines the attributes of a window such as the window's icon, the window's background and the window's procedure. To create a Window class, you call<code>RegisterClass</code> that accepts a<code>WNDCLASS</code> structure defining the properties of the Window class. Every window must have a window class, so typically,<code>RegisterClass</code>is called in<code>WinMain</code>.

Usually, the Message Loop is implemented as a basic <code>while</code> loop:

The <code>MSG</code> structure is a structure that holds all the information about the message: The window it was sent to, the message identifier, the 2<code>lParam</code>/<code>wParam</code> parameters that come with the message, the time at which the message was sent, and the position of the mouse when the message was sent.

The call to <code>GetMessage</code> tells windows to retrieve the first message in the Message Queue. If there is no message in the Message Queue,<code>GetMessage</code> will not return until there is. The return value from<code>GetMessage</code>depends on the message it retrieved: If it was a<code>WM_QUIT</code> message it will return<code>FALSE</code>, if it wasn't, it will return<code>TRUE</code>. The<code>TranslateMessage</code> function translates virtual-key messages into character messages. The character messages are posted to the calling thread's Message Queue, to be read the next time the thread calls the<code>GetMessage</code> function. For example, if you get a<code>WM_KEYDOWN</code> message,<code>TranslateMessage</code> will add a<code>WM_CHAR</code> message to your Message Queue. This is very useful because the<code>WM_KEYDOWN</code> will only tell you what key has been pressed, not the character itself. A<code>WM_KEYDOWN</code> for<code>VK_A</code> could mean "a" or "A", depending on the state of the Caps Lock and Shift key.<code>TranslateMessage</code> will do the work of checking if it should be capital for you. The call to<code>DispatchMessage</code> will call the Window Procedure associated with the window that received the message. That's the SDK Message Loop in a nutshell.

A Window Procedure is a function called by the Message Loop. Whenever a message is sent to a window, the Message Loop looks at the window's Window Class and calls the Window Procedure passing the message's information. A Window Procedure is prototyped as:

The <code>HWND</code> is the handle to the window that received the message. This parameter is important since you might create more than one window using the same window class.<code>uMsg</code> is the message identifier, and the last 2 parameters are the parameters sent with the message.

Typically, a Window Procedure is implemented as a set of <code>switch</code> statements, and a call to the default window procedure:

The <code>switch</code>-<code>case</code> block inspects the message identifier passed in the<code>uMsg</code> parameter and runs the corresponding message handler. The<code>PostQuitMessage</code> call will send a<code>WM_QUIT</code> message to the Message Loop, causing<code>GetMessage()</code> to return<code>FALSE</code>, and the Message Loop to halt.

As I stated in Part 1, Windows should handle any message you don't handle. The call to<code>DefWindowProc()</code> gives Windows a shot at the message. Some messages such as<code>WM_PAINT</code> and <code>WM_DESTROY</code> must be handled in your Window Procedure, and not in<code>DefWindowProc</code>.

Besides receiving messages, you will often find yourself sending messages. You might want to send messages to communicate between two windows in your program, or to communicate between different programs. In order to send a message, you need a handle to the target window. This can be retrieved using a variety of functions, including<code>FindWindow()</code>,<code>GetDlgItem()</code>, <code>GetParent()</code>,<code>EnumWindows()</code> and many more. The SDK has a<code>SendMessage()</code> function which allows you to send messages to a window. For example, let's say you have a handle to the Calculator, and you want to close it. What you should do is send a<code>WM_CLOSE</code> message, which will notify the Calculator that it should close. You can use the following code. In order to get a pointer to Calculator, I use the<code>FindWindow()</code> function and pass the title of the window, which in our case is "Calculator":

Often, one or more of the 32-bit <code>lParam</code> and<code>wParam</code> parameters are actually made of two 16-bit parameters. One case is the<code>WM_MOUSEMOVE</code> message. MSDN states that the<code>lParam</code> for this message is actually 2 values: the X position of the mouse, and the Y position of the mouse. But how do you retrieve the values from the<code>lParam</code>? The SDK has 2 macros designed for exactly this purpose:<code>LOWORD()</code> and<code>HIWORD()</code>. The <code>LOWORD</code> macro retrieves the low-order word from the given 32-bit value, and the<code>HIWORD()</code> macro retrieves the high-order word. So, given an<code>lParam</code> of <code>WM_MOUSEMOVE</code>, you can retrieve the coordinates using the following code:

<code>LOWORD</code> and <code>HIWORD</code> are fine if you want to split up the parameters, but what if you want to create a 32-bit value for use as an<code>lParam</code> or<code>wParam</code> parameter in a message? The SDK has 2 macros for this situation also:<code>MAKELPARAM</code> and<code>MAKEWPARAM</code> both combine two 16-bit values into a 32-bit value, that is usable for messages. For example, the following code sends a<code>WM_MOUSEMOVE</code> message to a window (<code>HWND hWndTarget</code>) with the<code>fFlags</code> parameter as the<code>wParam</code>, and the x/y coordinates as the<code>lParam</code>:

Handling a message in a dialog is very similar to handling a message in a normal window. Windows have Window Procedures, Dialogs have Dialog Procedures. One major difference is that you don't specify a window class for a dialog. When you create a dialog using one of the <code>CreateDialog...</code> functions or the<code>DialogBox...</code>functions, you pass a Dialog Procedure as one of the parameters. A Dialog Procedure is prototyped as:

You might have noticed that the Dialog Procedure looks very similar to the Window Procedure, but it isn't a real Window Procedure. The Window Procedure for the dialog is located inside windows. That Window Procedure calls your Dialog Procedure when various messages are sent to your window. Because of the above, there are messages that you will receive in a Window Procedure that you won't receive in a Dialog Procedure. There are a few major differences between a Window Procedure and a Dialog Procedure:

A Dialog Procedure returns a <code>BOOL</code>, a Window Procedure returns a<code>LRESULT</code>.

A Dialog Procedure doesn't need to handle <code>WM_PAINT</code> or<code>WM_DESTROY</code>.

A Dialog Procedure doesn't receive a <code>WM_CREATE</code> message, but rather a<code>WM_INITDIALOG</code> message

A Window Procedure calls <code>DefWindowProc()</code> for messages it does not handle. A Dialog Procedure should return<code>TRUE</code> if it handled the message or<code>FALSE</code> if not with one exception: if you set the input focus to a control in<code>WM_INITDIALOG</code>, you should return<code>FALSE</code>.

Sometimes, you will need to communicate between 2 windows in your application or between 2 windows from different applications. An easy way to do this is by using User-defined messages. The name "User-defined" can be confusing at first; you define a User-defined message and not the user of your program. I have stated in Part 1 that messages are identified by numbers, and that Windows predefines standard messages. The way of using user-defined messages is to simply use a number. To make sure that you don't conflict with the system defined messages, you should use a number in the range of <code>WM_APP</code> through 0xBFFF:

You handle a user-defined message just like you handle a regular message:

The <code>RegisterWindowMessage</code> function is used to define a new window message that is guaranteed to be unique throughout the system. Like user-defined messages, Registered Messages are handled like regular messages:

The registered message identifiers using this approach will be in the range of 0xC000 to 0xFFFF. And you send it using the regular<code>SendMessage()</code> method:

(四)

Subclassing is a technique that allows an application to intercept and process messages sent or posted to a particular window before the window has a chance to process them. This is typically done by replacing the Window Procedure for a window with application-defined window procedure. I will devide this article into 3:

Sometimes you want to take the functionality of a control and modify it slightly. One example is replacing the menu in an edit control, Another is adding a context menu when you press on a button. One of the most common questions I encounter is "How do I screen out characters from an edit control?". I will show the solution to this from an MFC approach and from an SDK approach, while I try to explain Subclassing.

The need for subclassing comes from the fact that the code for the Windows controls is within Windows, meaning you cannot edit the code. Although you cannot edit the code of the control itself, you intercept the messages sent to it, and handle them your self. You do so by subclassing the control. Subclassing involves replacing the Message Handlers of the control, and passing any unprocessed message to the controls Message Handler.

Although the Message Procedure for the control is located within windows, you can retrieve a pointer to it by using the<code>GetWindowLong</code> function with the<code>GWL_WNDPROC</code> identifier. Likewise, you can call<code>SetWindowLong</code> and specify a new Window Procedure for the control. This process is called Subclassing, and allows you to hook into a window/control and intercept any message it gets. Subclassing is the Windows term for replacing the Window Procedure of a window with a different Window Procedure and calling the old Window Procedure for default (superclass) functionality. Remember<code>DefWindowProc()</code>? instead of calling<code>DefWindowProc</code> for default Message Handling you use the old Window Procedure as the default Message Handler.

So, lets try to solve the classic question "How do I screen out characters from an edit control?", or "How do I create a letter-only edit control?"

An edit control is a window. It's window procedure lies within windows. Among other things, whenever it gets a<code>WM_CHAR</code> message it adds the character to the text it contains. Now that we know that, we can simply subclass the edit control, and intercept the<code>WM_CHAR</code> messages. Whenever the<code>WM_CHAR</code> message is a letter or a key like space bar or backspace we'll pass the message to the edit control. If it isn't one of the above, we'll just "Swallow" the message, blocking it from reaching the Edit Control.

The first step to subclassing is to add a global/static <code>WNDPROC</code> variable that will store the address of the edit control's Window Procedure.

The second step is to create a new Window Procedure for the edit control:

The <code>IsCharAlpha</code> function determines whether a character is an alphabetic character. This determination is based on the semantics of the language selected by the user during setup or by using Control Panel.

More interesting is the <code>CallWindowProc</code> function. The<code>CallWindowProc</code> function passes message information to the specified Window Procedure. A call to<code>CallWindowProc</code> will allow you to call the old Window Procedure with any message you receive, thus providing default message handling

The third step is to replace the Window Procedure for the edit control, and to store the old one ing_OldEdit. For example, if you want to subclass an edit control that resides in a dialog (hDlg) and has the ID of<code>IDC_EDIT1</code>you would use the following code:

The control is now subclassed. Any message to the edit control will first go through the<code>NewEditProc</code> Window Procedure, which will decide if the message should go to the edit control's Window Procedure .

Subclassing in both MFC and SDK programs is done by replacing the message handlers of a control. It is rather easy to subclass in a MFC program. First you inherit your class from a class that encapsulates the functionality of a the control. In ClassWizard, click on "Add Class", then "New". For the base class, choose the MFC Control class you are deriving from, in our case,<code>CEdit</code>.

windows消息處理(強烈推薦,收藏)

Using MFC relieves you from having to call the old Message Handlers, since MFC will take care of it for you.

The second step is to add Message Handlers to your new class. If you handle a message and you want the control's message handler to get a shot at it, you should call the base class member function the corresponds with the message. this is the subclassed<code>WM_CHAR</code> handler:

The third and final stage is to associate the window with an instance of our new class. In a dialog this is done simply by using ClassWizard to create acontrol member variable of your class in the window's parent, and associate it with the control.

windows消息處理(強烈推薦,收藏)

In a non-dialog parent you should add a member variable instance of your control to it, and call one of the two<code>CWnd</code> Subclassing functions:<code>CWnd::SubclassWindow or CWnd::SubclassDlgItem</code>. Both routines attach a<code>CWnd</code>object to an existing Windows<code>HWND</code>.<code>SubclassWindow</code> takes the<code>HWND</code> directly, and<code>SubclassDlgItem</code> is a helper that takes a control ID and the parent window (usually a dialog).<code>SubclassDlgItem</code>is designed for attaching C++ objects to dialog controls created from a dialog template.

When you subclass a control, besides handling the message it receives, in MFC you can also handle the notifications it sends to it's parent window. This technique is called Message Reflecting. Windows controls often send notifications to their parents, for example, a Button will send a <code>WM_COMMAND</code> message telling it's parent it has been clicked. Usually it is the parent's job to handle these messages, but MFC will also allow you to handle them in the control itself. In ClassWizard these messages appear with an "=" sign before them, indicating they are Reflected Messages. You handle them just like any other message. The macros for these messages are similar to the regular messages, but have<code>_REFLECT</code> added to the end of the macro. For example,<code>ON_NOTIFY()</code> becomes<code>ON_NOTIFY_REFLECT()</code>.

繼續閱讀