Click here to Skip to main content
15,889,281 members
Articles / Desktop Programming / MFC
Article

Painter Program

Rate me:
Please Sign up or sign in to vote.
3.37/5 (26 votes)
12 Feb 20036 min read 112.8K   39   21
A simple program that demonstrates how to use mouse messages and how to draw using MFC.

Introduction

This program demonstrates how to draw on the client area, it also demonstrates how to use the mouse messages. I think the best way to learn programming is programming, so, let's begin our program now.

The drawing flags

The user chooses a drawing tool to draw. For example when the user wants to draw a line, he chooses a line tool. So, we must know which tool the user has selected, the bDrawFlag for free drawing, bLineFlag for the line, bRectangleFlag for the rectangle, bEllipseFlag for the ellipse, and bFillFlag if the user wants to fill the shape with color.

When the user clicks on a point, (Anchor), and draws the mouse pointer to a point (DrawTo), and we have the bLineFlag = TRUE, we can easily draw the line when the user releases the mouse button.

Lets begin now to build the program. Use AppWizard, in the File menu, choose New, choose MFC AppWizard (exe), and write the project name (Painter) in the Project name text box. Then in the MFC AppWizard-Step1 Dialog box, choose Single document and click Finish.

In the class view, double click on the CPainterView class, the PainterView.h file will be opened. You can see the CPainterView class declaration. Add these variables and MakeAllFlagsFalse() method in the protected section in the class declaration:

class CPainterView : public CView
{
.
.
protected:
    CPoint Anchor;
    CPoint DrawTo;
    CPoint OldPoint; 
    BOOL bDrawFlag; 
    BOOL bLineFlag; 
    BOOL bRectangleFlag; 
    BOOL bEllipseFlag; 
    BOOL bFillFlag; 
    void MakeAllFlagsFalse(); 
.
.
};

These variables when they initialized, there values are FALSE. But we need to make them all FALSE if one of them is changed by the MakeALlFlagsFalse() method, we can simply write this method like this:

void CPainterView::MakeAllFlagsFalse()
{
    bDrawFlag = FALSE; 
    bLineFlag = FALSE; 
    bRectangleFlag = FALSE; 
    bEllipseFlag = FALSE; 
    bFillFlag = FALSE; 
}

Then we must call this method in the CPainterView class constructor:

CPainterView::CPainterView()
{
    // TODO: add construction code here
    MakeAllFlagsFalse(); 
}

Building the tools bar and the menu

In the recourses view, double click the menu, and double click the IDR_MAINFRAME, add new menu, call it Tools, give this menu five items, as illustrated bellow:

Image 1

Then add 5 buttons in the tool bar. Every time you add a button in the tool bar, the editor adds a blank button in the end of the tool bar, as illustrated bellow:

Image 2

Finally, connect these buttons to the menu items. Double click on the buttons on the tool bar, the Toolbar Button Properties dialog box will be shown as illustrated bellow. For example, in the ID assign ID_TOOLS_RECTANGLE for the rectangle button.

Image 3

You must do the same for all the toolbar buttons.

The interface between the flags and the drawing tools

Open the class wizard, (CTRL+w), in the class name choose CPaintView, in the object ID's choose ID_TOOLS_DRAWFREEHAND, in the messages, choose command, and click add function button, then click edit code. The Class Wizard will add the OnToolsDrawfreehand() function.

Add the following code to OnToolsDrawfreehand():

void CPainterView::OnToolsDrawfreehand() 
{
    MakeAllFlagsFalse(); 
    bDrawFlag = TRUE;     
}

Do the same for the following IDs:

  • ID_TOOLS_ELLIPSE
  • ID_TOOLS_FILLFIGURE
  • ID_TOOLS_LINE
  • ID_TOOLS_RECTANGLE

And add the following code to there functions:

void CPainterView::OnToolsEllipse() 
{
    MakeAllFlagsFalse(); 
    bEllipseFlag = TRUE; 
}

void CPainterView::OnToolsFillfigure() 
{
    MakeAllFlagsFalse(); 
    bFillFlag = TRUE; 
}

void CPainterView::OnToolsLine() 
{
    MakeAllFlagsFalse(); 
    bLineFlag = TRUE; 
}

void CPainterView::OnToolsRectangle() 
{
    MakeAllFlagsFalse(); 
    bRectangleFlag = TRUE; 
}

Draw check mark on the menu items

Open the class wizard, choose the CPaintView class in the class name, in the ID's choose ID_TOOLS_DRAWFREEHAND, in the message choose UPDATE_COMMAND_UI, then press add function, and edit code, add this code to the OnUpdateToolsDrawFreehand function:

void CPainterView::OnUpdateToolsDrawfreehand(CCmdUI* pCmdUI) 
{
    pCmdUI->SetCheck(bDrawFlag);     
}

Do the same for the other IDs, and add the following code to their functions:

00
void CPainterView::OnUpdateToolsEllipse(CCmdUI* pCmdUI) 
{
    pCmdUI->SetCheck(bEllipseFlag);     
}

void CPainterView::OnUpdateToolsFillfigure(CCmdUI* pCmdUI) 
{
    pCmdUI->SetCheck(bFillFlag);     
}

void CPainterView::OnUpdateToolsLine(CCmdUI* pCmdUI) 
{
    pCmdUI->SetCheck(bLineFlag);     
}

void CPainterView::OnUpdateToolsRectangle(CCmdUI* pCmdUI) 
{
    pCmdUI->SetCheck(bRectangleFlag);     
}

The mouse events

When the user clicks on the left button on the drawing area, WM_LBUTTON message will be sent, we can add a message handler for this message, in the class wizard. Be sure that the CPaintView class has been chosen in the class name, in the object ID's, choose CPaintView. In the message, scroll till you find the WM_LBUTTONDOWN, and press add function, and edit code. Add the following code to the function handler for the WM_LBUTTONDOWN message:

void CPainterView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    Anchor.x = point.x; 
    Anchor.y = point.y; 
.
.
}

The OnLButtonDown takes the point parameter, this parameter is a CPoint class, it saves the point that the user clicked.

Drawing Lines

When the user releases the mouse button, he creates a DrawTo point, and we must register this point. When the user releases the left mouse button, WM_LBUTTONUP message will be sent. Open the class wizard to add a function handler for the WM_LBUTTONUP message as illustrated before, add the following code to the message handler:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point) 
{
DrawTo.x = point.x; 
    DrawTo.y = point.y;
.
.
}

It also has the same parameter, CPoint point.

Drawing lines

How can we get the device context? We can get it by the CClientDC class which is inherited from the CDC class. Add the following code to the OnLButtonUp message handler:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point) 
{
DrawTo.x = point.x; 
    DrawTo.y = point.y; 

    CClientDC* pDC = new CClientDC(this);
.
.
}

The this points to the current object.

Then we must check the bLineFlag. If bLineFlag is TRUE, we are ready to draw the line by moving to the Anchor point (the point that the user clicked on), then we draw the line to the DrawTo point:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point) 
{
.
.
    CClientDC* pDC = new CClientDC(this); 

    if(bLineFlag){
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(DrawTo.x, DrawTo.y);
    }
.
.
}

Drawing rectangles

Drawing rectangles is easy, by checking the bRectangleFlag and adding this code:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point) 
{
.
.
    if(bLineFlag){
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(DrawTo.x, DrawTo.y);
}    
If(bRectangleFlag){
        pDC->SelectStockObject(NULL_BRUSH); 
        pDC->Rectangle(Anchor.x, Anchor.y, DrawTo.x, DrawTo.y);
    }
.
.
}

That will draw a transparent rectangle, because we selected the NULL_BRUSH in the SelectStockObject.

Add the following code in the OnLButtonUp handler to draw ellipses:

.
.
if(bEllipseFlag){
        pDC->SelectStockObject(NULL_BRUSH); 
        pDC->Ellipse(Anchor.x, Anchor.y, DrawTo.x, DrawTo.y); 
        }
.
.

Filling the shapes with color

Till now all the shapes are transparent, we can fill them by the FloodFill method. I'll use BLACK_BRUSH for the filling brush. When we finish, the OnLButtonUp handler must be like this:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point) 
{
    DrawTo.x = point.x; 
    DrawTo.y = point.y; 

    CClientDC* pDC = new CClientDC(this); 

    if(bLineFlag){
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(DrawTo.x, DrawTo.y); 
    }

    if(bRectangleFlag){
        pDC->SelectStockObject(NULL_BRUSH); 
        pDC->Rectangle(Anchor.x, Anchor.y, DrawTo.x, DrawTo.y); 
    }

    if(bEllipseFlag){
        pDC->SelectStockObject(NULL_BRUSH); 
        pDC->Ellipse(Anchor.x, Anchor.y, DrawTo.x, DrawTo.y); 
    }

    if(bFillFlag){
        pDC->SelectStockObject(BLACK_BRUSH); 
        pDC->FloodFill(Anchor.x, Anchor.y, RGB(0, 0, 0)); 
    }

    delete pDC; 
    
    CView::OnLButtonUp(nFlags, point);
}

Free drawing using the mouse

Add a message handler for the WM_MOUSEMOVE message:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point) 
{
    CClientDC* pDC = new CClientDC(this); 
    .
    .    
    delete pDC; 
    CView::OnMouseMove(nFlags, point);
}

Then we must check the bDrawFlag, if the mouse is still moving and the left button still unreleased. We can do that by the nFlags and the MK_LBUTTON:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point) 
{
    CClientDC* pDC = new CClientDC(this); 
    if((nFlags && MK_LBUTTON) && bDrawFlag){
        .
        .
    }    
    delete pDC; 
    CView::OnMouseMove(nFlags, point);
}

Suppose we are drawing using the free hand. When we draw from the Anchor point to the current point, the current point will become the Anchor point, like this:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point) 
{
    CClientDC* pDC = new CClientDC(this); 
    if((nFlags && MK_LBUTTON) && bDrawFlag){
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(point.x, point.y); 
        Anchor.x = point.x; 
        Anchor.y = point.y;
    }    
    delete pDC; 
    CView::OnMouseMove(nFlags, point);
}

Now our program has been finished, but there are two problems. When we draw we can't see what we have been drawing, and when we draw, and resize the window, every think will disappear.

To solve the first problem, we must draw from the Anchor to the DrawTo, then when the mouse moves (the user is still clicking on the left button), we must save the DrawTo in OldPoint and clear the line that we have been drawing and draw a new line to the DrawTo point.

We can do that as follows:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point) 
{
    int nOldMode; 

    .
    .
    if((nFlags && MK_LBUTTON) && bLineFlag){
        nOldMode = pDC->GetROP2();
        pDC->SetROP2(R2_NOT); 
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(OldPoint.x, OldPoint.y); 
        .
        . 
    }
    .
    .
}

Then draw the new line, and copy the current point to the OldPoint:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point) 
{
    int nOldMode; 
    .
    .
    if((nFlags && MK_LBUTTON) && bLineFlag){
        nOldMode = pDC->GetROP2();
        pDC->SetROP2(R2_NOT); 
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(OldPoint.x, OldPoint.y); 
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(point.x, point.y);
        OldPoint.x = point.x; 
        OldPoint.y = point.y; 
        pDC->SetROP2(nOldMode);
    }
    .
    .
}

Note when the user draws a line and passes over a black object, the line will appear in a white color.

In the same way, add the code for drawing the rectangles and ellipses. The OnMouseMove handler must be like this:

void CPainterView::OnMouseMove(UINT nFlags, CPoint point) 
{
    int nOldMode; 

    CClientDC* pDC = new CClientDC(this); 
    
    if((nFlags && MK_LBUTTON) && bDrawFlag){
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(point.x, point.y); 
        Anchor.x = point.x; 
        Anchor.y = point.y; 
    }

    if((nFlags && MK_LBUTTON) && bLineFlag){
        nOldMode = pDC->GetROP2();
        pDC->SetROP2(R2_NOT); 
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(OldPoint.x, OldPoint.y); 
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(point.x, point.y);
        OldPoint.x = point.x; 
        OldPoint.y = point.y; 
        pDC->SetROP2(nOldMode); 
    }
    
    if((nFlags && MK_LBUTTON) && bRectangleFlag){
        nOldMode = pDC->GetROP2();
        pDC->SetROP2(R2_NOT);
        pDC->SelectStockObject(NULL_BRUSH); 
        pDC->Rectangle(OldPoint.x, OldPoint.y, Anchor.x, Anchor.y); 
        pDC->Rectangle(Anchor.x, Anchor.y, point.x, point.y); 
        OldPoint.x = point.x; 
        OldPoint.y = point.y; 
        pDC->SetROP2(nOldMode); 
    }
    
    if((nFlags && MK_LBUTTON) && bEllipseFlag){
        nOldMode = pDC->GetROP2();
        pDC->SetROP2(R2_NOT);
        pDC->SelectStockObject(NULL_BRUSH); 
        pDC->Ellipse(OldPoint.x, OldPoint.y, Anchor.x, Anchor.y); 
        pDC->Ellipse(Anchor.x, Anchor.y, point.x, point.y); 
        OldPoint.x = point.x; 
        OldPoint.y = point.y; 
        pDC->SetROP2(nOldMode); 
    }

    delete pDC; 
    CView::OnMouseMove(nFlags, point);
}

Now, the first problem has been solved.

Refreshing our drawing

The metafile is a memory object that can support the device context, so, when the window is resized (maximized, minimized, etc), we can playback the metafile. That will redraw all the shapes that we have drawn.

Add this variable in the public section in the CPaintDoc class declaration:

CMetaFileDC* pMetaFileDC;

Then in the class constructor, add the following code:

CPainterDoc::CPainterDoc()
{
    pMetaFileDC = new CMetaFileDC(); 
    pMetaFileDC->Create(); 
}

Now, we must reflect every thing we draw in the metafile. That means when we call the device context, we must do the same in the metafile:

void CPainterView::OnLButtonUp(UINT nFlags, CPoint point) 
{
    CPainterDoc* pDoc = GetDocument(); 
    ASSERT_VALID(pDoc);

    DrawTo.x = point.x; 
    DrawTo.y = point.y; 

    CClientDC* pDC = new CClientDC(this); 

    if(bLineFlag){
        pDC->MoveTo(Anchor.x, Anchor.y); 
        pDC->LineTo(DrawTo.x, DrawTo.y); 
        pDoc->pMetaFileDC->MoveTo(Anchor.x, Anchor.y);
        pDoc->pMetaFileDC->LineTo(DrawTo.x, DrawTo.y); 
    }

    if(bRectangleFlag){
        pDC->SelectStockObject(NULL_BRUSH); 
        pDC->Rectangle(Anchor.x, Anchor.y, DrawTo.x, DrawTo.y); 
        pDoc->pMetaFileDC->SelectStockObject(NULL_BRUSH); 
        pDoc->pMetaFileDC->Rectangle(Anchor.x, 
                           Anchor.y, DrawTo.x, DrawTo.y); 
    }

    if(bEllipseFlag){
        pDC->SelectStockObject(NULL_BRUSH); 
        pDC->Ellipse(Anchor.x, Anchor.y, DrawTo.x, DrawTo.y); 
        pDoc->pMetaFileDC->SelectStockObject(NULL_BRUSH); 
        pDoc->pMetaFileDC->Rectangle(Anchor.x, 
                           Anchor.y, DrawTo.x, DrawTo.y); 
    }

    if(bFillFlag){
        pDC->SelectStockObject(BLACK_BRUSH); 
        pDC->FloodFill(Anchor.x, Anchor.y, RGB(0, 0, 0)); 
        pDoc->pMetaFileDC->SelectStockObject(NULL_BRUSH); 
        pDoc->pMetaFileDC->Rectangle(Anchor.x, 
                           Anchor.y, DrawTo.x, DrawTo.y); 
    }

    delete pDC; 
    
    CView::OnLButtonUp(nFlags, point);
}

When the window is required to redraw itself, it calls the OnDraw() function. What we have to do now is show the metafile. Add the following code to OnDraw function:

void CPainterView::OnDraw(CDC* pDC)
{
    CPainterDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    
    HMETAFILE MetaFileHandle = pDoc->pMetaFileDC->Close(); 
    pDC->PlayMetaFile(MetaFileHandle); 
    CMetaFileDC* ReplacementMetaFile = new CMetaFileDC(); 
    ReplacementMetaFile->Create(); 
    ReplacementMetaFile->PlayMetaFile(MetaFileHandle); 
    DeleteMetaFile(MetaFileHandle); 
    delete pDoc->pMetaFileDC; 
    pDoc->pMetaFileDC = ReplacementMetaFile; 
}

That will solve the second problem. Now we are ready to start our program. Enjoy.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Malaysia Malaysia
I'm iraqi citizen living in Libya, I have a B.Sc degree in computer engineering, my hobbies is only programing, now i'm working in a computer services centre as a computer engineer.

Comments and Discussions

 
GeneralClass Wizard? (visual studio 2005) Pin
Taronium26-Nov-07 12:07
Taronium26-Nov-07 12:07 
GeneralThe program has problems...Please Help me Pin
idan18819-Jul-07 2:27
idan18819-Jul-07 2:27 
Questionusb connection Pin
HOYAM13-Jul-07 23:30
HOYAM13-Jul-07 23:30 
GeneralHelp me about the SetRop2 Pin
wzh1983122115-Dec-06 1:09
wzh1983122115-Dec-06 1:09 
GeneralFinally - how to use the nFlags! Pin
nadiric19-Aug-06 13:11
nadiric19-Aug-06 13:11 
GeneralMany thanks Pin
Dang Xuan Ky14-Dec-05 15:02
Dang Xuan Ky14-Dec-05 15:02 
GeneralRe: Many thanks Pin
haozheng27-Jun-06 22:43
haozheng27-Jun-06 22:43 
QuestionHow to draw "anti aliases line" Pin
Yunus Kurniawan25-Jul-05 22:39
Yunus Kurniawan25-Jul-05 22:39 
GeneralHelp with CScrollView or CMetaFileDC pls Pin
Dimitris Vikeloudas14-Jan-05 0:24
Dimitris Vikeloudas14-Jan-05 0:24 
Generalthx!! Pin
atryr10-Jun-04 2:01
atryr10-Jun-04 2:01 
Generali need help to develop a program for a CNC machine! Pin
IT_student22-Jul-03 9:17
IT_student22-Jul-03 9:17 
QuestionSelect shape? Pin
syntaxZero7-Apr-03 5:46
syntaxZero7-Apr-03 5:46 
GeneralShloneck Habibi... Pin
zarzor14-Feb-03 15:32
zarzor14-Feb-03 15:32 
GeneralRe: Shloneck Habibi... Pin
Aymen++14-Feb-03 21:42
Aymen++14-Feb-03 21:42 
GeneralIngrates Pin
EpicBoy14-Feb-03 6:15
EpicBoy14-Feb-03 6:15 
GeneralRe: Ingrates Pin
Davide Pizzolato14-Feb-03 9:40
Davide Pizzolato14-Feb-03 9:40 
GeneralRe: Ingrates Pin
Dave_14-Feb-03 9:50
Dave_14-Feb-03 9:50 
QuestionWhat's the point? Pin
Quentin Pouplard14-Feb-03 3:55
Quentin Pouplard14-Feb-03 3:55 
GeneralPoor design Pin
R. Reyes14-Feb-03 1:20
R. Reyes14-Feb-03 1:20 
GeneralRe: Poor design Pin
Claudius Mokler14-Feb-03 1:37
Claudius Mokler14-Feb-03 1:37 
Another point:
Using a specific naming schemes for variables is a good idea, so why are member variables not named accordingly?
The MFC crowd is pretty used to the 'm_' prefix.

The best way to learn programming is debugging.
GeneralUm... Pin
Christian Graus13-Feb-03 9:49
protectorChristian Graus13-Feb-03 9:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.