Windows Development Using Visual Studio 2008

0
85
11 min read

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.

Windows Development Using Visual Studio 2008

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);


LEAVE A REPLY

Please enter your comment!
Please enter your name here