This article explains the terminology used in describing the framework and it's architectural principles. Most of it have information for developers who want understand the framework deeply and build their own controls and other framework entities. If you just want to create applications using Form Builder and installed packages, you may only read about forms, controls, components, list items, properties and events for better understanding what we mean by this terms.
Before further explanations it would be useful to familiarize yourself with terminology used. Please have a look at the following terms.
By Form we mean a window of application with it's graphical interface and behaviour. A form is a container for all elements which user can see and interact with. You can edit forms visually in Form Builder included into the framework or do it completely in C++ source code. Each form should be derived from IForm and implement it's methods properly. There is a helper class called CForm which has all methods already implemented. It is strongly recommended to derive your forms from CForm class. Form Builder generates form prototype header file with form prototype derived from it.
Component is a hidden element on a form which has no graphical representation but is used in source code to do something. For example, the Timer is a component. It is not drawn on the form but it helps to run some task periodically in application. Thus components have no visible parts there is no sense in putting them onto controls. You can only place them on the form directly. They also couldn't be resized or rotated due to the same reason. You might have already observed this behaviour in Form Builder. You may think about component like about very simplified version or the control. All dialogs of the Standard Package are components as well. Components are visible in Form Builder like small square box with icon on it but they aren't visible on the form in application. Each component should be derived from IComponent and implement it's methods properly. There is a class called CComponent which is already has all required methods implemented. All components of Standard Package are derived from this class. Most components could also be used without placing them onto a form at all but not all of them. For example, you may use Logger and dialog components without form, just create one and use it's methods. On contrary, the Timer component is useless without form because it depends on timers which could be accessed through a form.
Control is an entity which is visible on forms and usually used to interact with user. Button, Edit, Label are some examples of the controls. Some controls, like Panel and GroupBox, can have other controls on them. Some controls, like MainMenu, can only be placed directly on the form. Some controls, like TabSheet can only be created in Hierarchy editor and placed only onto a specific parent control. Each control should be derived from IControl and implement it's methods properly. There is a class called CControl which already has all required methods implemented. It is strongly recommended to derive all controls from it. All the Standard Package controls are derived from it.
Both controls and components also could be called widgets like it done in different frameworks. Sometimes we will use this term to talk about component or control when it has no matter which one is particularly mentioned because the statement is correct for both of them.
List item is an entity which can't be alone, it can be used only inside component, control, or other list item. List items are used in most controls which are supposed to have a set of similar items. For example, ListBox is a set of items where each item has only a caption. It can have no items at all, only one, or many items. Each item in ListBox is a List Item. Some controls, like ListBox and ColorBox can have only one level items. Other controls, like TreeView and MainMenu can have trees of items. It means items of the control can have subitems and control will handle it properly. Even more, controls like MainMenu and StatusBar can have different kind of items(or list items to be precisely). The goal of list items is to handle lists and trees uniformly while having different kind of items. All the list items are derived from IListItem. Some controls introduce theirs own list items interfaces and require all items to be derived from that interfaces and the interfaces themselves are derived from IListItem. For example, ColorBox require items to be derived from IListItemColor which is derived from IListItem and add some methods specific to colors. You can manage components' and controls' items in hierarchy editor of the Form Builder.
Notification is a method called when something happened and an entity may have to do something. For example, when user click on Button control it receives a notification about mouse down and mouse up. Forms, components, controls and some other entities have a predefined sets of notifications they are interested in. All notifications are located in the corresponding entity's service and shouldn't be called directly. They are called when user or system events happen, like user input or form close. There are a lot of notifications exists. You have only implement those of them which you want to do some job when the corresponding events occur. For example, if you want to ask user if he is sure he wants to close the application when he clicks on form's close button, you have to implement
NotifyOnClose() notification method of your form's service. All notification methods start from Notify. You may find a complete list in reference part of the documentation.
Event is a callback function which is called when corresponding notification appears. You may assign callback to the events you are interested in. Forms, Components, and Controls have events. They all just a public variables in corresponding class. If you assign a function to it, your function will be called when corresponding event occurs. Unlike the notifications, the events are for end users, not for the users who create new controls. When you create a new control you may want to add new event. In your control code you only should call the event function if it's assigned, you mustn't put any logic assigned to the event by default. Remember, user may assign to event any function, no function, or it even may be changed during program running.
By Property we mean a form/component/control/list item variable which can be accessed only with help of corresponding getter and setter methods. For example, a Button control has a property called Caption. You may edit it in Property editor in Form Builder and you also can access this from the source code of your application. But, in case of accessing from source code, you have to use getter method called getCaption to get the property value and setter method called
setCaption() to set the property value. There are two major types of properties. First one should be derived from IProperty and represents a value independent on control state. The second one represents several values, one for each available states. The base interface is IPropertyState.
By Interface we mean abstract class which also may have some public variables. All the entities of the framework core have an interfaces which describe the entity. For example, IForm describes a form and it's behaviour, IControl describes a control, IMouse describes an interaction with mouse, and so on. The primary goal of the framework is to make it easy to understand and extend. The interfaces are the tool of it. All the interaction between different parts of the framework is done via interfaces, not via particular classes. There is also a few basic interfaces needed to be learnt and understood to work with framework and create own entities. All interfaces has I at the beginning.
Service is an interface used to access hidden parts of the framework entities. They shouldn't be used by end user who only uses the framework and it's packages to build his applications. The services are for those developers who extend the framework, who create new controls. Most of the interfaces have method called
QueryService() which returns corresponding service interface. This interface allows to access the methods which are required only when building a new control or list item, or other entity.
Service splicing is a technique which allows your control temporary change behaviour of another control. It is achieved by replacing component/control service by another one. This service is called splice service. It also stores reference to the component/control original service. Components and controls have method
setService() which allows to set new service. There are also helper classes for component and control splice services. You can derive your splice services from them and overwrite only methods which should be altered, all other methods will just call corresponding methods of the service which was spliced. The classes are CComponentSpliceService and CControlSpliceService. For example, UpDown control splices Edit control service when it is assigned to it to add custom behaviour for some key events. When you use service splicing don't forget to restore original service when splicing is not required anymore(when control is detached from form/control/your control; when your control is being detached from parent/form). If you use helper classes mentioned before, you also should not worry about multiple splicing and correct service restoring. It is already handled in the helper classes. All you need is to create an instance of splice class to start splicing and destroy it(by it's
Release() method) to restore services.
Listener is just and interface which could be set for some entities and the entity will call it's methods when it wants to notify listener owner about some events occurred with the entity.
Callbacks are used in the same way as listeners but they are just function pointers. While listener may hold several methods for different events happened in entity, the callback is only for one event. For example, there is class called Gradient which allows to store and manage gradients. It has a callback to notify gradient owner when gradient was changed and gradient owner may take an action like repainting a control.
Window is a platform window where a form is drawn. It is described by IWindow interface. This interface describes interaction between operating system window and a form. It is one of the platform specific interfaces and it's implementations depend on the used platform. There are default implementations for different operating systems in Platform Package. Form requires a window to be drawn on.
Renderer is a platform specific class which derives from IRenderer interface and is responsible for drawing operations. The Platform Package has a Windows platform OpenGL renderer implementation as well as Android GLES renderer and Linux OpenGL one. A renderer is required by a form. All controls use form's renderer to paint themselves.
By The Framework Core we mean a set of interfaces and helper classes, functions, enumerations. Core doesn't contain any forms, components, controls, list items and other similar purpose entities. Core contains only a set of rules and implementation describing how to create specific entities, not the final entities, like controls, itself. The core consists of interfaces, base and default classes, built-in controls, helper types and functions.
Built-in control is an "almost" control. It implements all the required logic but can't be used without the real control. Lets see on some of the common controls. Memo, ListBox, ScrollBar, DropDown - all of these controls have something common. It is a possibility to scroll content. When there is a lot of content at those controls and it doesn't fit inside a control the scrolls help you to navigate to hidden parts of the content. Scroll is an excellent candidate to be a built-in control. Instead of writing a scroll code in all those controls we can write it once and use it in all the controls. There are several default built-in controls in the framework. They are dropdown, scroll, textinput, textarea and month calendar. You may use them when you create your own controls or you may write your own implementation. Moreover, most controls allow to set another implementation of built-in controls they use. It gives them a great extensibility.
Module is an independent set of functions and/or classes which has a specific goal. For example, the Scripting module is responsible for parsing and running different kinds of scripts. It is very powerful module. It is used in the framework to parse JSON and XML formats. All modules are located in the Modules subfolder of the core directory.
Package is a code describing controls, forms, components, etc. Usually it has a lot of very simple code which doesn't provide any functionality but provide a full description for the entities. This code isn't included in you applications. The goal of packages is to describe entities for the Form Builder. If you don't want controls, created by you, to be managed in Form Builder, you don't have to create package for them at all. But if you want to distribute your controls, you need to add their descriptions.
Property handler is an object which handles property editing in Property editor of the Form Builder. Controls may have a lot of different kinds of properties. By creating new controls you may want to add new kinds of properties. If you want such a new properties could be edited in Property editor in Form Builder, you should create a property handler for it. There are a lot of already implemented property editors for most used properties in Standard Package. You may use them for your properties if their types are match. Property handler should handle both single and stated properties(if they exist at all of course).
Casting is an attempt of getting one interfaces from another. Often, when developing controls, you will need to work with specific class while IControl interface can return only a basic interface type. For example, all controls implement their own IControlService. Thus, the Button has CButtonService which is derived indirectly from the IControlService. It is returned in the form of IControlService by
QueryService() method. What if you need to work with some methods of the CButtonService but you can only access a QueryService? Use casting. There is a helper core template function called cast. It tries to convert one type to another. It doesn't throw any exception. Instead, it returns empty pointer in case of specified object couldn't be converted to another one. The usage is simple. For example, to convert IControlService to CButtonService, all you need is to write
cast<CButtonService*>(service), where "service" is a value, returned by Button's
QueryService() method and having type IControlService. Be careful, always check if conversion was successful! You couldn't convert incompatible types. It means you will get an empty result from cast function if "service" has no any relation to CButtonService.
We also often mean by "event" not only an event callback but both callback and notification together because they are very close entities and usually executed together.
The framework is event driven. It means it do some job only when some event happens. It may be user input or some sort of system event. Please look at the diagram below. It shows how events flow between the framework parts. The diagram is drawn in terms of interfaces. In the source code it will be classes of course.
There are lots of events coming from system and user. The IApplication is responsible for getting all these events and passing it to the correct form. Speaking strictly it passes an event to the IWindow. Then IWindow passes it to the associated IForm. The IApplication is also responsible of closing and destroying all dependent windows and forms when the main one is closed. After a form receives an event it determine which component or control should receive it and passes it to the proper one.
All events are processed with help of notifications and event callbacks. Notifications are for internal usage by form and components/control and accessible with corresponding service. Event callbacks are for end user. User may assign callback function to it and do some job when it's called. All notifications in services are methods started with Notify. For example,
NotifyOnClose() method of form is called when user click on close button of the form.
NotifyOnLeftMouseButtonDown() control's notification is called when user down left mouse button over the control. A lot of notifications have corresponding event which starts of On and belongs not to service but to form/component/control directly.
Not all events are result of system changes or user input. Many events are generated by form and controls themselves. For example, when a control is about to be detached from corresponding form, the form generates
NotifyOnDetaching() notification and calls corresponding
OnDetaching event if a callback function is associated to it. Another example is an event of changing component/control. The component and control can generate this type of events when they are changed and want let to know another components and controls of the form about this change. For example, ImageList component generates ComponentChange event each time its image list changes. Controls, which use this ImageList, receive the notification and repaint themselves.
Some events are passed to specific control, another events are passed to all controls. For example, when component has just been added to a form, all other controls and components will receive ComponentAttach event.
User input produce events both for the form and for the corresponding control at the same time.
And finally, there is a set of events which are passed from child control to parent one. They all have a Child in a name and are used by child controls to let parent control or form know about changes.
A control which receives an event can be changed by so called "capturing input". Imagine a form which has a top menu(MainMenu) and a lot of controls. When menu is closed and user clicks on one of the controls below it the corresponding control, which is under the mouse pointer, gets a click event. It is correct behaviour. But what when menu is opened and the control under mouse pointer is now behind the menu? In this case MainMenu captures the input and the form sends events to menu instead of the real control under the mouse pointer. That is the goal of capturing input. It is done by form's methods
Some events can be automatically sent to parent widgets of the widget they appear on. Such events are: all keyboard events, all mouse events except for mouse hover and mouse leave ones, and drop files event. When one of the events happens on some widget it is automatically sent to all his parent widgets in widget to parent, to parent of parent and so on order. It is so called "event bubbling". To stop propagating event up onto widget hierarchy the
processed argument value should be set to true in the corresponding notification method or event callback function. Usually if widget processes corresponding event by itself it sets the
processed value to true indicating the event is processed and no need to send it to parents. There is also a way to indicate whether widget wants to receive such a bubbled events or not. It is done with help of
isAcceptBubbledEvents() method of IControl. If the method returns true than widget will receive bubbled events. If the method returns false than widget won't receive bubbled events. The CControl class implementation, which is usually the base class for a widget, returns false in that method so if you develop a widget which requires to receive bubbled events, you have to overwrite the method and return true instead.
Consider the following example showing where event bubbling can be useful. Lets say we have ScrollBox widget with many other widgets on it. One among the child widgets is the DropDown. Child widgets doesn't fit into ScrollBox client area and it has scroll bars and ability to scroll its content. Without event bubbling we could have scrolled only when mouse pointer is over ScrollBox scroll bars or area free of child widgets because of only in this case the ScrollBox receives mouse wheel events. With event bubbling the ScrollBox receives scroll events even when mouse pointer is over any child widget unless that child widget prevent sending scroll events to the parent. DropDown widget processes mouse wheel scroll event in its own way (it changes active item) and so it sets
processed to true in the
NotifyOnMouseVerticalWheel() notification of its own service thus preventing sending the event up to parents. So, in our example, when user scrolls mouse wheel when mouse pointer is over the DropDown it works as expected: items are changed and no scroll happens in ScrollBox. On contrary when user scrolls over, lets say Button, scrolling of ScrollBox takes place because of the Button doesn't process mouse wheel scroll.
To identify whether an event is regular or bubbled one you may use
IControl *Target member or a message data structure provided to notification method or event callback function. For example, see MessageMouse structure reference page. That member indicates the widget where event originally happened. It is equal to the widget which service receives a notification if it is regular event and doesn't equal if it's bubbled one.
Although there are a lot of interfaces and classes in the framework, there are only a few the most important of them. The other ones are designed just to be a helpful addition. Here is a list of major interfaces which are mandatory to use.
You already know the first important thing about rendering. The IRenderer interface is responsible for all drawing operation. Of course it can't have all required drawing methods for all purposes. If something is missing, you can draw it by yourself accessing graphical data directly.
The second important thing you must know is that neither form nor controls are drawn on the window immediately. That is done by so called "locking repaint". The form has two methods
UnlockRepaint(). All events are called inside lock and unlock repaint. This is done for optimization to avoid multiple repaint of the same area. Real drawing happens only when there are really invalid part of the window and the last
UnlockRepaint() is called. Yes, you may call
LockRepaint() several times and you should call the same count of
UnlockRepaint(). For example, when you are adding controls to the form either at form creating or later when changing the form appearance, it is a good idea to add all the controls between
UnlockRepaint(). In case you forget to add locks the form will be repainted each time you add control and change it's properties. If you have a few of them it is not a problem. But if you have tens of controls and change tens of properties of each one, the hundreds of repaints will appear. It not only can be much longer but user also may see how controls appear one by one. If you use alignments, the form layout may change greatly when adding a next control and all these metamorphoses will be shown to user. That is no good. Don't forget to lock repaint when you do something which results in multiple repaints of the form. You can also use helper class CLockRepaint which simplify lock-unlock process.
And the final, third, important thing you should know is that drawing has 2 stages. The drawing event has corresponding flag indicating whether it is first or final drawing phase. The first phase is used for common drawing, while the final one is for drawing only the part which should be above everything other. The good example is DropDown. When it is closed(no list is shown) it draws itself only in the first phase(or step) but when it is opened, the list part is drawn in the second step to be over all other controls.
When you create controls you shouldn't throw any exceptions. All errors should be processed silently. All methods of entities of the framework process errors silently, no exceptions are produced. We recommend to do the same when you develop your own controls. Instead of throwing an exception just return false from the method. It means method haven't done anything. Return false also when nothing is required to be done. For example, if you write a setter method for a property of your control, it is a good idea to check if the specified value is the same the control already have and return false if it is the same. When working with another classes which are parts of the framework or another packages, always check the return false of the method you call and proceed accordingly. It concerns mostly on such entities like controls, components, forms where an action could be ignored and it is not critical. On the other hand there are places when action is critical and if it wasn't accomplished due to wrong input data it will result in unexpected behaviour and even exceptions. Try to avoid such a critical behaviour in your controls.
Namespaces are widely used in the Nitisa framework. All the framework code, including packages, is placed inside
nitisa namespace. This is the global namespace. Some parts of the framework is placed into namespaces inside global one. They are modules and packages.
Modules are placed into namespaces with names similar to the module name. For example, Scripting module code is placed inside
nitisa::scripting namespace, the Artificial intelligence module code is completely located inside
nitisa::ai and namespaces inside it, and so on.
Standard Package code is located inside
nitisa::standard namespace, Charts Package code is located inside
nitisa::charts namespace, and so on.
Although it is not mandatory but we recommend to use similar namespace structure for your projects especially if you plan to redistribute them.