Writing Scribble with the Visual Component Framework (VCF)

Environment: Visual C++, Visual Component Framework (free)

Editor's Note

The VCF Toolkit is also featured on CodeGuru. Both that article and this one provide links to downloading the complete source code for this toolkit.

Introduction

The last article I wrote introduced some of the basic concepts of the Visual Component Framework. Much of it dealt with some of the techniques I used to get things like the advanced RTTI features to work, as well as other features of the core of the VCF, the Foundation Kit. In this article we'll see that all of the stuff is actually there for a reason ! We will walk through the steps of putting together a simple scribble application, which will cover things like drawing, events, customizing VCF component classes, and menus. As I know you are all breathless with anticipation, and in fact due to the millions of letters of fan mail, and the hundreds of thousands of adoring fans out there, let's dive right in !!!

Where did WinMain() go ?

Like any application, we need a place to begin, so what better place to start than the infamous main() function. Whoa there pardner - this is Win32 we're talkin' 'bout here ! There ain't no stinkin main ! Bill says were all supposed to use WinMain() ! Wrongo ! Since I wanted the VCF to have a cross platform architecture, and in every other OS out there programs start in a main(), I had to figure out how to make this work. Thankfully, after scouring through other people's code (is there any other way!), I found an example of doing just this, yet still making windows think the app is a Win32 app, not a console app. By altering the linker settings we can replace the default entry point to point somewhere else, in this case our main() function. If you look at the linker settings of any program you run in VC++ you'll see the subsystem set to "windows", which is important. If it were set to "console" a console window comes up first instead of a normal window. You can still create windows like normal, but this is hardly the kind of behavior for a well behaved windows application. Since the subsystem is windows, the default entry point of the application is also different, which is where WinMain is usually executed. This is where our little trick comes in. We can set the entry point symbol to read "mainCRTStartup", thus bypassing the default behavior, and running our main() function as the entry point.

OK, OK, enough of the technical blather, lets actually run something ! The first step in any VCF program is to create an instance of an VCF::Application derived object on the stack. If you do not need to override any functionality then you can simply use VCF::Application, otherwise use your VCF::Application derived class. Once this is done, you simply call the VCF::Application's static method VCF::Application::appMain(), passing in the argc and argv parameters that your main() gets, and voila ! you're done ! VCF will take care of the rest for your in terms of starting your app and calling the correct run methods. Lets check out this in code:

int main(int argc, char *argv[])
{
 VCF::Application app;
 VCF::Application::appMain( argc, argv );
 
 return 0;
}

This illustrates simply using the default VCF::Application object. The next sample uses a derived class to create the application object.

class ScribbleApplication : public Application {
public:
 ScribbleApplication(){};
 virtual ~ScribbleApplication(){};
};

int main(int argc, char *argv[])
{
 ScribbleApplication app;
 VCF::Application::appMain( argc, argv );
 
 return 0;
}

Now we have supplied our own ScribbleApplication class, as opposed to relying on the default Application class. So what is actually happening under the hood ? Basically the VCF runtime system is taking care of a bunch of start up stuff for you. The constructor of the Application class initializes the FoundationKit, the GraphicsKit, and finally the ApplicationKit, which in turn initializes things like UIToolkit (which is responsible for doling out the windowing specific Peer classes, like window frames, text widgets, etc), and registers all the core VCF component classes with the FoundationKit's ClassRegistry. The next thing it does (and this is fairly important) is to set itself as the current Application object for this process, which can be retrieved by the Application's static method Application::getRunningInstance(), which will return a pointer to the app object. It is important to note that you should NEVER create more than one Application or Application derived object for you process. Finally it creates a special peer, known as an ApplicationPeer object that handles some of the OS specific tasks related to running an application. 

Once the Application object is created we can move to the appMain() method, which simply verifies that we in fact do have a valid running app instance, calls an initialization function on the application's peer object, which in Win32 does stuff like calling InitCommonControlsEx() and OleInit(), sets the command line for the Application (for later use if necessary), and then calls the first of the Application's commonly overridden methods, initRunningApplication().

Init what ?

The initRunningApplication() call allows the developer to initialize whatever application specific startup code he or she wants here. For those familiar with MFC, it is familiar to CWinApp's InitInstance() method. It is typically where the main window of the application is created. The function returns true or false, true indicating it is OK to continue on, and false to signal an error. This return code is used in the appMain() method, to determine whether or not to continue, with a return value of true indicating it is safe to continue onwards. A return of false at this point will cause the Application's terminateRunningApplication() method to be called, and then the process will clean up and exit. The terminateRunningApplication() is another method you can override to ensure any of your application specific data gets cleaned up properly. Once the application has been initialized the call to the Application's run() method is made, which in turn invokes the peer's run() method where any OS specific code is then executed. In our case (under Win32), we find a typical message loop (something like the following):

MSG msg;
HACCEL hAccelTable = NULL;

while ( GetMessage( &msg, NULL, 0, 0 ) ) {		
 if (!TranslateAccelerator( msg.hwnd, 
                            hAccelTable, 
                            &msg ) ) {
  TranslateMessage( &msg );
  DispatchMessage( &msg );
 }		
}

When the loop ends (the main window is closed) the Application's terminateRunningApplication() is called, allowing you to gracefully exit and cleanup you app specific data. The peer's terminateApp() method is called, which does OS specific shutdown chores. Lastly, the UIToolkit is told to cleanup, and then we quit the process. As a developer with VCF you never have to touch this stuff, but it helps to at least be aware of what's going on. The main things to note are the virtual methods for initialization and termination of your application via overriding the initRunningApplication() and terminateRunningApplication().  

Creating our main window

So now that we've seen how to create our application object and start the ball rolling, how do we create actual windows ? And once this is answered, where is the best place to create them ? In general the best place to create the applications window(s) are inside of the initRunningApplication() method that you override in your Application derived class. Actually creating the windows is cake (MFC developers should hold onto something solid at this point). All you have to do is create a new instance of VCF::Window (or a class derived from it) and presto you have a window you can manipulate. You must also set the Application's main window, so a call to setMainWindow() will also have to made at this point as well. 

VCF does not use a two step creation process like some other frameworks for the creation of Controls (of which a Window is derived from). I believe this greatly simplifies coding, though there are some tweaks that had to happen in the base classes (but this is hidden from the developer, and irrelevant for deriving classes). When creating any kind of Component instance in the VCF, always, always do so on the heap using the new operator. So for our main window we code the following:

Window* mainWindow = new Window();

NOT

Window mainWindow;

The main window belongs to the Application (which is why we call setMainWindow()) , which will destroy it when the Application is closed. In turn all components and controls on the Window will in turn be destroyed when the Window is destroyed. This greatly simplifies development since you don't have to worry about who destroys what or what the special cases are etc. We talk more of this in a bit when we start to cover adding controls. 

Once you have created a Window, you can easily set properties on it, like the bounds (via the setBounds() method ), the caption (via the setCaption() method), and many others. So let's set our caption to read "VCF Scribble App", and the left, and right to be at 200, 200, respectively, and the width and height to be 500, 500, respectively.

mainWindow->setCaption( "VCF Scribble App" );
mainWindow->setBounds( &Rect(200,200,700,700) );

setCaption() takes a VCF::String as an argument, where a VCF::String is nothing more than a typedef around std::basic_string<VCFChar>. Eventually I would like to write an actual class that has the same STL interface as std::basic_string, but can also handle Unicode, so that all the internals used only Unicode and OS calls were in Unicode as well were possible, but for now this works pretty well (those interested in developing something like this, your help would be greatly appreciated !!). Setting the bounds requires a pointer to a Rect class, which we provide via a temporary stack object. The Rect has members for left, top, right, and bottom, so for a rectangle at 200, 200, and a width and height of 500, 500, we pass in 200, 200, 700, 700, these being the Rect's constructor arguments for the left, top, right, and bottom, respectively. Rect classes store their data internally as doubles for better accuracy and not having to convert back and forth so much, allowing for fewer rounding errors (hopefully). In fact if you look at the VCF Application Kit classes, and Graphics Kit classes, all coordinates are handled as doubles. I have found that many windowing systems (with the exception of Win32) do this and decided to include this in the VCF. 

Customizing the main window

Well, now we can run our app and display a window, but that in and of itself isn't horribly useful, so let's move on and customize things a bit. We'll derive a new class from VCF::Window, and add some more controls, as well as other features as the article progresses. In this first pass, we are going to add two panels to our window when it is created, one which will be aligned to the right, and another which is aligned to the remaining client area. So lets see what the code looks like and then we'll cover how it works.
class ScribbleWindow : public Window {
public:	
 ScribbleWindow(){
  panel1 = new Panel();
  panel1->setBounds( &Rect(0,0,100,200) );
  this->add( panel1, ALIGN_RIGHT );

  panel2 = new Panel();
  this->add( panel2, ALIGN_CLIENT );
 };

 virtual ~ScribbleWindow() {};

 VCF::Panel* panel1;
 VCF::Panel* panel2;
};

As you can see, we have two instances of the VCF::Panel class, panel1 and panel2, and we create them using the new operator. A VCF::Panel is a simple control that implement's the VCF::Container interface class, thus allowing it hold other child controls. A panel also has a default border that draws a 3D edges on the outsides of the control.  In our case we are going add panel1 to the window and align it to the right, while we will add panel2 and align it to the remaining client area. Once we have created panel1 we set it's bounds, with it's width becoming 100. This is done because when aligned to the right or the left, the control will still keep it's original width, but only change it height. The next step is to add the control, and this is done via the window's add() method, where we pass in the control and an enum that specifies the alignment we would like (our choices are ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP, ALIGN_BOTTOM, ALIGN_CLIENT, and ALIGN_NONE for fixed coordinates within the parent control). Like panel1, we create panel2 the same way, and then simply use the add method again, this time passing in panel2, and an alignment of ALIGN_CLIENT. Note we don't have to bother setting the bounds for panel2, since it is being added with an alignment of ALIGN_CLIENT, the parent container (in this case the window) will handle re-aligning and repositioning the control. At this point we now have a window, with two child controls, both which are aligned and will be automatically resized for us as we resize the parent window. In addition, that pesky old problem of flickering windows is gone!! Resize to your hearts content and you'll see nary a flicker ! This is because VCF will automatically double buffer for you during repaint messages. However, you can turn this off by simply setting calling the control's setDoubleBuffer() method and passing in false (passing in true will turn double buffering on again).

Adding controls

Adding controls to other controls is a very useful and handy thing thing to have, so let's go into a bit more detail as to how this actually works. A control that can hold other controls is referred to as a container in the VCF. Any control can be a container so long as it properly implements the methods of the VCF::Container interface class. To aid in this there is a class that implements these methods called VCF::AbstractContainer that you can inherit from to add this functionality to your classes. If we were to look at the declaration of the VCF::Panel class we can see it does just this. The main methods of the container you'll use is the add() methods and the remove() method. The add() methods come in two flavors, the first being to just add the control, and the container will simply use whatever alignment is specified in the control's alignment property, and the second takes a control and an alignment type to specify how to add the control to the container. When you add a control you are also specifying the newly added control's parent, which happens automatically in the add() method. You should never set the control's parent directly yourself, as this can lead to inconsistent behavior. Adding a control to a parent control also means the parent control will delete it's child when the parent is deleted, so you don't have to worry about freeing the memory associated with the control. 

When adding a control you can specify the alignment type, which determines how the container will resize the child control. Aligning to the left (ALIGN_LEFT) will resize the control so that it's left side will always be flush to either the extreme left of the client edge of the container control, or the right edge of the previously added control with left alignment. The control will maintain it's width, but the top, left and bottom will be determined by the container. Similar behavior happens for right, top, or bottom alignment (ALIGN_RIGHT, ALIGN_TOP, ALIGN_BOTTOM respectively). If the alignment is ALIGN_CLIENT, then the control is resized to any remaining client space after the other aligned controls have been positioned. If the control does not want any alignment rules enforced then it's alignment should be set to ALIGN_NONE, which is the default alignment for all of the basic controls. ALIGN_NONE means the coordinates specified are always respected and are not change during resizing of the container.

Custom controls: the ScribbleView control 

So this is all well and good, but lets make this a even more interesting. We now have two panels, one which will house some buttons, and another that will house a custom control we'll create that will serve as our scribble drawing surface. Whenever you want to create a custom control in the VCF you have several options on how to proceed. Your first issue is whether or not you want to start from scratch, or just enhance an already existing control, such as added beveled text effects to the Label control. If you want to start from scratch, often the best place to derive from is the VCF::CustomControl class. The next issue you'll face is whether or not you want the control to be "light weight" or "heavy weight". A heavy weight control is one that uses native windowing system resources for displaying and routing events, in other words, under Win32, a control that has a HWND and an HDC associated with it. A good example of this is a Window, or a Panel control, both of which are considered heavy weight controls. A light weight control, on the other hand, shares these resources with it's heavy weight parent (somewhere along the line), thus reducing the number of native resources in use. An example of this would be the VCF::Label control. This is extremely useful to have, especially when you are building controls that are components of another, more complex control (like a header, or toolbar buttons). Those of you who have used Java will recognize this in the Native heavy weight peer classes, while those from a Delphi background will see a similarity with the TWndControl and TGraphicControl classes. For those of you from an MFC background there is no comparable class within MFC (sadly), everything ends up being a window, unless you go to the trouble of writing something similar to light weight controls yourself.

For our purposes we are going to build a lightweight custom control, so we are going to derive it from VCF::CustomControl. In order to actually make the control either heavy weight or light weight, we pass a boolean flag into the constructor of the VCF::CustomControl, with true indicating that the control is a heavyweight control, and false for light weight controls (the default value is true). Lets look at some code:

class ScribbleView: public VCF::CustomControl {
public:
 ScribbleView():
 VCF::CustomControl( false )
 { 
 }

 virtual ~ScribbleView(){}
};

The next thing we are going to want to do is to customize our painting of the control. To do this, we just override the paint() method, call the super class if necessary, and then do our own stuff after that. When implementing a paint() method, you are passed in a pointer to a GraphicsContext object, which is how all drawing is handled. The GraphicsContext contains all the drawing state info, as well as a variety of methods for drawing 2D primitives. Before we go much further lets take a look at the code and then I'll explain more.

class ScribbleView: public VCF::CustomControl {
public:
 ScribbleView():
 VCF::CustomControl( false )
 {
 }

 virtual ~ScribbleView(){}

 virtual void paint( GraphicsContext* ctx ) {
  CustomControl::paint(ctx); 

  Color color(0.85f,0.85f,0.85f);
  ctx->setColor( &color );
  Rect r( 5, 5, getWidth() - 5, getHeight()-5 );
  ctx->rectangle( &r );
  ctx->fillPath();
 }

};

The first line of the paint() method calls the super classes paint() method first, then a Color object is created, and set to a light gray color. The GraphicsContext's color is then set with a call to the setColor() method. Doing this causes the GraphicsContext to use the specified color for all paths that are stroked or filled after this. After this we draw a rectangle by calling the GraphicsContext's rectangle() method, passing a pointer to a Rect object, and then to actually render the rectangle, we call fillPath(), which fill's any path operations with the GraphicsContext's current color.

The GraphicsContext works by specifying a series of drawing operations a la PostScript, like moveTo, lineTo, rectangle, ellipse, etc, and then you can either fill or stroke the path(s) generated by the previous functions. After filling or stroking the path(s), the path operations are deleted, and you continue on. In addition to these lower level 2D primitive calls, there are a series of high level calls that can be used to draw more complex geometry, as well as take advantage of the GraphicsContext's transform matrix for things like scaling, translation, rotation, and shearing (perhaps this will be covered in another article).

Well now that wasn't too bad, so lets actually add our control to our ScribbleWindow so we can see it. 

class ScribbleWindow : public Window {
public:	
 ScribbleWindow(){
  panel1 = new Panel();
  panel1->setBounds( &Rect(0,0,100,200) );
  this->add( panel1, ALIGN_RIGHT );

  panel2 = new Panel();
  this->add( panel2, ALIGN_CLIENT );

  scribView = new ScribbleView();
  panel2 ->add( scribView, ALIGN_CLIENT );
 }

 virtual ~ScribbleWindow() {};

 VCF::Panel* panel1;
 VCF::Panel* panel2;
 ScribbleView* scribView;
};

Mouse Events & ScribbleView

So now our control displays, but beyond that it doesn't do much. So, since we want to be able draw in it, lets hook up our event handling for mouse events. A control has three methods which can be overridden for this purpose: mouseDown(), mouseMove(), and mouseUp(), each of which get passed a pointer to a MouseEvent object. The MouseEvent object has information like what mouse button is pressed, as well as being able to determine if the Alt, Control, or Shift key is currently pressed. By overriding these methods on our control we can customize the behavior of the control, namely to draw lines whenever the left mouse button is held down and the mouse is dragged. So lets see this in action !

class ScribbleView: public VCF::CustomControl {
public:
 //other methods/constructors/destructors omitted...
 virtual void mouseDown( MouseEvent* event ) {
  CustomControl::mouseDown( event );
  dragPt = *event->getPoint();
  GraphicsContext* ctx = this->getContext();
  ctx->moveTo( dragPt.m_x, dragPt.m_y );
 }

 virtual void mouseMove( MouseEvent* event ) {
 CustomControl::mouseMove( event );
  if ( event->hasLeftButton() ) {
   Point* pt = event->getPoint();
   GraphicsContext* ctx = this->getContext();
   ctx->moveTo( dragPt.m_x, dragPt.m_y );
   dragPt = *pt;
   ctx->lineTo( dragPt.m_x, dragPt.m_y );
   ctx->setColor( Color::getColor( "red" ) );
   ctx->strokePath();	
  }
 }

 private:
 Point dragPt;
};

The two methods we're going to override are mouseDown() and mouseMove(). In both cases we make sure to call the super class's methods first, and then do our own stuff. In the mouseDown() method we get a pointer to a MouseEvent object which has  the current mouse coordinates, retrieved by a call to the event's getPoint() method, and store this in a member variable of the class. In the mouseMove() method, we verify that the left mouse button is being held down, through a quick call to the  MouseEvent's hasLeftButton() method which will return true if the left button is down, gets the current point, and then draws a red line on the control's GraphicsContext

Adding event handlers

Now that we've got our basic ScribbleView class done, we need to hook up some event handlers to make our app a bit more useful. What we're going to do is to add two command buttons to the right aligned panel, one that will be disabled until something is drawn on the ScribbleView, in which case it will then be enabled and if the user clicks on it, it will clear the ScribbleView. The second will exit the app if clicked. To make things look pretty, we'll also add a Label to the top the right aligned panel, and set it's caption to read "Commands". So, without further ado, let see some code !

class ScribbleWindow : public Window {
public:	
 ScribbleWindow(){
  panel1 = new Panel();
  panel1->setBounds( &Rect(0,0,100,200) );
  this->add( panel1, ALIGN_RIGHT );

  panel2 = new Panel();
  this->add( panel2, ALIGN_CLIENT );

  scribView = new ScribbleView();
  panel2 ->add( scribView, ALIGN_CLIENT );

  label1 = new Label();
  label1->setCaption( "Commands" );
  label1->setBounds( &Rect(10, 10, 80, 35) );
  panel1->add( label1, ALIGN_TOP );

  btn1->setBounds( &Rect(10, 50, 80, 75) );
  panel1->add( btn1 );
  btn1->setCaption( "Clear" );
  btn1->setEnabled( false );
  btn2 = new CommandButton();
  btn2->setBounds( &Rect(10, 90, 80, 115) );
  panel1->add( btn2 );
  btn2->setCaption( "Exit" );
 };

 virtual ~ScribbleWindow() {};

 VCF::Panel* panel1;
 VCF::Panel* panel2;
 ScribbleView* scribView;
};

As you can see this part is fairly similar to what we've seen previously in other parts of the code we've been writing. The label, and two buttons (btn1 and btn2) are all created on the heap, and then added to their appropriate parent control. Now we'll add the event handling by first setting up our functions in our ScribbleWindow and implementing the functionality we would like to have happen when the functions are called. 

class ScribbleWindow : public Window {
public:	
 ScribbleWindow(){
  panel1 = new Panel();
  panel1->setBounds( &Rect(0,0,100,200) );
  this->add( panel1, ALIGN_RIGHT );

  panel2 = new Panel();
  this->add( panel2, ALIGN_CLIENT );

  scribView = new ScribbleView();
  panel2 ->add( scribView, ALIGN_CLIENT );

  label1 = new Label();
  label1->setCaption( "Commands" );
  label1->setBounds( &Rect(10, 10, 80, 35) );
  panel1->add( label1, ALIGN_TOP );
 
  btn1->setBounds( &Rect(10, 50, 80, 75) );
  panel1->add( btn1 );
  btn1->setCaption( "Clear" );
  btn1->setEnabled( false );
  btn2 = new CommandButton();
  btn2->setBounds( &Rect(10, 90, 80, 115) );
  panel1->add( btn2 );
  btn2->setCaption( "Exit" );
 };

 virtual ~ScribbleWindow() {};

 void onScribbleViewMouseUp( MouseEvent* e ) {
  btn1->setEnabled( true );
 }


 void onBtn2Clicked( ButtonEvent* e ) {
  this->close();
 }

 void onBtn1Clicked( ButtonEvent* e ) {
  btn1->setEnabled( false );
  scribView->repaint();
 }

 VCF::Panel* panel1;
 VCF::Panel* panel2;
 ScribbleView* scribView;
};

We'll look at the functions one at a time. The first function, onScribbleViewMouseUp(), will be called when the mouse button is released on the ScribbleView control. When this happens, the enabled state of btn1 is set to true, meaning the button is enabled (not grayed out) and can accept user input. The next function, onBtn2Clicked(), is called whenever btn2 is clicked on by the user (or the click() method is called programmatically), and causes the window to close, which, since it is set as the Application's main window, will also cause the application to quit cleanly as well. The final function, onBtn1Clicked(), is called whenever btn1 is clicked, and it sets btn1's enabled state to false, which grays the button out (or disables it), and calls the ScribbleView's repaint() method (a method inherited from VCF::Control), which in turn causes the control to clear itself, erasing the contents in the process. 

Now that we've seen what our call back functions do, how do we actually hook them up to the events that the objects we're interested in fire off ? In the VCF this is done through a similar system to Java's Listener class, which itself is an implementation of the Observer pattern. A Listener, in the VCF, is a C++ interface class that defines one or more methods that get fired when an event happens. So, for example, the ButtonListener interface has a method called onButtonClicked() which will get called when the object we are listening to (in this case a button) fires off an appropriate event (in the case of the button, this happens when the clicked() method is invoked. To listen to a particular object we call the appropriate add listener method on the object we would like to listen to. If we wanted to listen to the button click events on a VCF::CommandButton object, we would call the button's addButtonListener() method, and pass in an object that implemented the ButtonListener C++ interface. To facilitate this, there are special classes, that are usually defined in the same header as the listener interface, called handlers, as in the ButtonHandler class that implements the ButtonListener interface. A handler class has a series of variables that are member function pointers, one for each method that is implemented for the listener interface. In addition, the handler also has a pointer to an object that is the handler's source, and where the member function pointers point to. This sounds more complicated than it actually is so lets see some code to hopefully try and make things a bit clearer.

class ScribbleWindow : public Window {
public:	
 ScribbleWindow(){
 //...initialization code omitted 

  ButtonHandler* bh = new ButtonHandler( this );
  bh->m_buttonClicked = (OnButtonEvent)ScribbleWindow::onBtn1Clicked;
  this->addEventHandler( "ButtonHandler", bh );
  btn1->addButtonListener( bh );

  bh = new ButtonHandler( this );
  bh->m_buttonClicked = (OnButtonEvent)ScribbleWindow::onBtn2Clicked;
  this->addEventHandler( "ButtonHandler2", bh );
  btn2->addButtonListener( bh );

  MouseHandler* mh = new MouseHandler( this );
  this->addEventHandler( "MouseHandler", mh );
  mh->m_mouseUp = (OnMouseEvent)ScribbleWindow::onScribbleViewMouseUp;
  view->addMouseListener( mh );
 };

 virtual ~ScribbleWindow() {};

 void onScribbleViewMouseUp( MouseEvent* e ) {
  btn1->setEnabled( true );
 }


 void onBtn2Clicked( ButtonEvent* e ) {
  this->close();
 }

 void onBtn1Clicked( ButtonEvent* e ) {
  btn1->setEnabled( false );
  scribView->repaint();
 }

 VCF::Panel* panel1;
 VCF::Panel* panel2;
 ScribbleView* scribView;
};

We are going to set up the event handlers for the buttons first, first for btn1, and then for btn2. We start by creating a new ButtonHandler object on the heap, using the new operator, passing in the source object to the constructor, in this case the ScribbleWindow instance. The ButtonHandler's m_buttonClicked member variable is set to point to the ScribbleWindow's onBtn1Clicked() method, and we then add the handler to the ScribbleWindow's list of event handlers, which will clean up all the event handler's in it's list for us when it is destroyed. The final step is to actually add the ButtonHandler object as a listener to the button (btn1), which is accomplished by calling btn1's addButtonListener() method and passing in the button handler object. At this point we are hooked up and ready to receive events from btn1! The same type of thing is done for the other two event handlers. You may be asking, "But why not just implement the ButtonHandler interface  on the ScribbleWindow class ?" Well this works OK if you know ahead of time you will only ever be listening to one object, but what about when you want to listen to multiple object's that use the same listener interface ? You end up with a nasty series of if statements testing the type of object, doing one thing for this type of object, another thing for another type of object, and so on. This makes it a little cleaner (in my opinion), and hopefully scales better, as well as being more direct, so you can "see" that function such and such will get called by object such and such, for this event.

Fun with Scribble Menus

The last thing we'll discuss in this article is adding menu items to the ScribbleWindow. There are two main types of menus: Menus that exist on the Window's frame (usually on the top of the window), and popup menus that usually are context dependent. The first type of menu is known as a VCF::MenuBar class, while the other kind is the VCF::PopupMenu. Our example will contain both types, a main menu on our ScribbleWindow, and a popup menu associated with our ScribbleView control. Both VCF::MenuBar and VCF::PopupMenu have a root menu item that you use to attach other menu items to. The simplest way to attach menu items is to create a new instance of a DefaultMenuItem, passing in the string caption of the menu item, the parent of the menu item, and the menu bar or popup menu for the item, into the constructor of the new menu item. This will automatically add the newly created menu item to the parent item passed in. Adding event handlers to the menu items is accomplished just like before, except we use the MenuItemHandler. Lets look at our last bit of code for the article to see this in action.

 

class ScribbleWindow : public Window {
public:	
 ScribbleWindow(){
  //...initialization code

  //previous event handler code...
  this->setMenuBar( new MenuBar() );

  MenuBar* menuBar = this->getMenuBar();
  MenuItem* item = menuBar->getRootMenuItem();

  DefaultMenuItem* file = new DefaultMenuItem( "&File", 
                                               item, 
                                               menuBar ); 	

  DefaultMenuItem* fileExit = new DefaultMenuItem( "&Exit", 
                                                   file, 
                                                   menuBar ); 

  DefaultMenuItem* view = new DefaultMenuItem( "&View", 
                                               item, 
                                               menuBar ); 	

  viewClear= new DefaultMenuItem( "&Clear", 
                                  view, 
                                  menuBar ); 	

  MenuItemHandler* menuHandler = new MenuItemHandler( this );

  menuHandler->m_menuItemClicked = 
   (OnMenuItemEvent)ScribbleWindow::onFileExitClicked;

  fileExit->addMenuItemListener( menuHandler );
  this->addEventHandler( "FileExit", menuHandler );


  MenuItemHandler* viewClearMenuHandler = new MenuItemHandler( this );

  viewClearMenuHandler->m_menuItemClicked = 
   (OnMenuItemEvent)ScribbleWindow::onViewClearClicked;

  viewClear->addMenuItemListener( viewClearMenuHandler );
  this->addEventHandler( "viewClear", 
                            viewClearMenuHandler );

  PopupMenu* popup = new PopupMenu( this );

  DefaultMenuItem* popupRoot = new DefaultMenuItem( "root", 
                                                    NULL, 
                                                    popup );

  DefaultMenuItem* popupEditClear = new DefaultMenuItem( "&Clear", 
                                                         popupRoot, 
                                                         popup );

  popupEditClear->addMenuItemListener( viewClearMenuHandler );

  popup->setRootMenuItem( popupRoot );

  scribbleView->setPopupMenu( popup );
 };

 virtual ~ScribbleWindow() {};

 //previous event handler functions here
 void onFileExitClicked( MenuItemEvent* e ) {
  this->close();
 }

 void onViewClearClicked( MenuItemEvent* e ) {
  btn1->setEnabled( false );
  viewClear->setEnabled( false );
  scribbleView->repaint();
 }

 VCF::Panel* panel1;
 VCF::Panel* panel2;
 ScribbleView* scribView;
 DefaultMenuItem* viewClear;
};

 

To create our main menu we create a new VCF::MenuBar, and then set the menu bar of our main window. Once this is done we can start to add menu items to the root as described previously. We create a "File" and "Exit" menu items, as well as a "View" and "Clear" items. As with the command buttons, we create an event handler, this time in the form of a VCF::MenuItemHandler, and assign it's m_menuItemClicked to point to the ScribbleWindow's onViewClearClicked() method. The popup menu is created in the same way, and it shares a handler with the viewClear menu item, since it only displays a single command "Clear". 

Whew!...That's all folks

So we have our Scribble app, which weighs in at around 200 lines of code, has auto alignment of all it's child windows, double buffering, allows you do draw/scribble on the left hand side, to clear the screen, exit the app, and also updates the state of the clear button when ever the drawing's state changes. This also includes event handling for the various objects we want to listen and respond to when they fire off events. Hopefully I have done a reasonable job of explaining how all this works and presenting an alternative C++ architecture for developing Win32 applications.


Downloads

Download Scribble project - 4 Kb
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

  • Dare to compare to Delphi

    Posted by Legacy on 08/02/2002 12:00am

    Originally posted by: Paolo Strazzera

    How can you compare this to Delphi? Are you joking, or what?

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

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • 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