Sourceforge.net - The VCF's Project Host
   The VCF Website Home   |   Online Discussion Forums   |   Sourceforge.net Project Page   

3.3. ApplicationKit

3.3.1. Introduction

The ApplicationKit is the primary library used for writing applications that need a Graphical User Interface (GUI). It depends on both the FoundationKit and the GraphicsKit.

The following sections will go over all the major features of the ApplicationKit starting with a very brief example of creating an application (for more on the exact details of this see the HelloWorld examples in the vcf/examples directory). It will then explain the basic features of the UIToolkit class, and then on to the specific features of the ApplicationKit library.

3.3.2. Applications

In the VCF, a user inteface application can have a central class that derives from the VCF::Application class. The application class handles the details of starting the application, initializing it, starting the event loop, and terminating gracefully when the user exits the application, or an exception occurrs.

It is possible to use the VCF with out an application class, particularly if you are integrating with another toolkit. In this case, it is assumed that an event loop is already running, and certain features that depend on an Application instance may not work.

An application either represents a program (or process), or a library in the form of a dynamic link library (DLL) or shared object (SO). If the application is part of a library then the specific instance must be derived from VCF::LibraryApplication. There can be more than one LibraryApplication instance loaded.

If the application represents a program, then there can only be one instance of it during the lifetime of the program. Creating more than the one instance is incorrect and will break the application.

To create a program that has a main Application class, create a new custom class that derives from VCF::Application. Add a constructor that takes an int argument and a char** argument, and then call the derived Application's constructor. Override the virtual method Application::initRunningApplication() and put in your own custom initialization code. In the program's main() function create a single instance of your class and then call the Application::appMain() function. And Voila! you have created a simple application with a running event loop. As an example:

		
class MyApp : public Application {
public:
	MyApp( int argc, char** argv ) : Application(argc,argv){}
	virtual bool initRunningApplication() {
		//your custom initialization here
		return Application::initRunningApplication();
	}
};

int main( int argc, char** argv ) 
{
	Application* app = new MyApp( argc, argv );	
	Application::appMain();
	return 0;
}

		

This will start the program running, call the Application's initRunningApplication() method, and then start the event loop. The Application stores it's arguments in a std::vector of Strings, in the member variable Application::m_cmdLine. You can retreive the current running instance of the Application by calling Application::getRunningInstance().

You can customize the initialization of your application by overriding the Application's initialize() method.


class MyApplication : public Application {
public:
	virtual bool initRunningApplication() {		
		bool result = Application::initRunningApplication();
		
		//your customizations here
		
		return result;
	}
};

		

[Note]Note

Don't forget to call the base class's initRunningApplication() method, or you may see incorrect or undefined behaviour.

You can customize functionality for closing the Application as well by overriding the terminateRunningApplication() method.

		
class MyApplication : public Application {
public:
	//rest of declaration
	virtual void terminateRunningApplication() {
		//your customizations here
		
		Application::terminateRunningApplication();
	}
};

		

3.3.3. UI Toolkit

3.3.3.1. Intro

The UI Toolkit is used to create instances of various peer's needed by the VCF, as well as providing certain low level services, such as modal and non-modal event loops, and getting at UI metrics. Each system the VCF is ported to has a concrete implementation of the UIToolkit. For eample, the VCF version that runs on Win32 compatible operating systems has a concrete implementation of the UIToolkit called the Win32Toolkit. Likewise for Mac OSX there's the OSXUIToolkit.

Many of the functions on the UIToolkit are used primarily by the framework. A developer using the VCF generally does not need to call or be aware of them as there are higher level classes that deal these.

The UIToolkit is a singleton, and there is only one instance for a given process running the VCF libraries. As a developer you will never create this instance - it's lifetime is completely managed by the framework.

Any of the functions that a developer might want to call are static functions, which in turn call a concrete implementation on the singleton instance of the toolkit.

3.3.3.2. UI Metrics

UI Metrics are used to indicate a variety of user interface values, such as the preferred font for controls are, or the preferred vertical spacing between controls. These values are read only and may not be changed.

UI Metrics are intended to be a guide. It allows you properly control the spacing between controls, something that is frequently guess work and done with hard coded values. By using the UI Metrics provided by the toolkit, you can ensure that the values you work with are standard and consistent for the platform your app is running on. It also makes sure that the values you use take into account the DPI resolution of the current screen settings, so that a change in these settings does not adversely affect your layout of your GUI.

An example of this is writing code that creates a label and an edit box, and wanting to ensure that the vertical spacing between the two is consistent and in line with the platform UI rules. This is extremely important when creating a professional UI.

Another feature of UI Metrics is the ability to get the right font for various common controls. For example, the font used for displaying status versus the font used in a message dialog, is frequently different, and may depend on the users settings within the system. By using the UI Metrics you dont' have to resort to guess work as to what this is.

Now that we know what metrics are for, lets look at some example usage. Lets examine how to get the proper spacing between two controls:


UIMetricsManager* metricMgr = UIToolkit::getUIMetricsManager();

Label* label = new Label();
TextControl* edit = new TextControl();

label->setBounds( 10, 10, 50, label->getPreferredHeight() );
Rect bounds = label->getBounds();
bounds.offset( metricMgr->getPreferredSpacingFor(UIMetricsManager::stControlHorizontalSpacing),
				0 );
edit->setBounds( &bounds );	

			

Here's an example of retrieving the correct font. Lets say you want to draw some text is the right font for displaying messages:

	
			
void drawSomeText( GraphicsContext* ctx )
{
	UIMetricsManager* metricMgr = UIToolkit::getUIMetricsManager();
	Font font = metricMgr->getDefaultFontFor(UIMetricsManager::ftMessageFont);
	ctx->setCurrentFont( &font );
	ctx->textAt( 20, 20, "Here's a message!" );
}

			

3.3.3.3. Event Loops

Please see the section on Event loops for a more through discussion.

3.3.3.4. Timers

A timer in the VCF is an instance of the EventHandler class that is called repeatedly until the handler is removed. The duration between calls is specified in milliseconds, and you can specify the duration in the call to register the timer handler. Each time the handler is called, a TimerEvent instance is created, with it event type set to TIMER_EVENT_PULSE. The event is then passed to the handler's invoke() method.

A timer in the VCF is not run in a separate thread, instead it is in the thread of execution that started the event loop. If you need a timer mechanism that is in a separate thread then you should use the Thread class for writing one.

To add a timer all you have to do is call the toolkit's UIToolkit::registerTimerHandler(Object* source, EventHandler* handler, const ulong32& timeoutInMilliSeconds ) function. The source is an instance of a VCF::Object (or some class derived from it), the handler is a VCF::EventHandler instance. The timeoutInMilliSeconds term is the duration of the timer, or how long to wait till the next time to call the handler's invoke() method. The Object you pass in represents the source that is used for all TimerEvents that get created.

You can stop a timer by calling the toolkit's UIToolkit::unregisterTimerHandler() method and passing in the same event handler instance that was passed to UIToolkit::registerTimerHandler().

Here is an example of using a timer.

			
class TimerEx :public EventHandler, Object
{

    public:
    	TimerEx(){
    		// Here we set up the timer.  This class is the source for
    		// the timer and it is also the event handler.  This timer
    		// will fire every 4 seconds.
    		UIToolkit::registerTimerHandler( this, this, 4000L);
    	}

	//The invoke takes an Event. For timers the event type is TimerEvent.
	void invoke(Event * evt){
		Object * src = evt->getSource();

		//Invoke any time specific code tied to this timer.
	}

	~TimerEx(){
		// before destruction remove the timer.  That way we don't
		// waste system resources.
		UIToolkit::unregisterTimerHandler( this );
	}
};                     
			
[Note]Note
Events are not coelesced in this release. So this means that you could slow the entire application down easily if event handling takes too long. Because all the timer events share one thread by default it is important that the EventHandler::invoke() function return quickly. It will tie up all other timers after it if event handling time of the timer events is very long.

If timer handling is complex or other methods called then consider creating a tread for handler code. The example above would need these changes if the handler was time intensive.

		
class MyTimerCallback : public Thread{
   public:
    	void run(){
    		//process complex code
    	}
};

class TimerEx2 :public EventHandler, Object
{
    public:
    	TimerEx2(){
    		// Here we set up the timer.  This class is the source for
    		// the timer and it is also the event handler.  This timer
    		// will fire every 4 seconds.
    		UIToolkit::registerTimerHandler( this, this, 4000L);
    	}

    	//The invoke takes an Event. For timers the event type is TimerEvent.
	void invoke(TimerEvent * evt){

		// Create and invoke the thread.
		MyTimerCallback  callbackThread = new MyTimerCallback();
		callbackThread->start();
	}

	~TimerEx2(){
		// before destruction remove the timer.  That way we don't
		// waste system resources.
		UIToolkit::unregisterTimerHandler( this );
	}
};

			

Alternativly, A new timer class could be created that would send all timer events on seperate threads. This could consume significant resources if there are many events getting fired at near real-time.

3.3.3.5. Carets

A caret is a little rectangular (usually) graphic that flashs and shows the current position within a section of text. Typically the caret is used in text controls.

3.3.3.6. Posting Events

Calling functions that fire events, or firing an event yourself via a Delegate is always synchronous behaviour, and you must wait for any callbacks before proceeding. However you can also post an event, which allows you to go on with your immediate processing and puts the event on the toolkits event queue for later processing.

To post an event you need to create a an event instance. With synchronous behaviour, you can create an event instance on the stack, for example:

			
//get the control some how
Control* someControl = ...;

MouseEvent e; //event is created on the stack
//fill in the mouse event
//send the event to the control for handling
someControl->handleEvent( &e ); //we block here till all the event handling is done
			
			

However when you post an event, you must create the event on heap, since you will not have control over when it gets destroyed (instead the framework will take care of the destruction for you). If you already have an existing event the easiest thing to do is to clone it, by calling the event's clone() method, and pass this cloned event instance to the post method.

[Note]Note
Object::clone() is actually a virtual method first defined in the VCF::Object class.

Once you have an event instance ready, you can post the event by either creating a new event handler instance or getting an existing one, and then call the UIToolkit::postEvent(EventHandler* eventHandler, Event* event, const bool& deleteHandler=true) method. You can optionally tell the toolkit to not auotmatically delete the handler for you by setting the deleteHandler to false (the default behaviour is to delete the handler you pass in). The event instance that is passed in is deleted automatically for you when the event is finished processing.

Let's look at a simple example:


class MyControl : public CustomControl {
public:
	MyControl() {
		EventHandler* ev = 
			new GenericEventHandler<MyControl>(this, &MyControl::myPostedEventHandler,
										"MyControl::myPostedEventHandler" );
	}
	void myPostedEventHandler( Event* e ) {
		Dialog::showMessage( "Recv'd the Event!" );
	}
	
	virtual void mouseDown( MouseEvent* e ) {
		CustomControl::mouseDown( e );
		Event* e = (Event*)e->clone();
		UIToolkit::postEvent( getEventHandler("MyControl::myPostedEventHandler"), e, false );
	}
};

			

We create a new event handler in our constructor. Then when the control's mouseDown method is called we clone the mouse event, and call the UIToolkit's postEvent() method, passing our newly cloned event instance, our event handler (named "MyControl::myPostedEventHandler"), and a flag that tells the UIToolkit not to delete the event handler when posted event has been processed. The new event instance will be deleted by the UIToolkit, so you don't need to worry about a memory leak here.

3.3.3.7. Default Buttons

A default button is a button whose click method will get called fired when the user hits the return key. The button does not have to have the current focus for this to happen. Typically a default button has some visual indicator that easily distinguishes it from other buttons.

[Note]Note
On Win32 systems the default button usually has a 1 pixel black border around it's edges. On MacOSX the button is darker shade of blue and pulses.

You can set the default button programmatically by calling the toolkit's setDefaultButton() method. You pass in any control that implements the VCF::Button interface.

To determine the current default button, call the toolkit's getDefaultButton() method, which will return the current default button instance, or NULL if there is no default button.

3.3.3.8. Accelerators

An accelerator is a special action that takes place when a key or key combination is pressed on the keyboard. For example, with many applications, hitting the "Ctrl" and the "C" keys together will copy something to the clipboard. Key combinations are one of the alpha-numeric keys, and 0 or more "modifier" keys, such as Ctrl, Alt (or Option),or Shift.

Accelerators are managed by the toolkit, which also ensures that keyboard events that match a particular accelerator get handled correctly. To register an accelerator you call the toolkit's registerAccelerator() method which takes an instance of a VCF::AcceleratorKey class. You can remove an accelerator by calling the removeAccelerator() method. You can query for the existance of an accelerator by calling the toolkit's getAccelerator() method, passing in the virtual key code, and a mask that indicates which modifier keys are pressed (if any).

To register an accelerator you need an instance of AcceleratorKey. To do this you need the following information to pass to the AcceleratorKey's constructor:

  • a control that is associated with the AcceleratorKey (this may be NULL).
  • a value for a virtual key code.
  • a value for a modifier mask (whether or not the accelerator uses the Ctrl, Shift, or Alt keys).
  • an EventHandler instance
  • and whether or not the accelerator is triggered via a mnemonic.

Once you have created the AcceleratorKey, call the toolkit's registerAccelerator() and pass in the new AcceleratorKey instance.

For more details on usage and how accelerators work pelase see the Accelerator Keys section.

3.3.3.9. Creating Peers

The toolkit peer creation methods should never have to be called for general VCF usage. If you are writing a new component/control that requires a peer to manage part of it's work, then you may have to call the appropriate peer creation method.

3.3.3.10. Miscellaneous

3.3.4. Event loops

An event loop is a special loop that runs and retrieves and dispatches events from the native windowing system's event queue. In general there are two main types of event loops. The most commonly used is the non-modal event loop. This is typically the main event loop that runs the application. You can move from window to window, and events get fired, etc.

The other event loop type is the modal event loop. This is usually associated with a control of some sort, typically popup dialogs. This runs another event loop (on top of whatever is currently running), and restricts the flow of events to only those events relevant to the control associated with the loop. Thus you get a dialog that pops up, and you can't continue (i.e. all mouse event, keyboard events, etc, are ignored) to other windows till you dismiss the dialog (usually by clicking "OK" or "Cancel" buttons). Hitting the "Escape" key on the keyboard will also stop the modal event loop.

Another difference between modal and non modal event loops is the return value. A non-modal event loop doesn't anything. A modal event does return a value, and this value is used to indicate a variety of things. The value returned is one of the ModalReturnType enumeration values. The most common value returns is either UIToolkit::mrOK, usually indicating the user pressed an OK button, or UIToolkit::mrCancel, typically indicating the user pressed the Cancel button. For more complete information see the UIToolkit::ModalReturnType enumeration documentation.

To run a non-modal event loop call the toolkit's runEventLoop() method. To run a modal event loop call the toolkit's runModalEventLoopFor() and pass in a valid control instance.

3.3.5. Event Types

The table below shows each event and the component or object type responisble for the event being fired. <TODO>validate all event data. </TODO>

Table 3.3. 

Event ClassSource Class(es)Description
ButtonEventPushButtonThis event is fired when button is pressed
ClipboardEventObjectFired when a cut copy or paste occurs
ColumnItemEvent Fired when column is selected
ColumnModelEvent Fired when model data is changed
ControlEvent  
FocusEventComponentFired when component focus changes
HelpEventComponentFired when help requested
ImageEvent  
ImageListEvent  
ItemEventCheckBoxControl, Fired when item selection changes
KeyboardEventObjectfided when a key is hit on the keyboard
ListModelEventListBoxControlFired when list data is changed
MenuItemEventMenuBarFired when menu item selected
MouseEventComponentFired when mouse is moved or mouse button clicked
PropertyChangeEventPropertyEditorManagerFired when propery changes
TabModelEventTabbedPagesFired when tab selection changes
TableModelEventTableControlFired when table data changes
TextEventTextControl 
ThreadEventThread or Runnable 
TimerEventObjectFired when timer expires
ToolTipEventComponentFired when tool tip needs shown
TreeModelEventDefaultTreeModelFired when tree model data changes
UndoRedoEventUndoRedoStackFired when undo or redo is required
ValidationEvent  
WhatsThisHelpEventComponentFired when context sensitive hit on UI item is made
WindowEventWindowFired when window size or location changes. Also when maximized or minimized.

3.3.6. Drag and Drop

Drag and Drop, some times referred to as a form direct manipulation, is the process of selecting something (text, a link, a graphic, a chart, a file, etc), dragging it to a target (an open document, a folder, a chart, etc ), and then releasing the selection to drop the selection and complete the operation. It is a form of transfer between a source and a target is similar to performing a cut/copy and paste operation, only more interactive (and with user feedback). The whole process can be broken into three main sections, the drag start, the drag movement itself, and the drop. Below we'll discuss each of these in more detail.

When performing a Drag and Drop operation there are two primary entities involved: the drag source and the drop target. The source is responsible for preparing the data that is going to be dragged, and the target is concerned with responding to the data as it is dragged over the target and when/if the data is dropped on the target.

Starting a drag operation is typically done when the primary mouse button is held down and while keeping this button held down, the user moves the mouse beyond some system specific thresh hold. This threshold can be retreived from the UIToolkit by calling the getDragDropDelta() method, which returns a Size object indicating the minimum change in position that each coordinate must exceed before the drag is started. These mouse events all take place on VCF::Control derived class, such as a VCF::Window, or VCF::TextControl, or some custom control.

Once it has been determined that a drag operation should start, the Control's beginDragDrop() method is called, passing in the current MouseEvent. Since beginDragDrop() is a virtual method, it is up to application developers to overide this and customize it as they see fit. Typically you will create a DragSource object in your custom beginDragDrop() method and then call the DragSource object's startDragDrop() passing in some valid instance of a ClipboardDataObject. At this point, we now hand control over to the DragSource which in turn talks to the native windowing system to negotiate the internals of the Drag and Drop operation. The DragSource object's startDragDrop() will block until the Drag and Drop operation has either been completed successfully or abandoned. Once the DragSource's startDragDrop() has been called it first fires off a SourceBegin event.

Once the started the next part of the process is dragging the source around, moving the mouse with the primary button held down. While this occurs, the DragSource will continuously fire SourceGiveFeedback events of type DragSource. This allows you to control the feedback displayed to the user, typically by changing the mouse cursor, and to set the type of Drag Drop operation is most appropriate. The operation types are defined in DragActionType enumeration. An event handler that gets notified of this event may call the DragSource event's setActionType() to indicate the preferred action type (typically a link, copy, or move). If the mouse is over an area that cannot receive a Drag Drop operation, then the action type should be none, or daNone.

In addition to the the SourceGiveFeedback events, DragSource also fires SourceCanContinueDragOp events. These control whether or not the operation should even continue at all. An event handler for this event can call the DragSource event's setActionType() method with a value of daStop to shutdown the Drag Drop operation.

While the mouse is dragging the source around, it is possible for it to encounter a drop target. A drop target is another control that can receive the source data of the Drag Drop operation. To act as a drop target the control registers itself with a DropTarget, which is usually a member variable whose lifetime is the same as the target controls. To register itself, the control passes itself into the DropTarget's constructor.

Once registered with the DropTarget instance, the DropTarget will do all the neccessary interaction with the windowing system, and will fire events at the appropriate times.

The first event fired is the DropTargetEntered event. This is fired the first time the mouse enters within the bounds of the control registered to the DropTarget instance during a Drag and Drop operation. When the mouse leaves the control's bounds, a DropTargetLeft event is fired. While the mouse is within the control's bounds a DropTargetDraggingOver event is fired for each mouse move. When the mouse is released, indicating the source will be dropped, the DropTargetDropped event is fired, completing the Drag and Drop operation.

Once the source data has been "dropped" you can retreive the data from the DropEvent instance that is fired because from the DropTargetDropped delegate. With this event you can retreive the data object and process it accordingly.

3.3.7. Components

Components represent the smallest nonvisual class that can be manipulated at design time. This manipulation takes place through property editors, event editors, and component editors. Technically the VCF RTTI system and event system will work with any class that is derived (directly or indirectly) from VCF::Object, but the VCF::Component represents the beginning of user interface related classes. In addition, any class derived from Component can be registered with the run time component information system, and will also show up in an IDE's component palette.

Components come in two flavors: non-visual components, such as the TimerComponent, and visual components, that derive from Control (which is itself derived from Component), and are visible to the user. Examples of visual components are the ListBoxControl, Window, and Panel control.

You do not create instances of Component. It is an abstract class that is intended to serve as a base class for other concrete components.

Components maintain a special state variable called component state, that indicates the current state of the component. This is useful, for example, in determining if the component is in design mode, or normal mode. The state is an enumeration of type ComponentState. In general the component's state is csNormal indicating that it's been created. When a component is in the process of being read in from a stream, it's state is marked as csLoading, and when the component is being written to a stream it's state is marked as csSaving. As a general rule most UI events are only fired if the component's state is csNormal. The other important state is the csDesigning state. This is set when the component is being edited through some visual editing tool (like the VCF Builder's Form Designer). A component in the csDesigning state can process some event, but ignores most others. This is particularly true if the component is a control. For example, if the component is a TextControl, then any keyboard events are ignored if it's state is set to csDesigning.

Components are intended to be reused by other applications. They can be part of a standalone library, that can be used by other applications, or a an application may create a series of components specifically for it's use. When part of a standalone library, library is referred to as a Visual Package Library (VPL). A VPL can be a static library, or a dynamic library in the form of a DLL or Shared Object. VPL's typically have a file extension of ".vpl". Remember if using Application to derive the libraries main class from LibraryApplication. To allow this form of re-use, Components need to provide not only RTTI information, but also language specific information, such as the name of the library they belong to, the header(s) they require for usage, etc. This information is stored in two main classes, ComponentInfo, which deals with the specific details of a particular Component class, and PackageInfo, which deals with the specifics of the package libary (VPL) that the Component belongs to.

The ComponentInfo class gives detailed information about the particular component class. It provides information about the author of the component, the company (if relevant),copyright, and text containing any other relevant information the developer wishes to provide. A developer can set the image to use for the component as well (if left alone a default image will be provided).

The ComponentInfo class also provides inforamation about which header(s) are needed by the component. The getRequiredHeaders() method returns an Enumerator of Strings that lists the headers in the orderd they should be included. The headers should not contain absolute paths. If the component is part of a namespace (other than the VCF namespace) it can expose this information via the getRequiredNamespaces() method. This will list any namespaces the component requires for usage.

The PackageInfo class describes the library the component is part of. It specifies the author of the package library, the company, copyright info, and text for other information. It gives out the name of the library excluding the platform specific extension (such as .lib or .a or .so). It indicates the platforms/toolchains it supports (if no source is provided). It indicates whether it allows static linkage, dynamic linkage, or both.

Components can be "owned" by other components, freeing the developer from having to worry about deleting the Components later on. To add a Component you simply call the Component's addComponent() method, passing in the instance of the component you want to "own". Once the component is added, it's owner will take care of destroying it when the owner is destroyed. You can also remove a component. If you remove a component, and then do not add it back to some other owner, the responsibility for destroying the component is up to you.

When components get added or removed they fire an event which can be handled. You can register for this event by adding an event handler to the either the Component's ComponentAdded or ComponentRemoved delegate.

3.3.8. Controls and Containers

A Control is a visual component, and is derived directly from VCF::Component. It is a user visible object that may respond to events generated by the underlying windowing system. Controls can "contain" other Controls, and a Control can be heavyweight (the default), or lightweight. We'll cover these topics and more in the following paragraphs.

A Control responds to a wide variety of events. These events range from mouse events, keyboard events, size and position change events, and many others. See the Control source documentation for further details. You can attach an event handler to any of these events.

A Control has a "parent" property which indicates the parent control that contains the child control. A control that has a parent is known as a child control. Typically controls with no parent are top level controls like a Window, Toolbar, or Dialog. The parent control is responsible for resizing the child if appropriate (see Layout for more information on this). It is okay for a parent to be NULL for top level controls like a Window or Dialog.

Each Control has the potential to act or behave as a container. This means the control can act as a parent for other child controls. You can dynamically specify or query the specific type of container by using the Control's setContainer() and/or getContainer() methods. In either case you'll be working with an instance of the Container class. The following example shows how to retreive the container instance of a control:


Control* panel = new Panel();
Container* container = panel->getContainer();
if ( NULL != container ) {
	Label* label = new Label();
	container->add( label, AlignTop );
}

		

This next fragment shows how to set the container of a control:


Control* panel = new Panel();
panel->setContainer( new HorizontalLayoutContainer () );

		

Heavyweight and Lightweight Controls.  A control in the VCF may be either a "heavyweight" or "lightweight" control. This describes how the control is implemented, and you can only specify this at the time the control is created, it's not something that can be changed later on. Control's that wrap native controls, like the TextControl, or TreeControl are always "heavyweight".

A heavy weight control represents a full native windowing system control, and thus it has it's own set of resources (as specificed by the underlying windowing system). In Win32 terms, this means that each heavyweight control has it's own HWND and HDC associated with it (just like any control/window that you created through using Win32's CreateWindow() function). On linux systems that use GTK, this mean that each heavyweight control has it's own GtkWidget (and it's associated GdkWindow handle). On Mac OS X systems a heavyweight control is associated with either a Carbon ControlRef or possibly with an NSControl instance if we switch to using ObjectiveC for the OS X port.

Heavyweight controls receive user interface events directly from the windowing systems, where they are caught by the VCF peer implementation(s), translated into VCF events and then dispatched to the VCF control. In contrast, lightweight controls do not have their own windowing system control resources. Instead they share these resources with the first heavyweight parent that contains the lightweight control. The VCF takes care of simulating events to lightweight controls, so from the perspective of the developer, the difference is basically non-existant.

Containers.  A Container has methods for adding, inserting, and removing controls. A Container instance owns all the child controls it has, meaning that the control's addComponent() method is called whenever a control is added to the container. The Container class itself cannot be created, as it is an abstract class. Instead you must create some concrete class derived from the Container class. Unless you want to completely control the collection type used to hold the child controls you don't need to derive directly from VCF::Container, but instead can derive from VCF::StandardContainer which does some of the basic grunge work for you. VCF::StandardContainer uses a vector to maintain it's collection of child controls.

A Containers primary job is to manage a collection of child controls and to respond to certain events, forwarding them along to other child controls. Containers can automatically resize their child controls when a resize event happens to the Control that owns the Container. When this happens, the default behaviour is to follow standard layout rules depending on the child's alignment or anchor settings. The event forwarding behaviour only takes place for child controls that are lightweight. Heavyweight controls do not need this, as they will receive events directly from the windowing system. This event forwarding is implemented in VCF::StandardContainer, which is yet another reason to derive from this class when implementing a custom container type.

3.3.8.1. Control Listing

CustomControl. 

CommandButton. 

ListViewControl. 

TreeControl. 

Toolbar. 

TextControl. 

MultilineTextControl. 

HTMLBrowserControl. 

Label. 

ComboBoxControl. 

HeaderControl. 

ImageControl. 

ListBoxControl. 

OpenGLControl. 

ProgressControl. 

PushButton. 

SliderControl. 

Splitter. 

TabbedPages. 

TableControl. 

TreeListControl. 

CheckBoxControl. 

RadioButtonControl. 

3.3.9. Borders

Borders are used to decorate the edges or non-client area of a control. You can change a control's border by calling the control's Control::setBorder(). Border's are themselves components and are owned by whatever component they are attached to.

Borders have two main areas of responsibility. The first is to paint themselves on a given GraphicsContext. This is done by calling the Border's paint() funtion. The second area is to provide a way to get the "client" area that results when using the border. This is done by calling the Border's getClientRect() function.

A Border may be created without being associated with a control, if you need to draw a border as part of your painting code. For example:


GraphicsContext* ctx = ...
Basic3DBorder bdr;
Rect r(0,0,100,100);
bdr.paint( &r, ctx );

		

This code will paint a border within the specified rectangle, using the passed in GraphicsContext. Note that the Border can be created on the stack in this case. This is one of the few classes derived from Component that can be used like this.

When a Border is used with a Control, the control's client bounds will automatically take into account the border, by calling the border instance's getClientRect(). The Win32 implementation also takes advantage of this by using the border to paint the non client area for windows that are common controls, like the tree, list, and edit windows. This means that these controls can use and have the same border look that vcf only control's use. To demonstrate this, here are some screenshots featuring a text control with several different borders:

This shows the TextControl and the use of Spy++ on Windows to show that this is just a wrapper for a RichEdit control. The next shot shows a text control with an etched border:

The follow shot shows a text control with a titled border (see the TitledBorder class). Notice how the title is put on the outside of the control - and the client area is still housing the normal text control:

The next shot confirms this, the total height of the control is 40 pixels, while the client area is only 25:

The final shot shows the text control with a Basic3DBorder, with inverted set to false.

3.3.10. Layout

Layout in the VCF is accomplished by the use of the Container class. The container maintains a list of all the child controls it owns. Using this list of controls, the container decides how to layout and position the controls. The default layout algorithm uses the alignment value or anchor values of it's child controls to determine how to lay them out within the parent control's client bounds. Layout is performed whenever the resizeChildren() method is called.

Layout will occur whenever the control is resized and it's width or height changes. The basic implementation of a container, the AbstractContainer class, will automatically detect resize events, and call resizeChildren(), which simplifies the work you need to do when implementing a custom container.

The control class has methods to determine it's alignment or it's anchor sides. You cannot set both of these, only one or the other with precendence given in the order the set methods are called. If you set the Alignment of your control to AlignTop, and then set it's anchor value, the anchor value will take precedence over the alignment, which will be overriden and set to AlignNone. Likewise, if you set the anchor of your control and then set it's alignment, the anchors will be turned off, and the alignment will take precendence. It is important to note that while the standard container makes use of these values, other custom containers may choose to ignore them.

Specifying a container for layout is trivial to do. You simply create a new container instance, or use an existing one, and then call the control's setContainer() method. Container's are components, so the same rules that apply to components for object lifetime and ownership apply to containers as well.

3.3.10.1. Standard Container layout

The standard layout allows for alignment to the left (AlignLeft), right (AlignRight), top (AlignTop), bottom (AlignBottom) or client area (AlignClient). It also allows for absolute positioning if the alignment value is AlignNone. Alternately you can specify that a control is "anchored" to one or more sides of it's parent's client area by using the AnchorLeft, AnchorRight, AnchorTop, and/or AnchorBottom values.

We can see an example of the the various alignment values in this shot:

Here we have a collection of controls aligned to different sides. If the frame is resized then the controls are repositioned accordingly:

To see the effect of resizing on anchored controls we can look at the following two shots. The first has a series of controls, a button that is anchored to the left and right (AnchorLeft | AnchorRight), a text control that is anchored only to the right, and a panel that is anchored on all sides (AnchorLeft | AnchorRight | AnchorTop | AnchorBottom). The first shot:

The second shot after it has been resized:

Layout algorithm for Standard containers.  The default layout algorithm, as implemented in the VCF::StandardContainer class, does the following:

  • Get the container controls client bounds
  • Adjust these bounds for the StandardContainer's top, left, right, and bottom border widths (which are 0.0 by default).
  • Determine if there are any child controls that may need to have their bounds adjusted because their are anchored to one or more sides of their parent control.
  • Determine if there are any child controls that may need to have their bounds adjusted because their are aligned to some side of the parent control.
  • If alignment work is needed, then adjust any child control bounds that is aligned to the top of the parent control. The child controls original height is respected but it's width is adjusted to that of the container's client bounds.
  • If alignment work is needed, then adjust any child control bounds that is aligned to the bottom of the parent control. The child controls original height is respected but it's width is adjusted to that of the container's client bounds.
  • If alignment work is needed, then adjust any child control bounds that is aligned to the left of the parent control. The child controls original width is respected but it's height is adjusted to that of the container's client bounds.
  • If alignment work is needed, then adjust any child control bounds that is aligned to the right of the parent control. The child controls original height is respected but it's width is adjusted to that of the container's client bounds.
  • If alignment work is needed, then adjust any child control bounds that is aligned to the client area of the parent control. The child control's bounds are assigned to whatever remaining bounds are left after aligning the appropriate child controls to top, bottom, left, and right.
  • If anchor work is needed, then adjust each child control that has anchors accordingly.
  • Any control that has it's alignment marked as AlignNone is left wherever it's bounds say it is - no adjusting at all is performed.

More advanced layouts can be achieved by writing a custom Container class and overriding the resizeChildren method. For examples of this see the HorizontalLayoutContainer and ColumnLayoutContainer classes.

3.3.10.2. Horizontal layout

The HorizontalLayoutContainer class provides a simple way to layout controls horizontally, using multiple rows as well. The HorizontalLayoutContainer allows you to set the number of columns (it defaults to 2), set the specific column widths, the spacing between the columns, the maximum row height, and the vertical spacing between rows. Once these are set (the class already comes with defaults set that make sense), all you need to do is to add controls. As controls are added to the container, it will automatically calculate new positions for all the controls based on the number of columns it has. So a HorizontalLayoutContainer with 10 controls, and 2 columns, will have 5 rows of controls.

This shot shows a custom dialog with a container instance of HorizontalLayoutContainer with 2 columns. The code to do so looks something like this:

class MyDialog : public Dialog {
public:
	MyDialog() {

		setWidth( 350 );
		setHeight( 150 );

		Panel* pane1 = new Panel();
		pane1->setBorder( NULL );

		HorizontalLayoutContainer* container = new HorizontalLayoutContainer();		
		pane1->setContainer( container );

		

		Label* lable1 = new Label();
		lable1->setCaption( "Name:" );
		pane1->add( lable1 );

		TextControl* edit1 = new TextControl();
		
		pane1->add( edit1 );


		Label* lable2 = new Label();
		lable2->setCaption( "Are you Mergatroid?:" );
		pane1->add( lable2 );

		CheckBoxControl* checkBox = new CheckBoxControl();
		checkBox->setCaption( "Yes I am!" );
		pane1->add( checkBox );

		add( pane1, AlignClient );

		//rest of code omitted
	}
};		
		 

3.3.11. Frames, Dialogs, and Windows

In most GUI applications there exist a special set of controls (or widgets) that house the rest of the user interface. In the VCF we refer to these as Frames. A frame (which is represented by the VCF::Frame class) is an abstract concept that represents the top level control (for lack of a better term) usually with some sort of "decoration" in the form of a caption bar and special buttons for closing the frame as well as buttons for minimizing and/or maximizing the frame. A frame can be resized and or/moved. Depending on the style of the frame it may be a standard frame or a floating toolbar style frame. The exact look of the frame is determined by the underlying windowing platform, and is not under the control of the VCF.

Frames are further broken down into two divisions: the Dialog (represented by the VCF::Dialog class) and the Window (represented by the VCF::Window class). A Window is the concrete implementation that represents the top level frame that a user will see and manipulate. Open a new browser window, and the frame that holds all the controls and HTML view is considers a "Window" in the VCF. A Dialog is a similar to a Window but is used specifically for either getting input from a user, such as a dialog that asks the user to input the number of pages to print, or to present a message to the user. Usually a dialog is run in a modal state, which means that all other user input is ignored by the application till the user dismisses the dialog. As a general rule a dialog is not resizeable, while a window is. There may be some subtle (or not so subtle) visual cues as well that distinguish a dialog from a window, but this depends on the underlying windowing platform.

3.3.11.1. Dialog usage in VFC

An application often needs to show modal and non modal dialogs. Modal dialogs do not allow any other GUI interaction while displayed. VFC supports modal and non modal dialog types.

3.3.11.1.1. Standard Dialogs

There are several standard dialogs already in VCF.

[Note]Note
These common dialogs are fully supported in Windows in this version. The OSX platform has full support in this release. The linux platform has partial support in this release.

The standard pattern of usage is to create the dialog, set various fields as appropriate, and then call the dialog instance's execute() method. This will show the dialog in a modal state. If the uses dismisses the dialog through whatever means (such as hitting the "Esc" key, or clicking the "Cancel" button) execute() will return false. If the options/settings on the dialog are accepted, execute will return true.

File load and save dialogs are derived from CommonFileDialog. To open a file dialog use the CommonFileOpen class. To bring up a save dialog, use the CommonFileSave class. Another related dialog is the CommonFileBrowse. This dialog is used when you want to browse the folders in a tree. Here is an example of how to use the common file related dialogs.

                  
                  
class MyWindow :public Window{

	/**
	* doFindFile
	* Purpose: Find a file to load
	* @param filename the filename to look for
	* @param allowMulti is multiselection allowed
	* @param startDir where do you want the dialog to initially start looing for the file.
	* @param title what title do you want to give the dialog.
	*/
	void doFindFile(String filename, bool allowMulti, String startDir, String title){
		// create open file dialog passing in parent component and start directory
		CommonFileOpen* openDialog = new CommonFileOpen( this, startDir);
			
		//Sets up the filters for available file searches.  
		openDialog.addFilter("All Files", "*.*");
					
		// limit selection to a single file.
		openDialog.setAllowsMultiSelect(allowMulti);

		//Set its title to let the user know what this is all about.
		openDialog.setTitle(title);

		//limit search to specific file names.
		openDialog.setCaption("Looking for " + filename);
		openDialog.setFilename(filename);
		
		
		//Now get the results.
		if(openDialog.execute()){
		        Enumerator<String>* fileList = openDialog.getSelectedFiles();

		        //cycle through the selected files.  Should only be one in
		        //this case.
		        while( fileList.hasMoreElements(){
			       String file = fileList.nextElement();

		               // additional processing logic based on file goes here.
		               //The file variable will be null if not found.
		        }
		}
	}
	/**
	* doSaveFile
	* Purpose: Find path to save a file.
	* @param filename the filename to save.  Initial value only, user can change.
	* @param path where do you want the dialog to initially set the save path to.
	* @param title what title do you want to give the dialog.
	*/
	void doSaveFile(String filename, String path, String title){

		// create save file dialog passing in parent component and start directory
		CommonFileSave* saveDialog = new CommonFileSave( this, path);


		//Set its title to let the user know what this is all about.
		saveDialog.setTitle(title);

		//limit search to specific file names.
		saveDialog.setCaption("Saving " + filename);
		saveDialog.setFilename(filename);

		//Now get the results.
		if( openDialog.execute()){
		    Enumerator<String>* fileList = saveDialog.getSelectedFiles();

		    //cycle through the selected files.  Should only be one in
		    //this case. Here we are getting the path where the user wants
		    //to save the file.  It is up to the application to save it there.
		    while( fileList.hasMoreElements(){
			String file = fileList.nextElement();

		       // additional processing logic based on file goes here.
		       //The file variable will be null if not found.
		    }
	        }
	}
	/**
	* doBrowseFolders
	* Purpose: Let user browse through folders
	* @param path where do you want the dialog to initially set the save path to.
	* @param title what title do you want to give the dialog.
	*/
	void doBrowseFolders(String path, String title){

		// create save file dialog passing in parent component and start directory
		CommonFileBrowse* browseDialog = new CommonFileBrowse();


		//Set its title to let the user know what this is all about.
		browseDialog.setTitle(title);

		//set initial direcory
		browseDialog.setDirectory(path);

		if( browseDialog.execute()){
			String dir = browseDialog.getDirectory();
		}

	}

	...
};
                    
		

File Open and Save Dialogs.  File Save (Windows):

File Open (Windows):

File Save (Mac OS X):

File Open (Mac OS X):

Other dialogs common to all platforms are the color picker and font chooser. The VCF::CommonColor class is a platform independet color picker. The VCF::CommonFont class is the platform independent font picker. Both classes are derived from CommonDialog.

Using the color and font dialogs is very simple. The example below creates and shows an example of the use of the color and font picker.


class UserPrefsExample: public Window{

	/**
	* getUserSelectedColor()
	* This method lets the user select the desired color.
	* @return returns the color the user selected or NULL if not selected.
	*/
	Color * getUserSelectedColor(){
		Color * ret = null;

		//Create the dialog and set the window as the paren.
		CommonColor* colorChooser = new CommonColor(this);
		colorChooser.showModal();
		if( colorChooser.execute()){
			ret = colorChooser.getSelectedColor();
		}
		return ret;
	}

	/**
	* getUserSelectedFont()
	* This method lets the user select the desired font.
	* @return returns the font the user selected or NULL if not selected.
	*/
	Font* getUserSelectedFont(){
		Font * ret = null;

		// Create the font dialog
		CommonFont * fontChooser = new CommonFont(this);
		if( fontChooser.execute() ){
			ret = fontChooser.getSelectedFont();
		}
		return ret;
	}
	...
};

Color and Font Dialogs.  Color (Windows):

Font (Windows):

Color Save (Mac OS X):

Other Common Dialogs.  Browse for Folder (Windows):

Browse for Folder (Mac OS X):

Printer (Windows):

There is also a common dialog for showing a text message to the user. This is included in the VCF::Dialog class. To send a popup message simply call the static method, showMessage() and pass in the message and optionally a caption. The signature for the showMessage() function is:

showMessage(const String& message, const String& caption="")

If you need custom control of the buttons and message style with the message choose this alternate signature. :

UIToolkit::ModalReturnType showMessage( const String& message, const String& caption, const long& messageButtons = mbOKCancel,const MessageStyle& messageStyle=msDefault )

where messageButtons can be any combination of the following :

  • mbOK - Okay button
  • mbHelp - a help button
  • mbYesNo - Yes and No buttons
  • mbYesNoCancel - Yes No and Cancel buttons
  • mbOKCancel - Okay and Cancel buttons
  • mbRetryCancel - Retry and Cancel buttons
  • mbAbortRetryIgnore - Retry and Ignore buttons

and, messageStyle can be any of the following:

  • msDefault - normal message
  • msError - error message
  • msInfo - information message
  • msWarning - warning message

Dialog Message Styles.  Dialog::msDefault (Windows):

Dialog::msError (Windows):

Dialog::msInfo (Windows):

Dialog::msWarning (Windows):

Dialog::msError (Mac OS X):

Dialog::msInfo (Mac OS X):

Dialog::msWarning (Mac OS X):

3.3.11.1.2. Custom Dialogs

Custom dialogs will typically be derived from the VCF::Dialog class and possibly CustomControl. The VCF::Dialog class has methods for showing the dialog, and setting the icon. Below is an example of an About box dialog:

		
class MyAbout : public VCF::Dialog {
   public:

   	/**
   	* Set up the about dialog.
	MyAbout(){
		// here we set the size of the dialog.
		setBounds( 100, 100, 150, 150 );

		//Add a VFC::Label
		Label* label = new Label();
		label->setBounds( &Rect(25, 10, 200, 45) );

		// add label to dialog frame.
		add( label, AlignClient );

		label->setCaption( "About My Application." );

		Rect clientRect = getClientBounds();

		//Create an okay button.
		CommandButton* okBtn = new CommandButton();
		okBtn->setWidth( okBtn->getPreferredWidth() );
		okBtn->setHeight( okBtn->getPreferredHeight() );

		okBtn->setTop( 10 );
		okBtn->setLeft( clientRect.right_ + 20 );
	
		//now add it 
		add( okBtn );
	
		clientRect.right_ += okBtn->getWidth() + 25 ;
		
		okBtn->setFocused();
		okBtn->setCommandType( BC_OK );
		okBtn->setCaption( "&OK" );
	
		setCaption( "About My Applications" );
	
		setClientBounds( &clientRect );	
	}
			
	virtual ~MyAbout(){
	}
};

		

Typically, this would be tied to the about menu item. The following snippet of code shows how this is shown:


	void onHelpAbout( VCF::Event* e ){
		MyAbout myAbout;
		myAbout.showModal();
	}	

		

3.3.11.2. Frame Sizing

A frame can be resized either programmatically or by the user. You can set the size of the frame by calling the setBounds() function, just like you would for a control. If the user sizes the frame, the frames will resize itself freely (or according to whatever platform specific window manager rules there are for this), \em unless the frame's minimum or maximum values are set. These are set by calling the Control::setMinSize() or Control::setMaxSize functions. If these values are set then the frames size may be constrained accordingly. You can set both maximum and minimum constraints or none at all.

3.3.11.3. Frame usage in the VFC

In the VCF you never create a Frame instance directly. Instead you create either a Window or Dialog instance (or classes that derive from Window or Dialog). However the Frame class has a number of methods that you can use to change things like it's style, it's icon image (this depends greatly on the windowing platform), controlling the background color, and whether or not the frame is a topmost frame.

You can change the frame's style by calling the Frame::setFrameStyle() method and pass in one of the 6 standard frame style enumeration values. These can be either fstSizeable, fstNoBorder, fstFixed, fstNoBorderFixed, fstToolbarBorder, or fstToolbarBorderFixed. These are "hints" to the windowing platform to behave in the following ways:

  • fstSizeable - the frame is sizeable and has the standard frame decorations like caption bar, etc. This is the default for a Window instance.

  • fstNoBorder - the frame's border is removed leaving only the frame rectangle. Some platforms may still leave the resize border (Win32).

  • fstFixed - the frame is not resizeable but still has the standard border. This is the default for Dialogs.

  • fstNoBorderFixed - the frame has no border area and is not resizeable. You might use this for a splashscreen.

  • fstToolbarBorder - the frame's border is decorated as a toolbar. Typically this means that the caption bar's height is shorter and the caption font is smaller. The frame is resizeable.

  • fstToolbarBorderFixed - similar to fstToolbarBorder but the frame is not resizeable.

Frequently a frame has a small image or icon associated with it. For example, on Win32 systems this is displayed on the border areas far left side. You can set this image by creating a small image, and then calling the Frame's setIconImage() function. Note that at the moment the image must be 32 by 32 pixels in size.


Window* newWindow = Window();

//assumes a resource exists named icon1
Image* img = Application::getRunningInstance()->getResourceBundle()->getImage( "icon1" );
newWindow->setIconImage( img );
delete img;

newWindow->setBounds( 300, 300, 500, 500 );
newWindow->show();

		

Note that you need to delete the image you created once the call to setIconImage() returns.

A Frame can have either a custom background color (that's set by calling Control::setColor() ) or it can be painted using the default theme background. The latter is the default for any frame. Calling Frame::setUseColorForBackground () toggles this behaviour. A value of true will cause the frame's color as set in Control::setColor() to be used, a value of false will enable the theme background to be used.

A frame can be toggled to be the topmost frame by calling Frame::setFrameTopmost(). A top most frame will be on the top of the Z-order of windows. This is typically called by frames that used as toolbar frames. Window* newWindow = Window(); //makes the frame float on top of all others newWindow->setFrameTopmost( true ); newWindow->setBounds( 300, 300, 500, 500 ); newWindow->show();

3.3.11.4. Window usage in the VFC

A Window is simply created and then one can add controls to it. By default the window instance is not visible when first created. To make the window visible you must call the Window's show() method. For example, let's create a window and add a multi-lined text control to it:

Window* newWindow = Window();

MultilineTextControl* edit = new MultilineTextControl ();
newWindow->add( edit, AlignClient ); //add the text edit control

//position the window at 300,300 
//and give it a width/height of 500,500
newWindow->setBounds( 300, 300, 500, 500 );

//display the window!
newWindow->show();

		

3.3.12. Model/View/Controller

MVC, also known as Model/View/Controller, is a common pattern for implementing UI interaction between the User, the presentation, and the data being viewed or modified. The pattern was pioneered in the mid to late 1970's at Xerox's PARC research center. It orginated in Alan Kay's Learning Research Group, the same group that would take Kay's Smalltalk language and implement it on PARC's Alto, Dorado, and later the commercial Xerox Star computer systems.

Some more modern uses of MVC can be found in Java's Swing library, Struts, Apple's (NeXTStep) Cocoa Objective-C framework, and even a basterdized version in Microsoft's MFC framework.

MVC makes a point of separating the data being used, which is generically referred to as the "Model", the control of the interaction, or the "Controller", and the presentation of the Model, referred to as the "View". Some frameworks merge the View and Controller into one unit, which is also something the VCF does.

The Model is represented by a single class that represents the data being worked with. An example of this might be a NumberModel, which might store a numerical value, or a GraphModel which might store a series of nodes to represent a graph heirarchy. Each Model has a collection of 0 or more Views' associated with a given Model instance.

A View is associate with 1, and only 1 Model instance. It is used to "render" or present a particular presentation of the Model it is associated with. When determing how to present the UI a View may use only a portion of all the information from it's Model. For example, a View of a GraphModel might render the Model as a tree that is vertically aligned (kind of like the view in the left side of the Explorer app for Windows) or it might render the model with free form nodes (similar to Maya's Graph Window that shows the 3D scene's scene graph structure).

A Controller mediates between the model and view. The Controller typically process user input and does something meaningful with it. For example, a Controller for a drawing app might receive notification of mouse clicks, and if the user right clicks the mouse over a shape, the Controller might delete the shape from the model. Generally there is a one Controller for a given Model.

In the VCF the View and Model are separate classes. The View class is an interface and the Model is derived from the VCF::Component class to support visual editing. In the VCF a Model can have views added to removed at runtime. A View maintains a pointer back to it's Model, and a View maintains a pointer to the Control it is ultimately a part of. A Model is able to update all it's views.

There is no formal Controller class (at least not yet). This role is largely performed by the VCF::Control class. In the VCF a Control is both a View and a Controller. Thus any Control instance may be added as a view to a Model. In addition, a Control can have a specific view associated with it. If this is the case, then all drawing is delegated to control's view (which is set/retreived bay calling Control::setView() or Control::getView() ).

3.3.12.1. Models

In the VCF a Model has 3 main duties:

  • To support emptying the model. Each model class should implement the empty() method. When empty() is called the implementation should clear out any data the model contains. If the model was a copllection of items then all the items should be deleted if empty() is called. In addition, calling empty() causes a change event to be fired.

  • Optionally support some form of validation. Validation makes it possible to verify that a potential change is acceptable to the model. It allows "outsiders" to participate in the logic by adding event handler's to the Model's Validate delegate.

  • Finally the model needs to support change notification. Any change to the model should fire an event on the ModelChanged delegate. This lets event handlers get notified when the model is changed. In addition to this, when a change occurs a model should probably call it's updateAllViews() method. Model::updateAllViews() will loop through the list of views that are attached to the model and call each view's updateView() method.

    [Note]Note

    Changing a model's contents from anything other than the main thread is not safe unless you're very careful in how you do it. The VCF, like most GUI toolkits, is not threadsafe, and making changes like this can cause problems. The best things to do, is to cause the thread to post an event back to the main UI thread (see UITookit::postEvent()), and then make the changes at that point.

    To make it easier to fire events correctly when the model changes, there is a utility method on the Model class called changed(). Calling this will pass the event to the model ModelChanged delegate, and call updateAllViews(). For example:

    
    ModelEvent event( this, ... );
    changed( &event );			
    
    			

3.3.12.2. Items

Some models may have a collection of items. To make this easier to use and to display, a custom collection model implementor may choose to use the Item class as a base class for the items in their model.

3.3.12.3. Views

In the VCF each view one and only one model instance that it is attached to at a given point in time. Each view has one control associated with it. This control is the surface that the view "renders" or paints itself onto. Each view has an update() method that, by default (as implemented in AbstractView), generally just calls the view control's repaint() method. The actual painting for the View happens when the view's paintView() is called.

3.3.12.4. Controls

A Control is derived from the View class, therefore it is a View. In addition, a Control has a View, which is accessed or set by the Control's getView() or setView() methods. This means that a control may added as a view of a Model.


Model* model = ... //get a model from somewhere
Control* control = ... //get a control from somewhere
model->addView( control ); //this is perfectly valid
		
		

A Control may also host a separate instance of a View.


Control* control = ... //get a control from somewhere
View* view = new MyCustomView();
control->setView( view );

		

By default a Control's view member is set to NULL. If a Control's view is "set" (by calling Control::setView() and passing in a valid, non NULL view instance), then the Control's paint() method will end up calling the view's paintView() method, and the control will do no further painting. This allows a View to completely override how a control paint's itself.

To recap this all, MVC provides a simple framework to store, manipulate, and display a programs data. The Model stores and manipulates the data, the View renders or paints the display of the Model, and the Control handles user interaction with the model.

3.3.12.5. Document/View Architecture

3.3.13. Undo/Redo and Commands

3.3.14. Help

The VCF tries to integrate with the standard Help system of the windowing system. For the Win32 platform this means using HTMLHelp, on Mac OSX this means using Apple Help. Help is displayed by either opening a new window that displays the help contents, or by displaying little popup windows that show some context specific piece of help text (think of the "What's This?" popups in Win32 dialogs).

Help that is displayed in a new window can show either the index page, the contents page or a specific page, based on the context. Creating these pages is platform specific and we'll cover that later, at least for Win32.

The "What's This?" help popups can display some helpful text to a user to inform them of how to use a certain piece of functionality. For example, you might have a dialog with multiple edit controls for entering different bits of data. If the user clicked for help on one of these control's they might get a popup describing what the control did.

3.3.14.1. Implementing Help for Win32 systems

For Win32 you'll need to implement HTML Help. This will guide you through the bare bones fo doing this for a given project.

[Note]Note
These instructions are, for the most part, not specific to the VCF - any Win32 project that want to add user help using HTML Help can do this.

3.3.14.1.1. Creating the HTML Help project file

First you need to create the HTML Help project file. There are a variety of software packages out there that can help with this, and there's a free tool from Microsoft, that while not very good as software, does the job, and it's free. You can download it here: Microsoft HTML Help Workshop or here Microsoft HTML Help Installer.

The project file is simply a plain text file that contains the instructions to build your compiled HTML help project. It ends in the extension ".hhp".

3.3.14.1.2. Adding HTML content

Add content to your project. The actual pages can reside anywhere you want - you'll just need to specify their location in the project when you add them. As a rule of thumb keep your HTML relatively simple. That said, you can use CSS, DHTML, etc. Just make sure to add any of these resource files (like the .css stylesheets) to the project.

3.3.14.1.3. Adding Index/Search support

Make sure to add an index page through your authoring tool. When this is done, you should end up with a file that ends in the .hhk extension. This is also a plain text file that just contains a series of index entries and uses HTML syntax. How you add index entries is up to you as an author.

3.3.14.1.4. Adding a Table of Contents

Make sure to add an TOC page through your authoring tool. When this is done, you should end up with a file that ends in the .hhc extension, and generally called "TOC.hhc". This is also a plain text file that just contains a series of index entries and uses HTML syntax. How you add index entries is up to you as an author, though some tools will do this automatically. Tools like doxygen and docbook can generate these entries for you.

3.3.14.1.5. Adding Context Sensitive entries

3.3.14.1.6. Adding "What's This" entries
3.3.14.1.7. Compiling the help with the HTML Help Compiler

Once you've got your content ready to go, you can "compile" the project. If you're using a third party tool they probably do this for you in the application. If you're using HTML Help Workshop, then it has a gui for compiling the project. However you can also build the project from the command line using the HTML Help Compiler. If you've installed the HTML Help workshop, you should see it in the same directory as the Workshop program. Look for the "hhc.exe", and then invoke it at a commandline by passing in the name of the project file. Based on the settings in your project file, this will create a .chm file (and possibly a .chi file if you're using binary TOC) in your output directory.

3.3.15. Control Focus and Activation

A control with focus is able to receive keyboard events. Without focus, no keyboard events will be received by the control. Only one control at a time may have the focus.

To get the focus, you simply call Control::setFocus(), which will return the control that previously had the focus (but does not anymore). Control::setFocus() will in turn pass the request to the underlying windowing system to get focus. Once the Control receives focus, it will receive a FocusGained event. The previously focused control will recieve a FocusLost event.

A control with focus also activates it's parent Frame, if the Frame was not previously active.

3.3.16. Accelerator Keys ("Hot Keys")

3.3.17. Peer Classes

   Comments or Suggestions?    License Information