Vous êtes sur la page 1sur 12

MFC TUTORIAL: Creating a Graphics Program

This tutorial shows how to create a "Paint" style program that allows the user to draw freehand, create
shapes and output text to the screen. Some of the things you will learn:

 Using windows common dialogs


o CColorDialog
o CFontDialog
 Using menu items as check marks and radio buttons
 Freehand Drawing
 Drawing Shapes (Rectangles, Ellipses and Polygons)
 Using Pens and Brushes
 Drawing Text to the Screen
 Selecting Fonts
 Handling Mouse Messages
o Left Button Down
o Right Button Down
o Mouse Dragging
o Shift and Control Clicks

First we create a Window Application width the following menu:

Notice that "Random Colors" has the "Checked" property selected. This is an option in the menu item
properties.

Select the "Pop-up" property for popup menus like the ones above. For example
Here are the other popup menus we need to create:

The last three items are going to be radio buttons. Only one of them can be selected at one time. The
selected item will have a black circle in front of it. This will be accomplished later in code.
The last three items will be radio buttons.

Setting the Color

We first add some member variables to the ChildView class. The ones relating to color are shown in
bold face.
These go in the .h file:

class CChildView : public CWnd


{
// Construction
public:
CChildView();

// Attributes
public:
int xPos, yPos;
bool mouseDown;
COLORREF color;
bool randomColors;
int shapeType, shapeWidth, shapeHeight;
int penWidth;
COLORREF background;
CString text;
LOGFONT* lfPtr;
int textBackground;

They are initialized in the class constructor (.cpp file):

CChildView::CChildView()
{
mouseDown = false;
shapeType = 0; shapeWidth = shapeHeight = 50;
randomColors = true;
color = 0;
penWidth = 3;
text = "";
textBackground = 2;
lfPtr = NULL; // no font chosen yet (use default font)
background = RGB(40,0,70); // dark blue/violet
}
We set the background color in the PreCreateWindow() function:

BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)


{
if (!CWnd::PreCreateWindow(cs))
return FALSE;

cs.dwExStyle |= WS_EX_CLIENTEDGE;
cs.style &= ~WS_BORDER;
cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
::LoadCursor(NULL, IDC_UPARROW), CreateSolidBrush(background), NULL);

return TRUE;
}

We want to let the user select a color or choose random colors. This is done using the COMMAND
and UPDATE_COMMAND_UI messages for the Random Colors menu item:

void CChildView::OnRandomcolors()
{
randomColors = !randomColors;
}

// set check mark if randomColors is true


// this function will be called whenever the menu item is displayed
void CChildView::OnUpdateRandomcolors(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(randomColors);

But how does the user choose a particualt (non-random) color? Windows is kind enough to provide a
built in dialog class for this: CColorDialog.

void CChildView::OnChooseColor()
{
randomColors = false;
CColorDialog dlg;
dlg.m_cc.Flags |= CC_FULLOPEN; // opens custom color selector
dlg.DoModal(); // open color dialog
color = dlg.GetColor(); // set current color to the one chosen
}

The ColorDialog looks like this:

The value of GetColor() will be whatever color is last selected when then dialog is closed. Notice
that we can select either a basic color or create our own color.

The last message we need to handle if for "Erase". This one is simple:
void CChildView::OnErase()
{
color = background;
randomColors = false;
}

When we draw, the Pen color will be the same as the background color.
Selecting the Shape

The shape menu will be used to set the variable shapeType. This variable can have the values 0, 1
or 2 corresponding to rectangle, oval or random. Here are the message handlers for the COMMAND
and UPDATE_COMMAND_UI messages:

void CChildView::OnRectangle()
{
shapeType = 0;

void CChildView::OnOval()
{
shapeType = 1;

}
void CChildView::OnRandomshape()
{
shapeType = 2;

}
// use menu items as radio buttons
void CChildView::OnUpdateRectangle(CCmdUI* pCmdUI)
{
pCmdUI->SetRadio(shapeType == 0);

void CChildView::OnUpdateOval(CCmdUI* pCmdUI)


{
pCmdUI->SetRadio(shapeType == 1);

void CChildView::OnUpdateRandomshape(CCmdUI* pCmdUI)


{
pCmdUI->SetRadio(shapeType == 2);

}
Choosing Sizes
There are two dialogs we have created using the resource editor. These are associated with the
CPenWidth and CShapeSize dialog classes. Remember how to do this? Right click on the dialog
and select Class Wizard. You will be prompted to create a new class for the dialog.

We associate member variables m_penWidth with the Pen Width edit box and m_shapeWidth and
m_shapeHeight with the other two edit boxes.

Now we can add message handler for the Size menu


void CChildView::OnPensize()
{
CPenWidth dlg;
dlg.m_penWidth = penWidth;
dlg.DoModal();
penWidth = dlg.m_penWidth;
}

void CChildView::OnShapesize()
{
SizeDlg dlg;
dlg.m_shapeHeight = shapeHeight;
dlg.m_shapeWidth = shapeWidth;
dlg.DoModal();
shapeWidth = dlg.m_shapeWidth;
shapeHeight = dlg.m_shapeHeight;

Notice that we transfer data into the dialog before opening it (via DoModal()) so that the user will
know what the current values are. Then we transfer data back to the member variables of CChildView
(e.g. penWidth) after closing the dialog. There is no need to call UpdateData() in this situation.

Creating Text
We want to be able to output text as well as shapes. Therefore we have a variable text in
CChildView of type CString. The user enters text via the Text dialog:
We create a CText class for this dialog and associate CString variable m_Text with the edit box.
Now we can add the following message handler for the Create Text menu item:

void CChildView::OnSelectText()
{
CText dlg;
dlg.m_Text = text;
dlg.DoModal();
text = dlg.m_Text;

The member variable text of CChildView will be used when we want to display text.

Selecting a Font

Again Windows provides us with a handy Common Dialog for this: CFontDialog. Here is the message
handler for the Select Font menu item:

void CChildView::OnFont()
{
CFontDialog dlg;
dlg.DoModal();
if (lfPtr == NULL) lfPtr = new LOGFONT;
dlg.GetCurrentFont(lfPtr);
}

Notice that we use the member variable lfPtr (its a pointer to a struct) to store the new font
attributes. This isn't itself a font but will be used later to create the font when we are ready to output
some text.

Setting the Text Background

Text can be displayed in a rectangle with some background color. Or we can make the background
transparent. Here are the menu items again:
And the event handlers for these three radio button menu items:

void CChildView::OnFontTransparentbackground()
{
textBackground = 0;
}

void CChildView::OnUpdateFontTransparentbackground(CCmdUI* pCmdUI)


{
pCmdUI->SetRadio(textBackground == 0);
}

void CChildView::OnFontWhitebackground()
{
textBackground = 1;
}
void CChildView::OnUpdateFontWhitebackground(CCmdUI* pCmdUI)
{
pCmdUI->SetRadio(textBackground == 1);
}

void CChildView::OnFontColorbackground()
{
textBackground = 2;
}

void CChildView::OnUpdateFontColorbackground(CCmdUI* pCmdUI)


{
pCmdUI->SetRadio(textBackground == 2);
}

Handling the Left Button Messages

When the user clicks the left mouse button we want to record the position and set the mouseDown
flag in case this is a drag event. If this is a Shift Click we want to output the value of our text member
variable. We could also test for the MK_CONTROL flag if we want to handle Control-click events.

void CChildView::OnLButtonDown(UINT nFlags, CPoint point)


{
if (nFlags & MK_SHIFT) { // draw text
if (text == "") {
MessageBox("You must create some text first"); return;
}
CClientDC dc(this);
if (lfPtr != NULL) { // user has opened the font dialog already
CFont font;
font.CreateFontIndirect(lfPtr);
dc.SelectObject(&font);
}
dc.SetTextColor(getColor());
switch (textBackground) {
case 0:
dc.SetBkMode(TRANSPARENT); break;
case 1:
dc.SetBkMode(OPAQUE);
dc.SetBkColor(RGB(255,255,255));
break;
case 2:
dc.SetBkMode(OPAQUE);
dc.SetBkColor(getColor());
}
dc.TextOut(point.x,point.y, text);
dc.SelectStockObject(SYSTEM_FONT);
return;
}
mouseDown = true;
xPos = point.x; yPos = point.y; // starting point for dragging

CWnd ::OnLButtonDown(nFlags, point);

Here is the getColor() function:

COLORREF CChildView::getColor()
{
if (randomColors) return RGB(rand()%256,rand()%256,rand()%256);
return color;

Here is the Left Button Up message handler:

void CChildView::OnLButtonUp(UINT nFlags, CPoint point)


{
mouseDown = false; // No more dragging
CWnd ::OnLButtonUp(nFlags, point);
}

Handling Mouse Move Messages

We use the mouse move messages only when the left mouse button is down. This is a dragging even.
We are going to draw whereever the cursor is:
void CChildView::OnMouseMove(UINT nFlags, CPoint point)
{
if (!mouseDown) return;

CClientDC dc(this);
// we need to create a Pen to draw lines
CPen p;
p.CreatePen(0,penWidth,getColor());
dc.SelectObject(&p);
dc.MoveTo(xPos,yPos);
dc.LineTo(point.x,point.y);
xPos = point.x;
yPos = point.y;
// the next line avoids that "freezing" problem
dc.SelectStockObject(NULL_PEN);
CWnd ::OnMouseMove(nFlags, point);
}

Handling Right Button Clicks


void CChildView::OnRButtonDown(UINT nFlags, CPoint point)
{
int xPos = point.x, yPos = point.y, h = shapeHeight, w = shapeWidth;
CClientDC dc(this);
CBrush b;
b.CreateSolidBrush(getColor());
dc.SelectObject(&b);

switch (shapeType) {
case 0:
dc.Rectangle(xPos -h/2,yPos - w/2,xPos +h/2,yPos + w/2); break;
case 1:
dc.Ellipse (xPos -h/2,yPos - w/2,xPos +h/2,yPos + w/2); break;
case 2:
POINT points[20];
int size = rand()%8 + 5;
for (int i = 0; i< size; i++) {
points[i].x = point.x+rand()%h- h/2;
points[i].y = point.y+rand()%w -w/2;
}
dc.Polygon(points,size);
}
dc.SelectStockObject(NULL_BRUSH);
CWnd ::OnRButtonDown(nFlags, point);
}

The shapes will be centered around the cursor when we click. Polygons will have a random number of
points configured in random locations. Zigzags basically.

The Finished Program:

Draw.exe

Vous aimerez peut-être aussi