In the beginning we will repeat process of creating and configuring empty project for the application which you have already done twice if you follow tutorials order. After that we will explain usage of existing widgets having forms/dialog boxes on the example of ColorDialog widget. You will also learn how to make your forms completely compatible with both dialog box and regular standalone modes.
This tutorial shows how to work with Nitisa downloaded as source code. If you wish to follow it using Nitisa extension for Visual Studio, you may find useful information about creating project and forms in this tutorial. All other remains the same and you may follow steps of this tutorial skipping manual creation of form and project as you can do it using Nitisa extension templates instead.
We will start with preparing the Nitisa framework for working with it. If you were following our tutorials you already know how to do it and if you have already done this, you may skip this chapter.
Here we run build twice. First one for creating of the executable file of the Form Builder and second one for creating framework library files which we will link with our applications. Of course we could omit second build and use library files from the C:\Nitisa-9.0.0\bin\Windows\x86\ReleaseRT folder. But it is better to use debug libraries while creating and debugging your applications and then, when everything was thoroughly tested, switch to release version and build final applications working with best speed.
After preparing Nitisa framework we will continue with creating empty project for our application. If you were following our tutorials you already know how to do it. Anyway we will repeat it again so that tutorial be complete.
This simple process of empty project creation is always the same with Nitisa. The only differences that may be here is using different folders and Runtime Library option. You can even make such empty project once, export is as template, and then use the template as starting point of your applications.
Some existing widgets, as you might already observed, show dialog forms to receive user action. For example, FindDialog shows form for searching a text, SysOpenDialog widget shows a form for selecting a file, and so on. Some of such widgets can work both in dialog box mode and as standalone forms. They use form mode by default. One of these widgets is ColorDialog widget which is used to show color selection interface. Lets see first how to use it by default.
By this simple steps we created a form for our application which will show color selection dialog when user clicks on button and show selected color on the panel on the form.
Now switch to the Visual Studio with empty project we created earlier and do following:
#pragma once
// Include file with form prototype which was generated by Form Builder when we saved the form
#include "IFormMain.h"
// As we develop with Nitisa framework we can simply put everything into this namespace to avoid adding it everywhere or adding "using namespace nitisa"
namespace nitisa
{
// By default Form Builder generate form prototype class in this namespace. So we put our form final class inside it as well
namespace app
{
// This is our form class which should be derived from the form prototype class generated when we saved the form in Form Builder
class CFormMain :public IFormMain
{
protected:
// Here are the methods which will be called when different events will happen. We added one when double clicked on button's "OnClick" event.
// If your form has many events it is easy to forget about some of them. We recommend just open form prototype header file, find there "protected" section, and see all the methods you need.
void Button1_OnClick(IControl *sender) override;
public:
// Form's constructor
CFormMain();
};
// Variable where we will store form instance
extern CFormMain *FormMain;
}
}
// Include header file with platform window management class
#include "Platform/Core/Window.h"
// Include header file with platform OpenGL renderer
#include "Platform/Core/Renderer.h"
// Include declaration of our form
#include "FormMain.h"
namespace nitisa
{
namespace app
{
// Global variable for the form instance. Will be used later
CFormMain *FormMain{ nullptr };
// Form constructor
CFormMain::CFormMain() :
// Calling of parent class constructor. It should be supplied with platform window and renderer objects
IFormMain(CWindow::Create(), CRenderer::Create())
{
// We need not to do anything here
// All the initialization and UI creation is done in the parent IFormMain class generated by the Form Builder
}
// Method called when user click on button
void CFormMain::Button1_OnClick(IControl *sender)
{
// Show color selection interface. Method returns "true" if user accept selected color
if (m_pColorDialog1->Execute())
// If user accepted selected color, set panel background color to the selected one
m_pPanel1->setBackgroundColor(m_pColorDialog1->getColor());
}
}
}
#include <Windows.h>
// Include file with platform application manager
#include "Platform/Core/Application.h"
// Include file with our form declaration
#include "FormMain.h"
// Tell the linker we want to link with the framework's core library. Mandatory
#pragma comment(lib, "Nitisa.lib")
// Tell the linker we want to link with the Standard package library as we use widgets from this package
// When we use widgets on the form from some package we should link our application with the library of that package
// Libraries for linking can also be specified in the project settings but we prefer doing this in this way as it is more informative
#pragma comment(lib, "Standard.lib")
// Tell the linker we want to link with platform-dependent code library. Required in most cases
#pragma comment(lib, "Platform.lib")
#pragma comment(lib, "opengl32.lib") // Add linking with OpenGL library
int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR, INT)
{
nitisa::CApplication app; // Create application
app.CreateForm(&nitisa::app::FormMain); // Create our form. Having variable for it makes it possible to use "CreateForm" but it can also be created as usual with "new" operator
app.setMainForm(nitisa::app::FormMain); // Tell the application the form is the main form of the application
app.Run(); // Run application
return 0;
}
This is how ColorDialog widget looks and works by default. As you may see it creates standalone form. You may move it wherever you want, even outside of the main form, even on another display. Additionally you cannot interact with the main form until you close color selection form.
First of all color selection interface looks a little bit different now. In dialog box mode it uses some widget as base class rather than a form. The widget for this purpose is called DialogBoxEx and is a part of Standard package. That widget has default layout different from a default layout of a form. That is the explanation of a little bit different view. You may also use this widget as a base when developing your own dialog boxes or use another ones available or create your owns.
The next difference is that the Execute()
method of ColorDialog returns true immediately and do not wait until user accepts color selection. You can see it by changing panel color to black one which is the default color of the ColorDialog. And now when user accept color selection panel color won't change. We will explain how fix it shortly.
Next difference is that color selection interface in dialog box mode can not be moved outside application form as you can see on the image above. As dialog box is just a widget it cannot draw itself outside form area.
And the final major difference is a user can still close, move, and resize application form although one cannot interact with form's widgets. This is standard behaviour but it can be changed if you like in any way you prefer.
Lets now see how to fix color selection accepting. Dialog boxes like forms have method Show()
and ShowModal()
. The first one works for both in the same way. The second one in case of a form waits until form closes and result of the method calling is the result with which form is closed but for dialog box it returns immediately. ColorDialog widget uses Color Form. In regular mode it is a standalone form while in dialog box mode it is implemented as dialog box. Method Execute()
of the ColorDialog just uses the ShowModal()
method of the form and thus it returns immediately in the dialog box mode in the same way the ShowModal()
method does. To be informed when user accept color selection a listener may be used. Lets see how to add and use it.
CFormMain
class in the FormMain.h file. By this step we declare listener class.// This small class implements listener to be used to get notification about accepting color selection by a user
// Class should implement IColorDialogListener interface, so it should be derived from it
class CColorDialogListener :public virtual standard::IColorDialogListener
{
private:
// In this member will be stored a pointer to our form
CFormMain *m_pForm;
public:
// Listener has only one method which will be called when user accepts color selection and we will use it to update panel's color
void OnApply(standard::IColorDialog *sender) override;
// Constructor
CColorDialogListener(CFormMain *form);
};
CColorDialogListener m_cColorDialogListener;
as well into private section of the form class right after listener class declaration. // Include header file with platform window management class
#include "Platform/Core/Window.h"
// Include header file with platform OpenGL renderer
#include "Platform/Core/Renderer.h"
// Include declaration of our form
#include "FormMain.h"
namespace nitisa
{
namespace app
{
// Global variable for the form instance. Will be used later
CFormMain *FormMain{ nullptr };
// In constructor we just store pointer to a form
CFormMain::CColorDialogListener::CColorDialogListener(CFormMain *form) :
m_pForm{ form }
{
}
// When color selection is confirmed we update panel color
void CFormMain::CColorDialogListener::OnApply(standard::IColorDialog *sender)
{
m_pForm->m_pPanel1->setBackgroundColor(m_pForm->m_pColorDialog1->getColor());
}
// Form constructor
CFormMain::CFormMain() :
// Calling of parent class constructor. It should be supplied with platform window and renderer objects
IFormMain(CWindow::Create(), CRenderer::Create()),
// Create listener sending pointer to the form to it
m_cColorDialogListener{ this }
{
// We need not to do anything here
// All the initialization and UI creation is done in the parent IFormMain class generated by the Form Builder
// Assign listener to ColorDialog
m_pColorDialog1->setColorDialogListener(&m_cColorDialogListener);
}
// Method called when user click on button
void CFormMain::Button1_OnClick(IControl *sender)
{
// Show color selection interface
m_pColorDialog1->Execute();
}
}
}
That is how we can handle accepting color in dialog box mode of the ColorDialog widget. By the way, this modification of the code will also work if you change UseDialogBox property to false again.
As you may notice we still have some issues with color selection layout interface. Particularly we would like it to have the same background color as our main form have and if you take a close look you will see that its right and bottom distance from widgets to dialog box right and bottom borders differs from the distance to left and top ones. This is a common minor issue for all dialog boxes derived from DialogBoxEx when they were created via Form Builder. The issue raises from different default layout settings of a form and the DialogBoxEx widget. But there is an easy fix for it. Lets fix both this issues in the following way.
#include "Standard/Forms/Color/DialogBoxColor.h"
Button1_OnClick
method of the form right after showing color selection interface by Execute()
method. The first line sets proper background color to the dialog box with color selection interface and the second line just adjust it's size and position.m_pColorDialog1->getDialogBox()->setBackgroundColor(getBackgroundColor());
m_pColorDialog1->getDialogBox()->AutoResize(true);
If you run application and clicks on Select color... button you will see the changes. Color selection interface background looks now more like the main form and its padding is equal from all the sides.
!
The only one important note we have here states that for optimization reasons dialog box of the ColorDialog widget, and all the widgets which uses dialog boxes, may not be accessible until its showing is requested. That is why we access it and change its properties and call its methods after calling Execute()
of ColorDialog but not before.
And a final improvement here we would like to show is checking whether ColorDialog is in dialog box mode or not before accessing its dialog box as well as checking if Execute()
method was called successfully. This final change makes our simple form completely compatible with both dialog box and standalone form modes and makes our code universal which means in does not require any changes when we change between those two modes. All is required in this case is building the project and that is all. You may also see that we added m_bDialogBoxAdjusted
boolean type member(initially set to false in form's constructor) to prevent adjusting dialog box each time user clicks on Select color... button. This is an optimization trick as after the first adjustment dialog box remains in the proper state and no new adjustment is required.
// Method called when user click on button
void CFormMain::Button1_OnClick(IControl *sender)
{
// Show color selection interface and adjust dialog box if required and dialog box mode is on
if (m_pColorDialog1->Execute() && m_pColorDialog1->isUseDialogBox() && !m_bDialogBoxAdjusted)
{
// Set background color of the dialog box to the color our form has
m_pColorDialog1->getDialogBox()->setBackgroundColor(getBackgroundColor());
// Automatically adjust dialog box size and position
m_pColorDialog1->getDialogBox()->AutoResize(true);
// Set flag so we do not adjust dialog box size and position each time when color selection interface is being shown
m_bDialogBoxAdjusted = true;
}
}
This method shows using a listener. There is another method: using OnApply
event of ColorDialog widget. We wanted to show you how to use listeners as all dialog boxes you will create and use have only listener method of getting information and have no corresponding events like in case of ColorDialog.
You can find source code of the project we just completed in the Windows → Tutorials → DialogBox folder of the Nitisa solution. It is called TestColorDialog.