5,696,038 members and growing! (16,093 online)
Email Password   helpLost your password?
General Reading » Hardware & System » Mouse Topics     Intermediate

Painter Program

By Aymen++

A simple program that demonstrates how to use mouse messages and how to draw using MFC.
VC6, C++Windows, Win2K, WinXP, Visual Studio, MFC, Dev

Posted: 12 Feb 2003
Updated: 12 Feb 2003
Views: 67,757
Bookmarked: 33 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
25 votes for this Article.
Popularity: 4.58 Rating: 3.27 out of 5
6 votes, 24.0%
1
4 votes, 16.0%
2
2 votes, 8.0%
3
2 votes, 8.0%
4
11 votes, 44.0%
5

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:

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:

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.

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

About the Author

Aymen++


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.
Occupation: Web Developer
Location: Malaysia Malaysia

Other popular Hardware & System articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 21 of 21 (Total in Forum: 21) (Refresh)FirstPrevNext
GeneralClass Wizard? (visual studio 2005)memberTaronium13:07 26 Nov '07  
GeneralThe program has problems...Please Help mememberidan1883:27 19 Jul '07  
Questionusb connectionmemberHOYAM0:30 14 Jul '07  
GeneralHelp me about the SetRop2memberwzh198312212:09 15 Dec '06  
GeneralFinally - how to use the nFlags!membernadiric14:11 19 Aug '06  
GeneralMany thanksmemberDang Xuan Ky16:02 14 Dec '05  
GeneralRe: Many thanksmemberhaozheng23:43 27 Jun '06  
GeneralHow to draw "anti aliases line"memberYunus Kurniawan23:39 25 Jul '05  
GeneralHelp with CScrollView or CMetaFileDC plsmemberDimitris Vikeloudas1:24 14 Jan '05  
Generalthx!!susssnorty3:01 10 Jun '04  
Generali need help to develop a program for a CNC machine!memberIT_student10:17 22 Jul '03  
GeneralSelect shape?membermystic6:46 7 Apr '03  
GeneralShloneck Habibi...memberzarzor16:32 14 Feb '03  
GeneralRe: Shloneck Habibi...memberAymen++22:42 14 Feb '03  
GeneralIngratesmemberEpicBoy7:15 14 Feb '03  
GeneralRe: IngratessupporterDavide Pizzolato10:40 14 Feb '03  
GeneralRe: IngratesmemberDave_10:50 14 Feb '03  
GeneralWhat's the point?memberQuentin Pouplard4:55 14 Feb '03  
GeneralPoor designmemberReyes2:20 14 Feb '03  
GeneralRe: Poor designmemberClaudius Mokler2:37 14 Feb '03  
GeneralUm...memberChristian Graus10:49 13 Feb '03  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 12 Feb 2003
Editor: Smitha Vijayan
Copyright 2003 by Aymen++
Everything else Copyright © CodeProject, 1999-2008
FileMail | Advertise on the Code Project