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


Comments

  • Wow - absolutely fantastic

    Posted by Mr. G on 06/30/2007 09:28pm

    I just wanted to let you know how much I appreciate QZoomView. It is absolutely magic. I have been developing a very expansive application with roughly 7 different document types and many, many different views. A lot of this is dense statistical information. Everything was terrific until I upgraded my machine and picked up a very high resolution LCD display. When I ported my development over to the new machine (Vista Home Premium) I was schocked to see that the small font sizes I had to use (to get all the data on the equivalent of an 8.5 X 11 sheet of paper now displayed so small the information was virtually unreadable. Today I found QZoomView through the VS 2005 IDE provided search. Within say 30 minutes I had it downloaded and integrated into one of my views, the most dense of all. Stunning, simply stunning. This, QZoomView, is without a doubt the most valuable piece of code I've seen in a very, very long while. Applause!!!!

    Reply
  • full size ?

    Posted by Legacy on 02/16/2004 12:00am

    Originally posted by: LIN

    Why is the display image with the fixed scale of 100 per cent and the size of the picture as 500x500 (MM_LOMETRIC 5x5 cm) approximate 10 per cent lesc than the real one ?

    Reply
  • Brilliant!

    Posted by Legacy on 02/06/2004 12:00am

    Originally posted by: A Nonny Mouse

    QBufferDC works perfectly, the best flicker-reduction
    solution I've seen anywhere (and I've tried lots of them!).
    It works even better than Keith Rule's CMemDC with
    CScrollView-derived classes...

    Thanks!

    Reply
  • Prinr Preview

    Posted by Legacy on 01/14/2004 12:00am

    Originally posted by: Browser

    It would be nice to add Print Preview support. Excellent job!

    Reply
  • bug in drag

    Posted by Legacy on 12/10/2003 12:00am

    Originally posted by: Thad Edens

    Try this scenario. Zoom in to 800%.
    Now drag the document so that the words
    "Hello World" disappear below the window bottom.
    Now drag the window up, but as you do this,
    jitter the window left and right rapidly. You should see
    that the words "Hello World" are not drawn correctly.
    The individual rows of pixels are staggered
    relative to one another.

    Reply
  • Bug under W98 (and FIX)

    Posted by Legacy on 11/11/2003 12:00am

    Originally posted by: Andrea Proli

    Thank you
    for sharing your excellent code, Sjaak!
    I am using your class in one project of mine and I
    found it very useful!
    However, I found out a bug in the OnPrepareDC's
    implementation: you do not take care of checking
    whether the arguments that you are passing to
    CDC::ScaleViewportExt(...) are less then 2^15 or not.
    W98 does not accept values greater than 32768 and
    such a call often results in strange behavior when
    zooming.

    Thus, instead of doing:
    pDC->ScaleViewportExt(num , denom, num, denom);

    you shoud do:
    pDC->ScaleViewportExt(num / 1000, denom / 1000, num / 1000, denom / 1000);

    or something equivalent (yes, I know, great optimization ;).

    I didn't check if similar calls are spread all over the
    code, I just realized that this correction made my project
    work under W98 and that was enough for the moment.

    Again, thank you very much for your great contribute.
    Andrea

    Reply
  • Awesome Job!!!!

    Posted by Legacy on 11/07/2003 12:00am

    Originally posted by: Anonymous

    This is really well done. Congradualtions. A very professional unser interface and great coding; easy to read and understand.


    Thanks for the great post.

    Reply
  • Not all zoom factors work with mapping mode MM_TEXT.

    Posted by Legacy on 10/03/2003 12:00am

    Originally posted by: Almut Branner

    I implemented this class in one of my applications and found it very useful. However, I am doing my drawing in MM_TEXT mapping mode and when I zoom in it seems to ignore the non-integer zoom factors.

    In taking a closer look I discovered that my viewport and window extent do not represent the total size of the scroll view but instead are (1, 1) and (2, 2) for zoom factor 2 and so on. Hence, my zoom factor simply was truncated to the next integer.

    Does anybody know why this is the case and what I have to do to get around this (apart from switching to another coordinate system)?

    Thanks,

    Almut

    Reply
  • Bugz

    Posted by Legacy on 10/02/2003 12:00am

    Originally posted by: Tamir

    Sorry for QAing your application.
    Try this scenario:
    1. Zoom to 35%.
    2. Choose the (-) zoom out tool.
    3. Click the picture.
    -> Oops, it is 800% again :(

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • As mobile devices have pushed their way into the enterprise, they have brought cloud apps along with them. This app explosion means account passwords are multiplying, which exposes corporate data and leads to help desk calls from frustrated users. This paper will discover how IT can improve user productivity, gain visibility and control over SaaS and mobile apps, and stop password sprawl. Download this white paper to learn: How you can leverage your existing AD to manage app access. Key capabilities to …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds