Component Essentials
CODComponent is the base class for all visual components and models in Objective Views. A component has properties, two-dimensional bounds, and can draw itself onto a device context. Every component is associated with a unique ID and component type. Components maintain a transformation matrix used to calculate points based on movement, rotation, and scaling.
The CODComponent class is derived from the SFL class MvcVisualComponent. The MvcVisualComponent class implements the IVisual and IBounds2D interfaces, so an MvcVisualComponent is essentially a rectangular object with two-dimensional bounds that can draw itself to a device context. CODComponent also inherits from the CODPropertyContainer class, which implements the IODPropertyContainer interface. The IODPropertyContainer interface provides components with a generic way to access properties. Properties help to define the appearance and behavior of a component. Font information, line attributes, fill attributes, and edit behaviors are stored as properties. Developers can easily add custom properties to classes derived from CODComponent.
A component can also be composite. A composite component is a complex object that comprises other simpler components. Every component keeps a list of child components that determine its appearance. The CODModel uses this scheme to maintain all the components on the canvas.
Component Properties
Each component can have zero or more properties associated with it. Properties help to define the appearance and behavior of a component. The CODComponent class implements the IODPropertyContainer interface for accessing the properties it contains. Property values are stored as a list of pointers to property value objects, which are instances of classes derived from CODProperty. The property values are cached in order to avoid duplication of information and reduce memory overhead.
The description of a property (ID, name, description, etc.) is stored separately from the property value. The description of a property (meta property) is defined by the IProperty interface. Property descriptions are registered in a static property map associated with each component class. Keeping the property description separate from the property value is necessary in order to avoid duplicating the name, description, and other property information for each value.
The CODComponent class defines methods for directly accessing its list of property value objects (instances of CODProperty derived classes). The GetProperty(), SetProperty(), and ChangeProperty() methods provide raw access to the list of property value objects maintained by a component. These methods are provided only for backward compatibility and should be avoided. Users should use the IODPropertyContainer interface for accessing a component’s properties instead.
Developers can add their own custom properties to a class of components. The RegisterProperty() method can be used to add property descriptions to the static property map. Registering a property makes it available to all instances of a given component class. Chapter 9 covers properties in more detail.
Component IDs
Each component has an ID that distinguishes it from the other components on the canvas. This ID consists of a long integer. It is generated randomly by the model.
This identifies the type of component. The type is set for primitive components. For example, a rectangle component is automatically assigned a type of “Rectangle.” You can change the type of a component by calling its SetType() method.
When you create symbols in Symbol Designer, you can open the Symbol Type dialog by selecting Symbol Type from the Edit menu.
Every component has a name. By default, the name is the same as the type. Give meaningful names to components so you can distinguish among components in your model. You can set the name through the component’s SetName() method.
Parents and Children
A component can have children. They are stored in a component set in the parent. However, the set can be empty if the component has no children. There are many methods you can use to add a child to a component or to access a child from a component. When you add a child, ensure that there is a subcomponent in memory so you only need to pass a pointer to the component.
A component can also have a parent. When a component is added to a composite as a child, the composite becomes the parent. If a component is at the top-level of the model, its parent is the model.
Component Transforms
If a component has not been moved, rotated or scaled directly, it will not have a transformation matrix. Typically, a child component uses its parent’s transform.
When an end-user manipulates a component on the screen, a transformation matrix is created to record what occurred to the component. This matrix is used to transform the points that define the coordinate just before it is drawn.
Control Points
There are nine control points on a component. They are situated around the component’s bounding box: one on each corner, one halfway along each side, and one in the center of the component. Control points set the origin for spatial calculations such as rotation and scaling. The points along the edges of the bounding box are also used as the default grab handles.
The grab handles appear on a component when it is selected by the end-user. These handles allow the end-user to see what is selected and then scale the component or move its vertices.
On most components, the grab handles are the eight control points around the edges of the bounding box. However, for lines and curves, the handles appear on the vertices of the component, which allows the end-user to move them around.
You can place any primitive component in “edit vertices mode” so that handles appear on the individual vertices of the component, which allows the end-user to change its shape.
Each component knows the amount of logical space it fills as defined by the canvas. The regions of a component’s children are combined with its own to form one logical region. This region is used extensively for hit testing and screen invalidation. The bounding box for a component is calculated from this region object.
User Data
A component also has a generic CObject pointer that you can set to hold anything you like. There is no specific use for this pointer other than to allow the programmer to associate application specific data with a component. The object assigned to the user data pointer must be derived from CObject and support MFC serialization.