Stingray® Foundation : Chapter 8 Model View Controller : Visual Components
Visual Components
Visual components are the cornerstone of visualization in MVC. Visual components provide a structured approach to rendering objects. A visual component is an object with two-dimensional bounds that can draw itself onto a device context. An MVC viewport is simply a visual component that observes and renders a model. A viewport may also contain other visual components. Some visual components support logical coordinate mapping, which forms the basis of zooming and scrolling support. Having a concise definition for visual components makes it possible to write generic code for manipulating and drawing visual objects.
Visual Component Interfaces
The IVisual interface shown in Example 56 defines methods for rendering objects to a device context. The OnPrepareDC() method gives visual components an opportunity to set up the device context prior to drawing. OnPrepareDC() is typically used to set mapping modes, window and viewport extents, select pens and brushes, etc. The OnRestoreDC() method returns the device context back to its original state. The Draw() method does the actual rendering of the visual component to the device context.
Example 56 – IVisual interface
class __declspec(uuid("E7707E00-1E4F-4f4e-A525-290CFA9C1EF3"))
IVisual : public IQueryGuid, public IRefCount
{
public:
virtual void Draw(CDC* pDC) = 0;
virtual void OnPrepareDC(CDC* pDC) = 0;
virtual void OnRestoreDC(CDC* pDC) = 0;
};
As mentioned previously, a visual component has two-dimensional bounds. The IBounds2D interface shown in Example 57 provides methods for manipulating the bounds of a visual component.
Example 57 – IBounds2D interface
class __declspec(uuid("A332FE8E-B30D-47ee-AF1B-7E863FDEFFE5"))
IBounds2D : public ISize2D
{
public:
virtual CRect GetBounds() const = 0;
virtual CPoint GetOrigin() const = 0;
virtual CPoint SetOrigin(int x, int y) = 0;
virtual CPoint MoveOrigin(int xOff,int yOff) = 0;
};
The IBounds2D interface is derived from the ISize2D interface shown in Example 58. These two interfaces are distinct because an object might have two-dimensional size, but no origin. The IBounds2D extents the ISize2D interface by adding methods for accessing the origin.
Example 58 – ISize2D interface
class __declspec(uuid("A989AFCB-D665-4faf-93A6-34E378BF75E0")
ISize2D : public IQueryGuid, public IRefCount
{
public:
virtual CSize GetSize() const = 0;
virtual CSize SetSize(int cx, int cy) = 0;
};
CMvcVisualComponent
The CMvcVisualComponent class provides an implementation of the IVisual and IBounds2D interfaces. A CMvcVisualComponent object is basically just a rectangle with a draw method. The CMvcVisualComponent class implements the IBounds2D interface by maintaining a CRect member variable. The implementations of the IVisual methods are just stubs. The declaration of CMvcVisualComponent is shown in Example 59 below.
Example 59 – Declaration of CMvcVisualComponent
class CMvcVisualComponent : public IVisual, public IBounds2D
{
. . .
// Attributes
protected:
CRect m_rc;
 
// Operations
public:
// IVisual and IBounds2D methods
. . .
};
CMvcVisualPart
A visual part is a type of visual component that maintains a back pointer to its container. It is a template class that takes the base class and container class as template parameters. CMvcVisualPart is typically instantiated with CMvcVisualComponent as the base class. The container class is assumed to support the InvalidateRect() and ValidateRect() functions, and is usually derived from the IVisualHost interface. A visual part is a visual component that supports nesting and invalidation.
The CMvcVisualComponentImpl class is a typedef that instantiates the CMvcVisualPart template class using CMvcVisualComponent and IVisualHost as the template parameters. It provides a convenient default for using the CMvcVisualPart class. The declaration of CMvcVisualComponentImpl is shown below.
 
typedef CMvcVisualPart<CMvcVisualComponent, IVisualHost>
CMvcVisualPartImpl;
Coordinate Mapping
The bounds of a visual component are relative to the logical coordinates of its container. Logical coordinates of the container are referred to as container points. The container might be a window or another visual component. A visual component can also have its own logical coordinate system, which maps logical coordinates onto container coordinates. Visual components that implement the ILogCoordinates interface provide a mapping of logical coordinates to container coordinates. The ILogCoordinates interface is shown in Example 60.
Example 60 – ILogCoordinates interface
class __declspec(uuid("9EBF6B30-E26A-4cea-BA7F-2C7E8220AA58"))
ILogCoordinates : public IQueryGuid
{
public:
virtual CPoint GetLogOrigin() const = 0;
virtual CSize GetLogSize() const = 0;
virtual CPoint GetVirtualOrigin() const = 0;
virtual CSize GetVirtualSize() const = 0;
virtual int GetMapMode() const = 0;
virtual CSize GetExtents() const = 0;
virtual CSize GetLogExtents() const = 0;
virtual YAxisDirection GetYAxisDirection() const = 0;
 
virtual void LPtoCP(LPPOINT lpPoints, int nCount) const = 0;
virtual void LPtoCP(LPRECT lpRect) const = 0;
virtual void LPtoCP(LPSIZE lpSize) const = 0;
virtual void CPtoLP(LPPOINT lpPoints, int nCount) const = 0;
virtual void CPtoLP(LPRECT lpRect) const = 0;
virtual void CPtoLP(LPSIZE lpSize) const = 0;
 
virtual void LPtoDP(LPPOINT lpPoints, int nCount) const = 0;
virtual void LPtoDP(LPRECT lpRect) const = 0;
virtual void LPtoDP(LPSIZE lpSize) const = 0;
virtual void DPtoLP(LPPOINT lpPoints, int nCount) const = 0;
virtual void DPtoLP(LPRECT lpRect) const = 0;
virtual void DPtoLP(LPSIZE lpSize) const = 0;
};
The ILogCoordinates interface provides methods for getting the mapping mode and extents, which can be used to set the coordinate mapping for device context and to convert between logical coordinates and container coordinates. The values returned by ILogCoordinates correspond directly to the coordinate mapping functions defined by the Windows API for a device context. The value returned by the GetExtents() function can be passed directly into the SetViewportExt() API function. The value returned by the GetLogExtents() function can be passed directly into the SetWindowExt() API function. The value returned by the GetLogOrigin() function corresponds to the window origin set by the SetWindowOrg() function. The ILogCoordinates interface provides a consistent way for retained mode graphical objects to expose coordinate mapping information.
The ILogCoordinates interface defines two sets of conversion functions. One set of conversion functions translates between logical points and container points. The second of conversion functions translates between logical points and device points. If the visual component is windowless and its container is a window, then there is no difference between container points and device points. If the visual component is a window, then device points are not the same as container points. The LPtoDP() and DPtoLP() functions translate between logical coordinates and pixels of the window that contains the visual component. Visual components may be windowed or windowless, and the same is true of containers. Therefore, device units and container units are not always the same.
The logical origin returned by GetLogOrigin() is the origin of the visual component in logical coordinates. The logical size returned by GetLogSize() is the size of the visual component in logical coordinates. The result of passing the logical origin into the LPtoCP() function is the origin returned by IBounds2D::GetOrigin(). Similarly, the result of passing the logical size into the LPtoCP() function is the size returned by ISize2D::GetSize().
The ILogCoordinates interface also defines methods for getting the virtual origin and size. The virtual origin and size define the virtual bounds of the visual component, which is the entire logical coordinate space that can be rendered by the visual component. Whereas the logical bounds defined by GetLogOrigin() and GetLogSize() define the visible area, the virtual bounds defined by GetVirtualOrigin() and GetVirtualSize() define the entire viewable area. Moving the logical origin scrolls the logical bounds within the virtual bounds.
CMvcLogicalPart
The CMvcLogicalPart class provides an implementation of the ILogCoordinates interface. It is a template class that takes the base class as a parameter. In addition to ILogCoordinates, the CMvcLogicalPart class implements the IZoom interface in order to support zooming. The declaration of CMvcLogicalPart is shown in Example 61.
Example 61 – Declaration of CMvcLogicalPart
template <class _Base>
class CMvcLogicalPart : public _Base,
public ILogCoordinatesImpl< CMvcLogicalPart<_Base> >,
public IZoom
{
. . .
};
The CMvcLogicalPart class actually inherits the implementation of ILogCoordinates from the ILogCoordinatesImpl class. The ILogCoordinatesImpl class maintains the mapping mode, logical origin, and extents and uses them to implement the ILogCoordinates interface.
The base class passed to CMvcLogicalPart as the template parameter is typically either CMvcVisualComponent or CMvcVisualPart. CMvcLogicalPart extends the base visual component class with a logical coordinate system and support for zooming and scrolling. MVC viewports are frequently derived from CMvcLogicalPart in order to support zooming and scrolling.
The IZoom interface provides support for zooming and is shown in Example 62. It contains methods for getting and setting the magnification of the X and Y axes.
Example 62 – IZoom Interface
class __declspec(uuid("8407B2B4-4B5E-11d3-AF1B-006008AFE059"))
IZoom : public IQueryGuid
{
public:
virtual CSize SetMagnification(const int nPctX,
const int nPctY)=0;
virtual CSize GetMagnification() const = 0;
virtual CSize IncreaseMagnification(const int nPctX,
const int nPctY) = 0;
virtual CSize DecreaseMagnification(const int nPctX,
const int nPctY) = 0;
virtual void ZoomExtents(CSize& szWndExt, CSize& szVpExt)
const = 0;
};
The CMvcLogicalPartImpl class provides a commonly used default usage of CMvcLogicalPart. It instantiates CMvcLogicalPart with CMvcVisualPart as the first template parameter and IVisualHost as the second parameter. CMvcLogicalPartImpl is shown in Example 63.
Example 63 – CMvcLogicalPartImpl class
typedef CMvcLogicalPart
< CMvcVisualPart<stingray::foundation::CMvcVisualComponent,
stingray::foundation::IVisualHost>
>
CMvcLogicalPartImpl;
Wrappers (Decorators)
The decorator design pattern is used to extend visual components with additional functionality. A decorator wraps the borders of a visual component with margins and draws in the margins. For example, a visual component may be wrapped with scroll bars or ruler guides. The term wrapper and decorator are used interchangeably.
MvcWrapper_T
The MVCWrapper_T template class is the base class for visual component wrappers. It implements the decorator design pattern by extending a base visual component class and adding margins around its borders. The template parameter passed to the MVCWrapper_T class is the base visual component class. Since the wrapper inherits from the visual component class, wrappers can be added without affecting client code that uses the visual component class. In other words, the wrapped visual component looks the same to client code as the plain visual component.
The MVCWrapper_T class overrides the SetOrigin() and SetSize() functions that it inherits from the visual component. The origin and size are reduced by the size of the margins before being passed to the base class. In other words, MVCWrapper_T subtracts the margins from the origin and size. The margins for the wrapper are maintained by MVCWrapper_T in a CRect member variable and are accessed through the SetMargin() and GetMargin() functions.
MvcBorderWrapper_T
This class decorates a visual component with a simple border. The size and color of the border are passed in as template parameters. The following code segment declares a red, 10 pixel border around a visual component.
 
MvcBorderWrapper_T<CMyVisualComp, RGB(255,0,0), 10> viscomp;
MvcScrollWrapper_T
This class decorates a visual component with scroll bars. It sets the wrapper margins to reflect the width of the scroll bars. The MvcScrollWrapper_T class assumes that the visual component it decorates implements the ILogCoordinates interface. Classes derived from CMvcLogicalPart are frequently used in conjunction with MvcScrollWrapper_T. The following code segment declares a visual component with a scroll wrapper.
 
MvcScrollWrapper_T<CMyVisualComp> viscomp;
Scroll wrappers are frequently used to add scrolling capabilities to MVC viewports. The MvcScrollWrapper_T class works equally well for plain visual components and viewports, since a viewport is simply a type of visual component.
MFC Specifics
There are several MFC-specific visual component classes that existing primarily for historical reasons. Previous versions of the Stingray MVC library used a slightly different naming convention and did not take full advantage of templates. In previous versions, the visual component inheritance hierarchy is hard-coded. It is a deep inheritance hierarchy that looks like this:
MvcVisualComponent<-MvcVisualPart<-MvcLogicalPart<-MvcViewport
This hierarchy has been replaced with framework neutral template classes, which provide a flatter and more flexible hierarchy. The old classes are still supported, but are implemented in terms of the new template classes. The definitions for MvcVisualComponent, MvcVisualPart, and MvcLogicalPart are shown in Example 64. There is no difference in functionality. These definitions simply provide aliases for the old names.
Example 64 – Definitions for MvcVisualComponent, MvcVisualPart and MvcLogicalPart
typedef CMvcVisualComponent MvcVisualComponent;
 
class MvcVisualPart : public CMvcVisualPart<MvcVisualComponent,
MvcVisualPart>
{
};
 
typedef CMvcLogicalPart< MvcVisualPart > MvcLogicalPart;