MFC basic structure questions
There are few things I'm not sure of :
When you create a basic SDI using MFC app wizard (let's call it TestMfc) you get :
4 major classes :
CTestMfcApp
CTestMfcView
CTestMfcDoc
CMainFrame
What I noticed is that CTestMfcApp
has those declaration
ON_COMMAND(ID_APP_ABOUT, &CTestMfcApp::OnAppAbout)
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
// Standard print setup command
ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinApp::OnFilePrintSetup)
while CTestMfcView
has these :
BEGIN_MESSAGE_MAP(CTestMfcView, CEditView)
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, &CEditView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, &CEditView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CEditView::OnFilePrintPreview)
END_MESSAGE_MAP()
What I don't understand is why does MFC create that separation? I can't see why the app class needs to declare functions for handling events... isn't that the view's job? If for exmaple there are few windows it's even becoming more clear.
Second, how those events get called? I know there's supposed to be WINPROC function who's supposed to get the MSG and call the right handler. is the ON_COMMAND the macro who sets some kind of pointer function who is later available to the the WINPROC function. And why do开发者_JS百科esn't the ON_COMMAND get a WINDOWS handle... if for example there is another WINDOW in the program with the same ID ?
Third and last, let's say I want to change some window's threads to an alert state. To do that I want to change the main loop (which keeps calling getmessage/dispatchmessage etc.. and insert the waitformultibleonject function. where is the winmain function? I can't find it when the appwizard is doing all the job for me.
Thanks!!
In MFC, messages "bubble up" until they find a handler. IIRC it's View -> Document -> Document Template -> MainFrame -> App. This allows to handle view-specific events in the view, document-specific events in the document etc.
Usually, global handlers end up in the mainframe class. However, you can use multiple mainframe windows - even with different behavior - where the distinction between "MainFrame" and "App class" becomes important.
Control-specific handlers do belong in the view class, though. I'd put only WM_COMMAND handlers into higher-up classes.
for your third question: I wouldn't do that. While MFC avoids a few common modal loops, you can't avoid them all. OnIdle is a good place to implement defered updates.
[update] MFC uses one global WNDPROC to handle all messages. It uses a HWND to CWnd mapping to locate the MFC object. When the WNDPROC sees a WM_COMMAND message, it will first check the message map of the receiver window if it contains a handler for that message. If there is none, it will do a variety of checks, e.g. "Is this not just a CWnd, but a CView?" Yes --> get the document and see if the documents message map has a handler for this particular command.
Introduction to the MFC architecture
The Microsoft Foundation Class library framework does a reasonable job of providing the basics of an MVC design pattern using messages of various sort to provide the communication between the various parts. However like any framework it provides a structure which can provide lots of functionality you do not have to write as well as quite a bit of restrictions and constraints if your approach is not congruent with the framework.
The framework uses messages between cooperating entities, extending the idea of Windows messages and using the message infrastructure of the Windows operating system. Some of these cooperating entities have their own Windows message pump (modal dialogs and threads) while the rest use the application message pump serviced by the main or UI thread.
Messages sent to the application's main window such as keyboard or mouse events or posted messages using PostMessage()
are pulled off the Windows message queue by the main window thread, also known as the main UI thread, servicing the message pump and then forwarded to the entity that it belongs to by the MFC framework. This is why actions that pause or wait the main UI thread make the application unresponsive. The main UI thread must continually run to pull Windows messages from the Windows message queue and then distribute them to the rest of the application.
The MFC framework forwards messages by looking through a list of message maps in order to find which entity or MFC class instance is expecting the message and if one is found, calling the C++ function specified in the message map entry with the arguments the MFC specification requires. The expectation is that any message processing function will either complete the action quickly and return back to the MFC framework's message dispatch functionality or will use a thread to perform any action that requires some time and then return.
The MVC design pattern views the application as made up of three cooperating entities, the Model entity containing the data, the View entity which presents a view of the Model and its data, and the Controller entity which is the affordance the user manipulates to change what is viewed as well as the model and its data.
The MFC framework architecture starts with a couple of base classes which are then specialized through inheritance. For instance there is a general view class which provides the message infrastructure for a general display then subclasses extend and specialize the general view class. For instance the CView
class is specialized with the CScrollView
class.
Main types of MFC UI classes
In general the document class will handle messages that involve changes to the document content and the view class will handle messages that involve the presentation of the document content. The document class is responsible for serializing all of the data or content to or from the document object from or to a file.
The document class uses the Observer design pattern to allow views to know when an update is needed. The document class has a list of view instances so that when a change is made to the document or model or data, all of the registered views are sent a message indicating a change and need to update.
The view class is responsible for displaying a view of the document data to a device such as a window on the screen or a printer. So it would be normal for a CView
class to have a function that would handle a print request sent via a message with a message Id of ID_FILE_PRINT
or ID_FILE_PRINT_PREVIEW
.
When data within the document changes, a message can be sent to all of the views registered with the document informing those views of the data change so that the views can decide whether to make changes to the view or not.
The third main class of the framework, the CWinApp
or application class is a container and manager for the document and view classes. Within the app object, which is the entry point for the application (a construct which is also used for an MFC DLL as well to provide for the DLL load and unload entry points), is the hooks for the main message pump. The purpose of the app class is to set up the initial environment and to then allow the programmer to hook their particular document and view objects into the MFC framework of the app class.
Since the CWinApp
class is a container for the application, it would be normal for it to handle such events as ID_FILE_NEW
or ID_FILE_OPEN
as it would cause the document class to close out the current document and reinitialize the application to start with a new document and its associated views.
My opinion is that the About dialog handling and Help handling was put into the CWinApp
class since it was the easiest place to put it. Typically an About is a dialog with a basic description of the application. And both Help and About may be things you want to access without having to start a document or view first.
The MFC framework has other, supporting classes for the main UI functionality. There are also other additional libraries that have been added since MFC was first introduced in the early 1990s.
A few details about messages
Windows messages have a message Id indicating the type of message. There are various ranges of message Id values that are allocated to different purposes. One range is the WM_USER
range that an application can use without concern of using a message Id assigned to a Windows message such as a keyboard message. Part of this range is allocated to MFC messages (see WM_USER vs WM_APP as well as Sending and receiving Windows messages as well as How to handle a posted, registered windows message within a VCL application? ).
These messages are standard Windows messages using PostMessage()
or SendMessage()
with the standard Windows format of handle of the target window, a message identifier, and the two parameters which are used to provide additional information. The MFC framework has lots of different C Preprocessor defines for various message identifiers. If you follow the class derivation hierarchy you will find that document classes, CDocument
, end up being derived from CCmdTarget
classes just as the view classes, CView
or derivations of CView
, are as well. CCmdTarget
in turn derives from the most basic MFC class, CObject
.
Much of the message map functionality seems to come from the functionality of the CCmdTarget
class which uses a method of accepting a message, checking to see if the message identifier is in the CCmdTarget
message list for the specific object and if not passing it on to the next CCmdTarget
object in the chain. So the MFC framework uses a kind of Strategy Design Pattern so that a message is passed along a chain of components until an object that can handle the message is found.
Due to the age of the MFC framework, quite a bit of the exposed pieces use the C Preprocessor and macros. You can inspect the macros to see what they are doing and how they are implemented using the Visual Studio IDE. And those parts of the MFC framework that are implemented as templates are also easily available through the Visual Studio IDE to read. Both macros and templates are in the MFC include files.
However to read the code in the actual classes you will need to find a copy of the MFC source body. Using the MFC Source Files from the Microsoft web site provides a starting point including where to find the source from your installation of Visual Studio.
The Microsoft Foundation Class (MFC) Library supplies full source code. Header files (.h) are in the \atlmfc\include directory; implementation files (.cpp) are in the \atlmfc\src\mfc directory.
I would not recommend forking this with your own changes.
Your third question is about threads and the approach you describe does not make sense within the MFC framework. With MFC you would normally create a thread using the AfxBeginThread()
or AfxBeginThreadEx()
functions. There are two kinds of threads, one with a user interface and one without.
It does not make sense that a multi-threaded MFC application would need to modify the message pump. You would instead send messages to the thread once you have created it using something like PostThreadMessage()
. The thread can receive messages by calling either the GetMessage()
or PeekMessage()
functions. Each MFC thread has a message pump and message queue that is set up "when the thread makes its first call to one of the User or GDI functions" (see PostThreadMessageW function).
References
Is MFC is based on any design pattern,if so which design pattern?
Windows Messages
SendMessage/PostMessage to a derived CView class not working for MFC application
MFC - How to post a message to all views that were derived from CView class?
Is the 'C' in MVC really necessary?
This article, Threads with MFC, in code project provides an overview and source example.
Print is normally handled in the view because in win32 you print by calling the OnPaint event to a printer rather than the screen. Also for any events that involve getting the mouse position it's easier to do it in the view.
You can easily get the current doc form the view but it's more effort to get the view form the doc.
精彩评论