This tutorial is the second one in the series dedicated to creating custom widgets and packages. In this tutorial we will focus our attention on making the widget we created in the first part of series appear in the Form Builder.
In the previous tutorial we created Registration widget and in this one we will see how to make our widget appear in the Form Builder. As you could see we did nothing related to the Form Builder when we were creating the widget. And that is an excellent feature of the Nitisa - the code of a widget is completely independent on the code required to handle widget in the Form Builder. It means you don't need to think about Form Builder at all when developing a widget and don't need to do anything additional if you don't want your widget appear in the Form Builder. On the other hand it also means if you already have some widgets created and you want them to be managed by the Form Builder, you can add such a possibility without changing source code of widgets at all.
So, how does Form Builder load and manage widgets? The process is simple in principle. Widgets are collected in a package. To load a package dynamically it should be in a form of Dynamic Link Library. In that dll the function returning information about the package should be. In what form the information should be? As it is usually for the Nitisa, it should be in the form of interface. That is how Form Builder loads packages. We will create dll project for that a little bit later. We will start from implementing the classes which describe the package.
There is an interface describing everything what package may provide to Form Builder. That interface is called IPackage. That interface simply provides methods returning information about package, like its manufacturer, version, documentation links, number of components, controls, forms, and so on as well as provides ability to search different objects package contains using different criteria. It provides pointers to interface instances of so called package entities which provide description of particular entity. For example, for describing components the package component interface is used, for describing controls the package control interface is used and so on. Those descriptions are used by the Form Builder to provide you ability to create forms in a comfortable way. So, lets start from creating a description of the Registration widget for the Form Builder.
The interface describing control for the Form Builder is called IPackageControl. There are more similar interfaces called IPackage[EntityName] which describe another entities (components, forms, styles and so on) for the Form Builder. So, to describe our widget we need to create a class which implements that interface. As in most cases there is a helper class implementing some features of the interface. The class is called CPackageControl and we will derive our class from it.
All related to information for Form Builder is usually located in the Package subdirectory of the package library project. So, create filter in the CoolWidgets project with that name. The Package subdirectory structure is similar to those we described in previous tutorial for package project. So, create a filter called Controls inside the Package filter. Here all the control descriptions are. In that directory create a filter for our widget. Name it accordingly to the widget name - Registration. Below you can see how it should look like.
In the Package\Controls\Registration filter create new header file called PackageRegistration.h. It should also be saved in the Package\Controls\Registration subdirectory of the project. The name for description class is usually formed as CPackage[Name], where [Name] is the entity name. So, in our case the class will be called CPackageRegistration and we named the header file for it (and will name source file as well) in the same way we name header and source files for widget.
Put following package widget class declaration in the header file.
#pragma once
#include "Nitisa/Core/Strings.h"
#include "Nitisa/Image/Image.h"
#include "Nitisa/Package/Core/PackageControl.h"
namespace nitisa
{
class IControl;
class IEventList;
class IPackage;
class IPropertyList;
namespace coolwidgets
{
class CPackageRegistration :public CPackageControl
{
private:
const Image m_cIcon;
public:
String getCategory() override;
String getClassName() override;
String getTitle() override;
String getDescription() override;
String getReferenceUrl() override;
const Image *getIcon() override;
bool isVisible() override;
bool isDisplayIcon() override;
bool isInstance(IControl *control) override;
bool isTransformable() override;
IControl *Create(IPropertyList **properties, IEventList **events) override;
IPropertyList *CreatePropertyList(IControl *control) override;
IEventList *CreateEventList(IControl *control) override;
CPackageRegistration(IPackage *package);
};
}
}
Most methods here just return information about widget. For example, getClassName()
method should return class name of the class implementing widget logic; isTransformable()
method should return whether widget size and rotation can be changed in the Form Builder; and so on. Their description can be found on IPackageControl reference page (as well on the reference page of the parent interface) and we will look at each of them more closely when we will implement them.
The most interesting method of this class is Create()
. This method should create and return widget class instance and should, if requested, create property and/or event lists. What are those lists? If you guessed they are all properties and events widget have and Form Builder shows in the Properties & Events section, you are absolutely right. As you can see those lists are described by IPropertyList and IEventList interfaces and thus we need to implement them as classes. So, lets for a while leave CPackageRegistration
class as is and implement these two lists as we will need them in the package widget class implementation.
Fortunately there are corresponding helper classes already implemented and we don't need to implement those interfaces from scratch. They are CPropertyList and CEventList. Moreover, we used CControl class as the base class of our widget and that class has actually a lot of properties and events already implemented and, as you might guess, there should be a helper property and event lists with all those properties. Indeed, there are both CControlPropertyList and CControlEventList. Moreover, there are similar helper classes with properties and events for default component, form, and other entities already implemented. So, all we need when describing our custom widget is to use that property and event lists, remove from them unused or not needed anymore default items and add our widget new properties and events. We will remove unused properties and events and add new ones in the next, the third, part of the tutorial series. For now we just create property and event lists for our widget and leave all default properties and events as is.
It is accepted to name property lists as [EntityName]PropertyList and event lists as [EntityName]EventList, where [EntityName] is a name of component, control, form, etc. So, create header file called RegistrationPropertyList.h in the Package\Controls\Registration directory and put following property list class declaration code in it.
#pragma once
#include "Nitisa/Package/PropertyLists/ControlPropertyList.h"
namespace nitisa
{
class IControl;
class IPackage;
class IPackageEntity;
namespace coolwidgets
{
class CRegistrationPropertyList :public CControlPropertyList
{
public:
CRegistrationPropertyList(IPackage *package, IControl *control, IPackageEntity *entity);
};
}
}
Whenever you create a header file in the CoolWidgets project don't forget to add it to the CoolWidgets.h header file. So, if you didn't do this yet, add PackageRegistration.h and RegistrationPropertyList.h header file includes there (don't forget to use correct paths).
Property class declaration is trivial. We just derive it from control property class and add the same constructor it has.
Add source code file called RegistrationPropertyList.cpp into the same directory and put following property list class implementation in it.
#include "stdafx.h"
namespace nitisa
{
namespace coolwidgets
{
CRegistrationPropertyList::CRegistrationPropertyList(IPackage *package, IControl *control, IPackageEntity *entity) :
CControlPropertyList(package, control, entity)
{
}
}
}
Again, implementation is trivial. We just call parent constructor in class constructor. We will make some changes here in the next tutorial. For now our widget will have the same properties the CControl class has.
Lets make the same for event list of our widget. Add header file called RegistrationEventList.h in the same folder and put following event list class declaration into it. Also don't forget to add include of this header file into CoolWidgets.h.
#pragma once
#include "Nitisa/Package/EventLists/ControlEventList.h"
namespace nitisa
{
class IControl;
class IPackage;
class IPackageEntity;
namespace coolwidgets
{
class CRegistrationEventList :public CControlEventList
{
public:
CRegistrationEventList(IPackage *package, IControl *control, IPackageEntity *entity);
};
}
}
It is very similar to the property class. The only difference is the parent class is control event list class instead of control property list class as it was earlier.
Create source code file called RegistrationEventList.cpp in the same directory and put following class implementation there.
#include "stdafx.h"
namespace nitisa
{
namespace coolwidgets
{
CRegistrationEventList::CRegistrationEventList(IPackage *package, IControl *control, IPackageEntity *entity) :
CControlEventList(package, control, entity)
{
}
}
}
Again, we will remove unused and add new events here in the next tutorial.
So, now we have property and event list classes for our widget and we may return to the package widget class implementation. Add source code file called PackageRegistration.cpp in the same Package\Controls\Registration directory and put following class implementation code there.
#include "stdafx.h"
namespace nitisa
{
namespace coolwidgets
{
const int IconWidth{ 26 }, IconHeight{ 26 };
const Color IconData[IconWidth * IconHeight]{
{ 255, 255, 255, 0 },{ 11, 11, 11, 0 },{ 11, 11, 11, 0 },{ 7, 7, 7, 0 },{ 18, 18, 18, 4 },{ 54, 54, 54, 166 },{ 52, 52, 52, 227 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 49, 49, 49, 226 },{ 58, 58, 58, 176 },{ 66, 66, 66, 13 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 55, 55, 55, 0 },{ 55, 55, 55, 0 },{ 56, 56, 56, 0 },{ 54, 54, 54, 139 },{ 50, 50, 50, 255 },{ 52, 52, 52, 110 },{ 53, 53, 53, 119 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 56, 56, 56, 121 },{ 56, 56, 56, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 53, 53, 53, 119 },{ 54, 54, 54, 106 },{ 48, 48, 48, 239 },{ 43, 43, 43, 151 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 49, 49, 49, 0 },{ 49, 49, 49, 0 },{ 49, 49, 49, 0 },{ 50, 50, 50, 176 },{ 51, 51, 51, 166 },{ 51, 51, 51, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 58, 58, 58, 0 },{ 62, 62, 62, 0 },{ 48, 48, 48, 0 },{ 49, 49, 49, 0 },{ 63, 63, 63, 0 },{ 58, 58, 58, 0 },{ 52, 52, 52, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 54, 54, 54, 0 },{ 55, 55, 55, 0 },{ 53, 53, 53, 0 },{ 44, 44, 44, 0 },{ 46, 46, 46, 146 },{ 53, 53, 53, 203 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 48, 48, 48, 0 },{ 47, 47, 47, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 52, 52, 52, 0 },{ 56, 56, 56, 0 },{ 47, 47, 47, 0 },{ 14, 14, 14, 0 },{ 13, 13, 13, 0 },{ 12, 12, 12, 0 },{ 16, 16, 16, 0 },{ 46, 46, 46, 0 },{ 55, 55, 55, 0 },{ 54, 54, 54, 0 },{ 53, 53, 53, 0 },{ 52, 52, 52, 0 },{ 54, 54, 54, 0 },{ 53, 53, 53, 0 },{ 44, 44, 44, 0 },{ 39, 39, 39, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 51, 51, 51, 0 },{ 49, 49, 49, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 172 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 55, 55, 55, 0 },{ 42, 42, 42, 0 },{ 7, 7, 7, 0 },{ 0, 0, 0, 0 },{ 18, 18, 18, 24 },{ 17, 17, 17, 24 },{ 0, 0, 0, 0 },{ 11, 11, 11, 0 },{ 51, 51, 51, 0 },{ 55, 55, 55, 0 },{ 52, 52, 52, 29 },{ 50, 50, 50, 20 },{ 50, 50, 50, 0 },{ 46, 46, 46, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 51, 51, 51, 2 },{ 52, 52, 52, 6 },{ 48, 48, 48, 0 },{ 49, 49, 49, 178 },{ 52, 52, 52, 174 },{ 51, 51, 51, 0 },{ 51, 51, 51, 1 },{ 45, 45, 45, 0 },{ 46, 46, 46, 0 },{ 53, 53, 53, 0 },{ 52, 52, 52, 167 },{ 53, 53, 53, 169 },{ 53, 53, 53, 0 },{ 49, 49, 49, 0 },{ 50, 50, 50, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 197 },{ 49, 49, 49, 128 },{ 48, 48, 48, 0 },{ 47, 47, 47, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 52, 52, 52, 106 },{ 51, 51, 51, 255 },{ 52, 52, 52, 226 },{ 51, 51, 51, 247 },{ 51, 51, 51, 247 },{ 50, 50, 50, 255 },{ 51, 51, 51, 139 },{ 57, 57, 57, 0 },{ 56, 56, 56, 0 },{ 52, 52, 52, 0 },{ 52, 52, 52, 181 },{ 52, 52, 52, 183 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 207 },{ 51, 51, 51, 133 },{ 52, 52, 52, 0 },{ 49, 49, 49, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 54, 54, 54, 34 },{ 51, 51, 51, 83 },{ 50, 50, 50, 46 },{ 50, 50, 50, 199 },{ 51, 51, 51, 196 },{ 51, 51, 51, 60 },{ 51, 51, 51, 44 },{ 52, 52, 52, 0 },{ 51, 51, 51, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 174 },{ 51, 51, 51, 176 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 56, 56, 56, 0 },{ 49, 49, 49, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 170 },{ 51, 51, 51, 168 },{ 52, 52, 52, 0 },{ 50, 50, 50, 0 },{ 48, 48, 48, 0 },{ 49, 49, 49, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 174 },{ 51, 51, 51, 176 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 53, 53, 53, 0 },{ 47, 47, 47, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 174 },{ 51, 51, 51, 176 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 47, 47, 47, 0 },{ 48, 48, 48, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 174 },{ 51, 51, 51, 176 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 48, 48, 48, 0 },{ 48, 48, 48, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 174 },{ 51, 51, 51, 176 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 48, 48, 48, 0 },{ 48, 48, 48, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 174 },{ 51, 51, 51, 176 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 48, 48, 48, 0 },{ 48, 48, 48, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 174 },{ 51, 51, 51, 176 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 48, 48, 48, 0 },{ 48, 48, 48, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 177 },{ 51, 51, 51, 178 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 47, 47, 47, 0 },{ 48, 48, 48, 0 },{ 48, 48, 48, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 0 },{ 50, 50, 50, 179 },{ 51, 51, 51, 181 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 53, 53, 53, 0 },{ 47, 47, 47, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 50, 50, 50, 0 },{ 52, 52, 52, 0 },{ 55, 55, 55, 0 },{ 54, 54, 54, 0 },{ 54, 54, 54, 56 },{ 51, 51, 51, 57 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 57, 57, 57, 0 },{ 49, 49, 49, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 170 },{ 51, 51, 51, 168 },{ 52, 52, 52, 0 },{ 50, 50, 50, 0 },{ 49, 49, 49, 0 },{ 53, 53, 53, 0 },{ 55, 55, 55, 0 },{ 54, 54, 54, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 55, 55, 55, 34 },{ 51, 51, 51, 83 },{ 50, 50, 50, 46 },{ 50, 50, 50, 199 },{ 51, 51, 51, 196 },{ 51, 51, 51, 60 },{ 51, 51, 51, 44 },{ 52, 52, 52, 0 },{ 52, 52, 52, 0 },{ 55, 55, 55, 0 },{ 54, 54, 54, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 203 },{ 50, 50, 50, 131 },{ 51, 51, 51, 0 },{ 48, 48, 48, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 52, 52, 52, 106 },{ 51, 51, 51, 255 },{ 52, 52, 52, 226 },{ 51, 51, 51, 247 },{ 51, 51, 51, 247 },{ 50, 50, 50, 255 },{ 52, 52, 52, 139 },{ 57, 57, 57, 0 },{ 55, 55, 55, 0 },{ 55, 55, 55, 0 },{ 54, 54, 54, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 207 },{ 51, 51, 51, 133 },{ 52, 52, 52, 0 },{ 49, 49, 49, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 51, 51, 51, 2 },{ 52, 52, 52, 6 },{ 48, 48, 48, 0 },{ 49, 49, 49, 178 },{ 52, 52, 52, 174 },{ 51, 51, 51, 0 },{ 51, 51, 51, 1 },{ 50, 50, 50, 0 },{ 50, 50, 50, 0 },{ 51, 51, 51, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 197 },{ 49, 49, 49, 128 },{ 48, 48, 48, 0 },{ 47, 47, 47, 0 },{ 42, 42, 42, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 51, 51, 51, 0 },{ 49, 49, 49, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 172 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 51, 51, 51, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 52, 52, 52, 0 },{ 52, 52, 52, 0 },{ 52, 52, 52, 0 },{ 52, 52, 52, 0 },{ 53, 53, 53, 29 },{ 54, 54, 54, 20 },{ 54, 54, 54, 0 },{ 47, 47, 47, 0 },{ 41, 41, 41, 0 },{ 42, 42, 42, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 48, 48, 48, 0 },{ 47, 47, 47, 0 },{ 47, 47, 47, 0 },{ 50, 50, 50, 176 },{ 52, 52, 52, 171 },{ 51, 51, 51, 0 },{ 52, 52, 52, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 56, 56, 56, 0 },{ 56, 56, 56, 0 },{ 53, 53, 53, 0 },{ 44, 44, 44, 0 },{ 39, 39, 39, 0 },{ 46, 46, 46, 151 },{ 53, 53, 53, 195 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 49, 49, 49, 0 },{ 49, 49, 49, 0 },{ 49, 49, 49, 0 },{ 50, 50, 50, 176 },{ 51, 51, 51, 166 },{ 51, 51, 51, 0 },{ 54, 54, 54, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 53, 53, 53, 0 },{ 55, 55, 55, 0 },{ 53, 53, 53, 0 },{ 44, 44, 44, 0 },{ 46, 46, 46, 146 },{ 53, 53, 53, 203 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 55, 55, 55, 0 },{ 55, 55, 55, 0 },{ 56, 56, 56, 0 },{ 54, 54, 54, 139 },{ 50, 50, 50, 255 },{ 52, 52, 52, 111 },{ 53, 53, 53, 119 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 52, 52, 52, 121 },{ 53, 53, 53, 119 },{ 54, 54, 54, 106 },{ 48, 48, 48, 239 },{ 43, 43, 43, 151 },{ 255, 255, 255, 0 },
{ 255, 255, 255, 0 },{ 11, 11, 11, 0 },{ 11, 11, 11, 0 },{ 7, 7, 7, 0 },{ 18, 18, 18, 4 },{ 54, 54, 54, 166 },{ 52, 52, 52, 227 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 51, 51, 51, 209 },{ 49, 49, 49, 226 },{ 58, 58, 58, 176 },{ 66, 66, 66, 13 },{ 255, 255, 255, 0 },
};
CPackageRegistration::CPackageRegistration(IPackage *package) :
CPackageControl(package, L"coolwidgets"),
m_cIcon{ IconWidth, IconHeight, IconData }
{
// Supported platforms
// Header files
AddHeaderFile(L"CoolWidgets/Controls/Registration/Registration.h");
}
IControl *CPackageRegistration::Create(IPropertyList **properties, IEventList **events)
{
IControl *result{ new CRegistration() };
if (properties)
*properties = new CRegistrationPropertyList(getPackage(), result, this);
if (events)
*events = new CRegistrationEventList(getPackage(), result, this);
if (properties || events)
getPackage()->QueryService()->NotifyOnCreateControl(result, this, *properties, *events);
return result;
}
IPropertyList *CPackageRegistration::CreatePropertyList(IControl *control)
{
if (isInstance(control))
return new CRegistrationPropertyList(getPackage(), control, this);
return nullptr;
}
IEventList *CPackageRegistration::CreateEventList(IControl *control)
{
if (isInstance(control))
return new CRegistrationEventList(getPackage(), control, this);
return nullptr;
}
String CPackageRegistration::getCategory()
{
return L"CoolWidgets";
}
String CPackageRegistration::getClassName()
{
return L"Registration";
}
String CPackageRegistration::getTitle()
{
return L"Registration";
}
String CPackageRegistration::getDescription()
{
return L"Registration widget";
}
String CPackageRegistration::getReferenceUrl()
{
return L"";
}
const Image *CPackageRegistration::getIcon()
{
return &m_cIcon;
}
bool CPackageRegistration::isVisible()
{
return true;
}
bool CPackageRegistration::isDisplayIcon()
{
return false;
}
bool CPackageRegistration::isInstance(IControl *control)
{
return cast<CRegistration*>(control) != nullptr;
}
bool CPackageRegistration::isTransformable()
{
return false;
}
}
}
It's a very good idea to have unique icon for a widget. Although Form Builder allows a widget to have no icon (in this case default icon will be displayed), we will show how to add an icon so a user could distinguish our widget from others on the Form Builder Controls and Components section. To define an icon for a widget, its package control/component should return pointer to icon in form of Image in method getIcon()
. As you might have noticed we defined const Image m_cIcon;
member in our package control class declaration. That is where the icon will be stored. We marked icon as a constant icon. It means it will exists always and will never be destroyed. It is required to icon be used many times without having multiple copies in memory. The Image class is designed to handle such constant images properly. To create such an image, it should be supplied with constant image data which is just an array of color values. So, at the beginning we defined three constants: image width, image height and array of colors. The image is in the column-major format. It means that it should be colors of first column (x = 0) in the array, then colors of second column (x = 1) and so on. You can easy create image to array converter by your own using Nitisa. You can, for instance, make a form with Choose file button and Memo widget. In OnClick notification of the button you may show picture selection dialog using SysOpenPictureDialog widget and, if picture is selected, just read it from file using IPicture interface pointer of Application->Picture
member to load a picture in form of Image and convert it to the color array and put text into Memo widget.
In the constructor of the package control we call parent class constructor (usual process when deriving one class from another) and supply m_sIcon
member with constant icon data and its dimensions. Parent constructor has two arguments: package instance, which is also an argument of our class and thus we just put there what we get, and namespace of the package, which is the "coolwidgets"
. In the constructor body there are two parameter to set. The first one is a list of supported platforms and the second one is a list of header files needed to use the widget. If widget is available for any platform or its source code doesn't have any platform dependent function calls, the list should be empty. Our widget is available for any platform, so we don't do anything with the list. If our widget was platform dependent we would used method AddPlatform()
to add information about all platform the widget available on. In the header file list we usually add at least the header file where widget class is declared. So we did it.
The most interesting method Create()
of the class creates widget class instance and property and/or event lists for it if they are required. So, the implementation of the method is not so difficult. We create widget instance with new operator and store it in the result
variable. If properties
variable has non-empty pointer, it means a caller wants us to create the property list as well. So we create the widget property list and put its pointer into the properties
variable. The same is true for events as well. And final and very important thing is, if either property or event list was created, we need to notify package of the widget that the widget and its properties and/or events where created. We do it by calling package (we get it in constructor as argument, put into parent class constructor, and use getPackage()
method to get it) service (which, as usually, can be get using QueryService()
method) NotifyOnCreateControl()
method. Package needs to know which object were created and destroyed in order to provide some search functionality to Form Builder. You can find needed methods on the IPackageService reference page.
The next two methods CreatePropertyList()
and CreateEventList()
are needed to create property and event lists for existing widget. All you need to do in them is to create needed property or event list but first you need to check whether the control passed to a method is really a widget class instance the package control describes. This is done by isInstance()
method which will be described shortly.
getCategory()
method should return category or tab name where widget will be placed in the Form Builder. The getClassName()
method should return widget class name without C. The getTitle()
method should return widget title which will be displayed in a popup hint when mouse pointer is over the widget button in Controls and Components section of the Form Builder. The getDescription()
method returns widget description. The getReferenceURL()
method returns URL to the widget documentation page if there is any or just an empty string. In the getIcon()
method we return pointer to the member storing widget icon. It is allowed to return nullptr if widget has no icon and Form Builder will show default predefined icon in such a case.
Method isVisible()
returns boolean value indicating whether widget should be placed in the Controls and Components section of the Form Builder at all. For most widgets this method returns true but there are some that should not be displayed there and thus user won't be able to create a widget in common way. One example is TabSheet widget. This widget can only be placed on the PageControl widget and can be created only with help of Hierarchy Editor.
isDisplayIcon()
method for most widgets returns false. It means there will be no additional icon displayed for a widget on a form in the Form Builder. Sometimes such icon is required. For example, PopupMenu widget is initially has no visual representation on a form and to be able to select it and edit its properties and events an additional icon should be shown on a form in the Form Builder when PopupMenu is placed on a form.
The isInstance()
method should check if the argument is an instance of the widget described by this package control. To do that all is needed is to try to convert control
argument to needed widget class pointer using helper template function cast() and check whether result is nullptr (conversion was unsuccessful) or not (conversion was successful).
And the final method we have here is the isTransformable()
method. If it returns false, the user won't be able to change size and rotation of a widget on a form in Form Builder using mouse. Size changing and rotation markers will not be displayed for widget at all.
There are also another methods you may need to overwrite to describe your another widgets, but not for this one. You can find them in the reference pages on the corresponding package classes with default implementation and reference pages of their ancestors.
So now we have description of our widget for Form Builder. All the description of a widget is almost always made of three classes: property list, event list and package widget class.
In the same way you need to describe another entities: components, forms, list items, and so on.
Before we start creating a project for a package dll we need to make two more classes. This will be a description of entire package and its service. The description of a package should implement IPackage interface and it has no default implementation so we need to implement all the methods ourselves. So, create header file called PackageCoolWidgets.h in the Package directory of the project and put following package class declaration into it. Also don't forget to add include of this file into CoolWidgets.h.
#pragma once
#include "Nitisa/Core/PlatformVersion.h"
#include "Nitisa/Core/Strings.h"
#include "Nitisa/Package/Interfaces/IPackage.h"
#include <vector>
namespace nitisa
{
class IApplication;
class IComponent;
class IControl;
class IEventList;
class IListItem;
class IPackageComponent;
class IPackageControl;
class IPackageForm;
class IPackageListItem;
class IPackageRenderer;
class IPackageService;
class IPackagePropertyHandler;
class IPropertyList;
class IStyle;
namespace coolwidgets
{
class CPackageCoolWidgetsService;
class CPackageCoolWidgets :public virtual IPackage
{
friend CPackageCoolWidgetsService;
private:
struct CONTROL
{
IControl *Control;
IPackageControl *PackageControl;
IPropertyList *Properties;
IEventList *Events;
};
std::vector<IPackageControl*> m_aControls;
std::vector<CONTROL> m_aCreatedControls;
bool m_bCreatedControlsSorted;
IPackageService *m_pService;
CONTROL m_sSearchControl;
void SortCreatedControls();
template<class Class, class Interface> Class *Create(std::vector<Interface*> &list)
{
Class *result{ new Class(this) };
list.push_back(result);
return result;
}
public:
// Common information
String getVendor() override;
String getName() override;
String getVersion() override;
String getDescription() override;
String getVendorUrl() override;
String getReferenceUrl() override;
String getLicenseText() override;
LicenseType getLicense() override;
bool hasStaticLinking() override;
bool hasDynamicLinking() override;
bool hasSourceCode() override;
int getDependencyCount() override;
Dependency getDependency(const int index) override;
// Supported platforms
int getPlatformCount() override;
PlatformVersion getPlatform(const int index) override;
// Components
int getComponentCount() override;
IPackageComponent *getComponent(const int index) override;
IPackageComponent *getComponent(const String &class_name) override;
IPackageComponent *getComponent(IComponent *component) override;
// Created components
int getCreatedComponentCount() override;
IComponent *getCreatedComponent(const int index) override;
bool getCreatedComponent(const int index, IComponent **component, IPackageComponent **package_component, IPropertyList **properties, IEventList **events) override;
// Controls
int getControlCount() override;
IPackageControl *getControl(const int index) override;
IPackageControl *getControl(const String &class_name) override;
IPackageControl *getControl(IControl *control) override;
// Created controls
int getCreatedControlCount() override;
IControl *getCreatedControl(const int index) override;
bool getCreatedControl(const int index, IControl **control, IPackageControl **package_control, IPropertyList **properties, IEventList **events) override;
// List items
int getListItemCount() override;
IPackageListItem *getListItem(const int index) override;
IPackageListItem *getListItem(const String &class_name) override;
IPackageListItem *getListItem(IListItem *listitem) override;
// Forms
int getFormCount() override;
IPackageForm *getForm(const int index) override;
IPackageForm *getForm(const String &class_name) override;
// Renderers
int getRendererCount() override;
IPackageRenderer *getRenderer(const int index) override;
IPackageRenderer *getRenderer(const String &class_name) override;
// Property handlers
int getPropertyHandlerCount() override;
IPackagePropertyHandler *getPropertyHandler(const int index) override;
IPackagePropertyHandler *getPropertyHandler(const String &name) override;
// Styles
int getStyleCount() override;
IPackageStyle *getStyle(const int index) override;
IPackageStyle *getStyle(const String &class_name) override;
IPackageStyle *getStyle(IStyle *style) override;
void Release() override;
IPackageService *QueryService() override;
CPackageCoolWidgets();
virtual ~CPackageCoolWidgets();
static IPackage *Create(IApplication *application);
};
}
}
In the beginning we added include of all header files needed for the class as well as forward declarations of all interfaces which are used by it. Although it is not necessary as the needed header files and forward declarations are already included and defined in the Nitisa/Package/Interfaces/IPackage.h, it is a good idea to do that to avoid relying on any assumptions about what is included and forward declared in the file(s) we include.
Next we defined forward declaration of package service class and make it friend to the package class at the beginning of the package class.
In the private section of the class we defined structure CONTROL
which describes widget. We will store information about every instance of our widget created in the Form Builder. For that we will use this structure and m_aCreatedControls
member. To speed up search we will sort m_aCreatedControls
array. For that we need m_bCreatedControlsSorted
member indicating whether the array is sorted or not and method SortCreatedControls()
for sorting. Also we are going to use member m_sSearchControl
to search needed control information in the sorted array m_aCreatedControls
.
The m_aControls
array will store instances of package controls describing controls of our package. We have only one control called Registration in the package so this array will have only one entry with CPackageRegistration
instance pointer in it.
In the m_pService
member the pointer to the package service instance will be stored.
We also defined helper template method Create()
. This method can be used to create package control description class instance and put it into specified array of such instances. This method, without any changes, can also be used to add components, forms, list items and other package entities into corresponding arrays. In our example we deal only with controls and you will se how this method is used in the package class constructor implementation.
In the public section we just declared overridden interface methods which we will implement. And at the end of the class we added constructor and destructor declaration and helper static method Create()
. The last one will be used to create the class instance.
Lets now implement all those methods we defined in the package class. Create source code file called PackageCoolWidgets.cpp in the Package directory of the project and put following code into it.
#include "stdafx.h"
namespace nitisa
{
namespace coolwidgets
{
#pragma region Constructor & destructor
CPackageCoolWidgets::CPackageCoolWidgets() :
m_bCreatedControlsSorted{ true },
m_pService{ nullptr }
{
Application->Editor->Register(Create<CPackageRegistration>(m_aControls));
}
CPackageCoolWidgets::~CPackageCoolWidgets()
{
for (auto pos = m_aControls.begin(); pos != m_aControls.end(); pos++)
(*pos)->Release();
if (m_pService)
m_pService->Release();
}
void CPackageCoolWidgets::Release()
{
delete this;
}
#pragma endregion
#pragma region Common information
String CPackageCoolWidgets::getVendor()
{
return L"I am";
}
String CPackageCoolWidgets::getName()
{
return L"CoolWidgets";
}
String CPackageCoolWidgets::getVersion()
{
return L"1.0.0";
}
String CPackageCoolWidgets::getDescription()
{
return L"Tutorial package";
}
String CPackageCoolWidgets::getVendorUrl()
{
return L"";
}
String CPackageCoolWidgets::getReferenceUrl()
{
return L"";
}
String CPackageCoolWidgets::getLicenseText()
{
return L"This package is free for use in any application until there are no modifications of the source code";
}
IPackage::LicenseType CPackageCoolWidgets::getLicense()
{
return LicenseType::Free;
}
bool CPackageCoolWidgets::hasStaticLinking()
{
return true;
}
bool CPackageCoolWidgets::hasDynamicLinking()
{
return true;
}
bool CPackageCoolWidgets::hasSourceCode()
{
return true;
}
int CPackageCoolWidgets::getDependencyCount()
{
return 0;
}
IPackage::Dependency CPackageCoolWidgets::getDependency(const int index)
{
return Dependency{ L"", L"", L"" };
}
#pragma endregion
#pragma region Supported platforms
int CPackageCoolWidgets::getPlatformCount()
{
return 0;
}
PlatformVersion CPackageCoolWidgets::getPlatform(const int index)
{
return PlatformVersion{ PlatformType::None };
}
#pragma endregion
#pragma region Components
int CPackageCoolWidgets::getComponentCount()
{
return 0;
}
IPackageComponent *CPackageCoolWidgets::getComponent(const int index)
{
return nullptr;
}
IPackageComponent *CPackageCoolWidgets::getComponent(const String &class_name)
{
return nullptr;
}
IPackageComponent *CPackageCoolWidgets::getComponent(IComponent *component)
{
return nullptr;
}
#pragma endregion
#pragma region Created components
int CPackageCoolWidgets::getCreatedComponentCount()
{
return 0;
}
IComponent *CPackageCoolWidgets::getCreatedComponent(const int index)
{
return nullptr;
}
bool CPackageCoolWidgets::getCreatedComponent(const int index, IComponent **component, IPackageComponent **package_component, IPropertyList **properties, IEventList **events)
{
return false;
}
#pragma endregion
#pragma region Controls
int CPackageCoolWidgets::getControlCount()
{
return (int)m_aControls.size();
}
IPackageControl *CPackageCoolWidgets::getControl(const int index)
{
if (index >= 0 && index < (int)m_aControls.size())
return m_aControls[index];
return nullptr;
}
IPackageControl *CPackageCoolWidgets::getControl(const String &class_name)
{
for (auto pos : m_aControls)
if (pos->getClassName() == class_name)
return pos;
return nullptr;
}
IPackageControl *CPackageCoolWidgets::getControl(IControl *control)
{
SortCreatedControls();
m_sSearchControl.Control = control;
auto pos{ std::lower_bound(m_aCreatedControls.begin(), m_aCreatedControls.end(), m_sSearchControl, [](const CONTROL &a, const CONTROL &b) {return a.Control < b.Control; }) };
if (pos != m_aCreatedControls.end() && pos->Control == control)
return pos->PackageControl;
return nullptr;
}
void CPackageCoolWidgets::SortCreatedControls()
{
if (!m_bCreatedControlsSorted)
{
std::sort(m_aCreatedControls.begin(), m_aCreatedControls.end(), [](const CONTROL &a, const CONTROL &b) {return a.Control < b.Control; });
m_bCreatedControlsSorted = true;
}
}
#pragma endregion
#pragma region Created controls
int CPackageCoolWidgets::getCreatedControlCount()
{
return (int)m_aCreatedControls.size();
}
IControl *CPackageCoolWidgets::getCreatedControl(const int index)
{
if (index >= 0 && index < (int)m_aCreatedControls.size())
{
SortCreatedControls();
return m_aCreatedControls[index].Control;
}
return nullptr;
}
bool CPackageCoolWidgets::getCreatedControl(const int index, IControl **control, IPackageControl **package_control, IPropertyList **properties, IEventList **events)
{
if (index >= 0 && index < (int)m_aCreatedControls.size())
{
SortCreatedControls();
*control = m_aCreatedControls[index].Control;
*package_control = m_aCreatedControls[index].PackageControl;
*properties = m_aCreatedControls[index].Properties;
*events = m_aCreatedControls[index].Events;
return true;
}
return false;
}
#pragma endregion
#pragma region Listitems
int CPackageCoolWidgets::getListItemCount()
{
return 0;
}
IPackageListItem *CPackageCoolWidgets::getListItem(const int index)
{
return nullptr;
}
IPackageListItem *CPackageCoolWidgets::getListItem(const String &class_name)
{
return nullptr;
}
IPackageListItem *CPackageCoolWidgets::getListItem(IListItem *listitem)
{
return nullptr;
}
#pragma endregion
#pragma region Forms
int CPackageCoolWidgets::getFormCount()
{
return 0;
}
IPackageForm *CPackageCoolWidgets::getForm(const int index)
{
return nullptr;
}
IPackageForm *CPackageCoolWidgets::getForm(const String &class_name)
{
return nullptr;
}
#pragma endregion
#pragma region Renderers
int CPackageCoolWidgets::getRendererCount()
{
return 0;
}
IPackageRenderer *CPackageCoolWidgets::getRenderer(const int index)
{
return nullptr;
}
IPackageRenderer *CPackageCoolWidgets::getRenderer(const String &class_name)
{
return nullptr;
}
#pragma endregion
#pragma region Property handlers
int CPackageCoolWidgets::getPropertyHandlerCount()
{
return 0;
}
IPackagePropertyHandler *CPackageCoolWidgets::getPropertyHandler(const int index)
{
return nullptr;
}
IPackagePropertyHandler *CPackageCoolWidgets::getPropertyHandler(const String &name)
{
return nullptr;
}
#pragma endregion
#pragma region Styles
int CPackageCoolWidgets::getStyleCount()
{
return 0;
}
IPackageStyle *CPackageCoolWidgets::getStyle(const int index)
{
return nullptr;
}
IPackageStyle *CPackageCoolWidgets::getStyle(const String &class_name)
{
return nullptr;
}
IPackageStyle *CPackageCoolWidgets::getStyle(IStyle *style)
{
return nullptr;
}
#pragma endregion
#pragma region Helpers
IPackageService *CPackageCoolWidgets::QueryService()
{
if (!m_pService)
m_pService = new CPackageCoolWidgetsService(this);
return m_pService;
}
IPackage *CPackageCoolWidgets::Create(IApplication *application)
{
if (application)
Application = application;
return new CPackageCoolWidgets();
}
#pragma endregion
}
}
As you might know the framework has one global variable called Application
which stores an instance of IApplication interface and provides access to some useful features of the framework. One of its members is called Editor
. When this member is nullptr it means application is a regular application. On the other hand if this member is not empty it means application is the Form Builder. In the package class we need to register all the package provides (components, controls, forms, etc.) in the Application->Editor
, which is pointer to the IEditor interface instance. That is what we did in the class constructor body (we actually create package control describing our Registration widget and added it to the internal storage m_aControls
, using Create()
template method, and registered it using Register()
method of the Application->Editor
). If we had more controls or another entities, like components, we would do the same for them as well. Additionally we set default values to m_bCreatedControlsSorted
and m_pService
members of the class. The first one means the created controls list is not sorted yet and the second one means we haven't created package service yet.
In the destructor we do cleaning up. We delete all created package control descriptions and destroy package service if it was created. We do not need to destroy controls in the m_aCreatedControls
array as their lifecycle is managed by a form where they are located (in our case it will be the form of the Form Builder). The Release()
method just destroys the class instance.
Methods from the Common information block return information about the package and its creator. getVendor()
method return creator name (company or individual name) and also can be empty. getName()
method returns name of the package. getVersion()
method returns package version in any format you wish. getDescription()
method returns short description of the package and can return empty string. If you have your own portal or a portal of your company you may return its URL in the getVendorUrl()
method. The getReferenceUrl()
method may return URL of the package documentation if there is one. The getLicenseText()
method returns license information/agreement which also may be empty. The getLicense()
method returns type of license information in form of IPackage::LicenseType
. The hasStaticLinking()
method returns boolean value indicating whether the package can be statically linked with applications. The hasDynamicLinking()
method returns boolean value indicating whether the package can be dynamically linked with applications. The hasSourceCode()
method returns boolean value indicating whether package source code is available (it should be true if you distribute your package with source code or false if you distribute it as binary file(s) only). The getDependencyCount()
method should return count of packages your package depends on and the getDependency()
method should return dependency information by its index. If your package doesn't depend on any other packages (most common situation) the first method should return 0. If specified index to the second method is out of bounds it returns dummy (empty) dependency information.
Methods getPlatformCount()
and getPlatform()
return information about supported platforms of the package. If your package is available for all platforms (it has no platform-dependent code or such a code exists for all platforms) this list can be empty.
Methods from the Components block return information about package components available in the package. The getComponentCount
returns count of package components available and others methods return package component information by either index, name or component class instance.
Methods from the Created components block return information about created components (they are widgets; the previous block is for descriptions - do not be confused). The getCreatedComponentCount()
returns number of component instances created (and not yet destroyed). The getCreatedComponent()
overloaded method returns either a created component by its index or full information (created component in the component argument, its package description in the package_component argument, its property list in the properties argument and its event list in the events argument) about created component.
Blocks Controls and Created controls do just the same as previous two blocks but they are for controls. In our package we don't have components so methods from those blocks are just dummy ones. Our package does have control(s) (it has Registration control we created early) so methods for controls have some logic which is actually pretty simple. We have m_aControls
array with list of package control descriptions. So the method getControlCount()
just returns the size of that list of a number of descriptions we have there. Overloaded method getControl()
returns package control description either by index, by class name or by widget class instance. The last one also has optimization example in form of sorting array of created controls. Code of methods for created controls is pretty clear and you can easy understand what it does without any further explanations.
Listitems block is needed for list items which is clear from its name and do the same as corresponding blocks for components and controls. The same is true of Forms, Renderers, Property handlers and Styles blocks. They all return description of corresponding entities if there are any. In our case they are all have dummy methods as our package doesn't have any of these entities yet (we will add property handler here in the next tutorial).
QueryService()
method returns package service. It creates service first if it was not created yet.
The static helper method Create(IApplication *application)
assign application manager instance to global variable Application
, create and return package class instance. We will use this method a little bit later in this tutorial.
Lets now create package service. Add new file with name PackageCoolWidgetsService.h to the Package directory of the project and put following service class declaration code into it. Also add include of this file into CoolWidgets.h header file.
#pragma once
#include "Nitisa/Package/Interfaces/IPackageService.h"
namespace nitisa
{
class IComponent;
class IControl;
class IEventList;
class IPackageComponent;
class IPackageControl;
class IPropertyList;
namespace coolwidgets
{
class CPackageCoolWidgets;
class CPackageCoolWidgetsService :public virtual IPackageService
{
private:
CPackageCoolWidgets *m_pPackage;
public:
void NotifyOnCreateComponent(IComponent *component, IPackageComponent *package_component, IPropertyList *properties, IEventList *events) override;
void NotifyOnCreateControl(IControl *control, IPackageControl *package_control, IPropertyList *properties, IEventList *events) override;
void NotifyOnDestroyComponent(IComponent *component) override;
void NotifyOnDestroyControl(IControl *control) override;
void Release() override;
CPackageCoolWidgetsService(CPackageCoolWidgets *package);
virtual ~CPackageCoolWidgetsService() = default;
};
}
}
The declaration is pretty simple. We just redeclared interface methods to implement them, declared constructor and make destructor virtual.
Create new source code file with name PackageCoolWidgetsService.cpp in the Package directory and put following service class implementation code into it.
#include "stdafx.h"
namespace nitisa
{
namespace coolwidgets
{
#pragma region Constructor & destructor
CPackageCoolWidgetsService::CPackageCoolWidgetsService(CPackageCoolWidgets *package) :
m_pPackage{ package }
{
}
void CPackageCoolWidgetsService::Release()
{
delete this;
}
#pragma endregion
#pragma region Notifications
void CPackageCoolWidgetsService::NotifyOnCreateComponent(IComponent *component, IPackageComponent *package_component, IPropertyList *properties, IEventList *events)
{
}
void CPackageCoolWidgetsService::NotifyOnCreateControl(IControl *control, IPackageControl *package_control, IPropertyList *properties, IEventList *events)
{
m_pPackage->m_aCreatedControls.push_back(CPackageCoolWidgets::CONTROL{ control, package_control, properties, events });
m_pPackage->m_bCreatedControlsSorted = false;
}
void CPackageCoolWidgetsService::NotifyOnDestroyComponent(IComponent *component)
{
}
void CPackageCoolWidgetsService::NotifyOnDestroyControl(IControl *control)
{
for (auto pos = m_pPackage->m_aCreatedControls.begin(); pos != m_pPackage->m_aCreatedControls.end(); pos++)
if (pos->Control == control)
{
m_pPackage->m_aCreatedControls.erase(pos);
break;
}
}
#pragma endregion
}
}
In the constructor we just store pointer to package class instance for further use and Release()
method just destroys the service class instance.
We have no components in the package so we left NotifyOnCreateComponent()
and NotifyOnDestroyComponent()
methods empty. If the package had components these methods would be very similar to the ones for controls which we will describe in the next paragraph.
The NotifyOnCreateControl()
method is called (by Form Builder) when control is created. All we need to do in this method is to add widget instance to the list of created controls and, as we use sorting of that list for faster search, we mark the list as unsorted. When control is being destroyed the NotifyOnDestroyControl()
notification is called. In this notification we search the control being destroyed in the list of created controls and just remove it from the list. We don't need to mark list as unsorted as the removing item from an array does't affect its elements order.
Now we have complete description of our package.
The only thing remains to be done is to build our package in form of dll so that Form Builder could load and use it. To do that proceed with following steps.
All properties we changed after adding main.cpp and exports.def files just copy settings from, lets say, Package.Standard project of the Nitisa solution. You don't need to remember them, just copy all setting from any Package.* project of the framework and that's all.
In the main.cpp file put following code.
#include "CoolWidgets/Package/PackageCoolWidgets.h"
#include <Windows.h>
#pragma comment(lib, "Nitisa.lib")
#pragma comment(lib, "Standard.lib")
#pragma comment(lib, "Platform.lib")
#pragma comment(lib, "CoolWidgets.lib")
#pragma comment(lib, "opengl32.lib")
namespace nitisa
{
class IApplication;
class IPackage;
}
BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID)
{
return TRUE;
}
nitisa::IPackage* __stdcall GetPackage(nitisa::IApplication *application)
{
return nitisa::coolwidgets::CPackageCoolWidgets::Create(application);
}
Here, in the first line, we include header file with the declaration of the package description class. Next we include standard Windows header file as we need its declaration to write standard dll main function.
We added linking with Nitisa core, Standard package, Platform package and our package library files as well as OpenGL library one. We need core library as our package uses its classes and functions directly. We need others as well as they are being used indirectly. Just remember that you need to add link with your package library and those four libraries as well each time you create dll project for your package. These linking can also be made in project settings.
Next we made forward declarations of the IApplication
and IPackage
interfaces as we need them in the GetPackage()
function.
After forward declarations we put standard DllMain()
function. We do nothing in it, just return TRUE.
And finally we put GetPackage()
function. We export this function later in the exports.def module definition file. Its declaration is standard and cannot be changed. It should have one argument with type IApplication pointer and it should return pointer to IPackage interface. The Form Builder calls this function to get package description from the dll file. It puts its own application manager instance into application
argument and the package should use it rather than creating its own application manager instance. So, in the function body we just create our package instance (using static method Create()
we added to the package class earlier) and return it.
Put following two lines into exports.def module definition file.
EXPORTS
GetPackage
By this two lines we just export GetPackage()
function from the dll file so that it can be used by Form Builder.
In the Project Dependencies manager of the Visual Studio set that the Package.CoolWidgets project depends on Nitisa, Platform, Standard and CoolWidgets projects (choose Windows project where there are several of them). In order to don't recall each time after changes that you need to rebuild the project before running FormBuilder application to debug it, you may also mark the FormBuilder as depending on Package.CoolWidgets one.
If you now set FormBuilder project as start-up project, build and run it you will find a new tab called CoolWidgets in the Components and Controls section of the Form Builder with our only widget Registration on it as shown on the screenshot below. You can add and manage its properties and events as usual.
As you can see creating widget description for Form Builder is not so hard. Creating widget itself is much more complicated task. All you need is to create description of widget in form of package control with its property and event lists and a package description together with its service and put it all in a form of dll file so that Form Builder could find and load it.
If you look at the properties and events of the Registration widget in the Form Builder, you will find that changing some of the properties, like Size and Align, has no effect and some events, like OnChildShow and OnHotkey, have no sense and never called. If you recall we just used default set of properties and events defined for control. In the next tutorial we will show how to change property and event lists of a widget and how to create completely custom properties and handlers for them.
You can find project Package.CoolWidgets in the latest release of the Nitisa framework in Tutorials\Packages
folder of the Nitisa solution. To build it you have to select DebugRT configuration and x86 platform. The project dll file will be placed in the DebugRT directory of the solution to avoid conflict with your code you created following this tutorial.