Look Closer with QZoomView

Environment: VC++ 6.0/7.0, Windows 98 and later.

A CScrollView with Zooming Capabilities

QZoomView is an MFC view class that supports zoom. It is derived from CScrollView, and adds a bunch of zoom methods. Some methods help to implement zoom menu commands. There are also functions to switch QZoomView to an interactive mode, which enables the user to zoom with the mouse.

A demonstration application, called QZoomViewDemo, shows most of the features. I guess that, after launching it, you'll immediately be on solid ground, because lots of applications around have zoom modes that are very similar. You can zoom stepwise by clicking, or zoom to a rectangle by dragging.

There is also a 'hand tool' to drag the document around, provided that it is bigger than the view. If you're using one of the 'magnifying glass tools,' you can temporarily switch to the hand tool by keeping the space bar pressed. If you press the space bar after dragging is started, the rectangle is moved instead of resized. Use the Alt key to resize the rectangle from the center.

The Ctrl key changes the plus loupe (zoom in) to the minus loupe (zoom out), and vice versa. Zooming is also possible with the mouse wheel, while keeping the Ctrl key pressed.

Coding with QZoomView

Integrating QZoomView in an MFC application is easy. Just let the App Wizard create a project and choose CScrollView as the base class for the view. In the resulting source files for the view, replace all occurences of 'CScrollView' with 'QZoomView'.

The following files of the demo project should be inserted in your MFC project:

  • QBufferDC.h and QBufferDC.cpp
  • QSelectTracker.h and QSelectTracker.cpp
  • QTracker.h and QTracker.cpp
  • QZoomView.h and QZoomView.cpp

Only QZoomView.h should be included. The project should compile, although no zooming facilities are implemented yet (with one exception: mouse wheel zooming should work).

As with CScrollView, you should call the member function SetScrollSizes() in the OnUpdate() or OnInitialUpdate() handlers (the App Wizard will set this up). If you don't, you'll get a runtime error. QZoomView supports the same mapping modes as CScrollView; that is, any of the Windows mapping modes except MM_ISOTROPIC or MM_ANISOTROPIC. Almut Branner (many thanks) pointed me to a bug in handling MM_TEXT. I repaired that in version 1.1.

As with CScrollView, QZoomView is an abstract base class. You must derive another class from it, and at least override OnDraw().

QZoomView has one extra overrideable member function, OnZoom(). You may use this in derived classes. It is called after zoom is processed, but before the window is invalidated. The default does nothing.

Member functions

QZoomView has four modes of operation:

ZoomViewOff QZoomView behaves like a normal CScrollView
ZoomViewZoomIn Clicking with the mouse zooms in; dragging zooms to rectangle
ZoomViewZoomOut Clicking zooms out; dragging is identical to ZoomViewZoomIn
ZoomViewDrag 'Hand mode': if the document is bigger than the view, dragging scrolls it

One of the first things your application would like to do is change this mode. So, add some commands. In the demo app, I implemented these as 'Tools', and also integrated them in the accelerator table. The commands simply call the method SetZoomMode():

  enum ZoomViewMode
  {
    ZoomViewOff,
    ZoomViewZoomIn,
    ZoomViewZoomOut,
    ZoomViewDrag
  };
  ZoomViewMode SetZoomMode(ZoomViewMode newMode);
  ZoomViewMode GetZoomMode() const;

In any mode (even in ZoomViewOff), the zoom factor can be set explicitly with the ZoomTo() method:

  float ZoomTo(float zoom);
  float GetZoom() const;

The zoom factor is a float between 0.05 and 20.0. A value of 1.0 is neutral (100%). ZoomTo() returns the previous zoom factor. The current zoom factor can be retrieved with GetZoom(). You may use this function to update a field in the status bar of your application. See the OnUpdateIndicatorZoom() method of the demo's view source for an example.

QZoomView also has a list of preset zoom factors. You can zoom stepwise through this range with the following member functions:

  BOOL ZoomIn(const LPPOINT pPoint = NULL);
  BOOL CanZoomIn() const;
  BOOL ZoomOut(const LPPOINT pPoint = NULL);
  BOOL CanZoomOut() const;
  void ZoomToPreset(int i, const LPPOINT pPoint = NULL);
  int GetPresetZoom() const;

If pPoint is not NULL, QZoomView tries to scroll so that the given point is in the view center. It will succeed completely if the document is big enough with respect to the window. The point is in logical coordinates.

The functions return TRUE if the zoom succeeded, or if the zoom is possible (the CanZoomXxx() functions). Use ZoomToPreset() to go to a preset zoom level in one step. GetPresetZoom() returns the current preset zoom level, or -1 if the current zoom level is not in the list of presets.

Modify the preset table with the method:

  BOOL SetPresets(const float * pPresets, int count);

The parameter pPresets must point to an array of count floats with increasing values between 0.05 and 20.0. The method returns TRUE if it succeeded, FALSE if there was an error. If the preset table is not set, a default table is used.

The method ZoomToWindow() lets the document conveniently fill the window:

  void ZoomToWindow();

In version 1.1, I added a function to retrieve the visible part of the document in logical coordinates:

  CRect VisibleRect();

User feedback

To offer the user convenient cursor feedback, a few cursors should be preloaded with the following static member function:

  enum CursorType
  {
    CursorLoupe,
    CursorLoupePlus,
    CursorLoupeMinus,
    CursorGripOpen,
    CursorGripClosed
  };
  static BOOL LoadCursor(CursorType type, UINT nResourceID,
                         HINSTANCE hInst = NULL);

If hInstance is NULL, the cursors are loaded from the application's resources. If no cursors are loaded, QZoomView always displays the standard arrow cursor. All instances of QZoomView share the same cursors, so loading them is only needed once per run. The demonstration project comes with a few useful cursors in the \res directory.

QZoomView gives an audible signal if the user tries to zoom outside the permitted range. Normally, this is the MB_ICONHAND sound, bu you may set the public member variable m_MessageBeep to something else. Consult the Windows documentation for MessageBeep() for other options. To suppress the audible signal altogether, set m_MessageBeep to NoBeep.

Implementation

For zooming to a dragged rectangle, QZoomView makes use of the QSelectTracker class, which is derived from QTracker. Both classes may be useful in their own right. For smooth screen drawing, double buffering is used. It is implemented with the QBufferDC class. In the first version, QTracker didn't check the track distance against the Windows smallest tracksize, sometimes yielding funny behaviour. This is now corrected (thanks to Tamir, who pointed me to this).

Download

Download demo project and source - 70 Kb