Visual Component Framework – Libraries and Projects

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Environment: VC 6, Windows NT/2K

Editor’s Note

The creator of this toolkit (Jim Crafton) has also sent in a
fantastic Scribble tutorial to
show how easy it is to get up to speed and create applications quickly using the VCF.

Introduction

The Visual Component Framework was inspired by the ease of use of

environments like NeXTStep’s Interface Builder, Java IDE’s like JBuilder, Visual

J++, and Borland’s Delphi and C++ Builder. I wanted a generic C++ class

framework I could use to build app’s quickly and visually (when designing UIs),

as well as having the core of the framework be as cross platform as possible.

This article will discuss some of the issues I ran into, and how I attempted to

solve them. The Visual Component Framework is an Open Source project, so feel

free to grab and use it if you think it might be useful. If you’re really adventuresome

you can volunteer to help develop it, making it even better, especially in tying

it into the VC++ environment as an add-in. For more information on either

the project, helping out, or just browsing the Doc++ generated documentation

please go to the VCF project on Source Forge here,

or the project website here. The code

is available from CVS (follow the how-to here for setting up CVS on windows), or

the a zip file here (it’s around 1.8 MB –

this includes the XML libs for Xerces – an XML parser. Check the site for the latest

version). 

The Visual Component Framework (VCF) is divided into three DLL’s, the

FoundationKit, the GraphicsKit, and the ApplicationKit. This article discusses

the FoundationKit, the heart of the VCF. The FoundationKit provides the basic

core classes and the advanced RTTI capabilities of the framework. Early on I

knew I needed some sort of RTTI, or reflection, similar to what is provided in

Java or ObjectPascal. This was a requirement because visual design environments

need to a way to expose a component’s properties and events, and to provide a

way to edit them in a consistent, customizable and extendable manner. With this

in mind the framework allows the developer to query an object for its Class,

which in turn provides access to the class’s name, superclass, Properties,

Events and Methods. To achieve this the framework makes heavy use of templates

and STL. Class is an abstract base class that template classes

device from. Classes provide the following information:

  • the name of the Class – this is stored in a member variable rather than

    relying on typeid(...).name() to retrieve the class name. Not all compiler’s

    support the typeid(...).name() function.

  • the Class ID – this represents a UUID (Universally Unique IDentifier) for the class. This will prove

    useful when distributed features creep in.

  • the ability to discover all the properties of a Class at runtime. A

    property is defined as some class attribute that is provided access to via

    getter and setter methods. Properties can be primitive types (int, long

    double, etc), Object derived types, or enums. Properties can also be

    collections of other objects.

  • retrieving the super class of the class.
  • the ability to create a new instance of the class the Class object

    represents. This of course assumes a default constructor is available.

In order for the RTTI to work in the Framework developers of derived classes

must do three things for their classes to participate in the Framework. Failure

to implement these steps will mean their classes will not have correct RTTI. A

series of macros (defined in ClassInfo.h) have been written to make this easier.

The first step is (obviously) making sure that your class is derived from a

Framework object. For example:


class Foo : public VCF::Object {  //this is OK

...

};



//this is bad - there is no way to hook the RTTI up without at

//least deriving from VCF::Object

class Foo {

};

Next you should define a class id (as a string) for your class. On Windows I use guidgen.exe to create

UUIDs. The define should look

something like this:


#define FOO_CLASSID  "1E8CBE21-2915-11d4-8E88-00207811CFAB"

The next step is to add to macros to your class definition (.h/.hpp file). These

are required for the basic RTTI info (class-super class relation ships) and to

make sure you’ll inherit any properties of your super class. For example:


class Foo : public VCF::Object {

public:

 BEGIN_CLASSINFO(Foo, "Foo", "VCF::Object", FOO_CLASSID)

 END_CLASSINFO(Foo)

...

};

The macro takes the class type-id, the string to use as the class name, the

string that represents the classes supper class, and a string that represents

the class id (where the class id is a string representation of a UUID). What the macros end up creating is a public nested class used to register your class that you’re

writing. The above macros generate the following inline code for the developer

of the Foo class.


class Foo : public VCF::Object {

 class FooInfo : public ClassInfo<Foo> {

 public:

  FooInfo( Foo* source ):

   ClassInfo<Foo>( source,

      "Foo",

      "VCF::Object",

      "1E8CBE21-2915-11d4-8E88-00207811CFAB" ){

   if ( true == isClassRegistered()  ){



   }

  }



  virtual ~FooInfo(){}



 };//end of FooInfo

 ...

};

The isClassRegistered() method checks the ClassRegistry

to see if the class is already registered, if it is not then a new entry in the ClassRegistry

is made. Classes are stored in a singleton ClassRegistry object,

which contains a single Class instance for each registered class

type, thus multiple object instance’s share a Class instance. To do

this the framework has an abstract class defined (Class), and two template

classes TypedClass and TypedAbstractClass.  The

template parameter specified in the  TypedClass and TypedAbstractClass

are used to safely allow class comparisons and to allow the creation of an

object at run time (this feature is only supported by TypedClass).

The two template classes are necessary because it is possible to have abstract

classes in the framework that by definition cannot be instantiated, but need to

in the class hierarchy, since all Class instances have a getSuperClass()

method, allowing the programmer to walk the class hierarchy at runtime.

The ClassRegistry

keeps all the classes in a map, and each time a new Class

instance is registered, the super class is found and all of it’s properties are

copied over to the newly registered instance, making sure that derived classes

"inherit" the properties and events of their super class.

To

add more detailed RTTI you can add properties, events, and methods. An example

of this can be found in the Component class declaration:




class APPKIT_API Component : public Object, public Persistable{

public:

 BEGIN_ABSTRACT_CLASSINFO(Component,

                          "VCF::Component",

                          "VCF::Object",

                          COMPONENT_CLASSID)

 PROPERTY(double, "

          left",

          Component::getLeft,

          Component::setLeft,

          PROP_DOUBLE );

 PROPERTY(double, "top",

          Component::getTop,

          Component::setTop,

          PROP_DOUBLE );

 PROPERTY(String, "name",

          Component::getName,

          Component::setName,

          PROP_STRING );

 EVENT("VCF::ComponentEvent",

       "onComponentCreated",

       "VCF::ComponentListener",

       "VCF::ComponentHandler" );

 EVENT("VCF::ComponentEvent",

       "onComponentDeleted",

       "VCF::ComponentListener",

       "VCF::ComponentHandler" );

 END_CLASSINFO(Component)

 ...

This demonstrates exposing three properties and two events. Properties allow

you to dynamically discover the attributes of an object at runtime. A Property

holds a method pointer to an accessor method (or "get" method), and

optionally a method pointer to a mutator or "set" method. In addition

a Property has display name, and a display description that can be

read and modified. Like the Class class, the Property

is abstract, with mostly virtual pure methods, to allow the framework to have an

arbitrary collection of properties without knowing the exact type . The real

work is done by template classes that derive from Property and

properly implement the methods.  To allow the generic getting and

setting of a wide variety of types, another class is used in conjunction with Property

called VariantData. This class wraps most of the C++ standard primitive types,

String‘s, Enum‘s (more on those later), and Object derived classes. The core of

the class is a union of types, the one exception being a reference to a String

(which is just a typedef around std::basic_string<char>). It

also has a member variable that describes the type of data the instance holds.


union{

 int IntVal;

 long LongVal;

 short ShortVal;

 unsigned long ULongVal;

 float FloatVal;

 char CharVal;

 double DblVal;

 bool BoolVal;

 Object* ObjVal;

 Enum* EnumVal;

};

The rest of the class provides conversion and assignment operators allowing you to write code like this:


VariantData v;

int i = 12;

double d = 123.456;

String s = "Foo";



v = s;

// v now holds a reference to the String s, 

// and it's data type is automatically set to PROP_STRING



v = i;

// v now stores the int value 12, 

// and data type is PROP_INT



v = d; 

// v now stores the double value 123.456, 

// and the data type is PROP_DOUBLE

The assignment functions allow for the sort of magic we see above. It also

makes sure the data type is set correctly. One of the conversion/assignment

functions looks like this:


operator float (){

 return FloatVal;

}



operator=( const float& newValue ){

 FloatVal = newValue;

 type = PROP_FLOAT;

}

Why is this useful ? Because we can have a collection of properties of a

given Object at runtime, we will not necessarily know what the types are. The VariantData

class allows us to ignore this, allowing the compiler to sort out what needs to

happen for us. In other words I might have a collection of properties, one of

which is an int, another some Object*, and a third a bool. The VariantData

lets me write the same style of code for any of the types, and the compiler will

resolve the type for me. This happens because our "get" and

"set" methods have type signatures with them and when the VariantData

instance is used, the compiler invokes the correct conversion operator based on

the method signature. 

Ah, but how do we get these method signatures defined ? Remember that the Property

class is an abstract one. The real work is done through several other template

classes that derive from Property, but actually implement the

methods of Property. So lets take a look at the TypedProperty

class. This class uses it’s template type to specify the type of data that it is

to represent. It then declares typedefs to get and set member functions for and

Object. 


template <class PROPERTY> class TypedProperty : public Property {

public:

 typedef PROPERTY (Object::*GetFunction)(void);

 typedef void (Object::*SetFunction)(const PROPERTY& );

 ...

protected:

 GetFunction m_getFunction;

 SetFunction m_setFunction;

};

Now we have member function pointers in our property class that are type

safe, in other words, if we specified a new Property for an int type (TypedProperty<int>),

then the get and set functions would read as follows: 

  • Get: typedef int (Object::*GetFunction)(void);
  • Set: typedef void (Object::*SetFunction( const int& );

So when our Property::set(Object* source, VariantData* value )

method is called, the magic in the VariantData  mentioned

above occurs, in other words, the set() method in TypedProperty<int>

class binds the source to the set method method pointer (m_setFunction),

and passes in the de-referenced value pointer, which in turn causes

the VariantData‘s int() conversion operator to be

called by the compiler, and now we have safely passed in an int value to the

function, without ever needing to worry about it. This process works the same

for any of the types mentioned above, though there are specific template classes

that derive from Property to support Object and Enum

pointers. 

I’ve mentioned a the Enum class a couple of times now, and I

imagine you’re all just dying with anticipation to discover just who and what

these little fella’s are. The Enum class is used, not surprisingly,

to wrap C++ enum types and provide a type safe way of using them without

worrying about the specific type at runtime. This allows for iterating the enum

values (and wrapping back to the beginning), and retrieving the first and last

enum values. It also allows for having string representations of the various

enum values for display purposes. Enum classes can be set either by

the actual enum type, as an int, or from a string.

Well that about wraps it up for now. I will try and write the next two

installments as soon as I can (assuming anyone is actually interested in this

kind of coding insanity !)

Downloads

Warning: This is a serious development framework and as such it might take a

while to download the entire set of source code

Download source for the VCF Framework (latest zip from CVS tree)- 3.2 Meg
Download source for the VCF Framework (InstallShield Installer) – 5.8 Meg

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read