Reliable Software Logo

C++ in Action: Windows Techniques

Painting

Application Icon

Every Windows program must have an icon. When you browse into the directory where the executable is stored, Windows browser will display this program's icon. When the program is running, this icon shows up in the taskbar and in the upper-left corner of the program's window. If you don't provide your program with an icon, Windows will provide a default.

The obvious place to specify an icon for your application is in the window class of the top-level window. Actually, it's best to provide two icons at once, the large one and the small one, otherwise Windows will try to stretch or shrink the one icon you give it, often with un-esthetic results.

Let's add a SetIcons method to Win::ClassMaker and embed two icon objects in it.

class ClassMaker
{
public:
    ...
    void SetIcons (int id);
protected:
    WNDCLASSEX    _class;
    StdIcon       _stdIcon;
    SmallIcon     _smallIcon;
};

We'll get to the implementation of StdIcon and SmalIcon soon. First, let's look at the implementation of SetIcons. The images of icons are loaded from program resources.

void ClassMaker::SetIcons (int id)
{
    _stdIcon.Load (_class.hInstance, id);
    _smallIcon.Load (_class.hInstance, id);
    _class.hIcon = _stdIcon;
    _class.hIconSm = _smallIcon;
}

Program resources are icons, bitmaps, strings, mouse cursors, dialog templates, etc., that you can tack on to your executable. Your program, instead of having to search the disk for files containing such resources, simply loads them from its own executable. How do you identify resources when you want to load them? You can either give them names or integer ids. For simplicity (and efficiency), we will use ids. The set of your program's resources is identified by the instance handle that is passed to WinMain.

Lets start with the base class, Win::Icon. When you load an icon, you have to specify the resources where it can be found, the unique id of the particular icon, its dimensions in pixels (if the actual icon has different dimensions, Windows will stretch or shrink it) and some flags.

class Icon
{
public:
    Icon (HINSTANCE res, 
          int id, 
          int dx = 0, 
          int dy = 0, 
          unsigned flag = LR_DEFAULTCOLOR)
    {
        Load (res, id, dx, dy, flag);
    }
    ~Icon ();
    operator HICON () const { return _h; }
protected:
    Icon () : _h (0) {}
    void Load (HINSTANCE res, 
            int id, 
            int dx = 0, 
            int dy = 0, 
            unsigned flag = LR_DEFAULTCOLOR);
protected:
    HICON _h;
};

The API to load an icon is called LoadImage and can be also used to load other types of images. It's return type is ambiguous, so it has to be cast to HICON. Once the icon is no longer used, DestroyIcon is called.

void Icon::Load (HINSTANCE res, int id, int dx, int dy, unsigned flag)
{
    _h = reinterpret_cast<HICON> (
        ::LoadImage (res, 
                     MAKEINTRESOURCE (id), 
                     IMAGE_ICON, 
                     dx, dy, 
                     flag));
    if (_h == 0)
        throw "Icon load image failed";
}

Icon::~Icon ()
{
    ::DestroyIcon (_h);
}

Notice that we can't pass the icon id directly to the API. We have to use a macro MAKEINTRESOURCE which does some cheating behind the scenes.

You see, LoadImage and several other APIs have to guess whether you are passing them a string or an id. Since these are C functions, they can't be overloaded. Instead, you have to trick them into accepting both types and then let them guess their real identity. MAKEINTRESOURCE mucks with the bits of the integer to make it look different than a pointer to char. (This is the kind of programming that was popular when Windows API was first designed.)

We can immediately subclass Icon to SmallIcon and StdIcon. Their constructors and Load methods are simpler--they don't require dimensions or flags.

class SmallIcon: public Icon
{
public:
    SmallIcon () {}
    SmallIcon (HINSTANCE res, int id);
    void Load (HINSTANCE res, int id);
};

class StdIcon: public Icon
{
public:
    StdIcon () {}
    StdIcon (HINSTANCE res, int id);
    void Load (HINSTANCE res, int id);
};

The Load methods are implemented using the parent class' Icon::Load method (you have to use the parent's class name followed by double colon to disambiguate--without it the compiler would understand it as a recursive call and the program would go into an infinite loop.

To find out what the correct sizes for small and standard icons are, we use the universal API, GetSystemMetrics that knows a lot about current system's defaults.

void SmallIcon::Load (HINSTANCE res, int id)
{
    Icon::Load (res, id, 
            ::GetSystemMetrics (SM_CXSMICON),
            ::GetSystemMetrics (SM_CYSMICON));
}

void StdIcon::Load (HINSTANCE res, int id)
{
    Icon::Load (res, id, 
            ::GetSystemMetrics (SM_CXICON),
            ::GetSystemMetrics (SM_CYICON));
}

There's one more thing: how does one create icons? There is a hard way and an easy way. The hard way is to have some kind of separate icon editor, write your own resource script that names the icon files and, using a special tool, compile it and link with the executable.

Just to give you an idea of what's involved, here are some details. Your resource script file, let's call it script.rc, should contain these two lines:

#include "resource.h"
IDI_MAIN   ICON   "main.ico"

IDI_MAIN is a constant defined in resource.h. The keyword ICON means that it corresponds to an icon. What follows is the name of the icon file, main.ico.

The header file, resource.h, contains the definitions of constants, for instance:

#define IDI_MAIN   101

Unfortunately, you can't use the sefer, C++ version of it,

const int IDI_MAIN = 101;

A macro substitution results in exactly the same code as const int definition. The only difference is that, as is usual with macros, you forgo type checking.

The script file has to be compiled using a program called rc.exe (resource compiler) to produce a file script.res. The linker will then link such file with the rest of the object files into one executable.

Or, if you have an integrated development environment with a resource editor, you can create an icon in it, add it to your resources under an appropriate symbolic id, and let the environment do the work for you. (A graphical resource editor becomes really indispensable when it comes to designing dialogs.)

Notice that I'm using the same id for both icons. It's possible, because you can have two (or more) images of different size in the same icon. When you call LoadImage, the one with the closest dimensions is picked. Normally, you'd create at least a 32x32 and a 16x16 icon.

I have created a set of two icons and gave them an integer id IDI_MAIN (defined in resource.h). All I need now is to make one additional call in WinMain.

    Win::ClassMaker winClass (className, hInst);
    winClass.SetIcons (IDI_MAIN);
    winClass.Register ();

Finally, you might be wondering: if you add many icons to your program resources, which one is used by the system as the icon for the whole executable? The answer is, the one with the lowest numerical id.

Window Painting and the View Object

Just like with any other action in Windows, window painting is done in response to some external actions. For instance, your program may paint someting whenever a user moves a mouse or clicks a mouse button, it may draw characters in response to key presses, and so on. The part of the window that you normally paint is called the client area--it doesn't include the window borders, the title bar, the menu, etc.

But there is one more situation when Windows may ask your program to redraw a part or the whole client area of your window. That happens because Windows is lazy (or short of resources). Whenever another application (or sometimes your own menu) overlaps your program's window, the system simply throws away the part of the image that's occluded. When your window is finally uncovered, somebody has to redraw the discarded part. Guess who! Your program! The same thing happens when a window is minimized and then maximized again. Or when the user resizes the window.

Since, from the point of view of your application, these actions happen more or less randomly, you have to be prepared, at any time, to paint the whole client area from scratch. There is a special message, WM_PAINT, that Windows sends to you when it needs your assistance in repainting the window. This message is also sent the first time the window is displayed.

To illustrate painting, we'll extend our Windows program to trace mouse movements. Whenever the mouse moves, we'll draw a line connecting the new cursor position with the previous one. But before we do that, we'll want to add the second object from the triad Model-View-Controller to our program. The View will take care of all painting operations. It will also store the last recorded position of the mouse.

class TopController: public Win::Controller
{
    ...
private:
    View _view;
};

The Canvas

All display operations are done in the context of a particular device, be it the screen, a printer, a plotter or something else. In the case of drawing to a window, we have to obtain a device context (DC) for this window's client area. Windows can internally create a DC for us and give us a handle to it. We use this handle for all window output operations. When done with the output, we must release the handle.

A DC is a resource and the best way to deal with it is to apply Resource Management methods to it. We'll call the generic owner of a DC, Canvas. We will have many different types of Canvas, depending on how the device context is created and disposed of. They will all, however, share the same functionality. For instance, we can call any Canvas object to draw a line or print some text. Let's make these two operations the starting point of our implementation.

namespace Win
{
    class Canvas
    {
    public:
        operator HDC () 
        { 
            return _hdc; 
        }
        void Line (int x1, int y1, int x2, int y2)
        {
            ::MoveToEx (_hdc, x1, y1, 0);
            ::LineTo (_hdc, x2, y2);
        }
        void Text (int x, int y, char const * buf, int count)
        {
            ::TextOut (_hdc, x, y, buf, count);
        }
    protected:
        Canvas (HDC hdc) :_hdc (hdc) {}

        HDC  _hdc;
    };
}

HDC is Windows data structure, a handle to a device context.

Our generic class, Canvas, doesn't provide any public way to initialize this handle--this responsibility is left to derived classes. The member operator HDC () provides implicit conversion from Canvas to HDC. It comes in handy when passing a Canvas object to an API that requires HDC.

In order to draw a line from one point to another, we have to make two API calls. The first one, MoveToEx, sets the "current position." The second, LineTo, draws a line from current position to the point specified as its argument (it also moves the current position to that point). Point positions are specified by two coordinates, x and y. In the default coordinate system, both are in units of screen pixels. The origin, corresponding to x = 0 and y = 0, is in the upper left corner of the client area of the window. The x coordinate increases from left to right, the y coordinate grows from top to bottom.

To print text, you have to specify where in the window you want it to appear. The x, y coordinates passed to TextOut tell Windows where to position the upper left corner of the string. This is different than printing to standard output, where the only control over placement was by means of newline characters. For a Windows device context, newlines have no meaning (they are blocked out like all other non-printable characters). In fact, the string-terminating null character is also meaningless to Windows. The string to be printed using TextOut doesn't have to be null-terminated. Instead, you are supposed to specify the count of characters you want printed.

So how and where should we obtain the device context? Since we want to do the drawing in response to every mouse move, we have to do it in the handler of the WM_MOUSEMOVE message. That means our Controller has to override the OnMouseMove virtual method of Win::Controller.

The type of Canvas that gets the DC from Windows outside of the processing of WM_PAINT, will be called UpdateCanvas. The pair of APIs to get and release a DC is GetDC and ReleaseDC, respectively.

class UpdateCanvas: public Canvas
{
public:
    UpdateCanvas (HWND hwnd)
        :   Canvas (::GetDC(hwnd)),
        _hwnd(hwnd)
    {}
    ~UpdateCanvas ()
    {
        ::ReleaseDC (_hwnd, _hdc);
    }
protected:
    HWND _hwnd;
};

We create the Canvas is in the appropriate Controller method--in this case OnMouseMove. This way the methods of View will work independent of the type of Canvas passed to them.

bool TopController::OnMouseMove 
        (int x, int y, Win::KeyState kState) throw ()
{
    Win::UpdateCanvas canvas (_h);
    _view.MoveTo (canvas, x, y);
    return true;
}

We are now ready to implement the View object.

class View
{
public:
    View () : _x (0), _y (0) {}
    void MoveTo (Win::Canvas & canvas, int x, int y)
    {
        canvas.Line (_x, _y, x, y);
        _x = x;
        _y = y;
        PrintPos (canvas);
    }
private:
    void PrintPos (Win::Canvas & canvas)
    {
        std::string str ("Mouse at: ");
        str += ToString (_x);
        str += ", ";
        str += ToString (_y);
        canvas.Text (0, 0, &str [0], str.length ());
    }
private:
    int _x, _y;
};

The PrintPos method is interesting. The purpose of this method is to print "Mouse at:" followed by the x and y coordinates of the mouse position. We want the string to appear in the upper left corner of the client area, at coordinates (0, 0).

First, we have to format the string. In particular, we have to convert two numbers to their string representations. The formatting of numbers for printing is built into standard streams so we'll just use the capabilities of a string-based stream. In fact, any type that is accepted by a stream can be converted to a string using this simple template function:

#include <sstream>

template<class T>
inline std::string ToString (T & val)
{
    std::stringstream out;
    out << val;
    return out.str ();
}

Afterwards, we use operator += to concatenate the various strings. Finally, converting a string to a pointer-to-character, as required by the API, is done by taking the address of the first character in the string.

You can now run this simple program, move the mouse cursor over the client area and see for yourself that it indeed leaves a trail and that the mouse coordinates are printed in the upper left corner. You will also discover several shortcomings. For instance, try minimizing the window. After you maximize it again, all the previous traces disappear and so does the mouse-position indicator. Also, if you cover part of the window with some other application window, and then uncover it again, the restored area will be empty--mouse traces will be erased. The same with resizing!

Other minor annoyances are related to the fact that, when the cursor leaves the window it's position is not updated, and when it enters the window again, a spurious line is drawn from the last remembered position to the new entry point.

To round out the list of complaints, try moving the mouse towards the lower right corner and back to the upper left corner. The string showing mouse coordinates becomes shorter (fewer digits), but the trailing digits from previous strings are not erased.

Let's try to address these problems one by one.

The WM_PAINT Message

First of all, every well-behaved Windows application must be able to respond to the WM_PAINT message. Ours didn't, so after its window was occludded or minimized and then restored, it didn't repaint its client area. What should we do when Windows asks us to restore the client area? Obviously, the best solution would be to redraw the mouse trace and redisplay mouse coordinates. The trouble is that we don't remember the trace. So let's start with a simple fix--redisplaying the coordinates.

A new case has to be added to our generic window procedure:

case WM_PAINT:
    if (pCtrl->OnPaint ())
        return 0;
    break;

Strictly speaking, WM_PAINT comes with a WPARAM that, in some special cases, having to do with common controls, might be set to a device context. For now, let's ignore this parameter and concentrate on the common case.

The standard way to obtain a device context in response to WM_PAINT is to call the API BeginPaint. This device context has to be released by a matching call to EndPaint. The ownership functionality is nicely encapsulated into the PaintCanvas object:

class PaintCanvas: public Canvas
{
public:
    PaintCanvas (HWND hwnd)
        :   Canvas (::BeginPaint (hwnd, &_paint)),
        _hwnd (hwnd)
    {}
    ~PaintCanvas ()
    {
        ::EndPaint(_hwnd, &_paint);
    }
    int Top () const    { return _paint.rcPaint.top; }
    int Bottom () const { return _paint.rcPaint.bottom; }
    int Left () const   { return _paint.rcPaint.left; }
    int Right () const  { return _paint.rcPaint.right; }
protected:
    PAINTSTRUCT _paint;
    HWND        _hwnd;
};

Notice that BeginPaint gives the caller access to some additional useful information by filling the PAINTSTRUCT structure. In particular, it is possible to retrieve the coordinates of the rectangular area that has to be repainted. In many cases this area is only a small subset of the client area (for instance, after uncovering a small portion of the window or resizing the window by a small increment). In our unsophisticated application we won't make use of this additional info--we'll just repaint the whole window from scratch.

Here's our own override of the OnPaint method of the controller. It creates a PaintCanvas and calls the appropriate View method.

bool TopController::OnPaint () throw ()
{
    Win::PaintCanvas canvas (_h);
    _view.Paint (canvas);
    return true;
}

View simply calls its private method PrintPos. Notice that View doesn't distinguish between UpdateCanvas and PaintCanvas. For all it knows, it is being given a generic Win::Canvas.

void View::Paint (Win::Canvas & canvas)
{
    PrintPos (canvas);
}

What can we do about the varying size of the string being printed? We need more control over formatting. The following code will make sure that each of the two numbers is be printed using a fixed field of width 4, by passing the std::setw (4) manipulator to the stream. If the number following it in the stream contains fewer than 4 digits, it will be padded with spaces.

void PrintPos (Win::Canvas & canvas)
{
    std::stringstream out;
    out << "Mouse at: " << std::setw (4) << _x;
    out << ", " << std::setw (4) << _y;
    out << "    ";
    std::string s = out.str ();
    canvas.Text (0, 0, &s [0], s.length ());
}
						

You may notice that, in a fit of defensive programming, I appended four spaces to the string. Actually, without these spaces, we would still have a problem of a shrinking string. The fact that the number of characters is now fixed to 24, doesn't guarantee that the displayed string will always occupy the same area. That's because Windows uses proportional fonts, in which some characters a wider than others. In particular a space is usually narrower than a non-space character. Whence the fudge factor.

The WM_PAINT message is also sent to our program in response to the user resizing the window. This is one of the cases when it would make sense to repaint only the newly added area (if any). The coordinates of the fresh rectangle are after all available through PaintCanvas.

This is not always as simple as it sounds. For instance, for some applications, any resizing of a window should call for a total repaint. Think of a window that stretches the picture to fill the whole client area.

Yet even for such an application, outside of resizing, it might make sense to limit the repainting only to freshly uncovered areas. So is it possible to have a cake and eat it too?

The answer is in two style bits that can be set when registering a window class. These bits are CS_HREDRAW and CS_VREDRAW. The first one tells the Windows to ask for the complete redraw (i.e., invalidate the whole client area) whenever a horizontal size (width) of the window changes. The second one does the same for height. You can set both by combining them using binary or.

Invalidating an area not only sends a WM_PAINT message with the appropriate bounding rectangle to our window, but it also erases the area by overpainting it with the background brush. The background brush is set in the window class definition--our default has been the standard brush with COLOR_WINDOW. (If instead the background brush is set to 0, no erasing will be done by Windows--try it!).

The Model

If we want to be able to do a meaningful redraw, we must store the history of mouse moves. This can mean only one thing: we are finally ready to introduce the Model part of the triad Model-View-Controller. In general, you don't want to put too much intelligence or history into the View or the Controller. Mouse trace is our program's data--its I/O-independent part. You could even imagine a command-line version of the program. It wouldn't be able to display the line visually, and you'd have to input each point by typing in it's coordinates, but the Model would be the same.

I decided to use a new data structure from the standard library, the deque (pronounced "deck"). It works like a double-ended vector. You can push and pop items from both ends, and the methods push_front and pop_front work as efficiently as push_back and pop_back.

We don't want the history to grow beyond MAX_SIZE points. So when we add a new point to it, if it would cause the deque to exceede this count, we will pop the oldest point. In fact, this is the gist of the traditional queue, or LIFO (Last In First Out), data structure.

#include <deque>
#include <utility> // pair

class Model
{
    enum { MAX_SIZE = 200 };
public:
    typedef std::deque< std::pair<int, int> >::const_iterator iter;

    Model ()
    {
        AddPoint (0, 0);
    }
    void AddPoint (int x, int y)
    {
        if (_queue.size () >= MAX_SIZE)
            _queue.pop_front ();
        _queue.push_back (std::make_pair (x, y));
    }
    iter begin () const { return _queue.begin (); }
    iter end () const { return _queue.end (); }
private:
    std::deque< std::pair<int, int> > _queue;
};

As you can see, points are stored as std::pairs of integers. I didn't bother to create a special data structure for a two-dimensional point. The function make_pair comes in handy when you don't want to explicitly specify the types of the members of the pair. You simply let the compiler deduce them from the types of arguments--in this case both are integers. Were we to use a pair of shorts instead, we would have to use the more explicit construct:

_queue.push_back (std::pair<short, short> (x, y));

The controller must have access to both, the model and the view. In response to a mouse move, it adds a new point to the model and, as before, tells the view to move to a new position.

bool TopController::OnMouseMove 
         (int x, int y, Win::KeyState kState) throw ()
{
    _model.AddPoint (x, y);
    Win::UpdateCanvas canvas (_h);
    _view.MoveTo (canvas, x, y);
    return true;
}

The repainting can now be done more intelligently (albeit wastefully--we still repaint the whole client area instead of just the rectangle passed to us in PaintCanvas). We obtain the iterator from the model and pass it to the view. The iterator gives the view access to the part of the trace that is still remembered.

bool TopController::OnPaint () throw ()
{
    Win::PaintCanvas canvas (_h);
    _view.Paint (canvas, _model.begin (), _model.end ());
    return true;
}

All the view has to do now is to connect the dots. We'll use a little trick from the <algorithm> section of the standard library. The for_each algorithm takes the starting iterator, the ending iterator and a functor.

We've already seen the use of a functor as a predicate for sorting. Here we'll make use of another type of functor that can operate on objects "pointed-to" by iterators. In our case, the iterator "points to" a pair of coordinates--what I mean is that, when it's dereferenced, it returns a reference to a std::pair<int, int>.

Our functor is called DrawLine and it draws a line from the last remembered position to the postion passed to it in a pair. We have to initialize it with the starting position and let the std::for_each template call it for each value from the iterator.

void View::Paint (Win::Canvas & canvas, 
                  Model::iter beg, 
                  Model::iter end)
{
    PrintPos (canvas);
    if (beg != end)
    {
        DrawLine draw (canvas, *beg);
        ++beg;
        std::for_each (beg, end, draw);
    }
}
						

If you're curious to know how for_each is implemented, it's really simple:

template<class Iter, class Op>
inline Op for_each (Iter it, Iter end, Op operation)
{
    for (; it != end; ++it)
        operation (*it);
    return (operation); 
}

Op here could be a pointer to a global function or an object of a class that overloads the function-call operator. This is the implementation of our functor:

class DrawLine
{
public:
    DrawLine (Win::Canvas & canvas, std::pair<int, int> const & p)
        : _canvas (canvas)
    {
        _canvas.MoveTo (p.first, p.second);
    }
    void operator () (std::pair<int, int> const & p)
    {
        _canvas.LineTo (p.first, p.second);
    }
private:
    Win::Canvas & _canvas;
};

The two new methods of Canvas, MoveTo and LineTo, are easy to implement.

Capturing the Mouse

It doesn't make much sense to unconditionally draw the trace of the mouse. If you have ever used any Windows graphical editor, you know that you're supposed to press and hold a mouse button while drawing. Otherwise there would be no way to "lift the pen" in order to access some controls or start drawing in a different place.

We could in principle use the Win::KeyState argument to OnMouseMove and check if a button is pressed. But we can do better than that and solve one more problem at the same time. I'm talking about being able to follow the mouse outside of the window.

Normally, mouse messages are sent only to the window over which the mouse cursor hovers. But if your window "captures" the mouse, Windows will redirect all mouse messages to it--even when the cursor is outside of its area. Obviously, capturing a mouse requires some caution. As long as the mouse is captured, the user will not be able to click any controls or interact with any other application (e.g., to activate it). That's why it is customary only to capture the mouse while it's being dragged--and the drag has to originate in the capturing window. Dragging is done by pressing and holding a mouse button while moving the mouse.

There are three APIs related to mouse capture: SetCapture, ReleaseCapture and GetCapture. SetCapture takes the handle to the window that wants to capture the mouse. ReleaseCapture ends the capture and sends the WM_CAPTURECHANGED message to the captor. GetCapture returns the handle to the window that currently has the capture, in the current application (strictly speaking, in the current thread).

We will add these APIs to our class Win::Dow representing a window handle.

namespace Win
{
    class Dow
    {
    public:
        Dow (HWND h = 0) : _h (h) {}
        void Init (HWND h) { _h = h; }
        operator HWND () const { return _h; }
        void CaptureMouse ()
        {
            ::SetCapture (_h);
        }
        void ReleaseMouse ()
        {
            if (HasCapture ())
                ::ReleaseCapture ();
        }
        bool HasCapture () const
        {
            return ::GetCapture () == _h;
        }
        // Window visibility
        void Show (int cmdShow = SW_SHOW) 
        { 
            ::ShowWindow (_h, cmdShow); 
        }
        void Hide () 
        { 
            ::ShowWindow (_h, SW_HIDE); 
        }
        void Update () 
        { 
            ::UpdateWindow (_h); 
        }
        void Display (int cmdShow)
        {
            Show (cmdShow);
            Update ();
        }
    private:
        HWND _h;
    };
}

The typical procedure for capturing a mouse goes like this:

  • Capture mouse when the user presses the appropriate mouse button.
  • While processing mouse-move messages, check if you have the capture. If so, implement dragging behavior.
  • Release capture when the user releases the mouse button.
  • Finish up dragging in response to WM_CAPTURECHANGED message.

It's important to wrap up dragging inside the WM_CAPTURECHANGED handler, rather than directly in response to button release. Your window may lose capture for unrelated reasons and it's important to clean up also in that case. (An example of externally initiated mouse capture change is when the system pops up an alert message.)

We will apply this procedure by overriding the handlers for WM_LBUTTONDOWN, WM_LBUTTONUP and WM_CAPTURECHANGED. (Notice that after all this talk, we leave the implementation of OnCaptureChange empty. That's because we don't have any dragging wrap-up.)

bool TopController::OnLButtonDown 
        (int x, int y, Win::KeyState kState) throw ()
{
    _h.CaptureMouse ();
    _model.AddPoint (x, y, true); // starting point
    Win::UpdateCanvas canvas (_h);
    _view.MoveTo (canvas, x, y, false); // don't draw
    return true;
}

bool TopController::OnLButtonUp 
        (int x, int y, Win::KeyState kState) throw ()
{
    // ReleaseMouse will send WM_CAPTURECHANGED
    _h.ReleaseMouse ();
    return true;
}

bool TopController::OnCaptureChanged 
        (HWND hwndNewCapture) throw ()
{
    return true;
}
						

The implementation of OnLButtonDown (that's Left Button Down) has some interesting points. Since the user now has the option to "lift the pen," we must be able to draw (and re-draw) non-continuous lines. The MoveTo method of View must be able to shift current position without drawing a line and the Model has to somehow mark the starting point of a new line. That's the meaning of the two Boolean flags in OnLButtonDown. We'll come back to that. Now let's examine the new implementation of the OnMouseMove method.

bool TopController::OnMouseMove 
        (int x, int y, Win::KeyState kState) throw ()
{
    Win::UpdateCanvas canvas (_h);
    if (_h.HasCapture ())
    {
        _model.AddPoint (x, y);
        _view.MoveTo (canvas, x, y);
    }
    else
        _view.PrintPos (canvas, x, y);
    return true;
}

Notice that we're recording the mouse position only if we have capture, otherwise we only update the display of mouse coordinates (and, since we don't have the mouse capture, we stop it when the mouse leaves the window boundaries).

Repainting the window gets slightly more complicated, because of the possibility of having multiple disconnected lines. I chose to store a Boolean flag with each remembered point, and set it to true only for points that are starting a new line (see the OnLButtonDown implementation, above). We have now outgrown our simple representation of a point as a std::pair. In fact, to save space, I decided to store coordinates as shorts (obviously, we'll have to rewrite this program when screen resolutions overcome the 32k pixels-per-line barrier).

class Model
{
public:
    class Point
    {
    public:
        Point (int x = 0, int y = 0, bool isStart = false)
            : _x (static_cast<short> (x)), 
              _y (static_cast<short> (y)), 
              _isStart (isStart)
        {} 
        int X () const { return _x; }
        int Y () const { return _y; }
        bool IsStart () const { return _isStart; }
    private:
        short _x;
        short _y;
        bool  _isStart;
    };

    typedef std::deque<Point>::const_iterator iter;
    ...
};

Notice the default values for all arguments of the Point contructor. It turns out that Point needs a default (no-arguments) constructor if we want to store it in a deque. The deque must be able to allocate and initialize new blocks of Points when it grows internally. It does it by calling the default constructors.

The implementation of View::Paint doesn't really change much, except that PrintPos now takes the values of coordinates to be displayed (and also updates the remembered position).

void View::Paint (Win::Canvas & canvas, 
                  Model::iter beg, 
                  Model::iter end)
{
    PrintPos (canvas, _x, _y);
    if (beg != end)
    {
        DrawLine draw (canvas, *beg);
        ++beg;
        std::for_each (beg, end, draw);
    }
}
						

The relevant change is in the implementation of the DrawLine functor. It doesn't draw lines that lead to starting points of line segments. It just quietly moves the current position.

class DrawLine
{
public:
    DrawLine (Win::Canvas & canvas, Model::Point const & p)
        : _canvas (canvas)
    {
        _canvas.MoveTo (p.X (), p.Y ());
    }
    void operator () (Model::Point const & p)
    {
        if (!p.IsStart ())
            _canvas.LineTo (p.X (), p.Y ());
        else
            _canvas.MoveTo (p.X (), p.Y ());
    }
private:
    Win::Canvas & _canvas;
};

Adding Colors and Frills

Let's have some fun with our program. For instance, how does one draw colored lines? The LineTo API doesn't have any arguments that could be used to specify color.

It's the device context, or Canvas, that stores all the parameters used in drawing. We have to instruct the canvas to change the drawing color. And, like good citizens, we should change it back after we're done with our drawing.

Various drawing and printing modes are conveniently grouped in GDI objects (GDI means Graphical Device Interface, in Winspeak). The one that controls the drawing of lines is called a pen. You change the properties of the DC by selecting and object into it. Again, if you're a good citizen, you deselect it afterwards, usually by selecting back the one you have displaced.

This object selection mechanism is best encapsulated in various Resource Management objects. Let's start with the most common type of object, the stock object. Windows conveniently provides a whole bunch of most commonly used device context settings in the form of predefined objects. We'll see in a moment how well stocked the Windows storeroom is. Right now we want to have a nice encapsulator for such objects.

namespace Win
{
    class StockObject
    {
    public:
        StockObject (int type)
            : _obj (::GetStockObject (type))
        {}
        operator HGDIOBJ () const { return _obj; }
    protected:
        HGDIOBJ _obj;
    };

    class StockObjectHolder
    {
    public:
        StockObjectHolder (HDC hdc, int type)
          : _hdc (hdc)
        {
            _hObjOld = ::SelectObject (_hdc, StockObject (type));
        }

        ~StockObjectHolder ()
        {
            ::SelectObject (_hdc, _hObjOld);
        }
    private:
        HGDIOBJ  _hObjOld;
        HDC      _hdc;
    };
}

The constructor of Win::StockObjectHolder takes a handle to device context (which means we can pass it any Canvas object) and selects a particular stock object into it. The SelectObject API returns the previous object of that type, which we diligently remember for later. We also remember the DC, so that, in the destructor, we can restore the previous object into its proper place in that DC.

That's the Resource Management part. The various types of stock objects are identified by predefined integer ids. Given such an id, we use GetStockObject to retrieve the proper object from the Windows storeroom. We'll mostly use Win::StockObjectHolder as a base class for specific stock object holders.

There are several stock pens that might be of use. For instance, there is a black pen and a white pen. We'll enclose all these in a new nemespace Pen, as embedded classes of a more general class Pen::Holder. This class, for the moment, will be otherwise empty--but that will change soon.

This kind of class embedding gives rise to a rather convenient naming convention. Not only do we have classes like Pen::Holder::White and Pen::Holder::Black, but also a more general class Pen::Holder, that will soon makes sense in itself.

namespace Pen
{
    class Holder
    {
    public:
        class White : public Win::StockObjectHolder
        {
        public:
            White (HDC hdc): Win::StockObjectHolder (hdc, WHITE_PEN) {}
        };
    
        class Black : public Win::StockObjectHolder
        {
        public:
            Black (HDC hdc): Win::StockObjectHolder (hdc, BLACK_PEN) {}
        };
    }
}

For simplicity, I didn't nest the Pen namespace inside Win; so instead of Win::Pen::Holder::Black, you simply call it Pen::Holder::Black. If I were to design a commercial library, I'd probably nest the namespaces, to avoid naming conflicts.

The way to use a stock pen is to define a holder in a given scope. For instance to draw white lines, youd write code like this:

{
    Pen::Holder::White wp (canvas);
    // do the drawing
}

The change in pen color applies to all line-drawing operations between the definition of wp and the end of scope, unless overridden by another pen change. End of scope always restores the pre-scope pen (automatic destructors!).

Beware of a common error of defining a stock pen holder without naming it. The code:

{
    Pen::Holder::White (canvas);
    // do the drawing
}

is also valid, although it doesn't produce the desired result. The compiler will create an anonymous temporary object and then immediately destroy it, without waiting for the end of the scope.

If you want to use real colors, not just black and white, you have to create your own GDI objects. A color pen is created by calling CreatePen and destroyed using DeleteObject. You specify the color by splitting it into three components: red, green and blue. Each of them can take values between 0 and 255. It doesn't necessarily mean that your computer can display different colors for all 256x256x256 combinations. Unless you set your display mode to at least 24-bit color (True Color, in Winspeak) the system will attempt to find the closest available match.

namespace Pen
{
    class Color
    {
    public:
        Color (int r, int g, int b, int style = PS_SOLID)
        {
            _hPen = ::CreatePen (style, 0, RGB (r, g, b));
        }
        ~Color ()
        {
            ::DeleteObject (_hPen);
        }
        operator HPEN () { return _hPen; }
    private:
        HPEN    _hPen;
    };
}

Creating a colored pen doesn't, by itself, change the color of lines being drawn. You still have to select your pen into the device context. According to the principles of Resource Management, we will encapsulate such a selection in an object called Pen::Holder--yes, that's the same class we used for embedding the classes White and Black. Defining a named object of the type Pen::Holder will temporarily change the color of all subsequent line drawings.

namespace Pen
{
    class Holder
    {
    public:
        Holder (HDC hdc, HPEN hPen)
            : _hdc (hdc)
        {
            _hPenOld = reinterpret_cast<HPEN> (
                           ::SelectObject (_hdc, hPen)); 
        }
        ~Holder ()
        {
            ::SelectObject (_hdc, _hPenOld);
        }
    private:
        HDC     _hdc;
        HPEN    _hPenOld;
    public:
        class White : public Win::StockObjectHolder
        ...
        class Black : public Win::StockObjectHolder
        ...
    };
}

Notice that, since a Win::Canvas can be implicitly converted to HDC, and a Pen::Color to HPEN; the holder's constructor is normally called with a Win::Canvas and a Pen::Color.

The standard way to use pens and holders is to define and store your pens in the View object and then "hold" them for the duration of a particular drawing operation. Let's define two pen colors, dark red and light blue, for our drawings.

class View
{
public:
    View () 
        : _x (0), _y (0), 
          _redPen (128, 0, 0),
          _bluePen (0, 0, 255)
    {}
    ...
private:
    int _x, _y;
    Pen::Color _redPen;
    Pen::Color _bluePen;
};

We'll use the blue pen for our regular drawing:

void View::MoveTo (Win::Canvas & canvas, int x, int y, bool visible)
{
    if (visible)
    {
        Pen::Holder ph (canvas, _bluePen);
        canvas.Line (_x, _y, x, y);
    }
    PrintPos (canvas, x, y);
}

and the red pen for re-drawing:

void View::Paint (Win::Canvas & canvas, 
                  Model::iter beg, 
                  Model::iter end)
{
    Pen::Holder ph (canvas, _redPen);
    PrintPos (canvas, _x, _y);
    if (beg != end)
    {
        DrawLine draw (canvas, *beg);
        ++beg;
        std::for_each (beg, end, draw);
    }
}

This is not what you would normally do--visually distinguishing between active drawing and passive redrawing--but here we are trying to learn something. Play with this program and observe what areas are being redrawn when you uncover parts of your window or resize it. You'll notice that the red lines appear only in the freshly uncovered areas, even though our algorithm seems to be repainting the whole client area in response to WM_PAINT messages.

Well, not everyting your program draws is displayed by Windows. For instance, you must be aware of the fact that parts of the lines that fall outside of the boundaries of the client area are never displayed (they would overwrite other windows!). Windows clips all drawings down to the client area. In fact, during the processing of WM_PAINT, Windows clips your drawings even further--down to the invalidated area. And even though in PaintCanvas, Windows sends us a single bounding rectangle for our convenience, the invalidated area may be more complicated (e.g., multiple rectangles) and the PaintCanvas will clip all subsequent drawings down to this area. If you really want to draw outside of this area, you have to create an additional UpdateCanvas and work with it.

But what should you do if, every time you draw, you want to use a different color? You can't possibly prepare all possible pens in the constructor of View! In that case, you are stuck with creating a pen on the spot, selecting it into the DC, and discarding it immediatly after deselecting. This use is common enough to call for a separate encapsulator, Pen::Holder::Instant.

namespace Pen
{
    class Holder
    {
    ...
    public:
    ...
        class Instant
        {
        public:
            Instant (HDC hdc, int r, int g, int b)
                : _hdc (hdc)
            {
                _hPen = ::CreatePen (PS_SOLID, 0, RGB (r, g, b));
                _hPenOld = reinterpret_cast<HPEN> (
                                   ::SelectObject (_hdc, _hPen));
            }
            ~Instant ()
            {
                ::SelectObject (_hdc, _hPenOld);
                ::DeleteObject (_hPen);
            }
        private:
            HDC     _hdc;
            HPEN    _hPen;
            HPEN    _hPenOld;
        };
    };
}

The same thing we've done with pens, you can do with brushes. A brush is used for filling areas--for instance if you draw a solid rectangle or ellipse. If you substitute the word Brush for Pen, you can almost directly copy the above code and use it for brushes. The only real difference is the use of the API CreateSolidBrush in place of CreatePen. There are other types of brushes besides solid--hatched and patterned--that you can use as well. Just provide the appropriate overloaded constructors for Brush::Color.

There is one special place where a brush is used--in the definition of a window class. This is the brush that is used for painting the window background. For instance, Windows automatically paints a fresh background over all the invalidated areas, in response to BeginPaint (called in the constructor of our PaintCanvas). By the way, EndPaint does the validation of this area, so if you forget to call it, Windows will keep sending WM_PAINT messages forever.

To choose a non-default background color for the windows of a particular class, we have to create a brush and pass it to the WNDCLASSEX structure. The brush will be automatically destroyed when the application exits (or the class is discarded).

void ClassMaker::SetBkColor (int r, int g, int b)
{
    _class.hbrBackground = ::CreateSolidBrush (RGB (r, g, b));
}

We call this method in main, setting the window background to light blue.

    Win::ClassMaker winClass (className, hInst);
    winClass.SetIcons (IDI_MAIN);
    winClass.SetBkColor (128, 128, 255);
    winClass.Register ();

Finally, we can play similar games with fonts. We can change the color of the printed text, the color of its background and even the type of the font. This last option, in particular, might help us with our incredible shrinking string. Changing the font from proportional to fixed does the trick. In a fixed-pitch font, all characters, including spaces, are the same size. We'll be able to get rid of the spurious spaces at the end of our string.

namespace Font
{
    class Stock: public Win::StockObject
    {
    public:
        Stock (int type) : Win::StockObject (type) {}
        operator HFONT () const 
        {
            return reinterpret_cast<HFONT> (_obj);
        }
    };

    class SysFixed: public Stock
    {
    public:
        SysFixed () : Stock (SYSTEM_FIXED_FONT) {}
    };

    class Holder
    {
    public:
        class Color
        {
        public:
            Color (HDC hdc, int r, int g, int b)
                : _hdc (hdc), 
                  _oldColor (::SetTextColor (_hdc, RGB (r, g, b)))
            {}
            ~Color ()
            {
                ::SetTextColor (_hdc, _oldColor);
            }
        private:
            HDC            _hdc;
            COLORREF    _oldColor;
        };
    
        class Background
        {
        public:
            Background (HDC hdc, int r, int g, int b)
                : _hdc (hdc),
                  _oldColor (::SetBkColor (_hdc, RGB (r, g, b)))
            {}
            ~Background ()
            {
                ::SetBkColor (_hdc, _oldColor);
            }
        private:
            HDC            _hdc;
            COLORREF    _oldColor;
        };
    
        class SysFixed : public Win::StockObjectHolder
        {
        public:
            SysFixed (HDC hdc)
                : Win::StockObjectHolder (hdc, SYSTEM_FIXED_FONT) 
			{}
        };
    };
}

Equipped with all these tools, we can now print the position of the mouse in yellow on dark blue, using a fixed-pitch system font.

void View::PrintPos (Win::Canvas & canvas, int x, int y)
{
    ...
    Font::Holder::Color fc (canvas, 255, 255, 0); // yellow
    Font::Holder::Background bk (canvas, 0, 64, 128); // dk blue
    Font::Holder::SysFixed fix (canvas); // fixed pitch
    canvas.Text (0, 0, &s [0], s.length ());
}
						

Stock Objects

Here's a complete list of Windows stock objects.

  • Brushes
    • WHITE_BRUSH
    • LTGRAY_BRUSH
    • GRAY_BRUSH
    • DKGRAY_BRUSH
    • BLACK_BRUSH
    • NULL_BRUSH
    • HOLLOW_BRUSH
    • DC_BRUSH
  • Pens
    • WHITE_PEN
    • BLACK_PEN
    • NULL_PEN
    • DC_PEN
  • Fonts
    • OEM_FIXED_FONT
    • ANSI_FIXED_FONT
    • ANSI_VAR_FONT
    • SYSTEM_FONT
    • DEVICE_DEFAULT_FONT
    • SYSTEM_FIXED_FONT
    • DEFAULT_GUI_FONT
  • Misc.
    • DEFAULT_PALETTE

Next PageNext: Windows Application