Step 4: Drawing in the window

Back Home Up Next

You can find the source for Step 4 in the file STEP04.CPP in the directory EXAMPLES\OWL\TUTORIAL. In this step, you'll add the ability to draw a line in the window by pressing the left mouse button and dragging. To do this, you'll add a two new events, WM_MOUSEMOVE and WM_LBUTTONUP, to the TDrawWindow response table, along with functions to handle those events. You'll also add a TClientDC * to the class.

Adding new events

To let the user draw on the window, the application must handle a number of events:

To start drawing the line, you have to look for the user to press the left mouse button. This is already taken care of by handling the WM_LBUTTONDOWN event.
Once the user has pressed the left button down, you have to look for them to move the mouse. At this point, you're drawing the line. To know when the user is moving the mouse, catch the WM_MOUSEMOVE event.
You then need to know when the user is finished drawing the line. The user is finished when the left mouse button is released. You can monitor for this by catching the WM_LBUTTONUP event.

You need to add two macros to the window class' response table, EV_WM_MOUSEMOVE and EV_WM_LBUTTONUP. The new response table should look something like this:

DEFINE_RESPONSE_TABLE1(TDrawWindow,  TWindow)
    EV_WM_LBUTTONDOWN,
    EV_WM_RBUTTONDOWN,
    EV_WM_MOUSEMOVE,
    EV_WM_LBUTTONUP,
END_RESPONSE_TABLE;
       

You also need to add the EvLButtonUp and EvMouseMove functions to the TDrawWindow class.

Adding a TClientDC pointer

The scheme used in Step 3 to draw a line isn't very robust:

In Step 3, you created a TClientDC object in the EvLButtonDown function that was automatically destroyed when the function returned. But now you need a valid device context across three different functions, EvLButtonDown, EvMouseMove, and EvLButtonUp.
You can catch the WM_MOUSEMOVE event and draw from the current point to the point passed into the EvMouseMove handling function. But WM_MOUSEMOVE events are sent out whenever the mouse is moved. You only want to draw a line when the mouse is moved with the left button pressed down.

You can take care of both of these problems rather easily by adding a new protected data member to TDrawWindow. This data member is a TDC * called DragDC. It works this way:

When the left mouse button is pressed, the EvLButtonDown function is called. This function creates a new TClientDC and assigns it to DragDC. It then sets the current point in DragDC to the point at which the mouse was clicked. The code for this function should look something like this:
void

TDrawWindow::EvLButtonDown(uint,  TPoint&  point)

{
    Invalidate();

    if  (!DragDC)  {
        SetCapture();

        DragDC  =  new  TClientDC(*this);

        DragDC->MoveTo(point);

    }

      }


                
When the left mouse button is released, the EvLButtonUp function is called. If DragDC is valid (that is, if it represents a valid device context), EvLButtonUp deletes it, setting it to 0. The code for this function should look something like this:

void

TDrawWindow::EvLButtonUp(uint,  TPoint&)

{
    if  (DragDC)  {

        ReleaseCapture();

        delete  DragDC;

        DragDC  =  0;

    }

}
                
When the mouse is moved, the EvMouseMove function is called. This function checks whether the left mouse button is pressed by checking DragDC. If DragDC is 0, either the mouse button has not been pressed at all or it has been pressed and released. Either way, the user is not drawing, and the function returns. If DragDC is valid, meaning that the left mouse button is currently pressed down, the function draws a line from the current point to the new point using the TWindow::LineTo function.

void
TDrawWindow::EvMouseMove(uint,  TPoint&  point)
{
    if  (DragDC)
        DragDC->LineTo(point);
}
            

Initializing DragDC

You must make sure that DragDC is set to 0 when you construct the TDrawWindow object:

TDrawWindow::TDrawWindow(TWindow  *parent)
{
    Init(parent,  0,  0);
    DragDC  =  0;
}
       

Cleaning up after DragDC

Because DragDC is a pointer to a TClientDC object, and not an actual TClientDC object, it isn't automatically destroyed when the TDrawWindow object is destroyed. You need to add a destructor to TDrawWindow to properly clean up. The only thing required is to call delete on DragDC. TDrawWindow should now look something like this:

class  TDrawWindow  :  public  TWindow
{
    public:
        TDrawWindow(TWindow  *parent  =  0);
~TDrawWindow()  {delete  DragDC;}

    protected:
        TDC  *DragDC;

        //  Override  member  function  of  TWindow
        bool  CanClose();

        //  Message  response  functions
        void  EvLButtonDown(uint,  TPoint&);
        void  EvRButtonDown(uint,  TPoint&);
        void  EvMouseMove(uint,  TPoint&);
        void  EvLButtonUp(uint,  TPoint&);

        DECLARE_RESPONSE_TABLE(TDrawWindow);
};
       

Note that, because the tutorial application has now become somewhat useful, the name of the main window has been changed from "Sample ObjectWindows Program" to "Drawing Pad":


SetMainWindow(new  TFrameWindow(0,  "Drawing  Pad",  new  TDrawWindow));
       

Where to find more information

Here's a guide to where you can find more information on the topics introduced in this step:

Event handling is discussed in "Event handling" in the ObjectWindows Programmer's Guide.
Device contexts and the TDC classes are discussed in "Graphics objects" in the ObjectWindows Programmer's Guide.
Predefined response table macros and their corresponding event-handling functions are listed in Chapter 3 in the ObjectWindows Reference Guide.
 


Copyright © 1998-2001 Yura Bidus. All rights reserved.