V - A Free C++ Graphical User Interface Framework

From EDM2
Jump to: navigation, search

Written by Jon Hacker

Note: This is a historical article from 1999 and contains some outdated information, please read V C++ GUI Framework for its current status.

Vshapes.gif

Introduction

V, a completely free cross-platform C++ Graphical User Interface Framework, is an easy to use programming system for building GUI applications. The second major upgrade to the OS/2 port of V was released this past February. The framework is small, elegant, and provides the tools required for building all but the most specialized applications. The V framework has also been designed to be portable, yet present a look and feel consistent with native applications. Currently, besides the OS/2 port, versions for the X Windowing System, Microsoft Windows 3.1, and Microsoft WIN32 (Windows 95 and NT) are available. The V system is freely available for use by anyone under the terms of the GNU Library General Public License. To give you some idea of the capabilities of V, a screen shot of a V-based application demonstrating the built-in OpenGL canvas is shown here running on OS/2. The total size of all the source code files needed to build this application is less than 15 kB, including both comments and the OpenGL code for creating and spinning the image.

History of V

V was conceived and developed by Bruce Wampler after he became tired of complicated, difficult to learn and use libraries for building interfaces, and wanted something easier. Bruce originally wrote V for the X and Windows 3.1 platforms. It was later ported to Win32. The OS/2 port was done separately by myself with the first release in February 1998. All ports continue to evolve and develop in a unified manner.

The main design goal for V is ease of programming. It is not reasonable that building the GUI part of an application should be the hardest part of the job, as is the case with most native GUI toolkits. V is small, easy to learn, easy to use, and provides the essentials of a good graphical user interface. V has less than 15 C++ classes that you will have to interact with. This is unlike many other frameworks that provide dozens and dozens of classes which you must learn and understand. The V framework only supports GUIs. It does not have templates, containers, and all kinds of other C++ classes. If you need a good list class, deploy your favorite one from another class library. Use V for your interface.

V has very good associated documentation. One reason V is easy to use is that it is accompanied by an above-average programming manual written as a well-organized hypertext HTML document. Each V class and function has not only a useful explanation, but also each description comes with a short example that shows how to use the V feature in a useful way. There are also several examples provided with the V distribution to help you get started with a basic V application.

For application development V includes an application generator (vgen) and an integrated development tool (vide) to help simplify the coding process. Vgen builds the required skeleton code to support your application after you define the basic windows and dialogs that you need. Vide is useful as a project manager and editor for developers who do not use commercial IDE tools, or for those who just want something they can customize to their own liking. Both are written as V applications and the complete source code is included. A screen shot of the application generator is shown here running on OS/2.

Vgen2.gif

There is, of course, a price to pay for the ease of programming with V. The main constraint is that you are somewhat restricted to following V's view of the world. The V model does not exactly conform to the native models of OS/2, Windows, and X, but it is a very good compromise. For the most part applications developed with V will in fact conform to the host look and feel, but may be lacking some of the bells and whistles of the most sophisticated commercial applications available for a given platform. For the vast majority of applications, this will not matter. You will end up with applications that look pretty good, and are likely to have a much cleaner and better interface than they might have otherwise.

If you are a C programmer, then the fact V is a C++ library might be a problem. While it is a fully object-oriented C++ framework, it can be used with C code if you know a bit about C++. Also, V does not allow you to do everything you could if you programmed in the native windowing library. You won't have every single conceivable control, and some controls are slightly restricted in how you can use them.

Getting Started with V

As with any new system, V has a learning curve before you can write applications of your own. V's learning curve is actually pretty short. Experience has shown the best way to get started with V is to step through an example V application.

The V application generator, vgen, included with the V distribution is the easiest way to start building a V application. Run vgen, select the basic options you want to include in your application, select the directory to save the generated code in, and then generate the basic skeleton application. From the skeleton app, it is relatively easy to add your own functionality.

The tutorial application described in the next section is also an excellent V example. Start by getting the example to compile. Then modify the code to add or remove features. Before long, you will have a good feel for V, and be able to add all the features you need.

Before we leap into an example, though, let's first describe in general terms the main pieces that form the core of a typical V application and how they interact with one another.

The Application

In many ways, the heart of a Standard V Application is the application class derived from the vApp class. By convention, this derived class is called myApp (but you can use a different name if you want). There will always be exactly one instance of the myApp class. The myApp class acts as a coordinator between the windows that implement the user interface (the views), and the objects and algorithms that actually make up the application (the model). The myApp class will contain the windows defined by the application, as well as any classes needed to implement the application.

The vApp class has several utility methods that are usually used unmodified, plus several methods that are generally overridden by the myApp class. In addition, your myApp class will normally have several other programmer-defined methods for interfacing the command windows with the application model.

Windows and Canvases

Each Standard V Application will have at least one top-level window, and possibly subwindows. These will usually be command windows derived from the vCmdWindow class. Your main derived class should be called myCmdWindow, and include a constructor that defines a menu bar, a canvas, and maybe a command bar (V terminology for a toolbar) and status bars. Of course, there will be a corresponding destructor. The .cpp file will contain the static definitions of the menu and any command and status bars. It will also override the WindowCommand method of the vCmdWindow superclass. In your WindowCommand method, you will have a switch with a case for each menu item and button defined for the window.

Since a vCmdWindow contains different panes such as vMenus (for pulldown and popup menus), vCanvasPanes (for drawing graphics and displaying text in a window), vCommandPanes (for toolbars), and vStatusPanes (for status bars), your top-level command window object will usually define the appropriate pointers to each of these objects as required by the specific application. The myCmdWindow constructor will then have a new for each pane used.

Each instance of a window will be built using a call to the vApp::NewAppWin method. This allows the app object to track windows and control interaction between the app model and the views represented by each window.

Some applications need to open subwindows. These windows may or may not use the same menu, command bar, and canvas as the top-level window. If they do, then they can use the same static definitions as the top- level window. Subwindows may also have their own menu, button, and canvas definitions.

Canvases for Windows

Since each window usually needs a canvas, you will usually derive a canvas object from the vCanvasPane class. At this point in the life of V, there are three possible kinds of canvas. The first, for graphics drawing (a subset of the OS/2 Gpi API), is derived directly from the vCanvasPane class. There is also an OpenGL canvas, and a text canvas derived from the vTextCanvasPane class. The derived class will define override methods required for the user to interact with the canvas.

Optional Dialogs

Most applications will need dialogs - either modeless or modal. A Standard V dialog consists of a .cpp file with the static definition of the dialog commands, and the definitions of methods derived from the vDialog class. These will include a constructor and destructor, and a DialogCommand override that has a switch with a case for each command defined for the dialog. Each case will have the code required to carry out useful work.

The top-level window (or the subwindow that defines and uses the dialog) will create an instance of each dialog it needs (via new). The constructor for the dialog sets up the commands used for the dialog.

Typically, the top-level window defines menu and button commands that result in the creation of a dialog. The top-level window is thus usually responsible for invoking dialogs.

A Tutorial V Application

Now that you have read about the parts of a standard V application, it will be useful to go over a bare-bones example to make our theoretical discussion more concrete. Included with the V installation are a number of applications, each in its own subdirectory. We will focus our attention here upon one of the simplest V examples provided. It is called tutapp, and the source code can be found in the V\tutor\ directory. The code is written clearly to serve as a tutorial, and is well commented. You can read the code on your own to get a good understanding of what elements are required for a V application. Before you go off on your own and read it over though, I will first try to provide an overview of the tutapp source code focusing mainly upon those parts of the code that are particularly noteworthy.

To compile the tutapp application you can use the appropriate makefile for your compiler. IBM VisualAge users can find the makefile vtutor.mak in the directory V\ibmcpp. Instructions for local configuration of the makefile for your system can also be found in V\ibmcpp. EMX users can use the makefile in the V\tutor\ directory, after first editing the configuration file config.mk following the instructions in the readme file in the subdirectory V\emx. Borland C++ users can find the tutor.prj project file in V\bcos2. You will probably need to edit the project settings to match your local directory structure. Watcom users will find only limited support so far in V\watos2. However, that will be corrected with the next release as I have now received a complete set of project files from Herbert Bushong for Watcom 11.0.

Tutorial Source Code Overview

The code for tutapp is broken down into five sections, corresponding to the main application, the main window, a simple canvas, and modal and modeless dialogs. The source code for each of these parts is a separate .cpp file. The source code is extensively commented, and the comments contain extensive details on how you should structure a V application. The following sections give a brief overview of each source file included in the tutorial example and highlight the most important sections.

The previous section suggested using myApp for names. This tutorial uses a t prefix instead of my. You really can use whatever names you want. It helps to be consistent, however.

The Base Application Class

The file tutapp.cpp contains the overridden definitions of the classes NewAppWin, Exit, CloseAppWin, AppCommand, and KeyIn methods. These examples don't do much work here, but are provided to show you how to access them for building your own more complete application.

The single definition of the application tut_App("TutorApp"), and the AppMain main function are also in this file. One thing that can be difficult to grasp when using a framework such as V is understanding where the program starts, and how you get things rolling. This happens in tutapp.cpp, so it is especially important to understand this piece of code. The essential thing to understand is that C++ will invoke the constructors of static objects before beginning execution of the program proper. Thus, you declare a static instance of the vApp object, and its constructor is used to initialize the native GUI library and get things going.

Your program will not have a main() function, but something similar in spirit is provided by the V function called AppMain(). The initial window is created in AppMain by calling NewAppWin as shown below. Note the use of the automatically defined global pointer theApp that is used to access vApp methods from inside Appmain.

static tutApp tut_App("TutorApp");  // The single instance of the app

//===========================>>> AppMain <<<==============================
 int AppMain(int argc, char** argv)
 {
   // The V framework defines the instance of main. After some
   // processing of command line arguments, AppMain is called with
   // cleaned up command line arguments. Note that at this time, no
   // windows have been defined. Normally, AppMain is the place to
   // start up the first window. You can perform any initialization you
   // need to do here.

   (void) theApp->NewAppWin(0, "Tutorial V Example", 350, 100, 0);

   // At this point, the window is up, and all events are being
   // routed through its methods.

   // We MUST return 0 if the status is OK at this point.
   return 0;
 }

The Command Window

The file tcmdwin.cpp contains the code for the main command window. Of particular interest are the definitions of the pulldown menus, command pane (toolbar), and status pane. These panes are defined and added to the window in the constructor. The definition for the File pulldown menu is shown below. Each line represents one item on the menu, and various flags and variables are used to define the menu id, accelerators, and other initial attributes. These are described fully in the V documentation. Note that no resource files are ever used as in a normal OS/2 application; instead, everything is built at runtime using static definitions in the code.

// Now, the static declarations of the menu arrays. You first define
// the pulldown menus, one for each main menu bar label.

   static vMenu FileMenu[] =    // Items for File menu
     {
       {"New",M_New,isSens,notChk,noKeyLbl,noKey,noSub},
       {"Open",M_Open,isSens,notChk,noKeyLbl,noKey,noSub},
       {"Save",M_Save,notSens,notChk,noKeyLbl,noKey,noSub},
       {"Save As",M_SaveAs,notSens,notChk,noKeyLbl,noKey,noSub},
       {"-",M_Line,notSens,notChk,noKeyLbl,noKey,noSub},
       {"Debug",M_SetDebug,isSens,notChk,noKeyLbl,noKey,noSub},
       {"-",M_Line,notSens,notChk,noKeyLbl,noKey,noSub},
       {"Exit",M_Exit,isSens,notChk,noKeyLbl,noKey,noSub},
       {NULL}
     };

Toolbars (command bars in V terminology) and status bars are defined in a like manner. Again, each line represents a CommandObject (control), and various flags and variables define its id and initial attributes. Position is controlled by the order in which the controls are specified, starting from the left and working towards the right. Of course, methods are also provided to modify and interact with the controls dynamically as the code runs. In this code segment, a command bar consisting of a text label followed by a row of four pushbuttons is defined.

// We now define a command bar. Command bars are optional, and there
// may be more than one. You can place any CommandObject you want on a
// command bar.

   static CommandObject CommandBar[] =  // A simple command bar
     {
       {C_Label,999,0 ,"Command Bar",NoList,CA_None,
           isSens,NoFrame,0,0},
       {C_Button,M_Copy,M_Copy,"Copy",NoList,CA_None,
           notSens,NoFrame,0,0},
       {C_Button,m_Dialog,m_Dialog,"Dialog",NoList,CA_None,
           isSens,NoFrame,0,0},
       {C_Button,m_Clear,m_Clear,"Clear",NoList,CA_None,
           isSens,NoFrame,0,0},
       {C_Button,M_Exit,M_Exit,"Exit",NoList,CA_None,
           isSens,NoFrame,0,0},
       {C_EndOfList,0,0,0,0,CA_None,0,0,0}  // This ends list
     };

Once the static definitions for the various panes are defined, the panes are added to the window using the AddPane() function. This is typically done in the constructor for the command window as shown below for the command pane (CommandBar) we defined above.

// Create and add the command pane to this window
    myCmdPane = new vCommandPane(CommandBar);
    AddPane(myCmdPane);

There is also code to demonstrate handling keyboard and window command events in the KeyIn and WindowCommand methods. There is also a simple example of using the vFileSelect utility class, as well as invoking modeless and modal dialogs.

The Canvas

The file tcanvas.cpp contains the code for the canvas. This is a really simple canvas example which supports drawing a few lines. This class handles redrawing after expose events very simply, but demonstrates what must be done in general. Typical of the functions you will override with your own methods is Redraw(). The code you would add here to handle the redraw event is completely analogous to the code you would write for a native OS/2 wm_paint message. Other member functions for handling mouse interaction and resize events should similarly be overridden to provide whatever functionality your application needs.

//========================>>> tCanvasPane::Redraw <<<=====================
 void tCanvasPane::Redraw(int x, int y, int w, int h)
 {
   // This is a simple Redraw that just redraws everything.
   // Often, that will be more than fast enough, but the input
   // parameters can be used to make a more intelligent redraw.

   for (int i = 0 ; i < _nextpt ; i += 2)
       DrawLine(_pt[i].x, _pt[i].y, _pt[i+1].x, _pt[i+1].y);
 }

A Modeless Dialog

The file tdialog.cpp contains the code for a modeless dialog. There are just a few example buttons, check boxes, and radio buttons. The DialogCommand methods demonstrate how to handle commands from a dialog. The dialog controls are specified using static definitions in a manner similar to what was described above for command and status bars. However, the positioning of controls is more flexible than for toolbars and involves specifying the ids of other controls positioned to the left of or above it. This is similar to the way Java positions controls and provides good compatibility across different platforms. Controls can also be grouped in frames to further control alignment and position. Here are the first few lines of the dialog static definition named DefaultCmds that defines a text label and a frame containing another text label and two pushbuttons.

   static CommandObject DefaultCmds[] =
   {
     {C_Label, mdLbl1, 0,"X",NoList,CA_MainMsg,isSens,NoFrame, 0, 0},
     {C_Frame,mdFrmV2,0,"",NoList,CA_None,isSens,NoFrame,0,mdLbl1},
     {C_Label,mdLbl4,0,"Buttons",NoList,CA_None,isSens,mdFrmV2,0,0},
     {C_Button,mdBtn1,mdBtn1,"Button 1",NoList,CA_None,isSens,mdFrmV2,0,mdLbl4},
     {C_Button,mdBtn2,mdBtn2,"Button 2",NoList,CA_None,isSens,mdFrmV2,0,mdBtn1},
   }

Once the static data is defined, the dialog is built in the dialog constructor by using the AddDialogCmds() function and feeding it the static definition of the dialog (DefaultCmds)we defined above.

 tDialog::tDialog(vBaseWindow* bw) : vDialog(bw)
 {
   // The constructor for a derived dialog calls the superclass
   // constructor, and then adds the command objects to the dialog
   // by calling AddDialogCmds.

   AddDialogCmds(DefaultCmds);         // add the command objects
 }

To display the dialog, our application first defines an instance of the desired dialog in its command window constructor. For our example, this is the tCmdWindow constructor found in tcmdwin.cpp.

   // In the V model, a window may have dialogs. Each dialog used
   // by a window must have an instance pointer. The easiest way
   // to create dialogs is to construct each one using a new here
   // which only defines the dialog - you need to use its
   // ShowDialog method at the appropriate time to display it).
   // You delete dialogs in the destructor for this window.
   // 
   // Now, create whatever dialogs this app defines:

   sampleDialog = new tDialog(this);

To actually display the dialog, typically the user does something that then invokes the ShowDialog method. In our example, the user selects the menu item defined as m_Dialog to open the dialog. The actual code that does the work is in the WindowCommand() method for our main window in the file tcmdwin.cpp as shown below.

 case m_Dialog:          // Invoke our dialog
 {
   if (!sampleDialog->IsDisplayed())   // not twice!
     sampleDialog->ShowDialog("Sample Modeless Dialog");
   break;
 }

The user interaction with the dialog is then handled by the DialogCommand override in tdialog.cpp with a case for each command defined for the dialog. Each case will have the code required to carry out useful work.

A Modal Dialog

The file tmodal.cpp contains the code for a modal dialog. The definition of a modal dialog is nearly identical to a modeless dialog. The main difference is how they are invoked, which is shown in the tcmdwin.cpp code. The ShowModalDialog method waits until the modal dialog is dismissed, at which time it returns with the id and value of the control used to dismiss the dialog.

case m_ModalDialog:     // Invoke our modal dialog
   {
      ItemVal val, id;
      id = sampleModalDialog->ShowModalDialog("Sample Modal",val);
      // Now do something useful with id and val ...
      break;
   }

Conclusion

While this article has tried to gently introduce the programming concepts of V, experience suggests that the only way to truly learn is to jump right into the code and try compiling your own. A good place to start is with the tutorial application described above. You should study this code, paying special attention to the comments, and start by making a simple modification such as adding an extra button to the tool bar. Most of the information you need to build a typical V application is explained in this code and you will quickly find coding with V to be easy and fast.

Users can also join the V mailing list to interact with other V users, ask for and give technical support to others, and discuss new features to be added in the future. To subscribe send email with the subject "subscribe" to vgui-discuss-request@other.debian.org.

For regular postings please use vgui-discuss@other.debian.org. Back issues of the list are archived on [1]

I hope you enjoyed this article and now have a better understanding of how to build V applications.