Creating a Form


The best way of creating a form is using Form Builder. But sometimes you may want to do it by yourself or, if you work on the platform unsupported by the Form Builder, couldn't use the Form Builder. This article describes how to create form in pure C++ without using the Form Builder application.


It's very easy to create a form without visual editor. The first you should do is to include all required files. The form class should be derived from CForm which is located in the framework installation path under Nitisa/Core subdirectory in the file called Form.h. So, this file should be included in any case. You also should include all the controls you want to use on the form. Let our form goal is to get information about user. It will have inputs for first and last names, and for age. It will, of course, have labels for all the inputs. If will have 2 buttons - OK and Cancel. The form is supposed to be a part of the application which manages user list and supposed to be called modally. So, we need to include header files for our controls: Edit, Label, Button. And finally, we will need a window and a renderer for the form. Here is it.

#include "Nitisa/Interfaces/IFormService.h" // Add to have access to the form service methods 
#include "Nitisa/Interfaces/ITransform.h" // Declaration of ITransform interface is here. We need it as we access it via getTransform() method of widgets and use its method to change positions of widgets of the form. 
#include "Nitisa/Core/Utils.h" // "cast" function is here 
#include "Nitisa/Core/Form.h" // Default form implementation 
#include "Nitisa/Core/LockRepaint.h" // Helper class to facilitate prevent temporary pausing of repainting the form 
#include "Standard/Controls/Label/Label.h" // Label widget 
#include "Standard/Controls/Edit/Edit.h" // Edit widget 
#include "Standard/Controls/Button/Button.h" // Button widget 
#include "Platform/Core/Window.h" // Window class 
#include "Platform/Core/Renderer.h" // Renderer creation class

We have also included LockRepaint.h which contains a helper class for lock repaint.

Now lets start writing a class. We start from declaring all required controls.

standard::CLabel *m_pLabel1; // Label for "First Name" input 
standard::CLabel *m_pLabel2; // Label for "Last Name" input 
standard::CLabel *m_pLabel3; // Label for "Age" input 
standard::CEdit *m_pEditFirstName; // Input for "First Name" 
standard::CEdit *m_pEditLastName; // Input for "Last Name" 
standard::CEdit *m_pEditAge; // Input for "Age" 
standard::CButton *m_pButtonOk; // OK button 
standard::CButton *m_pButtonCancel; // Cancel button

The next step is to write an event processing methods. We will process two events. One when user clicks on OK and the other when he clicks on Cancel button. In both cases we just close the form with proper ModalResult. Remember, the form is supposed to be modal one and should be called from outside. Of course in real application you will add some validation in event from OK button and, probably, asking a user if he really wants lose changes before closing a form if user has made any changes. But we create a simple form, just to show how it could be done at all.

void ButtonOk_OnClick(IControl *sender)
{
    setModalResult(ModalResult::Ok);
}

void ButtonCancel_OnClick(IControl *sender)
{
    setModalResult(ModalResult::Cancel);
}

The next thing we need is a method which will create and initialize properties of the all controls and the form as well. First, all the controls should be created.

m_pLabel1 = new standard::CLabel(this);
m_pLabel2 = new standard::CLabel(this);
m_pLabel3 = new standard::CLabel(this);
m_pEditFirstName = new standard::CEdit(this);
m_pEditLastName = new standard::CEdit(this);
m_pEditAge = new standard::CEdit(this);
m_pButtonOk = new standard::CButton(this);
m_pButtonCancel = new standard::CButton(this);

Next, set their properties. We show here only a part corresponding for age input. Entire code you will find at the end of the article.

m_pEditAge->setName(L"EditAge");
m_pEditAge->getTransform()->Translate(96, 56, 0);
m_pEditAge->setText(L"");

It is not easy work to find a best size and position for controls when you can see them only after building and running application and accessing a form. That is why the Form Builder was created. You can save a lot of time by designing a form layout in it instead of doing it by writing only a code and running application again and again to find correct values.

The final part required to be implemented is the constructor of the form.

CFormUser() :
    CForm(L"FormUser", nullptr, CRenderer::Create()),
    m_pLabel1{ nullptr },
    m_pLabel2{ nullptr },
    m_pLabel3{ nullptr },
    m_pEditFirstName{ nullptr },
    m_pEditLastName{ nullptr },
    m_pEditAge{ nullptr },
    m_pButtonOk{ nullptr },
    m_pButtonCancel{ nullptr }
{
    CLockRepaint lock(this);
    setState(WindowState::Hidden); // The form is hidden by default 
    setName(L"FormUser");
    setCaption(L"User");
    setClientSize(Point{ 342, 121 });
    QueryService()->setWindow(CWindow::Create());
    Application->QueryService()->Add(this);
    Initialize();
}

Here we should call a parent class constructor. It requires a form name, a window, and a renderer. We pass an empty window. That is okay. CForm accept it and allows to set window later. We pass the newly created renderer. We do not use any parameters when create renderer with Create() method so it will be default OpenGL renderer without double buffering and anti aliasing. After calling a parent constructor it is the place to initialize all variables. Although it is not necessary to set default values to control variables, we did it. Next, in the constructor body, first of all we create a lock which prevents repainting each time when something is changed on the form. Creating CLockRepaint class instance just calls the form's LockRepaint() method. And when lock variable will be destroyed at the constructor end, it will call UnlockRepaint().

Before we assign a window to the form we can set some properties of it. In this example it is caption and client area size. Next, create a window and assign it to the form. After, we should register form in the application. Access to Application global variable and its service requires to include two more header files which waren't included before. We will show them in final code soon. Finally we call Initialize method which is responsible for creation of all the controls and setting their properties.

The final code of the form is below. Of course you may move methods implementation into source file if you wish.

#include "Nitisa/Interfaces/IApplication.h" // Add to have access to the "Application" variable. We need to register form there 
#include "Nitisa/Interfaces/IApplicationService.h" // Add to have access to the application service methods 
#include "Nitisa/Interfaces/IFormService.h" // Add to have access to the form service methods 
#include "Nitisa/Interfaces/ITransform.h" // Declaration of ITransform interface is here. We need it as we access it via getTransform() method of widgets and use its method to change positions of widgets of the form. 
#include "Nitisa/Core/Utils.h" // "cast" function is here 
#include "Nitisa/Core/Form.h" // Default form implementation 
#include "Nitisa/Core/LockRepaint.h" // Helper class to facilitate prevent temporary pausing of repainting the form 
#include "Standard/Controls/Label/Label.h" // Label widget 
#include "Standard/Controls/Edit/Edit.h" // Edit widget 
#include "Standard/Controls/Button/Button.h" // Button widget 
#include "Platform/Core/Window.h" // Window class 
#include "Platform/Core/Renderer.h" // Renderer creation class 

namespace nitisa
{
    class CFormUser : public CForm
    {
    public:
        // Controls 
        standard::CLabel *m_pLabel1;
        standard::CLabel *m_pLabel2;
        standard::CLabel *m_pLabel3;
        standard::CEdit *m_pEditFirstName;
        standard::CEdit *m_pEditLastName;
        standard::CEdit *m_pEditAge;
        standard::CButton *m_pButtonOk;
        standard::CButton *m_pButtonCancel;
    protected:
        // Events 
        void ButtonOk_OnClick(IControl *sender)
        {
            setModalResult(ModalResult::Ok);
        }

        void ButtonCancel_OnClick(IControl *sender)
        {
            setModalResult(ModalResult::Cancel);
        }

    private:
        void Initialize()
        {
            // Create controls 
            m_pLabel1 = new standard::CLabel(this);
            m_pLabel2 = new standard::CLabel(this);
            m_pLabel3 = new standard::CLabel(this);
            m_pEditFirstName = new standard::CEdit(this);
            m_pEditLastName = new standard::CEdit(this);
            m_pEditAge = new standard::CEdit(this);
            m_pButtonOk = new standard::CButton(this);
            m_pButtonCancel = new standard::CButton(this);

            // Initialize controls 
            m_pLabel1->setName(L"Label1");
            m_pLabel1->getTransform()->Translate(11, 12, 0);
            m_pLabel1->setSize(PointF{ 55, 13 });
            m_pLabel1->setCaption(L"First Name:");

            m_pLabel2->setName(L"Label2");
            m_pLabel2->getTransform()->Translate(12, 36, 0);
            m_pLabel2->setSize(PointF{ 54, 13 });
            m_pLabel2->setCaption(L"Last Name:");

            m_pLabel3->setName(L"Label3");
            m_pLabel3->getTransform()->Translate(13, 59, 0);
            m_pLabel3->setSize(PointF{ 23, 13 });
            m_pLabel3->setCaption(L"Age:");

            m_pEditFirstName->setName(L"EditFirstName");
            m_pEditFirstName->getTransform()->Translate(96, 8, 0);
            m_pEditFirstName->setSize(PointF{ 240, 20 });
            m_pEditFirstName->setText(L"");

            m_pEditLastName->setName(L"EditLastName");
            m_pEditLastName->getTransform()->Translate(96, 32, 0);
            m_pEditLastName->setSize(PointF{ 240, 20 });
            m_pEditLastName->setText(L"");

            m_pEditAge->setName(L"EditAge");
            m_pEditAge->getTransform()->Translate(96, 56, 0);
            m_pEditAge->setText(L"");

            m_pButtonOk->setName(L"ButtonOk");
            m_pButtonOk->getTransform()->Translate(8, 88, 0);
            m_pButtonOk->setCaption(L"OK");
            m_pButtonOk->OnClick = []( IControl *sender)->void { cast<CFormUser*>(sender->getForm())->ButtonOk_OnClick(sender); };

            m_pButtonCancel->setName(L"ButtonCancel");
            m_pButtonCancel->getTransform()->Translate(120, 88, 0);
            m_pButtonCancel->setCaption(L"Cancel");
            m_pButtonCancel->OnClick = []( IControl *sender)->void { cast<CFormUser*>(sender->getForm())->ButtonCancel_OnClick(sender); };
        }

    public:
        CFormUser() :
            CForm(L"FormUser", nullptr, CRenderer::Create()),
            m_pLabel1{ nullptr },
            m_pLabel2{ nullptr },
            m_pLabel3{ nullptr },
            m_pEditFirstName{ nullptr },
            m_pEditLastName{ nullptr },
            m_pEditAge{ nullptr },
            m_pButtonOk{ nullptr },
            m_pButtonCancel{ nullptr }
        {
            CLockRepaint lock(this);
            setState(WindowState::Hidden);
            setName(L"FormUser");
            setCaption(L"User");
            setClientSize(Point{ 342, 121 });
            QueryService()->setWindow(CWindow::Create());
            Application->QueryService()->Add(this);
            Initialize();
        }
    };
}

It is finished. All you need is to include the form file into your application and create it in the way described in Get started article(declare a variable of CFormUser* type and call app.CreateForm(formVar); after setting the main form). To show the form just call formVar->ShowModal(). This method will return the same value you have set on event processing methods. The form looks like on the picture below.

Final form for input information about user