Visual Component Framework - Libraries and Projects

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


Comments

  • Looks good

    Posted by holyhhw on 09/20/2005 04:43am

    Seems it is the way for trying

    Reply
  • Simply rename the *.zip to *.exe

    Posted by Legacy on 01/12/2003 12:00am

    Originally posted by: QCWorld.net

    Don't worry for those found the zip sth wrong...
    that's right, it has a MZ signature, not a zip file,
    it is an exe excutable...
    Simply rename the *.zip to *.exe
    then things would be fine...

    Reply
  • Installer is not a zip file (having 'MZ' signature)

    Posted by Legacy on 11/24/2001 12:00am

    Originally posted by: Liviu Catrina

    ...all whe have to do is to rename to exe.

    Reply
  • Download vcf-Installer from ftp://vcf.sourceforge.net/pub/vcf/VCFInstaller.exe

    Posted by Legacy on 09/07/2001 12:00am

    Originally posted by: CeeBee

    I have loaded the vcf-Installer from ftp://vcf.sourceforge.net/pub/vcf/VCFInstaller.exe

    Reply
  • Also can't unpack

    Posted by Legacy on 08/31/2001 12:00am

    Originally posted by: Friedhelm

    Hi,

    as Jordan remarked earlier, I do have again same problem with unzipping the installer file. There was no comment to Jordans posting, so I ask for help again.

    TIA
    Friedhelm

    Reply
  • Great ideea but somehow impractical to use

    Posted by Legacy on 03/28/2001 12:00am

    Originally posted by: Eugen Paval

    First of all this is something very impressive. The possibilities opened by having RTTI and reflection available to you are endless especially when you are concerned with building generic tools, classes, etc. What I really don't like is the fact that I, as a developer, have to hardcode all this extra information into my classes. I can easily make mistakes or leave things out of sync like declaring type information for one event as something and having the actual event handler being a (totally) different thing. This stands true especially in large projects involving multiple team members that are modifying frantically the code every day. Althoug being a huge undertake you might want to consider generating the necessary type information with a precompiler tool. In this way we will have the best of both worlds: reflection and the same way of writing code (no macros, no extra coding). Being an open source project someone might want to pick up this job.

    Reply
  • Build and run problem!

    Posted by Legacy on 03/22/2001 12:00am

    Originally posted by: doon.

    hi, jim.
    i have installed the VCF successfully. Following the steps i have built the DLLs: FoundationKitDll, FoudationKit, GraphicsKitDll, GraphicsKit, ApplicationKitDll, ApplicationKit, then i built VCFBUILDER.exe and INTEGRATOR.exe. when i run VCFBUILDER.exe, i get a crash. but when i run VCFBUILDER_D.exe, I can only see 3 three windows: the mainapp, the object inspector window and project window. but all these window is blank and i can see nothing in them, only menus at mainapp window. and press the menu i can't insert any VCF objects and open any dialogs. some menu can cause a crash.

    also i have run the scribble example, i found that it can only run for debug version.

    what shall i do to solve these problems?

    doon.

    Reply
  • Can't unpack InstallShield Installer

    Posted by Legacy on 03/21/2001 12:00am

    Originally posted by: jjordan

    I've tried to download it a couple of times. It says "The file is damaged or unknown format". Second ZIP file is ok.

    Thanks.

    Reply
  • I don�t find RefVectorOf.c file

    Posted by Legacy on 03/15/2001 12:00am

    Originally posted by: David Dietta

    I am very interesting on this subject, but I do not make
    ApplicationKitDLL.dll. The next file is missing RefVectorOf.c
    I try looking for in www.apache.org and www.ibm.com without
    succes. May anybody help me ?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Agile methodologies give development and test teams the ability to build software at a faster rate than ever before. Combining DevOps with hybrid cloud architectures give teams not just the principles, but also the technology necessary to achieve their goals. By combining hybrid cloud and DevOps: IT departments maintain control, visibility, and security Dev/test teams remain agile and collaborative Organizational barriers are broken down Innovation and automation can thrive Download this white paper to …

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds