Look Closer with QZoomView

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():

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:

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:

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

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!!!!

bug in drag

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.

Bug under W98 (and FIX)

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.

Not all zoom factors work with mapping mode MM_TEXT.

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)?