Add Zoom and Scale Capabilities to CScrollView

. This article first appeared in Windows Developers Journal, Oct 95. Brad has since enhanced the code and also made it available at the website http://www.visualc.com

Many Windows applications need the ability to arbitrarily scale, or zoom, the contents of a window. An example of this is print preview, where you can zoom in and out on a portion of the printed page. Programming information on this subject is minimal and very obfuscated. Not any more! In this article I explain how to easily use the built it scaling capabilities of Windows, and apply it to a C++ MFC extension class that I call CZoomView.

Learning To Scale

Proper scaling in Microsoft Windows requires you to set the map mode, window extent, window origin, viewport extent, and viewport origin for a window. Are you confused yet? Try reading the manual entries for any corresponding API - SetWindowExt: "Sets the x- and y-extents of the window...". So it wasn't just a clever name.

A key phrase, however, comes next: "defines how GDI maps points in the logical coordinate system to points in the device coordinate system." Although this information tells you nothing on how to use it, it is the clue to how it works -- mapping from one coordinate system to another. To make scaling easy, we want Windows to do as much of this conversion work as possible. Luckily for us, it can be done easily if you arm yourself with just a little bit of API knowledge..

For a drawing page to be automatically scaled in a window, two concepts must be understood. The first is how to draw the page, and the second is how to view the page. The Windows operating system uses both pieces of information together so that a user can view your drawing in a window at any arbitrary zooming scale. As a programmer, the easiest way to understand and implement scaling is to "divide and conquer," handling each concept separately.

Step One: Drawing the Page

The first thing that you need to do is choose the "logical units" for your drawing. Think of this as the number of virtual horizontal and vertical units on a page you will draw on. This is what Windows calls the "Window Extent," and is set by the SetWindowExt API. A logical page size of x by y units would simply have a window extent of {x, y}. Keep in mind that "units" is an arbitrary amount of measure that you pick, so do not be stuck thinking that it has to be pixels, inches, or any other measure.

Once you choose a logical coordinate system, all GDI drawing will be done using it. For example, lets say that you want to draw a line from the top left corner to the bottom right corner of a page that has a window extent of 100 by 100 logical units. Using the MoveTo and LineTo API functions, you would pass {0, 0} as the starting logical coordinate to MoveTo, and {100, 100} as the ending logical coordinate to LineTo. Any other GDI function (Rectangle, Arc, CreateFont, TextOut, etc.) will also use logical units. // ADD DRAWING ONE HERE

Using logical units allows your program display device independence. Drawing objects coordinates can all be stored in logical units, and you will use those coordinates when painting your windows without needing to do any conversions for scaling, different size screens, or different size windows. Doing any device dependent conversions, and determining which part of the drawing to actually scale and display to the user in a window, will be handled automatically by Windows after you follow the next step.

Step 2: Viewing the Page

Now that you understand logical drawing on your page, the second part of adding automatic scaling to your program is actually viewing it in a window. Viewing functionality uses device coordinates, in pixel units.

With this in mind, you need to tell Windows the viewing size, in pixels, of your entire drawing page. This is what Windows calls the "Viewport Extent," and is set by the SetViewportExt API. This does not confine a real window to that size, but rather describes the mapping of your logical units into pixels. By setting a window extent to {100, 100} and a viewport extent of {200, 200}, you are telling Windows that 100 by 100 logical units is exactly equal to 200 by 200 pixels. Note: to help in translating back and forth from logical to device units, the Windows API supplies the LPtoDP function to convert logical to device units, and the DPtoLP function to convert back.

When you set window and viewport extents you are required to change the mapping mode, set by the SetMapMode API. When using the SetWindowExtent and SetViewportExtent API's, only two mapping modes are allowed -- MM_ISOTROPIC and MM_ANISOTROPIC. The MM_ISOTROPIC mode is used when you want identical logical units for both the x and y axes, where MM_ANISOTROPIC is used for arbitrary logical x and y axis units. For our generic scaling requirements, we will use the MM_ANISOTROPIC map mode.

The final puzzle piece that Windows needs to do automatic scaling is the size of the client area of the physical window that will display your drawing (also in pixels). Because this information is inherent to the window, no API call is necessary. The pixel size of the window client area is how many pixels of the viewport will be shown. For example, setting the viewport extent to be the same number of pixels as the client area of the window would scale the entire logical drawing to fit the window. Setting the viewport extent to twice the size of the window's client area would make the user see one forth (half the width and half the height) of the page, and the drawing will appear larger (or "zoomed in"). // ADD DRAWING TWO HERE

The viewport extent is where you control the scaling factor for a window. To zoom in, increase its size. To zoom out, decrease its size. It really is that easy! Keep in mind that in order to maintain the original width to height ratio of your logical drawing, always set the viewport extent to the same ratio as the window extent, and scale it the same in both the x and y direction.

A Quick Scrolling Primer

Once a window has the ability to scale, zooming in shows only a portion of the entire drawing. By adding scrolling to the window, the entire drawing can be viewed without requiring zooming back out. If you are using the MFC C++ class library, a scrolling class called CScrollView is supplied, which encapsulates all the necessary Window's scrolling code. For SDK users and those of you that don't want to read the 700+ lines of code in CScrollView, I will briefly explain the scrolling concepts needed when using automatic scaling.

To set up scrolling, you need to manipulate the viewport origin using the SetViewportOrg Window's API. This is the viewport's origin in relation to your window's origin. Because we are scaling by manipulating the viewport only, the window origin, set with the SetWindowOrg API, should remain {0, 0}.

A good way to visualize origin manipulation for scrolling is to think of your drawing as a transparency, and the window to display it in as an overhead projector. The upper left corner of the transparency is the viewport origin, and the upper left corner of the projector is your window's origin (always {0, 0}). If the transparency is too big to see all at once (zoomed in), you need to slide it around with your hand (scroll) to see all of it. To see more to the right, you slide the paper to the left. To see more down, you slide the paper up.

Applying this back to Windows, to scroll n pixels in a given direction, change the viewport origin n pixels in the opposite direction. For example, if you started with both origins at {0, 0} and wanted to scroll 10 pixels to the right and 10 pixels down, simply set the viewport origin to {-10, -10}.

Putting the Concept to Work in MFC

To demonstrate the above scaling concepts, I have encapsulated it all in an MFC extension class that I call CZoomView. This class is derived from the MFC class CScrollView, allowing me to just write the code for scaling. It could of just as easily be done using straight SDK C code, but then you would have to write your own scrolling code that MFC provides for free.

Using the CZoomView class requires only three things: derive your view from CZoomView, set the logical units using the SetZoomSizes member function, and do all drawing in your OnDraw member function (like the WM_PAINT message) in terms of your logical units (step one, above). The class handles all the scaling (step two, above) and scrolling for you.

How CZoomView Works

For each device context handle (HDC) you use, you must set its map mode, window extent, viewport extent and viewport origin. MFC handles when to do this automatically by supplying the CView::OnPrepareDC member function. This is where CZoomView sets up everything.

To control the scaling, CZoomView keeps track of the window extent in the m_totalLog member variable, and the viewport extent in the m_totalDev member variable. In the OnPrepareDC member function, the map mode is set to MM_ANISOTROPIC and the window and viewport extent's are set using the appropriate member variable. If your derived class needs to do some processing during this function, make sure you call CZoomView::OnPrepareDC first.

Zooming is handled by keeping track of the current zoom scale (starting at 1.0) in the m_zoomScale member variable. To change the viewport extent for a given zoom scale, the original viewport extent (stored in m_origTotalDev) is multiplied by the m_zoomScale variable.

To zoom in (using the DoZoomIn member function), the zoom scale is multiplied by a constant, defaulted to 1.25. This increases the size of the viewport extent, making the drawing appear larger or "zoomed in." To zoom out (using the DoZoomOut member function), the zoom scale is divided by the same constant. This decreases the viewport extent, making the drawing appear smaller or "zoomed out."

Binding Zooming to the User Interface

The CZoomView class was written to be flexible! Proper zooming requires a user interface that a user would be comfortable using. I wrote three possible types of zooming into CZoomView: default zooming, point zooming, and selection zooming. Described below, all three methods are demonstrated in the sample program supplied.

Default zooming is where the current view is zoomed in or out by a small constant ratio, maintaining the same center reference point. This could be triggered by a user pressing a function key, where say F7 zooms in, and F8 zooms out, or by toolbar buttons. To implement this from within the class, call the DoZoomIn and DoZoomOut member functions with no arguments.

Point zooming is where the current view is zoomed in or out, again by a small constant ratio, centering on a given reference point. This is best applied to a mouse, where the user selects whether to zoom in or out from a key combination or toolbar button, then clicks on the view with the mouse where the zoom should occur. To implement this from within the class, call the DoZoomIn and DoZoomOut member functions with a logical CPoint for the new center point.

Selection zooming applies to zooming in, where a specific region of the logical drawing to made to fit within the window. The best way to implement this is to again have the user select zoom in from a key press or toolbar button, then drag a box with the mouse indicating the region that should be zoomed to fit the window. To implement this from within the class, call the DoZoomIn member function with a logical CRect for the region to fit into the window. Special considerations are made for this type of zooming. Since users will most likely select an abnormally sized region, the class forces the region to the correct window extent ratio by shrinking either the width or height of the region if necessary. // ADD DRAWING THREE HERE

Wrapping It All Up

Adding scaling or zooming capabilities to a Windows program is fairly simple and straight forward if you know which Windows API to use, and how to use them. This article explains the needed API's, and lays out the steps required for you to add automatic scaling to your own program. If you use the given C++ class it will be very easy and requires as little as three steps, however applying this to a straight C SDK program would be just as straight forward. Three methods of zoom interactions with the user are implemented in an example program written in MFC, supplied with this article. So don't be scared by the manuals, add automatic scaling easily to your program today!

About the Author

I am a software consultant for Visual Consulting Inc. in Burlingame, CA, where I specialize in C++ MFC GUI programming. My email address is brad@visualc.com.

Download Source



About the Author

Brad Pirtle

My company is Virtual Software Inc. (http://www.virtualsoftware.net). Our main product is Virtual Timecard, a web-based timecard, invoicing and payroll product that is integrated with QuickBooks. I also consult with expertise in C++, MFC, ISAPI and ODBC technologies.

Comments

  • Flipping Y axis

    Posted by rnathreddy on 03/08/2004 10:33pm

    Brad,
    
    This article is very useful to me how Zoom or scale the contents of the view. In this article it is assumed that positive Y goes down. But in my case positive y is towards up. I have tried changing viewport extent to {1,-1} and window extent to {1,1} and viewport origing to {0, 600}. I drawn a rectangle with {100,100, 500,500} units. When scroll position is chnaged rectangle is flickering(portion is drawing). Is there any way to overcome this?

    Reply
  • Zooming view

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

    Originally posted by: arasakumaran

    I have a problem with setting font sizes & zooming views. I have different text boxes in my view - each with possible different font settings (I store LOGFONT data for each box). My Draw program draws the boxes and prints the text - all using logical coordinates. When I zoom out, the box paints correctly (becoming smaller as I zoom out), but the text inside does not. The fonts do not get correspondingly smaller. Do I need to change the height (lfHeight of the LOGFONT structure) of the font? - I thought it was in logical units.

    Reply
  • Zoom In/Out, line width not consistent (Rounding problem with MM_ANISOTROPIC)

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

    Originally posted by: Paulo Gomes

    Hi,

    I need an opinion/suggestion about a problem.
    I've got an aplication with zooming capabilities (using MM_ANISOTROPIC map mode). The problem is, if i draw an horizontal line, when zooming it, the line's width is not consistent, for example:
    Zoom In : line width = 2 pix
    Zoom In : line width = 2 pix
    Zoom In : line width = 3 pix // The problem is here
    Zoom In : line width = 2 pix // the width should be always growing
    Zoom In : line width = 3 pix
    Zoom In : line width = 3 pix
    Zoom In : line width = 3 pix
    Zoom In : line width = 4 pix
    Zoom In : line width = 4 pix

    I've read that the Anisotropic map mode has some rouding problems. Can someone help me?

    Reply
  • How to enlarge the Property of CDialogBar

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

    Originally posted by: Allie

    well, i am new to VC, the problem i am having is how to change the property of CDialogBar. i have already developed CDialogBarin my application using as a tool bar which have a CListBox.And now i want to add new elements to it,such as a line,or changing the background......But i did not know how to solve it. i would be greatful if any of you tell me in few steps what to do and how to include the funtinality
    you may mail me at iamahorse2003@yahoo.com.cn

    Reply
  • HOw to use CZoomView?

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

    Originally posted by: Shahzad

    well, i am new to VC, the problem i am having is how to add zooming functionality even after using your class. i have already developed my application using CScrollView and now i am unable to add zooming functinoality to my class even after reading the artical n using cZoomView. i would be greatful if any of you tell me in few steps what to do and how to include the funtinality
    you may mail me at sam19_@hotmail.com or iamshahzad@msn.com
    regards
    sam

    Reply
  • Printing/Previewing problems

    Posted by Legacy on 04/30/2003 12:00am

    Originally posted by: Sabotto Massimo

    Great work!
    Just a little problem: it seems that print preview and print (of course) does not work!
    Preview is displayed as a very little stretched image if made on a A4 landscape sheet, otherwise scaled to fit (and stretched) if on a A4 portrait sheet.:(
    Does anyone know why? Any suggestion on correcting this bug?(isn't it?).

    Thank you in advance.

    Reply
  • add zoom capabilities to control

    Posted by Legacy on 02/28/2002 12:00am

    Originally posted by: Marcel

    Hi,

    I want to add zoom capabilities to a webbrowser control. Any ideas on how to do this ? I know how to zoom pictures, but I want to keep the focus on the browser and continue working with another scale.

    Any ideas how to do this ??

    Regards,

    Marcel

    Reply
  • Debug ASSERTION

    Posted by Legacy on 01/22/2002 12:00am

    Originally posted by: Michele

    Sometimes I have this assertion when I change the size:
    
    

    void CScrollView::ScrollToDevicePosition(POINT ptDev)
    {
    ASSERT(ptDev.x >= 0);
    ASSERT(ptDev.y >= 0);

    // Note: ScrollToDevicePosition can and is used to scroll out-of-range
    // areas as far as CScrollView is concerned -- specifically in
    // the print-preview code. Since OnScrollBy makes sure the range is
    // valid, ScrollToDevicePosition does not vector through OnScrollBy.

    int xOrig = GetScrollPos(SB_HORZ);
    SetScrollPos(SB_HORZ, ptDev.x);
    int yOrig = GetScrollPos(SB_VERT);
    SetScrollPos(SB_VERT, ptDev.y);
    ScrollWindow(xOrig - ptDev.x, yOrig - ptDev.y);
    }

    What can I do to avoid this behavior?

    Thanks a lot!
    Bye

    Reply
  • Offscreen draw in CZoomView

    Posted by Legacy on 01/10/2002 12:00am

    Originally posted by: fernando

    Anyone had implemented a sort of offscreen drawing in CZoomView?

    Reply
  • HELP : Zoom the graph that generated by my program

    Posted by Legacy on 11/28/2001 12:00am

    Originally posted by: megadeth

    hi there,

    My program generates a graph from up to 10000 by 10000 data.

    To draw it again and again while do the zooming is going to take long time since it takes a while to do the calculation.


    Is it possible to use this CZoomView class for me to be able to use its capabilities?

    How to draw it logically?

    If you have similar project, I would appreciate it if you can share the thought.

    thanks in advance,
    M

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • A modern mobile IT strategy is no longer an option, it is an absolute business necessity. Today's most productive employees are not tied to a desk, an office, or a location. They are mobile. And your company's IT strategy has to be ready to support them with easy, reliable, 24/7 access to the business information they need, from anywhere in the world, across a broad range of communication devices. Here's how some of the nation's most progressive corporations are meeting the many needs of their mobile workers …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds