The CODViewport Class
The viewport contains all the logic you need to render your graphical model to the screen. It handles the drawing and printing of the model, as well as the scrolling, zooming, and panning. The viewport’s main function is to update sections of the screen when the model changes. When the model changes, it informs all of its observers of the change ensuring that every viewport associated with that model receives the update message. The viewport needs only to override the creation of the controller.
By default, each viewport is overlaid with a grid to which the components snap. You can show or hide the grid and turn on or turn off the snap feature. You can also change the color of the grid so that it is visible against the background color of the canvas.
By default, the grid rows and columns are 1/8 inch apart. You can also change the distance. The grids in each viewport are independent of each other. Consequently, if you change the spacing or color of the grid in one viewport, the grids in other viewports will not be affected.
Angle Snap
Angle Snap is related to the “snap to grid” feature. When you turn on angle snap, the rotation of components is restricted to 15 degree increments.
Page Boundaries
Another visual aspect of the viewport is the page boundaries. The viewport checks the page settings in the model to determine how the canvas will span the paper. These boundaries are drawn over the canvas to show you how the canvas will look when you print it. You can turn off this display if you like.
Zoom Limits
The end-user of your application can zoom in and out on the canvas. There are limits set to this zoom factor that you can change. The default is a minimum of 5% and a maximum of 1500%.
Selection Handle Size
Another visual detail that you should consider is how your components appear when they are selected. The selection handles are maintained by the viewport. You can set how large they are. You can also override the methods that draw the handles to change their appearance.
Component Tracking
Tracking the manipulation of components is another responsibility of the viewport. When an end-user moves, rotates or scales a component, tracking places an outline around the component. The purpose of this outline is to convey how the component would look if you dropped it in its current position. The controller accomplishes tracking by interpreting the end-user’s actions and then asking the viewport to draw the “ghosting” of the component.
The viewport observes a model. When an aspect of the model changes, it notifies all of its observers. The viewport has an OnUpdate() method that receives these notifications. Then, it determines which sections of the viewport were effected by the change and redraws them.
The CODScrollViewport class extends the CODViewport class by adding scrolling capabilities. This class uses the MvcScrollWrapper_T template class to subclass the viewport and add scrollbars.
Off-Screen Buffering
The CODBufferedViewport class extends the CODViewport class by adding off-screen buffering. Off-screen buffering eliminates screen flicker and improves performance by rendering the image in memory before drawing on the screen. The screen updates are reduced to a single BitBlt() or paint operation.
Logical Units
Logical units are abstract units stored as integers. Logical units have no real-world equivalent until they are applied to a coordinate mapping. A coordinate mapping consists of a mapping mode, window extents, and viewport extents. (See documentation for CDC::SetMapMode() in the MFC Class Library Reference for more information on mapping modes).
The coordinate mapping defines how logical units are mapped onto device units. The device resolution determines the real-world size of device units on the output device. Combining the coordinate mapping and device resolution determines the size of a logical unit on the output device. For mapping modes other than MM_ANISOTROPIC, MM_ISOTROPIC, and MM_TEXT the mapping mode alone is enough to determine the size of a logical unit on the output device. For example, MM_HIMETRIC mode specifies 1 logical unit to be 0.01 mm on the output device. The number of pixels that 1 logical unit maps to in MM_HIMETRIC mode varies based on the device resolution. For MM_ANISOTROPIC, MM_ISOTROPIC, and MM_TEXT mapping modes, the window extents and viewport extents define a ratio that maps logical units to device units. The default coordinate mapping for Objective Diagram is MM_ANISOTROPIC with the extents set at a 1:1 ratio, which is equivalent to MM_TEXT.
Given the default coordinate mapping (MM_ANISOTROPIC with 1:1 extent ratios) and a device resolution of 96 dpi, 1 logical unit is 1/96 = 0.0104167 inches.
You can experiment with mapping modes and extents using the coordinate mapping page in the diagram measurement property sheet (CODMeasurePropSheet). The size of a logical unit is displayed on the property page.
Coordinate Mapping
The coordinate mapping defines how logical units are mapped onto device units. A coordinate mapping consists of a mapping mode, window extents, and viewport extents. (See documentation for CDC::SetMapMode() in the MFC Class Library Reference for more information on mapping modes). The coordinate mapping can be set interactively through the Coordinate Mapping page in the Measurement property sheet. It can also be set programmatically in your CODViewport derived class. The methods for setting the coordinate mapping in the viewport are SetMapMode(), SetExtents(), and SetLogExtents(). CODViewport inherits these methods from MvcLogicalPart.
Here are the GDI equivalents of these MvcLogicalPart methods.
The default coordinate mapping is set in CODViewport::OnInitialUpdate() to the following values.
This is equivalent to MM_TEXT, where 1 logical unit equals 1 device unit (i.e. pixel). If the screen resolution is 96 dpi, then 1 logical unit will equal 0.0104167 inches on the screen.
Inverting the Y Axis
In order to preserve backward compatibility, in Objective Views the default direction of the Y-axis is positive Y down. Several of the Windows mapping modes such as MM_LOMETRIC, MM_HIMETRIC, MM_LOENGLISH, and MM_HIENGLISH set the orientation of the Y-axis so that it matches a Cartesian coordinate system (positive Y is up and negative Y is down). Objective Views supports both possible configurations of the Y-axis.
There are a few things that must be done in order to invert the Y-axis and use Cartesian coordinates in your Objective Views application. First, determine what your virtual bounds will be. The virtual bounds are determined by the bounds of your CODModel-derived class. One way to set the virtual bounds is to assign the origin and size of the model in the Initialize() method, as shown in Example 1.
Example 1 – The CODModel Initialize method - Assigning origin and size
void CCartesianModel::Initialize()
Note that the size along the Y-axis is specified as -2000. The size is always added to the origin, so 2000 + (-4000) yields -2000. Therefore, the range on the Y-axis is 2000 to -2000. The logical bounds can also be set as shown in Example 2.
Example 2 – The CODModel Initialize() method - Setting logical bounds
void CCartesianModel::Initialize()
Both methods of setting the virtual bounds are equally valid and produce the same result.
It is very important to note that the bounds of components are always assumed to be normalized rectangles. This means that the top of the rectangle is always less than the bottom. In other words, < RECT.bottom regardless of the direction of the Y-axis. This is necessary because numerous Windows API functions assume normalized rectangles (such as hit testing functions). This assumption applies to any code that manipulates logical rectangles.
Next, set the direction of the Y axis to YAXIS_UP in the viewport. This can be done in the OnInitializeUpdate() method as in Example 3.
Example 3 – The CODModel OnInitializeUpdate() method
void CCartesianViewport::OnInitialUpdate()
NOTE >> In order to preserve backward compatibility, the default direction of the Y-axis is positive Y down. That is why you need to explicitly set the Y-axis direction up.
There is a method in the viewport to draw the X and Y axes called DrawAxes(). You can override DrawRect() or DrawGrid() in your CODViewport-derived class and add a call to DrawAxes() in order to see the coordinate axes.
Drawing Scale
The drawing scale feature allows you to specify what a given size on the output device represents. It influences how measurements are presented to the user. If you use the drawing scale to indicate that 1 inch = 1 foot, then all measurements are scaled by a factor of 12. If your mapping mode is MM_LOENGLISH (1 logical unit = 0.01 inch), then 1 logical unit would be presented to the user as 0.01 * 12 = 0.12 inches. This does not literally scale the object or zoom in. The drawing scale only determines how measurements are taken.
The default drawing scale is 1:1, so that real-world measurements in the user’s domain match real-world measurements on the output device.
Print and Print View
Objective Views provides the print methods PreparePrinting() and EndPrinting() that can be used to override the standard MFC methods OnPreparePrinting() and OnEndPrinting(). These extended methods provide a variety of ways to print the view displayed on a canvas. The options are:
*Print the entire canvas at the currently defined zoom value.
virtual BOOL PreparePrinting(CPrintInfo* pInfo, long percentOfNorm);
For example, if a user has set the zoom to 400% such that only a part of the canvas can be viewed at a given time, the print operation will nevertheless print the entire canvas, both the seen and unseen parts. Note that the logical size of the canvas in such a case might be large, resulting in a printout spread across a number of pages.
*Print the entire canvas as X pages wide by Y pages tall.
virtual BOOL PreparePrinting(CPrintInfo* pInfo, long x_pages_wide, long y_pages_tall);
For example, let’s say the logical size of the canvas is 32 inches wide by 20 inches tall. You could specify a printout that was 4 pages wide by 2 pages tall, giving you an effective area (assuming 8.5 by 11 pages in portrait orientation), of 34 inches wide by 22 inches tall. This method assumes a normal (100%) zoom setting. You can set a property, SetIsPrintMaintainAspect(TRUE|FALSE), that either maintains the aspect ratio (the default) or stretches the canvas to fit the available print area.
*Print the portion of the canvas currently in view.
virtual BOOL PreparePrinting(CPrintInfo* pInfo, long percentOfNorm, CRect rcCurView);
This feature prints exactly what is in view in the user application. It maintains the current zoom setting, and takes into account the user-defined page setup information.
*Print the current view as X pages wide by Y pages high.
virtual BOOL PreparePrinting(CPrintInfo* pInfo, long percentOfNorm, long x_pages_wide, long y_pages_tall, CRect rcCurView);
This feaure is essentially a combination of items two and three above. The current view at the current zoom setting is printed as X pages wide by Y pages high.
The above features are demonstrated in the Showcase sample, located at <installdir>\Samples\Views\Showcase.
The general requirements for using these features are illustrated below, using the first of new listed features as the example:
*Override OnPreparePrinting() with PreparePrinting():
BOOL CShowcaseView::OnPreparePrinting(CPrintInfo* pInfo)
... // do things: see Showcase sample for details
... // do things: see Showcase sample for details
if(!GetViewport()->PreparePrinting(pInfo, fZoom))
return FALSE;
// Default Preparation
return DoPreparePrinting(pInfo);
*Override OnEndPrinting() with EndPriniting():
void CShowcaseView::OnEndPrinting(CDC* pDC, CPrintInfo* pInfo)
// Call this to restore the viewport state.
GetViewport()->EndPrinting(pDC, pInfo);