Visual Studio
Visual Studio is an environment for developing applications in Windows. It has a number of tools, such as an editor, compilers, linkers, a debugger, and a project manager. It also has several Wizards—tools designed for rapid development. The Wizard you will first encounter is the Application Wizard. It generates code for an Application Framework. The idea is that we use the Application Wizard to design a skeleton application that is later completed with more application-specific code. There is no real magic about wizards, all they do is generate the skeleton code. We could write the code ourselves, but it is a rather tedious job. Moreover, an application can be run in either debug or release mode. In debug mode, additional information is added in order to allow debugging; in release mode, all such information is omitted in order to make the execution as fast as possible. The code of this article is developed with Visual Studio 2008.
The Windows 32 bits Application Programming Interface (Win32 API) is a huge C function library. It contains a couple of thousand functions for managing the Windows system. With the help of Win32 API it is possible to totally control the Windows operating system. However, as the library is written in C, it could be a rather tedious job to develop a large application, even though it is quite possible. That is the main reason for the existence of the Microsoft Foundation Classes (MFC). It is a large C++ class library containing many classes encapsulating the functionality of Win32 API. It does also hold some generic classes to handle lists, maps, and arrays. MFC combines the power of Win32 API with the advantages of C++. However, on some occasions MFC is not enough. When that happens, we can simply call an appropriable Win32 API function, even though the application is written in C++ and uses MFC.
Most of the classes of MFC belong to a class hierarchy with CObject at the top. On some occasions, we have to let our classes inherit CObject in order to achieve some special functionality. The baseclass Figure in the Draw and Tetris applications inherits CObject in order to read or write objects of unknown classes. The methods UpdateAllViews and OnUpdate communicate by sending pointers to CObject objects. The Windows main class is CWnd.
In this environment, there is no function main. Actually, there is a main, but it is embedded in the framework. We do not write our own main function, and there is not one generated by the Application Wizard. Instead, there is the object theApp, which is an instance of the application class. The application is launched by its constructor.
When the first version of MFC was released, there was no standard logical type in C++. Therefore, the type BOOL with the values TRUE and FALSE was introduced. After that, the type bool was introduced to C++. We must use BOOL when dealing with MFC method calls, and we could use bool otherwise. However, in order to keep things simple, let us use BOOL everywhere.
In the same way, there is a MFC class CString that we must use when calling MFC methods. We could use the C++ built-in class string otherwise. However, let us use CString everywhere. The two classes are more or less equivalent.
There are two types for storing a character, char and wchar_t. In earlier version of Windows, you were supposed to use char for handling text, and in more modern versions you use wchar_t. In order to make our application independent of which version it is run on, there are two macros TCHAR and TEXT. TCHAR is the character type that replaces char and wchar_t. TEXT is intended to encapsulate character and string constants.
TCHAR *pBuffer;
stScore.Format(TEXT("Score: %d."), iScore);
There is also the MFC type BYTE which holds a value of the size of one byte, and UINT which is shorthand for unsigned integer. Finally, all generated framework classes have a capital C at the beginning of the name. The classes we write ourselves do not.
The Document/View model
The applications in this article are based on the Document/View model. Its main idea is to have two classes with different responsibilities. Let us say we name the application Demo, the Application Wizard will name the document class CDemoDoc and the view class will be named CDemoView. The view class has two responsibilities: to accept input from the user by the keyboard or the mouse, and to repaint the client area (partly or completely) at the request of the document class or the system. The document’s responsibility is mainly to manage and modify the application data.
The model comes in two forms: Single Document Interface (SDI) and Multiple Document Interface (MDI). When the application starts, a document object and a view object are created, and connected to each other. In the SDI, it will continue that way. In the MDI form, the users can then add or remove as many views they want to. There is always exactly one document object, but there may be one or more view objects, or no one at all.
The objects are connected to each other by pointers. The document object has a list of pointers to the associated view objects. Each view object has a fieldm_pDocument that points at the document object. When a change in the document’s data has occurred, the document instructs all of its views to repaint their client area by calling the method UpdateAllViews in order to reflect the change.
The message system
Windows is built on messages. When the users press one of the mouse buttons or a key, when they resize a window, or when they select a menu item, a message is generated and sent to the current appropriate class.
The messages are routed by a message map. The map is generated by the Application Wizard. It can be modified manually or with the Properties Window View (the Messages or Events button).
The message map is declared in the file class’ header file as follows:
DECLARE_MESSAGE_MAP()
The message map is implemented in the class’ implementation file as follows:
BEGIN_MESSAGE_MAP(this_class, base_class)
// Message handlers.
END_MESSAGE_MAP()
Each message has it own handle, and is connected to a method of a specific form that catches the message. There are different handlers for different types of messages. There are around 200 messages in Windows. Here follows a table with the most common ones. Note that we do not have to catch every message. We just catch those we are interested in, the rest will be handled by the framework.
Message |
Handler/Method |
Sent |
WM_CREATE |
ON_WM_CREATE/OnCreate |
When the window is created, but not yet showed. |
WM_SIZE |
ON_WM_SIZE/OnSize |
When the window has been resized. |
WM_MOVE |
ON_WM_MOVE/OnMove |
When the window has been moved. |
WM_SETFOCUS |
ON_WM_SETFOCUS/ OnSetFocus |
When the window receives input focus. |
WM_KILLFOCUS |
ON_WM_KILLFOCUS/ OnKillFocus |
When the window loses input focus. |
WM_VSCROLL |
ON_WM_VSCROLL/ OnVScroll |
When the user scrolls the vertical bar. |
WM_HSCROLL |
ON_WM_HSCROLL/ OnHScroll |
When the user scrolls the horizontal bar. |
WM_LBUTTONDOWN
WM_MBUTTONDOWN
WM_RBUTTONDOWN |
ON_WM_LBUTTONDOWN/ OnLButtonDown ON_WM_MBUTTONDOWN/ OnMButtonDown ON_WM_RBUTTONDOWN/ OnRButtonDown |
When the user presses the left, middle, or right mouse button. |
WM_MOUSEMOVE |
ON_WM_MOUSEMOVE/ OnMouseMove |
When the user moves the mouse, there are flags available to decide whether the buttons are pressed. |
WM_LBUTTONUP
WM_MBUTTONUP
WM_RBUTTONUP |
ON_WM_LBUTTONUP/ OnLButtonUp ON_WM_MUTTONUP/ OnMButtonUp ON_WM_RUTTONUP/ OnRButtonUp |
When the user releases the left, middle, or right button. |
WM_CHAR |
ON_WM_CHAR/OnChar |
When the user inputs a writable character of the keyboard. |
WM_KEYDOWN |
ON_WM_KEYDOWN/ OnKeyDown |
When the user presses a key of the keyboard. |
WM_KEYUP |
ON_WM_KEYUP/ OnKeyUp |
When the user releases a key of the keyboard. |
WM_PAINT |
ON_WM_PAINT/OnPaint |
When the client area of the window needs to be repainted, partly or completely. |
WM_CLOSE |
ON_WM_CLOSE/OnClose |
When the user clicks at the close button in the upper right corner of the window. |
WM_DESTROY |
ON_WM_DESTROY/ OnDestroy |
When the window is to be closed. |
WM_COMMAND |
ON_COMMAND(Identifier, Name)/OnName
|
When the user selects a menu item, a toolbar button, or a accelerator key connected to the identifier. |
WM_COMMAND_ UPDATE |
ON_COMMAND_ UPDATE_UI(Identifier, Name)/OnUpdateName |
On idle time, when the system is not busy with any other task, this message is sent in order to enable/disable or to check menu items and toolbar buttons. |
When a user selects a menu item, a command message is sent to the application. Thanks to MFC, the message can be routed to virtually any class in the application. However, in the applications of this article, all menu messages are routed to the document class. It is possible to connect an accelerator key or a toolbar button to the same message, simply by giving it the same identity number.
Moreover, when the system is in idle mode (not busy with any other task) thecommand update message is sent to the application. This gives us an opportunity to check or disable some of the menu items. For instance, the Save item in the File menu should be grayed (disabled) when the document has not been modified and does not have to be saved. Say that we have a program where the users can paint in one of three colors. The current color should be marked by a radio box.
The message map and its methods can be written manually or be generated with the Resource View (the View menu in Visual Studio) which can help us generate the method prototype, its skeleton definition, and its entry in the message map.
The Resource is a system of graphical objects that are linked to the application. When the framework is created by the Application Wizard, the standard menu bar and toolbar are included. We can add our own menus and buttons in Resource Editor, a graphical tool of Visual Studio.
The coordinate system
In Windows, there are device (physical) and logical coordinates. There are several logical coordinate mapping systems in Windows. The simplest one is the text system; it simply maps one physical unit to the size of a pixel, which means that graphical figures will have different size monitors with different sizes or resolutions. This system is used in the Ring and Tetris applications.
The metric system maps one physical unit to a tenth of a millimeter (low metric) or a hundredth of a millimeter (high metric). There is also the British system that maps one physical unit to a hundredth of an inch (low English) or a thousandth of an inch (high English). The British system is not used in this article.
The position of a mouse click is always given in device units. When a part of the client area is invalidated (marked for repainting), the coordinates are also given in device units, and when we create or locate the caret, we use device coordinates. Except for these events, we translate the positions into logical units of our choice. We do not have to write translation routines ourselves, there are device context methods LPtoDP (Logical Point to Device Point) and DPtoLP (Device Point to Logical Point) in the next section that do the job for us. The setting of the logical unit system is done in OnInitialUpdate and OnPrepareDC in the view classes.
In the Ring and Tetris Applications, we just ignore the coordinates system and use pixels. In the Draw application, the view class is a subclass of the MFC class CScrollView. It has a method SetScrollSizes that takes the logical coordinate system and the total size of the client area (in logical units). Then the mapping between the device and logical system is done automatically and the scroll bars are set to appropriate values when the view is created and each time its size is changed.
void SetScrollSizes(int nMapMode, CSize sizeTotal,
const CSize& sizePage = sizeDefault,
const CSize& sizeLine = sizeDefault);
In the Calc and Word Applications, however, we set the mapping between the device and logical system manually by overriding the OnPrepareDC method. It calls the method SetMapMode which sets the logical horizontal and vertical units to be equal. This ensures that circles will be kept round. The MFC device context method GetDeviceCaps returns the size of the screen in pixels and millimeters. Those values are used in the call to SetWindowExt and SetViewportExt, so that the logical unit is one hundredth of a millimeter also in those applications. The SetWindowOrg method sets the origin of the view’s client area in relation to the current positions of the scroll bars, which implies that we can draw figures and text without regarding the current positions of the scroll bars.
int SetMapMode(int iMapMode);
int GetDeviceCaps(int iIndex) const;
CSize SetWindowExt(CSize szScreen);
CSize SetViewportExt(CSize szScreen);
CPoint SetWindowOrg(CPoint ptorigin);