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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read