Note: This article demonstrates the example using Visual Studio 2003. If you have Visual Studio 6.0, the steps are different - that I leave it to you to explore.
1. GDI and DC
GDI stands for Graphical Device Interface. It aids drawing on the Windows using the DC. DC stands for Device Contexts.
Device context is kind of Drawing Board with some set of characteristics assigned to it. These characteristics involve how the line drawings should appear and how much is the thickness of the line (
Pen), how the texts will appear (
Font), How do you display images (
Bitmap), how do you draw filled surfaces (
Brush), etc. The equipments (
Font, etc.) used to control the drawing behavior on the drawing board (DC) are called GDI objects.
Here, we will create an example that uses the
Brush GDI objects and draw something on the client area. Also we will see the messages that need to be handled. Messages in Windows programming world are nothing but a kind of event that will be sent to a windows. Somebody will send a message using
Postmessage and some code will define a handler function to respond.
2. Types of Device Contexts
MFC framework supports four different types of device contexts. The types are:
All the above show a class in the MFC Framework to deal with different types of DCs. Before Knowing them, consider the below picture that shows a sample window [Notepad]:
The window may have a Frame window (Combination of Menu, Toolbar), Status Window (Status bars) and the Client Window. In the above sample, the Notepad has Frame Window and Client Window. Whatever we type goes to the client window and based on the Font Object, the letters are getting drawn.
Let us say that you want to draw a circle on the client area, the choice is either
CPaintDC. So both these device contexts are used to perform drawing on the Windows Client Area. With
WindowDC, you can draw anywhere in the window and that means you can draw a Traffic button on your Menu Bar (
FrameWindow) and put some fancy texts on your status bar.
MetaFileDC is used to record your GDI drawings in a file for later playback.
CPaintDC can be used only inside the
OnPaint() message handler.
CClientDC can be in any function except
3. Create SDI Application
- Open Visual Studio 2005
- Select Visual C++ as Project Types
- Select MFC Application in the Template List at the right hand side
- After selecting the Name and Location, click the OK button
- In the Wizard, click the application type link on the Left side pane. Use the below picture as reference to set the options:
- Click the Finish button. If you need, take a walk by clicking the next and click finish at the End.
- Right click the project name and select build. Click the start-debugging button on the tool bar. The application is shown below:
Now you had created a SDI (Single Document Interface) application without
Document and View Architecture. I will write about document and View architecture in a different write-up. Here, we are going to draw something on the Client Area of the above window. First, we will set the Background color to Black and then start our drawing.
4. Classes Created by the Framework
Now look at the class view. It shows classes created by the application wizard. To look at the class view, click the class view tab on the right/left side pane. If it is not available, access it from the View>Class view. Don’t have class view menu item under view!! What bad luck?! Follow the steps below:
- Click the menu item Tools->Customize
- In the categories list, select View
- Drag the Class View from Commands List to a tool bar or View menu popup
- Close the customize dialog and click the class view now
The classes created by the app wizard are shown below:
CGDIApp is the application class. The object of this will get created in the
Code Segment of the memory when the application is launched. That means the wizard declares the
CGDIApp as global. Search for
CChildView is derived from the view class and it is responsible for making a drawing on the
ClientWindow (If you forgot it, have a look at the Notepad picture). In the example, we are going to write code here.
CMainFrame class derived from the
CFrameWnd. This class is responsible for hosting the Menu bar, Toolbar, dialog bar, etc. Our application by default displays a toolbar and menu bar.
5. Changing the Background
To change the background color of the client window, we need to handle the message
WM_ERASEBKGND. To handle the message, first open the class wizard. Follow the steps below to get a handler for the
- Right click the
- Select property from the context menu that appeared
- In the property window, select the Message button
- Search for the Message on the left
WM_ERASEBKGND side list of message. Here
WM_ stands for window message.
- Select the first item (
Add OnEraseBKGnd) in the drop down that appears next to Message that you had selected.
- This will bring you to the code editor and we are going to set the background for the client area of the application here.
In the handler, first we will get the Client Window dimension in the form of
RECT structure wrapped by the Built-in class
CRect. Below is the code for this:
Next, we need a brush and a color that the brush uses.
CBrush GDI object is created using the
COLORREF value. This
COLORREF value is
DWORD. That means, a 32-bit value that stores the color in the form Red, Green, Blue and Alpha. Each component takes 8 bits. MFC provides a RGB macro which combines the Red, Green, Blue values (Alpha is set to default – No transparent) and packs it into 32-bit value to form a
DWORD. So we can use the
RGB macro to get the Color combination from Red, green and blue and assigns it to the
COLORREF. Below is the code that creates a color brush:
COLORREF backcolor = RGB(0,0,0);
Next we use
FillRect function to paint the entire client window by the brush that we created. The code is given below:
As we handled the background painting, do not allow the call for base class to draw a default white background. The code is as follows:
The above specified 4 code snippets set the background for the window. The message
WM_ERASEBKGND will be sent whenever a window requires a re-drawing. The redraw for a window is required in the following scenario as I know:
- When the Window is moved
- When the window is resized
- When the Window is maximized
- When a overlapping window moved
- When a overlapping window resized
- When a overlapping disappeared (Closed or Minimized)
If you run the application at this stage, then it will look like below:
6. Making the Drawing using Pen and Brush
6.1) If you browse the
CChildView implementation file, you will find a function called
OnPaint. This is actually the handler for the
WM_PAINT message provided by the application wizard. Also the wizard already created Device Context instant of type
CPaintDC ready to use. So let us start our drawing using the
First, we ask the
PaintDC to select a white pen from the stock pool and draw a line on the black background that we already set. Call to the function
SelectStockObject will allow the dc to select any GDI object available in the default stock pool. We used this function to select a white pen to the device context. Note at the same time we are storing the pen that was used before. Whenever you select a GDI object to the device context, it will return the similar object (
Pen in the first code snipped). The returned object is used to re-store the default GDI used by the DC.
Next, we asked the device context to move to a particular location by making a call to
MoveTo function. The function
LineTo will specify the End Point and join the Start point specified by the
MoveTo by drawing a line using the
Pen currently selected into the device context. Our first code snippet draws a line in the client window. Below is the code snippet:
CPen * oldPen;
CBrush * oldBrush;
oldPen = (CPen *) dc.SelectStockObject(WHITE_PEN);
6.2) In this code snippet, a solid pen with thickness of 3 is created. Also we specified that the color of the thick pen is green using the RGB macro. Now when you draw the line, it appears thicker than the previous white pen. Below is the code:
CPen thick_pen(PS_SOLID, 3, RGB(0,255,0));
oldPen = dc.SelectObject(&thick_pen);
6.3) After this green thick line, a 3d rectangle is drawn. The rectangle will appear in 3d based on the color you specify. For a simple example, I created a rectangle in
paintbrush. First, I set the background color to gray. Then top and left line are drawn in white color. Bottom and right lines are drawn in black color. Look at the screen shot below. It looks like a push button, right?
This is how windows creates a button, and when you press the button, windows will replace all the 4 lines in the same color. Below is the code that we used to draw a 3d rectangle:
dc.Draw3dRect(5,30,80,70, RGB(255,255,255), RGB(120,120,120));
6.4) In the below code snippet, we are creating the
Pen is for drawing the lines and
brush is for painting the closed surfaces. Now we are going to draw the rectangle filled by some specified color by the
brush that we create. When we are creating the
COLORREF value is passed to its constructor. Then the
brush is selected to the device context before drawing the rectangle. If you do not want to fill the rectangle, then a
NULL brush should be used.
oldBrush = dc.SelectObject(&brush);
6.5) The below code snippet makes a call to
FillRect and uses a Hatch Brush. I hope an explanation is not required as you gained some hands-on in the previous code snippets. You can refer to MSDN for the CDC class to know more drawing functions and you can use those efficiently by using the
Brush GDI objects. I had not covered other GDI objects here.
CBrush* hatBrush = new CBrush();
oldBrush = dc.SelectObject(hatBrush);
dc.FillRect(new CRect(5,160,80,190), hatBrush);
Below is the screen shot of what we draw in the Client Window:
- 3rd December, 2010: Initial post