wxFormBuilder Internals

About this document

This document is intended to describe how wxFormBuilder works, and provide a technical reference for developers who are interested in improving it or extending its functionality.

wxFormBuilder's Architecture

In this section we will try to describe wxFormBuilder's architecture, the main components of the application, and how they interacts between each other.

wxFormBuilder architecture is illustrated in the following picture:

Insert diagram here...

When you start the application you will see the following parts of the main frame:

  • Component Palette: it lets you add objects to your project.
  • Object Tree: it shows a tree-representation of the project, but this representation is not exactly the same that the 'real' object tree. We will discuss it later.
  • Visual Designer: it provides a visual representation of GUI that is being designed.
  • Object Properties: it displays all property values of the selected object.
  • C++ code editor: it shows the read-only code generated for C++.
  • XRC code editor: it shows the read-only code generated for wxWidgets XML resources.

You can notice how these main parts work together. For example, if you edit the property 'label' of a wxButton object you will see how the label changes on the visual designer and also, the status bar will show a message saying that a property has been changed.

wxFB design tries to keep all these parts as independent as possible by using the 'observer design pattern' for that. In this design, there are two main interfaces:

  • Observer interface: all objects that implements this interface are suitable to receive notifications when the observed object changes. In other words, this interface contains all methods to handle wxFB notifications. All parts described above are objects that implements this interface. We use wxEvtHandler for the Observer interface.
  • Observable interface: it provides all methods for data edition and observers attachment. All observers can access to this object in order to make changes into the project. This is the ApplicationData (appdata.h/.cpp) object, which is the big wxFB mediator. This performs all wxFB commands such object creation, property modifications, etc., and fires wxEvents to the observers after executing the commands.

Notice that MainFrame contains the rest of Observers, it is not shown in the picture, but you can see that they are inside of the frame. All of the Observers register their wxEvtHandlers into ApplicationData in their constructors, and remove their wxEvtHandlers in their destructors.

All of the Observers can access the ApplicationData instance because they get the data from that. When a particular Observer calls any method of ApplicationData, ApplicationData notifies the rest.

Let's view an example: What happens when you create an object from component palette?

Here is a summery of the process:

  1. OnButtonClick event handler is called. Because every tool (wxToolBar) stores the component name, the handler only takes the name of the component and calls ApplicationData::CreateObject(name).
  2. ApplicationData tries to create a new instance of the requested component as child of the selected object.
  3. ApplicationData object notifies all registered Observers by firing a wxFBObjectEvent with id wxEVT_FB_OBJECT_CREATED to all of the registered event handlers.
  4. ObjectTree handles this event by taking the project tree from ApplicationData instance and rebuilding the wxTreeCtrl.
  5. ObjectInspector handles this event by taking the selected object from ApplicationData and showing its properties.
  6. VisualDesigner handles this event by taking the project tree and rebuilding the GUI view.
  7. The rest of the Observers do nothing.

The most important thing is that every Observer is unaware of the rest of them, so they independently do their own job. This fact makes the code clearer and more scalable. For example, CppPanel and XrcPanel are responsible for C++ and XRC code generation respectively, so there will no problem with creating a PythonPanel that will be responsible of Python code generation. We only have to create it in the MainFrame constructor and register it's event handler in the ApplicationData instance.

The CommandProcessor class implements the Undo/Redo feature. Every command over the project is encapsulated into a Command subclass. The concrete command should provide operations for doing and undoing the command.

For example, InsertObjectCmd (appdata.cpp) inserts an object into the object tree. It stores two objects:

  1. Object to insert.
  2. Object that will be the parent of that object after insertion.

So, when command processor calls Execute() method of this command, the attachment between these objects will be done. After that, if command processor calls Restore, it will detach them. So, CommandProcessor only has to keep two stacks of commands, commands for undo and commands for redo, in order to provide this very useful feature.

GlobalApplicationData was created in order to provide access to some variables that they are needed in a lot of places of code. For example, project path is needed to get the complete path of a bitmap, because they are stored as a relative path to the project.

wxFormBuilder's Data Model

An outstanding feature of wxFB is how components are implemented; they are not embedded into source code. Instead, they are built as external modules that are loaded at runtime.

These external modules or packages are formed by a xml object descriptor (.xml), shared library (.dll/.so) and a code generation template file (.cppcode).

When wxFB starts, it searches the "plugins" directory for plugin directories containing the plugin's files (the .xml, .cppcode, and .so/.dll). When it finds a plugin with all of the pieces, it loads all modules and they are stored into a ObjectDatabase (database.h/.cpp) instance.

The following picture illustrate the object data model of wxFormBuilder:

Insert diagram here...

  • ObjectDatabase: stores references to all objects with the reference counted boost::shared_ptr, so no objects will be destroyed while the ObjectDatabase instance exists. Also, this class is responsible of the creation of objects (ObjectBase), because it knows all information about them.
  • ObjectPackage: represents a package of objects and it stores a list of the objects contained inside it. The component palette shows all components grouped by packages.
  • ObjectInfo: contains the description of a component. Object description is formed by the object type, a set of properties, a set of base classes (used for properties inheritance) and a unique name.
  • ObjectType: It defines the type of the object. This type is used to create a well formed object tree. For example, all wx-controls have type 'widget', sizers type 'sizer' and objects that can contain other widgets (such wxPanel) have type 'container'. wxFB will let you create a widget inside (below) a sizer type object, but not inside another widget.

Also, ObjectTypes can be set with the 'item' flag. It says to wxFB, that the object has to be treated as a 'item object'. Item objects are virtual objects that extend the set of properties of the object inside it. For example, when we create a widget inside a sizer, wxFB will perform the type checking, and it will determine that it has to create a 'sizeritem' for that. So, if we select the object we will see the layout properties (sizeritem properties). Item objects won't be displayed in the object tree.

  • PropertyInfo: describes a property. A property has a name, a default value, and a type. It can also have a list of options for properties of type "bitlist" or "option", or a list of children for properties of type "parent".
  • PropertyType: an 'enum' typedef with the type of the property (PT_TEXT, PT_BOOL, PT_INTEGER, ...)
  • ObjectBase: a component instance that will be placed in the user project tree. These instances will be created by the ObjectDatabase instance. Methods for property adding are not private, but they only can be used by ObjectDatabase because it knows what properties need to be created.
  • Property: represent a property of a object which has a value that the user sets. All property values are internally represented as strings, independent of its property type. So, there are several functions for type conversion from and to strings (typeconv.h/cpp).

All objects except for ObjectBase and Property are stored in the wxFB's xml files. These files are:

  • objtypes.xml: contains all definitions and placement rules of ObjectTypes. Inside, objects are declared and parent-child relationships are described.

The 'nmax' attribute of a childtype object indicates the maximum number of instances of that child type. For example, sizeritem object can contain only one widget, but a splitter object can contain two containers.

The 'item' attribute of objtype indicates whether the object type is a 'item object' or not. As mentioned above, item object are created by wxFB automatically and they are not shown in the object tree.

Visual Editor

The designer is the part of wxFormBuilder that builds the visual representation of user's forms. In the wxFormBuilder model there are two kinds of wxWidgets elements: sizers and windows. All concrete window components should be treated the same way, and all concrete sizer components should be treated the same way. The main idea is to let the plugins do all the concrete work and let wxFormBuilder do the common work of all components. wxFormBuilder should only do the work for windows and sizers but not for concrete components such as notebooks.

Current designer design is illustrated by the following picture:

Insert diagram here...

  • VisualEditor: the main class, it shows the GUI preview and responds to wxFBEvents. It stores a reference to the last form generated in order to avoid complete regeneration when another object of the same form is selected.
  • wxObjectMap and ObjectBaseMap: when an object is selected, wxFB fires a wxFBObjectEvent with id wxEVT_FB_OBJECT_SELECTED to all Observers, the ObjectBase reference in the event object refers to the object which has been selected. These maps allow finding the wxObject associated with that particular ObjectBase.
  • VObjEvtHandler: an event handler for all wxObjects of the designer. It enables changing the object selected when you click on the wxObject from the designer, for example. When a wxObject is created, an object of VObjEvtHandler is pushed on the wxObject's event handler stack.
  • IObjectView and IObject: these classes are interfaces (pure virtual) used from plugin code. Plugins do not use smart pointers so these interfaces provides another kind of access.

It's really difficult to get all requirements in order to create a good design for this task. So, the decision taken was to write a preliminary design and discover bit a bit what things are needed. A lot of changes has been done from first designs, maybe this is a good moment to redesign it.

Plugins

Plugins are loaded from the "plugins" subdirectory of the wxFB base directory at startup. wxFB scans this directory for subdirectories. In each subdirectory, it looks for a "xml" directory, an "icons" directory, and a shared library (.so/.dll). If it finds an "xml" directory, it looks for files with a .xml extension within it. If a .xml file is found, it parses it for object descriptors of all the objects included in the plugin.

Example object descriptor for wxTextCtrl:

<objectinfo class="wxTextCtrl" icon="text_ctrl.xpm" type="widget">
    <inherits class="wxWindow" />
    <property name="name" type="text">m_textCtrl</property>
    <property name="style" type="bitlist">
      <option name="wxTE_MULTILINE"     help="The text control allows multiple lines."/>
      <option name="wxTE_PASSWORD"      help="The text will be echoed as asterisks."/>
      <option name="wxTE_READONLY"      help="The text will not be user-editable."/>
    </property>
    <property name="value" type="wxString_i18n" />
    <property name="maxlength" type="uint" help="The maximum length of user-entered text.">0</property>
  </objectinfo>

wxFB with load the icon "text_ctrl.xpm" from the plugin's "icons" directory. The shared library for the plugin should include a component class that will create a wxTextCtrl and return in to wxFB when the user attempts to create one. The shared library for the plugin can have any name, and is specified by the "lib" attribute of the root element of the plugin's xml file.

Example:

<package name="Common" lib="libcommon" icon="button16x16.xpm" desc="wxWidgets common controls">

It is possible to modify properties of the object from the plugin using the wxFBManager through the IManager interface. For example, in the component class for wxTextCtrl a custom event handler is pushed on to every new wxTextCtrl's event handler stack.

    wxTextCtrl* tc = new wxTextCtrl( ... );
    tc->PushEventHandler( new ComponentEvtHandler( tc, GetManager() ) );

In the event handler for the EVT_TEXT of the wxTextCtrl, the value of the text control is used to modify the "value" property.

void ComponentEvtHandler::OnText( wxCommandEvent& event )
{
	wxTextCtrl* tc = wxDynamicCast( m_window, wxTextCtrl );
	if ( tc != NULL )
	{
		m_manager->ModifyProperty( m_window, _("value"), tc->GetValue() );
		tc->SetInsertionPointEnd();
		tc->SetFocus();
	}
}

Code generation

The approach taken for code generation is based on the concept of subclassing. Source files generated by wxFB cannot be edited because all changes will be lost in the next code generation. However, you can create another class deriving from the generated class and put all event handlers and other function code inside the derived class. Of course, this subclass has to be in another source file, in which it will include the generated header file.

Other GUI designers introduce special comments into generated code in order to preserve the user changes. However, this approach is dangerous and it could damage your file if you modify any of these marks by mistake.

Another valuable feature of wxFB is that code generation is done using code templates. Let's pay attention in the following example:

This is a typical wxButton creation:

 myButton = new wxButton(parent, ID_MYBUTTON, wxT(“Click me!”), wxDefaultPosition, wxDefaultSize, 0);

Now, let's see a typical wxTextCtrl creation:

 myTextCtrl = new wxTextCtrl(parent, ID_MYTEXTCTRL, wxT("My Text!"), wxDefaultPosition, wxDefaultSize, 0 );

As we can see, they are very similar, it only changes one line of code. So, it would be ideal if the code to write for the component code generation would take just one line of code, too.

But, if we use a typical OO design we will not achieve that. Probably, this approach would consist of creating an interface with a few member functions for code generation sections such as object construction, object declaration, etc. In this approach, we would have to implement every function of the interface for every component in C++.

This could be an example of that:

void ButtonComponent::ObjectConstruction(ObjectBase *theObject, OutStream &os)
{
  os << theObject->GetPropertyAsString(“name”) << “ = new wxButton(“
  << theObject->GetWindowParent()->GetPropertyAsString(“name”) << “,”
  << theObject->GetPropertyAsString(“id”) << “, wxT(\””
  << theObject->GetPropertyAsString(“label”) << “),”
  << theObject->GetPropertyAsString(“pos”) << “,”
  << theObject->GetPropertyAsString(“size”) << “,”
  << theObject->GetPropertyAsString(“style”) << “);”;
}

We can figure out the big amount of code that we would have to write for that...

In wxFB, it was decided to create a small template language that allows you to introduce a template string which wxFB will process and generate the code automatically. Templates are defined in external text files and not embedded into code, so if you make a mistake you can fix it just by editing this file without needing to recompile. This is the template for the previous example:

 $name = new wxButton( #wxparent $name, $id, $label, $pos, $size, $style );

Just one line of code! It's pretty, don't you think?

wxFB implements a recursive-descent parser that is able to recognize a small set of commands. All property values are specified by the '$' char.

For example, '$name' will be replaced by the value of the 'name' property of the object. This property has type 'text', so template parser will generate the value as text.

The property 'label' has the 'wxString' type, so the template parser will generate the output as a wxString, e.g. wxT(“MyLabel”).

The command '#wxparent $name' says to the parser that it has to generate the value of the property 'name' of the near ancestor object of type wxWindow.

All templates of a package are defined in a xml file with extension .cppcode. XML is not a good format for templates because you lose control over the syntax (i.e. whitespace and carriage returns are removed), but this is how it is implemented.

This is all the text that is needed for the wxButton component:

<templates class="wxButton">
  <template name="declaration">wxButton* $name;</template>
  <template name="construction">$name = new wxButton( #wxparent $name, $id, $label, $pos, $size, $style );</template>
</templates>

The template parser should be considered as a tool for code generation, but not as the only way the generate code. For example, the template parser eases the work for C++ code generation, but it is not proper for XRC files. So, code generation for XRC does not use templates.

All templates are identified by a name and you can define as many templates as you need in your code generation engine.

C++ code generation engine assumes that there are the following templates (some of them has been created for specific objects):

  • declaration: generates an object declaration inside the class declaration.
  • construction: generates the object construction.
  • settings: generates optional settings of an object and the settings of the inherited objects.
  • include: lets you specify a file dependency in the include section.
  • base: template for base class of a form.
  • cons_decl: constructor declaration of a form.
  • cons_def: constructor definition of a form.

There are many more templates for concrete objects which were created according to the needs. The design is somewhat lacking, because we are forced to use the GetObjectTypeName method to do some tasks. The design needs some thought because things like these are unacceptable:

if (  type == "notebook"        ||
      type == "flatnotebook"    ||
      type == "listbook"        ||
      type == "choicebook"      ||
      type == "widget"          ||
      type == "expanded_widget" ||
      type == "statusbar"       ||
      type == "container"       ||
      type == "menubar"         ||
      type == "toolbar"         ||
      type == "splitter"        )

In the visual editor, many of the problems were solved by introducing the visual object component type:

  • COMPONENT_TYPE_WINDOW: for window objects.
  • COMPONENT_TYPE_SIZER: for sizer objects.
  • COMPONENT_TYPE_ABSTRACT: for the rest.

There are a lot of kinds of 'component types' which makes the code more confusing. This is evidence that the model does not fit the need, and we must not continue carrying these problems.

Anyway, this is the code generation design picture:

Insert diagram here...

As we can see, TemplateParser is a base class because it does not know how to create the values of properties. It will depend of the concrete programming language.

CppTemplateParser redefines the pure virtual functions of TemplateParser in order to generate C++ output.

CodeWriter is an interface to stream out the code. FileCodeWriter writes to a file and TCCodeWriter writes to a wxScintilla control.

CodeGenerator provides a common interface for code generation, however this class could be omitted.

CppPanel and XrcPanel are Observers, so these classes perform the code generation when they receive a notification of the code generation request from the ApplicationData instance.