Tutorial: Packages. Part 1. Registration widget


This tutorial is a first one in the tutorial cycle dedicated to packages and their usage in Form Builder. Here we describe entire project goal and create custom widget to be used in further tutorials. We suppose you already familiar with creating forms and applications with Nitisa and won't describe basic steps you need to perform as it was done in Basic and Medium level tutorials. Instead we will focus on new information and key moments only. In this tutorial you will learn how to create your own custom widgets, how to use built-in controls and modify default behaviour of controls.



Introduction

You are already know how it's easy to create forms for applications in the Form Builder. You just pick needed widget, put it where you need it on a form and so on. But you also might notice that a set of widgets is limited and you might want to create your own widgets needed for your projects as well as you might want your widgets are also appear in the Form Builder and be properly managed by it. In this multipart tutorial we will cover entire process from creating a widget till adding it to the Form Builder.

The first part (this one) of the series is dedicated to creating a custom widget. In the second part we will show how to add widget into Form Builder. And in the last part you will learn how to make Form Builder properly work with custom properties your custom widgets may have.

In this tutorial we are going to create Registration widget. This widget will be something like a small registration form where user can type his first and last name and also select his date of birth. Such a functionality can easy be added to any application by putting only 6 widgets (3 Label widgets to label input fields meaning, 2 Edit widgets for typing first and last names, and DatePicker widget to select date of birth) to a form. But if you create such forms often you need each time do that, each time move widgets to proper positions and do other staff. That might be a little inconvenient and you might want to do all that staff in couple of clicks. So, such set of widgets can be a good candidate to be made a separate widget.

It might seem hard to create such a complicated widget as it has besides labels, which are just texts drawn on a widget and easy implementable, such parts as inputs for texts and even a calendar. Fortunately there is a built-in controls which already implement required functionality and thus using them will extremely simplify our work.

Custom widgets can be added only to a standalone Form Builder. So in this tutorial we will work only with Nitisa downloaded as source code which you can do at this page. It's Okay to have both extension and source code at the same time, so, if you also has Nitisa extension for Visual Studio installed, you don't need to uninstall it. Just download the latest release and unpack it somewhere. Lets assume you unpacked it into C: drive, so the Nitisa source code is in the C:\Nitisa-10.2.0 directory (the version part may be different of course).

Library project

When you create something which can be used many times in different projects it's a good idea to put it into a separate library. In the Nitisa such a library is called a Package. You put into a package widgets, forms and other staff you are going to use in multiple projects. The package even can be sent to another developers so they could use it as well. You also can redistribute your package(s) via our portal as described in Distributing Packages article.

Package is meant to be used many times in different projects and thus it should be thoroughly tested and cleaned from bugs but it's a library and it means we need some application to facilitate running library code. On the other hand, it's almost definitely will be used in the Form Builder. And, if it is managed by the Form Builder it can be easy to create an application having widgets from the package on forms and thus it can be easy to create such an application for better testing. But before creating additional applications for testing package code it would be really helpful to be able to debug library when it is managed by the Form Builder itself. So, how can we do that? You know that Nitisa source code has Nitisa.sln solution file for Visual Studio. If you open it, you will see all the Nitisa projects inside. They are packages for different platforms, tutorials source code and also Form Builder code as well. So, we can set FormBuilder project as start-up project and debug it in Visual Studio.

Form Builder loads packages and it will load ours, so we can add our new package and related projects to the Nitisa solution and thus be able to debug it easily using FormBuilder application. That is why we will create all projects directly in the Nitisa solution. Later, when you finish with your package, you can simply delete all previously added projects.

So, lets create a library for our first custom widget in the Nitisa solution.

  1. Open Nitisa.sln file in Visual Studio. The file is in the C:\Nitisa-10.2.0 directory.
  2. Add new solution folder called CoolWidgets to the Tutorials folder. We name our package CoolWidgets.
  3. Add new console project with name CoolWidgets as shown below.
    Create console project
  4. Permanently remove all files and filters from the created project.

As you might recall Form Builder is available in four configurations (Debug, DebugRT, Release and ReleaseRT) and two platforms (x86 and x64). We, for simplicity, will use only DebugRT configuration and x86 platform. When you create a real package you are going to share with another developers, you will also definitely use x64 platform and ReleaseRT configuration. As for Debug and Release configurations you will need it only if you want us to include your package into Nitisa extension for Visual Studio. So, lets setup our package.

  1. Remove x64 platform and Release configuration from the project using Configuration Manager of Visual Studio. Also rename Debug configuration to DebugRT there. It's a good practice to remove everything which is not being used.
  2. Using the same Configuration Manager ensure the project is selected to build only in DebugRT configuration and Win32 platform. This is not mandatory as well but may help avoid some mistakes.
  3. Add stdafx.h and stdafx.cpp new files to the project. We will use precompiled headers.
  4. In the properties of stdafx.cpp file change Precompiled Header property to Create (/Yc) selecting it in drop down menu.
  5. Add #include "stdafx.h" at the beginning of the stdafx.cpp file.
  6. Change project's Windows SDK Version to <inherit from parent or project defaults> in project settings using drop down menu.
  7. Change project's Output Directory to $(SolutionDir)bin\Windows\x86\DebugRT\ in project settings. That is the standard output directory for libraries and Form Builder application.
  8. Change project's Intermediate Directory to $(SolutionDir)obj\Windows\CoolWidgets\x86\DebugRT\ in project settings. This point is not mandatory. It just do the same that is done for all projects in solution. So all intermediate files are in the one place.
  9. Change project's Configuration Type to Static library (.lib) in project settings.
  10. Add $(ProjectDir);$(ProjectDir)../../..; to the Include Directories paths. The first part is needed to get access to precompiled header file from everywhere in the project (we will add it a little bit later). The second path gives access to the root directory of Nitisa.
  11. If project property SDL checks has some value, change it to <inherit from parent or project defaults>.
  12. Change project's Runtime Library to Multi-threaded Debug DLL (/MDd) if it is different by default.
  13. If project property Conformance Mode has some value, change it to <inherit from parent or project defaults>.
  14. Change project's Precompiled Header property to Use (/Yu).
  15. Change project's Object File Name property to $(IntDir)%(RelativeDir)%(filename).obj.
  16. Change project's Target Machine property to MachineX86 (/MACHINE:X86).

It may look quite complicated but all we've done here is just copied settings from, lets say, Standard project in the solution. All packages in the solution have the same options. Yours should also have them. The only difference is in paths. That is because of we put the project into Tutorials folder. If you create a real package, you will place it into Packages directory and all the paths will be just like in another packages.

Pay attention!
Default settings of a new project changes between releases of Visual Studio. You need to check settings all the time you create a new project.

Package may have a lot of files and user who will use it may be interested to include everything from a package with one include line only. It is a good idea to have a header file for that. You might object we already have stdafx.h file which can be used for that but it's now quite true. The stdafx.h file should have all includes required by the package. It may not only be package header files but also any others like header files of Standard Template Library and others. So lets create a separate file which will contain includes of all the package header files.

  1. Create new header file and name it CoolWidgets.h. It is a good idea to name such a global include file the same name the package called. Leave the file empty. We will add inclusions later when start working on a widget.
  2. Add #include "CoolWidgets.h" to the stdafx.h file.
  3. We are going to use Nitisa core objects extensively so add #include "Nitisa/Nitisa.h" to the stdafx.h file as well.

You may now select DebugRT configuration and x86 platform and build the project. If everything is done right, you will see CoolWidgets.lib library file in the C:\Nitisa-10.2.0\bin\Windows\x86\DebugRT directory.

Application project

By the next step we will create a simple application project we will use to debug our widget later. We need it in development a widget as we will add possibility to manage widget in the Form Builder only in the next tutorial.

  1. Add Windows Desktop Application to the Tutorials\CoolWidgets solution directory and call it TestApp.
  2. Permanently remove all files and filters from the project.
  3. Add main.cpp source code file to the project.
  4. Again remove x64 platform and Release configuration using Configuration Manager and also rename Debug configuration to DebugRT.
  5. Make the project to be not selected to build in any configurations and platforms but DebugRT and x86.
  6. Make sure project's Windows SDK Version is <inherit from parent or project defaults>.
  7. Add $(SolutionDir);$(SolutionDir)Packages;$(SolutionDir)Tutorials\CoolWidgets; paths to the project's property Include Directories.
  8. Add $(SolutionDir)bin\Windows\x86\DebugRT; path to the project's property Library Directories.
  9. Ensure SDL checks property is empty.
  10. Ensure Runtime Library property is set to Multi-threaded Debug DLL (/MDd).
  11. Ensure Conformance mode property is empty.

When you have several projects depending on each other in the solution it is a good idea to set dependencies so that projects are re-built automatic when it is needed by Visual Studio. Our just added application depends on the package project we added earlier and it will also depend on Nitisa project (framework core), Standard package as it is needed for the next one, and it depends on Platform project as well (here are all platform-dependent classes, like window, renderer, application, leaves). So click on the project name by right mouse button and in the popup menu select Build Dependencies -> Project Dependencies and check those four projects there. There should be several projects with the same names. You need the once marked as Windows like shown below.

Dependencies of test application

Package project CoolWidgets needn't to depend on anything. Only applications and dynamic libraries require dependencies.

To finish with the test application project lets add an empty form and application initialization into main.cpp file.

Add new FormMain.h header file and put following code into it.

#pragma once

#include "Nitisa/Core/Form.h"

namespace nitisa
{
	class CFormMain :public CForm
	{
	public:
		CFormMain();
	};

	extern CFormMain *FormMain;
}

This is a pretty simple form declaration. It is very similar to what you did before when created applications with Nitisa. There is only two small differences. The first one is that we omit nested namespace and put form class declaration directly into nitisa namespace. We did this for simplicity. And the next difference is that our form class is derived directly from CForm class instead of a form prototype class as usually. Our form is empty and thus we need no additional initialization performed in form prototype class generated by the Form Builder. So we do not use Form Builder and derive from directly form the default form implementation class.

Add new FormMain.cpp source file and put following code into it.

#include "Platform/Core/Renderer.h"
#include "Platform/Core/Window.h"

#include "CoolWidgets/CoolWidgets.h"

#include "FormMain.h"

namespace nitisa
{
	CFormMain *FormMain{ nullptr };

	CFormMain::CFormMain() :
		CForm(L"FormMain", CWindow::Create(), CRenderer::Create())
	{

	}
}

This one is also obvious. CForm constructor has additional first parameter in comparison to form prototypes and thus we added it (it just need the form class name, without "C" at the beginning). We also included main header file of our package we created earlier. We will use widget from it later.

And finally put following code into main.cpp file.

#include "Platform/Core/Application.h" // Include application manager 

#include "FormMain.h" // Include main form 

#include <Windows.h> // Include windows declarations 

// Import libraries
#pragma comment(lib, "Nitisa.lib") // Nitisa core. Required 
#pragma comment(lib, "Standard.lib") // Standard widgets library 
#pragma comment(lib, "Platform.lib") // Platform dependent classes implementations (CApplication, CRenderer, CWindow) 
#pragma comment(lib, "opengl32.lib") // Renderer uses OpenGL graphics, so OpenGL library is required 

#pragma comment(lib, "CoolWidgets.lib") // Link with CoolWidgets package library we are working on 

using namespace nitisa;

int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR, INT)
{
	CApplication app; // Create application 
	app.CreateForm(&FormMain); // Create main form 
	app.setMainForm(FormMain); // Tell application which form is the main one 
	app.Run(); // Run application 
	return 0;
}

It is almost exact copy of the corresponding files from other tutorials. We only added linking with our CoolWidgets package and added using nitisa namespace to simplify WinMain() function a little.

If you build and run application you will see an empty form. That is all we want from this application for now. Later, after creating a widget, we will add it on the form to check it works properly.

Widget

There are two major kinds of widgets in the Nitisa. The first one is called Component. Components are widgets that have no visual representation on a form. It's a widgets you can add to a form but when application is running you can't see it on a form. For example, it can be a timer component which run some task periodically or an image list component which stores a list of images which can be used by another widgets on a form. The other kind is called Control and controls are widgets which have visual representation on a form. It's such a common widgets like text inputs, labels, buttons and others. User can interact with controls as they are visible on a form. Both components and controls, as well as most everything in the framework, are described by their interfaces. Components are described by IComponent interface and controls are described by IControl interface. Instead of starting developing widget from interface it is almost already easier to start from a corresponding helper class which already has most common features implemented. For components and controls such classes are CComponent and CControl.

Our registration widget will be displayed on a form and user will be able to interact with it so it will be control and thus it should implement IControl interface and, as stated above, we will derive our widget class from CControl class.

Development of a widget is usually divided into two big parts. The first part is designing a widget. It includes adding all needed properties and methods for making widget do what it can as well as rendering methods. All this staff is implemented in a widget class. The second part is implementation of widget interactions. Interactions are driven by events occurred on a form or in a system. For example, when user clicks somewhere on a form, the form detects which widget should get notification of that click and passes it to that widget. All such notifications are handled in widget service. Widget service is implemented as a separate class and we will implement it in the next paragraph. In this paragraph we focus on implementing widget class.

It is accepted in the Nitisa to have interfaces for widgets as well. Those interfaces describe only minimum required features and should not have any layout customization members. All layout members should be in a widget's class, not in a widget's interface. Following that custom we will also declare an interface for our widget.

It is recommended to put components in Components directory in a package. Controls should be in the Controls directory, forms should be in the Forms directory and so on. In that directories should be interfaces for the components, controls, forms and so on respectfully. For each component, control, form there should be subdirectory called the same name the component, control, form is called. In the component, control, form subdirectory should be a header and source files with declaration and implementation of the corresponding component, control, form and so on. Primary files implementing component, control, form should have the same name the component, control, form has. Additionally everything in the package should be inside nitisa::[package-namespace] namespace, where [package-namespace] is the namespace name as close as possible to the package name. So, for our widget we will create a folder called Controls in the CoolWidgets project. In that folder we will create a file called IRegistration.h with declaration of the interface for our widget (we are going to name the widget Registration). Inside Controls directory we will create subdirectory for the widget implementation called Registration and inside it we will have primary files Registration.h and Registration.cpp with declaration and implementation of the widget. We will have some additional files there but we will describe them later.

If you see on the Form Builder, you may find that most widgets have a lot of properties which allow to change widget appearance and behaviour. We won't add many to our widget. It will have only three properties for demonstration what property types widgets can have. The first property will be a simple one. We will call it BackgroundColor and it will control, as it's already clear from the name, the color of the background of the widget. The second one will control inputs border color and will have two states: normal state and the state when input is active or focused and user can type something in it. And the final property will be a complex one. It will store three parameters: first name, last name and date of birth. We will talk more about properties and their handling in the Form Builder in the last part of the tutorial.

The second property storing input border color depending on state, lets call it InputBorderColor, will have two states as we wrote before. The first state is a normal state, when input is not active. And the second one is when input is active (or focused). Usually there are much more states. For example, input can be under a mouse pointer or a pointer can be outside an input, also pointer can be down over the input and not released yet. Input can also be in disabled state if widget is disabled and so on. Our widget will have only two states for simplicity. To describe that states we are going to have following enumeration.

enum class State
{
    Normal,
    Active
};

For the third, the complicated one, property we define structure like following. It, as stated above, contains first and last name and date of birth in the three separate fields Year, Month and Day.

struct RegistrationData
{
    String FirstName;
    String LastName;
    int Year;
    int Month;
    int Day;
};

We talked above of properties but classes in C++ don't have properties. By property we mean getter method for getting stored value and setter method for changing the value. So called readonly properties have only a getter method. In the Nitisa the name of a getter method is formed as get[PropertyName], where [PropertyName] is a name of a property. In our example it will be getBackgroundColor() and getInputBorderColor() methods. The name of setter method is formed similarly but with set prefix.

Besides properties widgets have events. Event is just a pointer to a function which will be called when something happens. We will add one event for our widget. That event will be called when user type first or last name or change date of birth. The event will be called OnChange.

When user clicks on the first name input of our widget we set that input as focused input. In the same way when user clicks on the second input (last name input), we set that input as focused. Our input for entering date of birth will be just a text shown currently selected date of birth and a small arrow in form of a triangle, by clicking on which a calendar will appear. So, we need to know somehow at which part of a widget user clicks. For that we are going to have helper method getElement(const PointF &p) which will return a part of widget under a specified coordinate. The best way for specifying a widget part is an enumeration. We will have such parts as first name input, last name input, date of birth, date of birth arrow and calendar in it and define it as:

enum class Element
{
    None,
    InputFirstName,
    InputLastName,
    Date,
    DateArrow,
    Calendar
};

So the full declaration of the method is Element getElement(const PointF &p).

How does form know whether a mouse pointer is over a widget or not? It uses widget's IControl *getControl(const PointF &position) method. The method return widget under a specified position or nullptr if neither widget which method is called nor its any child widget is under the specified position. The default implementation of the method from CControl class just uses widget rectangle returned by widget's getRect() method and widget transformation including transformations of all parent widgets to determine whether the specified position is inside that rectangle or not. If widget has child widgets the method first called for all the child widgets. That is ideally suited for widgets with rectangular shape. What if widget's shape is not a rectangle? In this case the method should be overwritten and perform calculation taking into account the non-rectangular shape. Is our widget rectangular? Not quite. We can treat its shape as rectangle until calendar isn't shown. When calendar for selecting date of birth is shown our widget shape can be described by two rectangles: original rectangle with labels and inputs plus the rectangle of the calendar part. So we will overwrite the method so that a form could correctly process events when our widget have calendar opened.

By the way there are two more rectangles the control has. The first one is render rectangle. The render rectangle is just a widget rectangle with all the effects which take no part in interaction with widget. For example, widget can have shadow. When user clicks on the shadow nothing happens - shadow is just an effect and doesn't participate into interactions with widget. But the shadow should be taking into account when widget is being drawn. That is the purpose of the render rectangle of a widget. Render rectangle is being returned by the getRenderRect() method of the widget. The last rectangle is a client rectangle. It is returned by the getClientRect() method and is being used in alignment of child widgets on a widget. When widget has another widgets on it and that widgets have alignments different form Align::None widget uses client rectangle instead of all its rectangle to place child widgets. Default implementations of those three methods in CControl class return the same values equal to the rectangle of a control. Our widget doesn't have effects and child widgets so we don't need to overwrite any of these methods.

Control allows to set different cursor when mouse pointer is over it area by setCursor() method. What if we want to have different cursors on different parts of a widget? Lets our widget has default cursor everywhere except when mouse pointer is over inputs and over the arrow for showing the calendar. When pointer is over text inputs we want it have "I-Beam" shape and when it is over the calendar showing arrow we want it to be in a form of hand pointer so that user understand the arrow can be clicked and some action will follow the click. How can we do that? Form uses getCursor() method of a widget to determine which shape of widget should be shown. It does it all the time mouse pointer moves. So the answer is simple: we need to overwrite the method and return needed shape depending on what part of the widget is under the mouse pointer.

If your widget allows size changing you need to do nothing as that is the default behaviour and it is already implemented in the CControl class. Lets make our widget non-resizable to show how it can be done. There is a way to restrict widget size to certain range by changing its Constraints property. But changing the property still allows changes to that property from source code and it means if we just change constraints in the Form Builder user still will be able to change constraints to a new value in the source code. To completely prevent user from changing size of a widget we need to overwrite three methods. The first one is setSize() which is obvious. The second one is the setConstraints() which is less obvious (size cannot exceed constraints, so constraints effect size and thus constraints changes should be prevented as well). And the final way when the size of the widget can be changes is aligning. When, for instance, aligning is set to client area, the widget takes all available parent area independent on its constraints or current size. So, we need also prevent changing the alignment by overwriting the setAlign() method. Although there are other constraints and size changing methods (like setMinWidth() and setHeight()) they all call setConstraints() and setSize() so we don't need to overwrite them.

And the last method of CControl we are going to override is the setEnabled() method. Our widget will always be enabled.

Many widgets have common parts. For example, many widgets can have scroll bars when their content cannot be fit into the widget area. Another example is a field where user can type something. Text input, editable tables, even time picker can have it. Some of the most commonly used parts are already implemented in the framework and are called built-in controls. Sure you can implement all needed functionality by your own but in our example we want to show how to use some of the existing built-in controls. It will also greatly simplify the widget development.

So, lets see what built-in controls we need. We have two text inputs to type first and last name so we can use built-in TextInput control for them. We also have somehow to allow user to enter date of birth. In this case built-in MonthCalendar comes in handy.

Built-in controls cannot be used as is as they have abstract method(s). So, to use them we need to create class derived from built-in control we are going to use and implement those method(s). Fortunately for us all the standard built-in controls have only one abstract method called getControl() and its implementation is trivial. It should just return control to which it belongs. So we will have two classes CTextInput derived from CBuiltInTextInput and CMonthCalendar derived from CBuiltInMonthCalendar. Built-in controls may notify parent control of some changes or actions the main control should take. For example, they may notify parent of needed repainting or of text/value change. To get that notifications parent widget should provide built-in control with listener. Listener is a small interface which methods will be called at certain events. To implement a listener we need to create a class derived from needed listener and implement its methods. All standard built-in controls have listeners. For our built-in controls they are IBuiltInTextInputListener and IBuiltInMonthCalendarListener. Their implementation is pretty simple as they both have only two methods. The first one is called when repaint is required and accordingly we just repaint everything and the second method of both of them is called when change of text or date is happened. When text is changed in input we just call our OnChange event. When date changed in calendar we also call the event and additionally close calendar and repaint widget.

We could have created built-in controls in the constructor of our widget but we will do it another way and this is why. The less objects you create at initialization the faster it finishes and the best way is not to do any job until it is required. Built-in calendar control is required only when user clicks on arrow to show it. User can never do it so why to create built-in calendar in constructor of the widget if it may never be used? So the built-in calendar control of our widget is a good candidate to be created on the fly - right when it is required. Creating only one small part is almost always fast enough to be done without any observable lag. So we will have helper method getCalendar() which will create and setup built-in calendar if it is not created yet and, instead of accessing calendar via its variable, we will access it via this method. In our widget inputs are needed always if widget is created as they are visible all the time but we will anyway use the same method to create them and thus we will have two more helper methods getInputFirstName() and getInputLastName().

Additionally we are going to need some more helper methods. We need two of them for open and close calendar, two for rendering our widget and rendering calendar. Also we are going to store current element under a mouse pointer (hovered element). Instead of finding it every time we need it we will store it in private variable and update its value only when mouse pointer position is changed. For that we will have UpdateHoveredElement() method.

And finally we need a method to update our widget properties when its style is changed. As you might know widgets in Nitisa support styling. By default there is no style and all widgets are being displayed as they were designed. But user may create its own style and apply it to a form and/or to a widgets. The style may contain settings which are familiar for a widget and widget applies them in that case. Our widget has two properties BackgroundColor and InputBorderColor. Lets look for those properties in style and apply them if they are there. We also should not forget when creating custom widget that built-in controls can also support styling and thus we need to call theirs UpdateFromStyle() methods so they can properly handle it.

Lets now put it all together. Create header file IRegistration.h in the Controls directory and put there following widget interface declaration code.

#pragma once

#include "Nitisa/Core/Strings.h" // We use String type declared here 
#include "Nitisa/Interfaces/IControl.h" // IControl interface declaration is here 

namespace nitisa // Everything should be in this global namespace 
{
	namespace coolwidgets // Namespace of our package 
	{
		class IRegistration :public virtual IControl // Widget interface should be derived from control interface 
		{
		public:
			struct RegistrationData // Structure describing widget data 
			{
				String FirstName;
				String LastName;
				// Date of birth 
				int Year;
				int Month;
				int Day;
			};
		public:
			virtual RegistrationData getRegistrationData() = 0; // Getter method of RegistrationData property 

			virtual bool setRegistrationData(const RegistrationData &value) = 0; // Setter method of RegistrationData property 
		};
	}
}

Widget interface is pretty simple. It has, accordingly to what was said earlier, only getter and setter methods for widget data and has no methods for layout properties. By the next step create Registration.h header file in the Controls\Registration directory and put following widget class declaration code in it.

#pragma once

#include "Nitisa/BuiltInControls/MonthCalendar/BuiltInMonthCalendar.h" // Built-in month calendar control is declared here 
#include "Nitisa/BuiltInControls/TextInput/BuiltInTextInput.h" // Built-in text input control is declared here 
#include "Nitisa/BuiltInControls/IBuiltInMonthCalendarListener.h" // Built-in month calendar control listener is declared here 
#include "Nitisa/BuiltInControls/IBuiltInTextInputListener.h" // Built-in text input control listener is declared here 
#include "Nitisa/Core/Align.h" // Align enumeration is declared here 
#include "Nitisa/Core/Control.h" // CControl class is declared here 
#include "Nitisa/Image/Color.h" // Color is declared here 
#include "Nitisa/Math/PointF.h" // PointF is declared here 
#include "Nitisa/Math/RectF.h" // RectF is declared here 

#include "../IRegistration.h" // Include declaration of widget interface 

namespace nitisa
{
	// Forward declaration of interfaces we use 
	class IBuiltInTextInput;
	class IBuiltInMonthCalendar;
	class IControl;
	class IForm;
	class IRenderer;
	class IStyle;
	class ITexture;

	namespace coolwidgets
	{
		class CRegistrationService; // Forward declaration of widget service 

		class CRegistration :public virtual IRegistration, public CControl // Widget class implement widget interface and is derived form default control implementation in CControl 
		{
			friend CRegistrationService; // Widget service need to be a friend to have access to widget private properties and methods 
		public:
			enum class State // Enumeration describing input states 
			{
				Normal, // Normal state, not focused 
				Active // Input is focused 
			};
		private:
			enum class Element // Enumeration describing widget parts 
			{
				None, // Nothing or irrelevant part 
				InputFirstName, // Input where user can type his first name 
				InputLastName, // Input where user can type his last name 
				Date, // Area where current date of birth is displayed 
				DateArrow, // Arrow which can be used to show calendar 
				Calendar // Calendar 
			};

			class CTextInput :public CBuiltInTextInput // Built-in text input derived from its base class 
			{
			private:
				CRegistration *m_pControl;
			public:
				IControl *getControl() override; // Implement abstract method which should return parent widget 

				CTextInput(CRegistration *control);
			};

			class CTextInputListener :public virtual IBuiltInTextInputListener // Built-in text input listener implementation class derived from corresponding interface 
			{
			private:
				CRegistration *m_pControl;
			public:
				void NotifyOnRepaint(IBuiltInControl *sender, const RectF &rect) override; // Notification called when repainting is required 
				void NotifyOnTextChanged(IBuiltInControl *sender) override; // Notification called when text is changed in the input 

				CTextInputListener(CRegistration *control);
			};

			class CMonthCalendar :public CBuiltInMonthCalendar // Built-in month calendar derived from its base class 
			{
			private:
				CRegistration *m_pControl;
			public:
				IControl *getControl() override; // Implement abstract method which should return parent widget 

				CMonthCalendar(CRegistration *control);
			};

			class CMonthCalendarListener :public virtual IBuiltInMonthCalendarListener // Built-in month calendar listener implementation class derived from corresponding interface 
			{
			private:
				CRegistration *m_pControl;
			public:
				void NotifyOnRepaint(IBuiltInControl *sender, const RectF &rect) override; // Notification called when repainting is required 
				void NotifyOnChange(IBuiltInControl *sender) override; // Notification called when selected date is changed in month calendar 

				CMonthCalendarListener(CRegistration *control);
			};
		private:
			Color m_sBackgroundColor; // We store BackgroundColor property value here 
			Color m_aInputBorderColor[(int)State::Active + 1]; // We store InputBorderColor property here. It has two values for each of states 

			IBuiltInTextInput *m_pInputFirstName; // We store built-in text input instance for first name here 
			IBuiltInTextInput *m_pInputLastName; // We store built-in text input instance for last name here 
			IBuiltInMonthCalendar *m_pCalendar; // We store built-in calendar instance here 
			Element m_eFocusedElement; // We store currently focused element here. It can be first name input, last name input or date of birth area 
			Element m_eHoveredElement; // We store element which is under mouse pointer here 
			Element m_eDownElement; // We store element at which mouse pointer was down but not yet released. Can be either first name input or last name input 
			ITexture *m_pCanvas; // Texture in which our widget will be drawn 
			ITexture *m_pCanvasCalendar; // Texture in which calendar part will be drawn 
			CTextInputListener m_cTextInputListener; // Listener for text inputs 
			CMonthCalendarListener m_cMonthCalendarListener; // Listener for calendar 
			bool m_bCalendarOpened; // It indicates whether calendar is shown or not 
			RectF m_sInputFirstNameRect; // Rectangle of the area with first name input 
			RectF m_sInputLastNameRect; // Rectangle of the area with last name input 
			RectF m_sDateRect; // Rectangle of the area with date of birth 
			RectF m_sCalendarBorderWidth; // Border widths of calendar 
			RectF m_sCalendarPadding; // Padding between borders and calendar 
			Color m_sCalendarBorderColor; // Color of calendar border 
			Color m_sCalendarBackgroundColor; // Color of calendar background 
			RectF m_sCalendarRect; // Calendar rectangle when it is shown 

			Element getElement(const PointF &p); // Find which element is under the specified point (in widget coordinate system) 
			IBuiltInTextInput *getInputFirstName(); // Return first name input. Create it if it was not created yet 
			IBuiltInTextInput *getInputLastName(); // Return last name input. Create it if it was not created yet 
			IBuiltInMonthCalendar *getCalendar(); // Return calendar. Create it if it was not created yet 

			void OpenCalendar(); // Show calendar 
			void CloseCalendar(); // Hide calendar 

			void UpdateFromStyle(IStyle *style); // Update properties from style 
			bool UpdateHoveredElement(const PointF &p); // Update m_eHoveredElement property when mouse pointer is in specified point (in widget coordinate system). Return true if hovered element was changed 
			void RenderControl(IRenderer *renderer); // Render widget 
			void RenderCalendar(IRenderer *renderer); // Render calendar 
		public:
			void(*OnChange)(IControl *sender); // Event called when data is changed 
            
			// IControl getters 
			IControl *getControl(const PointF &position) override;
			CursorType getCursor() override;

			// IControl setters 
			bool setAlign(const Align value) override;
			bool setConstraints(const RectF &value) override;
			bool setSize(const PointF &value) override;
			bool setEnabled(const bool value) override;

			// IRegistration getters 
			RegistrationData getRegistrationData() override;

			// IRegistration setters 
			bool setRegistrationData(const RegistrationData &value) override;

			// Constructors & destructor 
			CRegistration();
			CRegistration(IControl *parent);
			CRegistration(IForm *parent);
			~CRegistration() override;

			// Layout property getters 
			Color getBackgroundColor();
			Color getInputBorderColor(const State state);

			// Layout property setters 
			bool setBackgroundColor(const Color &value);
			bool setInputBorderColor(const State state, const Color &value);
		};
	}
}

As you may noticed we also added forward declaration for the widget service class and marked it as widget class friend. We will implement service in the next paragraph.

Built-in text input and calendar have no background. We need to define and draw it ourselves. Text inputs we will draw on the white background and for the calendar we defined m_sCalendarBorderWidth, m_sCalendarPadding, m_sCalendarBorderColor and m_sCalendarBackgroundColor members to define nice layout. You may set colors to transparent (last component is 0) later to see how month calendar looks by default. We don't add getters and setter for these properties. You may later do it so that they also be editable in the Form Builder.

We declared three constructors in the class. It is common practice to have exactly three of them. The first one just creates widget and has no arguments. The second one creates widget and places it on a specified form and the last one creates widget and places it on a specified another widget. The second or third constructor may be missing if widget cannot be placed on a form or on another widget. As we create built-in controls in the widget we need to destroy them somewhere when they are not needed anymore. For that we have the destructor.

Classes for built-in controls and corresponding listeners are declared inside a private section of the widget class. They are not supposed to be used by the end user so we make them unavailable in such a way. It is also allowed to make service private but it's more common to have it public as the user may want to slightly adjust a widget by overriding some of its methods and may be some methods of its service will need some adjustments as well. We will create service in the next paragraph and we will make it available for widget users.

Add these two header files to CoolWidgets.h file as shown below.

#pragma once

#include "Controls/IRegistration.h"

#include "Controls/Registration/Registration.h"

Although we declared built-in control classes and listeners inside the CRegistration class, it's a good idea to put implementations into a separate source code files. So lets create file called TextInput.cpp in the Controls\Registration directory and put following built-in text input implementation there.

#include "stdafx.h"

namespace nitisa
{
	namespace coolwidgets
	{
		CRegistration::CTextInput::CTextInput(CRegistration *control) :
			CBuiltInTextInput(),
			m_pControl{ control }
		{

		}

		IControl *CRegistration::CTextInput::getControl()
		{
			return m_pControl;
		}
	}
}

The implementation is trivial. In constructor we just call parent class constructor and store pointer to widget class instance and in the getControl() method we return stored widget class instance.

Create source code file MonthCalendar.cpp in the same directory and put following code in it.

#include "stdafx.h"

namespace nitisa
{
	namespace coolwidgets
	{
		CRegistration::CMonthCalendar::CMonthCalendar(CRegistration *control) :
			CBuiltInMonthCalendar(),
			m_pControl{ control }
		{

		}

		IControl * CRegistration::CMonthCalendar::getControl()
		{
			return m_pControl;
		}
	}
}

As you can see it does absolutely the same as previous class.

Create source code file called TextInputListener.cpp in the same directory and put following code into it.

#include "stdafx.h"

namespace nitisa
{
	namespace coolwidgets
	{
		CRegistration::CTextInputListener::CTextInputListener(CRegistration *control) :
			m_pControl{ control }
		{

		}

		void CRegistration::CTextInputListener::NotifyOnRepaint(IBuiltInControl *sender, const RectF &rect)
		{
			if (m_pControl->m_pCanvas)
				m_pControl->m_pCanvas->setValid(false);
			m_pControl->Repaint(rect, true);
		}

		void CRegistration::CTextInputListener::NotifyOnTextChanged(IBuiltInControl *sender)
		{
			if (m_pControl->OnChange)
				m_pControl->OnChange(m_pControl);
		}
	}
}

Built-in text input listener class implementation is also pretty simple. In constructor we just store pointer to the widget class instance. We need it as we use it later in other methods. In the NotifyOnRepaint() notification method we repaint the widget. We will discuss canvas usage a little bit later when we will talk about widget drawing method. The only thing we do in the NotifyOnTextChanged() notification is calling widget OnChange event if it has a callback function assigned.

Create source code file called MonthCalendarListener.cpp and put following code into it.

#include "stdafx.h"

namespace nitisa
{
	namespace coolwidgets
	{
		CRegistration::CMonthCalendarListener::CMonthCalendarListener(CRegistration *control) :
			m_pControl{ control }
		{

		}

		void CRegistration::CMonthCalendarListener::NotifyOnRepaint(IBuiltInControl *sender, const RectF &rect)
		{
			if (m_pControl->m_pCanvasCalendar)
				m_pControl->m_pCanvasCalendar->setValid(false);
			m_pControl->Repaint(false);
		}

		void CRegistration::CMonthCalendarListener::NotifyOnChange(IBuiltInControl *sender)
		{
			m_pControl->CloseCalendar();
			if (m_pControl->OnChange)
				m_pControl->OnChange(m_pControl);
			if (m_pControl->m_pCanvas)
				m_pControl->m_pCanvas->setValid(false);
			m_pControl->Repaint(false);
		}
	}
}

Built-in month calendar listener implementation is similar to the built-in text input listener implementation except for a change notification. Here we just hide (or close) the calendar, call event callback function if it is assigned and repaint widget.

Create Registration.cpp file in the Controls\Registration directory and put following code with widget class implementation in it.

#include "stdafx.h"

namespace nitisa
{
	namespace coolwidgets
	{
	#pragma region Constructor & destructor
		CRegistration::CRegistration():
			// Call parent class constructor 
			CControl(L"Registration", true, true, false, true, false, true),
			// Set default values for properties 
			m_sBackgroundColor{ 0, 0, 0, 0 },
			m_aInputBorderColor{ Color{ 127, 127, 127, 255 }, Color{ 127, 127, 255, 255 } },
			m_pInputFirstName{ nullptr },
			m_pInputLastName{ nullptr },
			m_pCalendar{ nullptr },
			m_eFocusedElement{ Element::InputFirstName },
			m_eHoveredElement{ Element::None },
			m_eDownElement{ Element::None },
			m_pCanvas{ nullptr },
			m_pCanvasCalendar{ nullptr },
			m_cTextInputListener{ this },
			m_cMonthCalendarListener{ this },
			m_bCalendarOpened{ false },
			m_sInputFirstNameRect{ 152, 0, 300, 24 },
			m_sInputLastNameRect{ 152, 28, 300, 52 },
			m_sDateRect{ 152, 56, 300, 80 },
			m_sCalendarBorderWidth{ 1, 1, 1, 1 },
			m_sCalendarPadding{ 5, 3, 5, 3 },
			m_sCalendarBorderColor{ 151, 151, 151, 255 },
			m_sCalendarBackgroundColor{ 255, 255, 255, 255 },
			OnChange{ nullptr }
		{
			setService(new CRegistrationService(this), true); // Create and set widget service additionally destroying default one 
			CControl::setSize(PointF{ 300, 80 }); // Set size of the widget 
		}

		CRegistration::CRegistration(IControl *parent) :CRegistration()
		{
			setParent(parent);
		}

		CRegistration::CRegistration(IForm *parent) : CRegistration()
		{
			setForm(parent);
		}

		CRegistration::~CRegistration()
		{
			if (m_pInputFirstName)
				m_pInputFirstName->Release();
			if (m_pInputLastName)
				m_pInputLastName->Release();
			if (m_pCalendar)
				m_pCalendar->Release();
		}
	#pragma endregion

	#pragma region IControl getters
		IControl *CRegistration::getControl(const PointF &position)
		{
			if (m_bCalendarOpened)
			{
				Vec4f v{ ntl::Inversed<float>(getTransformMatrix()) * Vec4f { position.X, position.Y, 0, 1 } }; // Convert "position" into widget coordinate space 
				if (v.X >= m_sCalendarRect.Left && v.X < m_sCalendarRect.Right && v.Y >= m_sCalendarRect.Top && v.Y < m_sCalendarRect.Bottom) // Check whether position is inside calendar rectangle 
					return this;
				if (v.X >= 0 && v.X < getWidth() && v.Y >= 0 && v.Y < getHeight()) // Check whether position is inside widget rectangle 
					return this;
				return nullptr; // Position is neither in calendar nor in widget rectangle, return nullptr 
			}
			return CControl::getControl(position); // If calendar is not shown, default getControl() is OK for us 
		}

		CursorType CRegistration::getCursor()
		{
			switch (m_eHoveredElement)
			{
			// If hovered element is either of text inputs, return I-Beam cursor type 
			case Element::InputFirstName:
			case Element::InputLastName:
				return CursorType::IBeam;
			// If hovered element is arrow for showing calendar, return Hand cursor type 
			case Element::DateArrow:
				return CursorType::Hand;
			// In other cases return value assigned to Cursor property 
			default:
				return CControl::getCursor();
			}
		}
	#pragma endregion

	#pragma region IControl setters
		bool CRegistration::setAlign(const Align value)
		{
			// Aligning is not supported 
			return false;
		}

		bool CRegistration::setConstraints(const RectF &value)
		{
			// Constraints cannot be changed 
			return false;
		}

		bool CRegistration::setSize(const PointF &value)
		{
			// Size cannot be changed 
			return false;
		}

		bool CRegistration::setEnabled(const bool value)
		{
			// Widget is always enabled and this cannot be changed 
			return false;
		}
	#pragma endregion

	#pragma region IRegistration getters
		IRegistration::RegistrationData CRegistration::getRegistrationData()
		{
			// Return widget data getting values directly from built-in controls 
			return RegistrationData{
				getInputFirstName()->getText(),
				getInputLastName()->getText(),
				getCalendar()->getYear(),
				getCalendar()->getMonth(),
				getCalendar()->getDay() };
		}
	#pragma endregion

	#pragma region IRegistration setters
		bool CRegistration::setRegistrationData(const RegistrationData &value)
		{
			// Set widget values directly in controls 
			CLockRepaint lock{ this };
			bool result{ false };
			result = getInputFirstName()->setText(value.FirstName) || result;
			result = getInputLastName()->setText(value.LastName) || result;
			result = getCalendar()->setYear(value.Year) || result;
			result = getCalendar()->setMonth(value.Month) || result;
			result = getCalendar()->setDay(value.Day) || result;
			if (result)
			{
				// Repaint only if something was really changed 
				if (m_pCanvas)
					m_pCanvas->setValid(false);
				if (m_bCalendarOpened && m_pCanvasCalendar)
					m_pCanvasCalendar->setValid(false);
				Repaint(false);
			}
			return result;
		}
	#pragma endregion

	#pragma region Getters
		Color CRegistration::getBackgroundColor()
		{
			return m_sBackgroundColor;
		}

		Color CRegistration::getInputBorderColor(const State state)
		{
			return m_aInputBorderColor[(int)state];
		}
	#pragma endregion

	#pragma region Setters
		bool CRegistration::setBackgroundColor(const Color &value)
		{
			if (value != m_sBackgroundColor)
			{
				m_sBackgroundColor = value;
				if (m_pCanvas)
					m_pCanvas->setValid(false);
				Repaint(false);
				return true;
			}
			return false;
		}

		bool CRegistration::setInputBorderColor(const State state, const Color &value)
		{
			if (value != m_aInputBorderColor[(int)state])
			{
				m_aInputBorderColor[(int)state] = value;
				if (m_pCanvas)
					m_pCanvas->setValid(false);
				Repaint(false);
				return true;
			}
			return false;
		}
	#pragma endregion

	#pragma region Helpers
		CRegistration::Element CRegistration::getElement(const PointF &p)
		{
			// Check whether point is inside calendar rectangle 
			if (m_bCalendarOpened && p.X >= m_sCalendarRect.Left && p.X < m_sCalendarRect.Right && p.Y >= m_sCalendarRect.Top && p.Y < m_sCalendarRect.Bottom)
				return Element::Calendar;
			// Check whether point is inside first name built-in text input 
			if (p.X >= m_sInputFirstNameRect.Left + 2 && p.X < m_sInputFirstNameRect.Right - 2 && p.Y >= m_sInputFirstNameRect.Top + 2 && p.Y < m_sInputFirstNameRect.Bottom - 2)
				return Element::InputFirstName;
			// Check whether point is inside last name built-in text input 
			if (p.X >= m_sInputLastNameRect.Left + 2 && p.X < m_sInputLastNameRect.Right - 2 && p.Y >= m_sInputLastNameRect.Top + 2 && p.Y < m_sInputLastNameRect.Bottom - 2)
				return Element::InputLastName;
			// Check whether point is inside date of birth region 
			if (p.X >= m_sDateRect.Left && p.X < m_sDateRect.Right && p.Y >= m_sDateRect.Top && p.Y < m_sDateRect.Bottom)
			{
				// Check whether point is inside calendar show arrow region 
				if (p.X >= m_sDateRect.Right - m_sDateRect.height())
					return Element::DateArrow;
				return Element::Date;
			}
			return Element::None;
		}

		IBuiltInTextInput *CRegistration::getInputFirstName()
		{
			if (!m_pInputFirstName)
			{
				// If input is not yet created, create it and set its options 
				m_pInputFirstName = new CTextInput(this);
				m_pInputFirstName->setSize(PointF{ m_sInputFirstNameRect.width() - 4, m_sInputFirstNameRect.height() - 4 });
				m_pInputFirstName->setPosition(PointF{ m_sInputFirstNameRect.Left + 2, m_sInputFirstNameRect.Top + 2 });
				m_pInputFirstName->setFocused(isFocused() && m_eFocusedElement == Element::InputFirstName);
				m_pInputFirstName->setListener(&m_cTextInputListener);
			}
			return m_pInputFirstName;
		}

		IBuiltInTextInput *CRegistration::getInputLastName()
		{
			if (!m_pInputLastName)
			{
				// If input is not yet created, create it and set its options 
				m_pInputLastName = new CTextInput(this);
				m_pInputLastName->setSize(PointF{ m_sInputLastNameRect.width() - 4, m_sInputLastNameRect.height() - 4 });
				m_pInputLastName->setPosition(PointF{ m_sInputLastNameRect.Left + 2, m_sInputLastNameRect.Top + 2 });
				m_pInputLastName->setFocused(isFocused() && m_eFocusedElement == Element::InputLastName);
				m_pInputLastName->setListener(&m_cTextInputListener);
			}
			return m_pInputLastName;
		}

		IBuiltInMonthCalendar *CRegistration::getCalendar()
		{
			if (!m_pCalendar)
			{
				// If calendar is not yet created, create it and set its options 
				m_pCalendar = new CMonthCalendar(this);
				m_pCalendar->setListener(&m_cMonthCalendarListener);
			}
			return m_pCalendar;
		}

		void CRegistration::OpenCalendar()
		{
			if (!m_bCalendarOpened)
			{
				m_bCalendarOpened = true; // Set flag indicating whether calendar is shown 
				m_eDownElement = Element::None; // Down element can be input only, so reset it to None just in case 
				PointF size{ getCalendar()->getRequiredSize() }; // Get calendar size 
				PointF disp{ m_sCalendarBorderWidth.Left + m_sCalendarPadding.Left, m_sCalendarBorderWidth.Top + m_sCalendarPadding.Top }; // Calculate where built-in calendar is placed relative to calendar area 
				float w{ disp.X + size.X + m_sCalendarBorderWidth.Right + m_sCalendarPadding.Right }; // Calculate total required width for calendar 
				float h{ disp.Y + size.Y + m_sCalendarBorderWidth.Bottom + m_sCalendarPadding.Bottom }; // Calculate total required height for calendar 
				// Update calendar rectangle with calculated values 
				m_sCalendarRect.Left = m_sDateRect.Right - w;
				m_sCalendarRect.Right = m_sCalendarRect.Left + w;
				m_sCalendarRect.Top = m_sDateRect.Bottom;
				m_sCalendarRect.Bottom = m_sCalendarRect.Top + h;
				getCalendar()->setSize(size); // Set built-in calendar size 
				getCalendar()->setPosition(disp); // Set built-in calendar position 
				getForm()->CaptureMouse(this, true); // Request to capture mouse input 
				if (m_pCanvasCalendar)
					m_pCanvasCalendar->setValid(false); // Invalidate calendar canvas 
				Repaint(false); // Repaint widget 
			}
		}

		void CRegistration::CloseCalendar()
		{
			if (m_bCalendarOpened)
			{
				CLockRepaint lock{ this }; // Lock form's repaint to prevent multiple drawing until we finish with changes 
				Repaint(false); // Repaint widget area before hiding calendar 
				m_bCalendarOpened = false; // Set flag indicating whether calendar is shown 
				getCalendar()->NotifyOnFreeResources(); // Free resources used by built-in calendar 
				if (m_pCanvasCalendar)
				{
					// Free calendar canvas 
					m_pCanvasCalendar->Release();
					m_pCanvasCalendar = nullptr;
				}
				if (isCaptureMouse())
					getForm()->ReleaseCaptureMouse(); // Release mouse capture 
			}
		}

		bool CRegistration::UpdateHoveredElement(const PointF &p)
		{
			Element element{ getElement(p) }; // Find which element is under the specified point 
			if (element != m_eHoveredElement) // If current hovered element is different 
			{
				switch (m_eHoveredElement) // Notify element which is loosing hovered state about it 
				{
				case Element::InputFirstName:
					getInputFirstName()->NotifyOnMouseLeave();
					break;
				case Element::InputLastName:
					getInputLastName()->NotifyOnMouseLeave();
					break;
				case Element::Calendar:
					getCalendar()->NotifyOnMouseLeave();
					break;
				}
				m_eHoveredElement = element; // Update hovered element property 
				switch (m_eHoveredElement) // Notify new hovered element that he have got hovered state 
				{
				case Element::InputFirstName:
					getInputFirstName()->NotifyOnMouseHover(p - getInputFirstName()->getPosition());
					break;
				case Element::InputLastName:
					getInputLastName()->NotifyOnMouseHover(p - getInputLastName()->getPosition());
					break;
				case Element::Calendar:
					getCalendar()->NotifyOnMouseHover(p - m_sCalendarRect.LeftTop);
					break;
				}
				return true;
			}
			return false;
		}

		void CRegistration::UpdateFromStyle(IStyle *style)
		{
			// Update widget properties 
			style->getOption(m_sClassName + L".BackgroundColor", m_sBackgroundColor);
			style->getOption(m_sClassName + L".InputBorderColor[Normal]", m_aInputBorderColor[0]);
			style->getOption(m_sClassName + L".InputBorderColor[Focused]", m_aInputBorderColor[1]);
			// Update built-in controls properties 
			getInputFirstName()->UpdateFromStyle(style, m_sClassName + L".TextInput");
			getInputLastName()->UpdateFromStyle(style, m_sClassName + L".TextInput");
			getCalendar()->UpdateFromStyle(style, m_sClassName + L".MonthCalendar");
		}
	#pragma endregion

	#pragma region Render
		void CRegistration::RenderControl(IRenderer *renderer)
		{
			if (PrepareCanvas(renderer, &m_pCanvas, getSize())) // If we can draw on canvas 
			{
				IFont *font{ getFont() }; // Store font instance 
				IPlatformFont *pf{ font->getPlatformFont(renderer) }; // Store platform font instance 
				String text_date{ nitisa::ToString(getCalendar()->getMonth()) + L"/" + nitisa::ToString(getCalendar()->getDay()) + L"/" + nitisa::ToString(getCalendar()->getYear()) }; // Make string with date in m/d/Y format 
				PointF text_first_name_size{ pf->getStringSize(L"First name:", font->Distance) }; // Calculate size of "First name:" string 
				PointF text_last_name_size{ pf->getStringSize(L"Last name:", font->Distance) }; // Calculate size of "Last name:" string 
				PointF text_date_of_birth_size{ pf->getStringSize(L"Date of birth:", font->Distance) }; // Calculate size of "Date of birth:" string 
				PointF text_date_size{ pf->getStringSize(text_date, font->Distance) }; // Calculate size of string with date 
				BlockColors normal{ m_aInputBorderColor[0], m_aInputBorderColor[0], m_aInputBorderColor[0], m_aInputBorderColor[0], Color{ 255, 255, 255, 255 }, Color{ 0, 0, 0, 0 } }; // Normal state block colors 
				BlockColors focused{ m_aInputBorderColor[1], m_aInputBorderColor[1], m_aInputBorderColor[1], m_aInputBorderColor[1], Color{ 255, 255, 255, 255 }, Color{ 0, 0, 0, 0 } }; // Focused state block colors 
				CStoreTarget s_target{ renderer }; // Store active target 
				CStorePrimitiveMatrix s_matrix{ renderer }; // Store active primitive matrix 
				renderer
					->ActivateTarget(m_pCanvas) // Activate widget canvas to draw on it 
					->Clear(Color{ 0, 0, 0, 0 }) // Clear canvas 
					->DrawRectangle(getRect(), m_sBackgroundColor) // Draw widget background 
					->DrawBlock( // Draw first name input border and background 
						m_sInputFirstNameRect,
						RectF{ 1, 1, 1, 1 },
						RectF{ 0, 0, 0, 0 },
						(isFocused() && m_eFocusedElement == Element::InputFirstName) ? focused : normal)
					->DrawBlock( // Draw last name input border and background 
						m_sInputLastNameRect,
						RectF{ 1, 1, 1, 1 },
						RectF{ 0, 0, 0, 0 },
						(isFocused() && m_eFocusedElement == Element::InputLastName) ? focused : normal)
					->DrawBlock( // Draw date of birth area border and background 
						m_sDateRect,
						RectF{ 1, 1, 1, 1 },
						RectF{ 0, 0, 0, 0 },
						(isFocused() && m_eFocusedElement == Element::Date) ? focused : normal)
					// Draw first name input label 
					->ActivatePrimitiveMatrix(ntl::Mat4Translate<float>(148 - text_first_name_size.X, std::roundf(12 - text_first_name_size.Y * 0.5f), 0))
					->DrawText(L"First name:", pf, font->Distance, CColors::Black)
					// Draw last name input label 
					->ActivatePrimitiveMatrix(ntl::Mat4Translate<float>(148 - text_last_name_size.X, std::roundf(40 - text_last_name_size.Y * 0.5f), 0))
					->DrawText(L"Last name:", pf, font->Distance, CColors::Black)
					// Draw date of birth are label 
					->ActivatePrimitiveMatrix(ntl::Mat4Translate<float>(148 - text_date_of_birth_size.X, std::roundf(68 - text_date_of_birth_size.Y * 0.5f), 0))
					->DrawText(L"Date of birth:", pf, font->Distance, CColors::Black)
					// Draw date of birth date 
					->ActivatePrimitiveMatrix(ntl::Mat4Translate<float>(154, std::roundf(68 - text_date_size.Y * 0.5f), 0))
					->DrawText(text_date, pf, font->Distance, CColors::Black)
					// Draw calendar show/hide arrow 
					->ActivatePrimitiveMatrix(ntl::Mat4Translate<float>(m_sDateRect.Right - m_sDateRect.height(), m_sDateRect.Top, 0))
					->DrawTriangle(PointF{ 6, 8 }, PointF{ m_sDateRect.height() - 6, 8 }, PointF{ m_sDateRect.height() * 0.5f, m_sDateRect.height() - 9 }, CColors::Black)
					// Set primitive matrix to identity before drawing built-in inputs 
					->ActivatePrimitiveMatrix(nullptr);

				// Draw built-in inputs 
				getInputFirstName()->Render(false, ntl::Mat4Translate<float>(m_sInputFirstNameRect.Left + 2, m_sInputFirstNameRect.Top + 2, 0), nullptr);
				getInputLastName()->Render(false, ntl::Mat4Translate<float>(m_sInputLastNameRect.Left + 2, m_sInputLastNameRect.Top + 2, 0), nullptr);

				// Make canvas valid 
				m_pCanvas->setValid(true);
			}
			DrawCanvas(renderer, getTransformMatrix(), m_pCanvas, PointF{ 0, 0 }); // Draw canvas on a form 
		}

		void CRegistration::RenderCalendar(IRenderer *renderer)
		{
			if (!m_pCanvasCalendar || !m_pCanvasCalendar->isValid() || getCalendar()->isAnimating())
			{
				if (PrepareCanvas(renderer, &m_pCanvasCalendar, m_sCalendarRect))
				{
					CStoreTarget s_target{ renderer }; // Store active target 
					// Draw calendar background 
					renderer
						->ActivateTarget(m_pCanvasCalendar)
						->Clear(Color{ 0, 0, 0, 0 })
						->DrawBlock(
							RectF{ 0, 0, m_sCalendarRect.width(), m_sCalendarRect.height() },
							RectF{ 1, 1, 1, 1 },
							RectF{ 0, 0, 0, 0 },
							BlockColors{ m_sCalendarBorderColor, m_sCalendarBorderColor, m_sCalendarBorderColor, m_sCalendarBorderColor, m_sCalendarBackgroundColor, Color{ 0, 0, 0, 0 } });
					// Draw calendar built-in control 
					getCalendar()->Render(false, ntl::Mat4Translate<float>(getCalendar()->getLeft(), getCalendar()->getTop(), 0), nullptr);
					m_pCanvasCalendar->setValid(true); // Make canvas valid 
				}
			}
			DrawCanvas(renderer, getTransformMatrix() * ntl::Mat4Translate<float>(m_sCalendarRect.Left, m_sCalendarRect.Top, 0), m_pCanvasCalendar, PointF{ 0, 0 }); // Draw calendar canvas on a form 
		}
	#pragma endregion
	}
}

We use #pragma region - #pragma endregion blocks. It does nothing to the source code, it just tells Visual Studio to make a block of all lines between region start and end which can be folded. It's just an another code organization tool. Widget class implementation can be large and it may be very useful to group some methods together like we did. It may greatly help to navigate through it. Please note that this type of making blocks of code is for Visual Studio only and it will raise warning if you compile such a code using another compilers. So you will need to add ignoring unknown pragma options to settings of all projects which are supposed to be compiled by another than Microsoft C++ compilers. To be particular, you need to add -Wno-unknown-pragmas options to Android and Linux projects to avoid warnings.

In the main constructor of widget class (the one without arguments) we call constructor of parent class and set default values for all needed properties of the class. CControl class constructor has a lot of arguments. Meaning of all of them you can find on the class reference page. Besides telling our widget class name, which is "Registration" (we need to omit C at the beginning) we told parent class that our widget can be placed both on a form and on another widget but no widgets can be placed on it, our widget can be focused and can be focused by Tab and Shift+Tab key combination, which is used to switch focus from one widget to another on a form, and our widget cannot be modal. In the first line of constructor body we replaced widget service with our own which we will implement in the next paragraph and set widget size to 300 by 80 pixels.

The rest two constructors just call the main one and put widget either on a form or on another widget. In destructor we just remove built-in controls instances if they where created.

In the getControl() method, if the calendar is shown, we check whether the specified position is inside the calendar or widget rectangle and return our widget instance pointer if so or nullptr otherwise. The method argument is in the form coordinate system so we need first to convert it into widget coordinate system. Widget's getTransformMatrix() method returns transformation matrix of the widget used to place it on a form. It takes into account that widget may have transformations (like rotation) as well as it may be placed not on a form directly but onto another widget. The method returns final transformation matrix. We will also use it in drawing method. So, to convert position from form coordinate system to widget one, all we need to do is apply inverted matrix to it which is done in the calculation of v variable.

getCursor() method simply return cursor type depending on what element is under the mouse pointer at the moment.

Methods from IControl setters group just return false. It tells caller that nothing has been changed by the method call. We did it to prevent size changes as well as to make the widget always enabled.

getRegistrationData() method takes values form built-in controls and return it in a form of RegistrationData structure. Accordingly setRegistrationData() does the opposite. It puts specified data into built-in controls and, if data differs, repaint the widget. You may have noticed that we didn't call event here although we change data. Setter methods are called from source code written by application developers and thus they new exactly when this happens and calling an event has no much sense. On contrary, event has sense when data is changed by a user who uses an application. That is why setter methods almost never call events.

Getters and setters of the two properties of the widget act the same.

In the getElement() we find out which element is under the specified point. The point is specified in the widget coordinate space so we don't need any additional transformations. We defined input rectangles in m_sInputFirstNameRect and m_sInputLastNameRect rectangles but those rectangles are with border (1 pixel) and padding (1 pixel) so in the calculations in this method we add/subtract those 2 pixels. Also we assume that the calendar show arrow is a part of date of birth area and is located at the right of it. It has the height of the date of birth block height and the same width.

The next three methods for getting built-in text input and calendar instances just check if corresponding built-in control is already created and, if not, create it, set default parameters and corresponding listener. We do not set any parameters in the getCalendar() method. We set them in the OpenCalendar() method instead.

In the OpenCalendar() method we calculate what size and position the calendar should have, update m_sCalendarRect property, which stores both position and size in form of a rectangle, update built-in calendar properties, capture mouse input and repaint widget. Capture input means that all input events will be sent to the widget which captures the input instead of the widget which, for example, under the mouse pointer. In Nitisa keyboard and mouse input can be captured separately. We do not need keyboard input as the calendar handle mouse events only. Why do we need to capture mouse input? The answer is simple: we want to know where user clicks. If he clicks inside the calendar area, it means he works with calendar and we do nothing or close the calendar if he selects a date. But if user clicks outside the calendar area it means the calendar should be closed. If we didn't capture the input we would have missed clicks outside the calendar and widget area. Additionally, by the second argument of CaptureMouse() methods, we told that the capture should be system wide. In this case we won't miss clicks even if they are outside a form. The CloseCalendar() method hides the calendar.

UpdateHoveredElement() finds out which element is under the specified point (which is also in the widget coordinate space) and, if it is another element that is stored in the m_eHoveredElement member, update the member and depending of what element was hovered tells corresponding build-in control that element is not hovered anymore and, for the element which becomes hovered it tells that the element is hovered now. When telling element it is hovered the method should specify point in the corresponding built-in control coordinate space. We stored built-in text input positions in the built-in text inputs when created them, so the coordinate in the input coordinate space is just a coordinate in the widget space (which is p argument) minus the input position. The same is for the calendar but in this case calendar position is stored in the m_sCalendarRect member.

UpdateFromStyle() method will be called from widget service when style is changed. In this method we get values from style and update properties supported styling. As a style can have tons of properties for different widgets it is imperative to identify which properties are ours and which ones are not. To do this we suppose our widget properties in a style will have our widget class name at the beginning. This name is stored in m_sClassName when we call CControl constructor (the first argument). We can choose any name for the properties in style. To allow style makers to use them you need include the names in the documentation of your widgets. The good choice can be something like namespace::classname.property or even company::namespace::classname.property. Anyway, the choice is your. Just don't forget it should be as much unique as possible to avoid using wrong style properties supposed for another widget with the same name.

The most important and most interesting methods in a widget class are usually the drawing methods. In our widget we have two. Most widgets don't draw themselves directly on a form. They usually draw themselves into an internal texture, usually called canvas, and then they draw this canvas on a form. This is a good optimization solution as the form repaint happens much more often then the change of a widget layout and drawing a simple image is way to faster than drawing all the elements of a widget each time. Our widget draw two parts separately. The first part is a widget without calendar and the second one is the calendar. For this parts we defined two canvas m_pCanvas and m_pCanvasCalendar and two drawing methods RenderControl() and RenderCalendar() accordingly.

At the beginning of drawing we need to ensure we need it. There is overloaded helper function PrepareCanvas() for that. All this function does is create canvas if it was not created earlier or set its size if it's not equal to required one. The function returns true if canvas was just created or its size was just changed or it's marked as invalid. In other cases it returns false and it means we can't or needn't to draw. When we draw our widget on a canvas we need to mark it as valid so that next time we omit drawing (unless the size of a widget and its canvas is changed). We do that by calling canvas method setValid(true). We do it near the end of the RenderControl() method. The canvas remains in the valid state until we change it manually. So, in order to force drawing onto canvas again we need to call setValid(false). That is what we did in many methods earlier and left without explanation. See, for example, setBackgroundColor() method of the widget class. In it, if new color differs from used one, we invalidate canvas and call Repaint() function. This will trigger paint event later and the RenderControl() method will be called from the widget service and PrepareCanvas() will return true as the canvas is marked as invalid. By the way, it is very important to check whether canvas exists before working with it.

Lets now come back to the widget rendering. We use font and platform font to draw texts. As we use them multiple times, it is a good idea to store their instances in the variables to avoid multiple calls of the same methods. That is what we did in font and pf variables. Next, we declare string variable and put into it a string representation of a date stored in the calendar. The date is represented in the m/d/Y format. By the next four lines we get needed string sizes and store them in variables with type PointF.

By the next two lines we define color sets in form of BlockColors for background of inputs and date of birth. It's a set of six colors. The first four are left, top, right and bottom border colors. The fifth is the inside color and the last one is outside color. We use them in DrawBlock() method of renderer.

Next two lines store renderer active target and primitive matrix. We need to store every active renderer state we change and restore them when we finish drawing. To do that we use two helper classes CStoreTarget and CStorePrimitiveMatrix.

After all the preparations we perform real drawing using renderer methods. To draw on a canvas (texture) we first activate it by ActivateTarget() method, then we clear it by Clear() method. After that we draw background of the widget. It is done with the call of DrawRectangle() method. The next three calls of DrawBlock() method draw backgrounds for inputs and date of birth part using either normal or focused color set depending whether part is focused or not.

After drawing background parts we draw all texts: three labels and date of birth. Renderer methods for drawing text don't have argument which allow to specify where the text should be drawn. Instead we need to use active primitive matrix. That is what we do: we activate primitive matrix which have text position and which is built using Mat4Translate() template function and then draw text. We use std::roundf() for Y coordinate to avoid placing text in the middle of a pixel as we have multiplication by a 0.5f. That is not required for X coordinate.

Next we set identity primitive matrix as the built-in controls assume all the renderer settings they don't use are set to default values. After it we call built-in text inputs Render() method to draw them. And finally we mark canvas as valid one.

All that drawing we perform if we need and we can accordingly to PrepareCanvas() function. At the end we draw canvas on a form with helper function DrawCanvas(). That is how we draw our widget.

The last method RenderCalendar() perform drawing of the calendar in the similar but more simple way as there are much less parts to draw. Also it draws into m_pCanvasCalendar canvas and draw that canvas on the form in the place where the calendar is. It is done by multiplying widget transformation matrix by matrix with calendar position in widget.

Notifications

There are many events happen while application is running. User may change language, click somewhere in the application form, move mouse pointer, type something using keyboard or even shutdown the PC. All those events as well as a lot more others can be handled by application. In the Nitisa they are divided into notifications and events. Notifications is what the form and widget uses internally and theirs behaviour is defined during form or widget development. Events are used by the end users, users who use already existing widgets and forms. Event is customizable behaviour of a widget (or a form) while notification is not. In the most cases notifications and events go together. For example, there are both LeftMouseButtonDown notification and event. Notifications are methods defined and implemented at the widget development. Events are also defined at the widget development by the do nothing by default. Events are just properties to which a user defined callback function can be assigned and it will be called if corresponding event happens. The same widget can have different logic in different applications assigned to the same event. Notifications are fixed. They behave the same way for all instances of a widget.

So, notifications define widget response on certain events, like mouse move. All notifications are implemented in widget service. Service is an interface with a set of methods. That interface is usually separated from widget. That is done to avoid mistakes like calling notification methods by end user as all the notifications are responses for some events. Although some events are generated by user actions, user do it by moving mouse, type text, but not by calling notification method of a widget. So they are implemented separately from a widget class. There is an interface called IControlService which describes all notifications a control can have in the framework. There is similar interface called IComponentService for component service and so on. Besides notifications services can have (and they actually do) some other helper methods. For example, control interface has such methods as setParent(), MoveControl() and others. They are needed for proper interaction between widgets and a form. In 99.9% of cases you need only notification methods from services. Service interfaces have corresponding default implementations in classes called the same way just starting from C instead of I. For example, control service interface default implementation is called CControlService. CControl class creates instance of that default service implementation and assign it to itself to work with. But all default service implementations have empty notification methods. It means all notifications are ignored, widget doesn't react to user actions. That means we need to override notification methods and put there some logic to make widget respond to events.

When we have service class implemented we need to assign it to our widget. It can be done using setService() method of a widget. That is what we did in the widget constructor.

In the control service interface there are a lot of notifications. We will use default control service class implementation in the CControlService class so we need only implement those notification our control is interested in. For example, our control doesn't have any child controls so it isn't interested in child control notifications at all. So, what are the notifications our widget need to handle?

Every widget that uses platform dependent resources need to implement NotifyOnFreeResources() notification method. The main platform dependent resources are timers, textures and platform fonts. This notification is called when something happen that require to release all the platform resources acquired. We use textures draw on and we have built-in controls which might also use platform resources, so we need implement this notification.

Built-in controls also have NotifyOnAttach() notification. So we, at least, need to pass these notification to them. The notifications the built-in controls have are described by their interfaces. All built-in controls are derived from IBuiltInControl interface and so you may find notification they all have in the reference page of that interface. Some of them may not be relevant. For example, they have NotifyOnDeactivate() notification. It should be called when widget loses active state but our widget never gets active state, so it will never receive notification about loosing such a state and thus we have nothing to transfer to built-in controls. Thus this notification can be omitted.

Built-in month calendar draw some texts and support internationalization. This means that we need to handle NotifyOnTranslateChange() notification as well. In our example we will only close the calendar. We won't change texts of the widget. But when creating production widgets you may want to handle application layout language change as well. All you need in this case is to load proper texts using ITranslate interface which you can get as Application->Translate member and after it repaint the widget.

As our widget and built-in controls we use support styling we need to handle style changing notifications. There are two of them. The NotifyOnParenStyleChange() notifications is called when one of the parent (widget or form) style is changed and our widget uses this parent style. The second notification is NotifyOnStyleChange() is called when the style of the widget is changed. It happens when widget has his own style assigned. In the same way we need to handle font changes as we draw texts using font. The font change notifications are NotifyOnParentFontChange() and NotifyOnFontChange().

We also need to know when our widget get and loose input focus. The notifications are NotifyOnSetFocus() and NotifyOnKillFocus(). We don't need notification about getting mouse capture because that happens only by our request. We do it on showing the calendar and will do when user downs left mouse button over any text input. But we do need the opposite notification NotifyOnKillCaptureMouse() as capture loose may happen independently on our desire and we need handle it properly.

Sure we need a paint notification to know when widget should be repainted. There are two paint notifications. The first one is NotifyOnPaint(). This notification is called at the beginning of drawing. The second one is NotifyOnPaintEnd(). This notification is called at the end of drawing (after drawing of all child controls of a widget). We will draw our widget in the first one and won't use the second notification.

As we have inputs where user may type texts we definitely need to handle keyboard notifications. Moreover, we need to handle all of them as all of them are defined and might be used in the built-in controls. Additionally we will switch focused/active part of our widget by Up and Down arrows so user can use this keys to select what he wants to type: first name, last name or change date of birth. Also when date of birth part is active we will use F4 key to show and hide calendar.

Most of the interaction with widgets are done via mouse and our widget is not an exception. This means we need to handle mouse events. We need all of them except for scrolling ones as our widget has no scrolling parts.

And the final notification we will handle is the NotifyOnPasteString() one. This notification is called when user pastes string from clipboard.

So, create a header file with name RegistrationService.h in the Controls\Registration directory and put following code inside it. Don't forget to add it to the CoolWidgets.h

#pragma once

#include "Nitisa/Core/ControlService.h"

namespace nitisa
{
	namespace coolwidgets
	{
		class CRegistration;

		class CRegistrationService :public CControlService
		{
		private:
			CRegistration *m_pControl;

			void CancelDown(const bool release_capture);
		public:
			// State change notifications 
			void NotifyOnAttach() override;
			void NotifyOnFreeResources() override;

			// Application notifications 
			void NotifyOnTranslateChange() override;

			// Notifications from parent control 
			void NotifyOnParentStyleChange() override;
			void NotifyOnParentFontChange() override;

			// State change notifications 
			void NotifyOnStyleChange() override;
			void NotifyOnFontChange() override;
			void NotifyOnSetFocus(const MessageFocus &m) override;
			void NotifyOnKillFocus() override;
			void NotifyOnKillCaptureMouse() override;

			// Paint notifications 
			void NotifyOnPaint(const MessagePaint &m, bool &draw_children) override;

			// Keyboard input notifications 
			void NotifyOnKeyDown(const MessageKey &m, bool &processed) override;
			void NotifyOnKeyUp(const MessageKey &m, bool &processed) override;
			void NotifyOnChar(const MessageChar &m, bool &processed) override;
			void NotifyOnDeadChar(const MessageChar &m, bool &processed) override;

			// Mouse input notifications 
			void NotifyOnMouseHover(const MessagePosition &m) override;
			void NotifyOnMouseLeave() override;
			void NotifyOnMouseMove(const MessageMouse &m, bool &processed) override;
			void NotifyOnLeftMouseButtonDown(const MessageMouse &m, bool &processed) override;
			void NotifyOnLeftMouseButtonUp(const MessageMouse &m, bool &processed) override;
			void NotifyOnLeftMouseButtonDoubleClick(const MessageMouse &m, bool &processed) override;
			void NotifyOnRightMouseButtonDown(const MessageMouse &m, bool &processed) override;
			void NotifyOnRightMouseButtonUp(const MessageMouse &m, bool &processed) override;
			void NotifyOnRightMouseButtonDoubleClick(const MessageMouse &m, bool &processed) override;
			void NotifyOnMiddleMouseButtonDown(const MessageMouse &m, bool &processed) override;
			void NotifyOnMiddleMouseButtonUp(const MessageMouse &m, bool &processed) override;
			void NotifyOnMiddleMouseButtonDoubleClick(const MessageMouse &m, bool &processed) override;

			// Clipboard notifications 
			void NotifyOnPasteString(const MessagePasteString &m) override;

			CRegistrationService(CRegistration *control);
		};
	}
}

Here we additionally declared helper method CancelDown() which we will use every time we need to revert changes made when left mouse button was down.

Create source code file called RegistrationService.cpp in Controls\Registration directory and put following code into it.

#include "stdafx.h"

namespace nitisa
{
	namespace coolwidgets
	{
	#pragma region Constructor & destructor
		CRegistrationService::CRegistrationService(CRegistration *control):
			CControlService(control),
			m_pControl{ control }
		{

		}
	#pragma endregion

	#pragma region Helpers
		void CRegistrationService::CancelDown(const bool release_capture)
		{
			if (m_pControl->m_eDownElement != CRegistration::Element::None)
			{
				// Send cancel down notification to the input on which down was done 
				switch (m_pControl->m_eDownElement)
				{
				case CRegistration::Element::InputFirstName:
					m_pControl->getInputFirstName()->NotifyOnMouseDownCancel();
					break;
				case CRegistration::Element::InputLastName:
					m_pControl->getInputLastName()->NotifyOnMouseDownCancel();
					break;
				}
				m_pControl->m_eDownElement = CRegistration::Element::None;
			}
			if (release_capture && m_pControl->isCaptureMouse())
				m_pControl->getForm()->ReleaseCaptureMouse();
		}
	#pragma endregion

	#pragma region State change notifications
		void CRegistrationService::NotifyOnAttach()
		{
			if (m_pControl->getForm())
			{
				m_pControl->getInputFirstName()->NotifyOnAttach();
				m_pControl->getInputLastName()->NotifyOnAttach();
				m_pControl->getCalendar()->NotifyOnAttach();
			}
		}

		void CRegistrationService::NotifyOnFreeResources()
		{
			// Close calendar if it's opened 
			m_pControl->CloseCalendar();
			// Delete both canvas if they are created 
			if (m_pControl->m_pCanvas)
			{
				m_pControl->m_pCanvas->Release();
				m_pControl->m_pCanvas = nullptr;
			}
			if (m_pControl->m_pCanvasCalendar)
			{
				m_pControl->m_pCanvasCalendar->Release();
				m_pControl->m_pCanvasCalendar = nullptr;
			}
			// Notify built-in controls 
			if (m_pControl->m_pInputFirstName)
				m_pControl->m_pInputFirstName->NotifyOnFreeResources();
			if (m_pControl->m_pInputLastName)
				m_pControl->m_pInputLastName->NotifyOnFreeResources();
			if (m_pControl->m_pCalendar)
				m_pControl->m_pCalendar->NotifyOnFreeResources();
		}
	#pragma endregion

	#pragma region Application notifications
		void CRegistrationService::NotifyOnTranslateChange()
		{
			m_pControl->CloseCalendar();
		}
	#pragma endregion

	#pragma region Notifications from parent control
		void CRegistrationService::NotifyOnParentStyleChange()
		{
			if (m_pControl->getStyle())
			{
				m_pControl->CloseCalendar();
				m_pControl->UpdateFromStyle(m_pControl->getStyle());
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
			}
		}

		void CRegistrationService::NotifyOnParentFontChange()
		{
			m_pControl->CloseCalendar();
			if (m_pControl->m_pCanvas)
				m_pControl->m_pCanvas->setValid(false);
		}
	#pragma endregion

	#pragma region State change notifications
		void CRegistrationService::NotifyOnStyleChange()
		{
			if (m_pControl->getStyle())
			{
				m_pControl->CloseCalendar();
				m_pControl->UpdateFromStyle(m_pControl->getStyle());
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
			}
		}

		void CRegistrationService::NotifyOnFontChange()
		{
			m_pControl->CloseCalendar();
			if (m_pControl->m_pCanvas)
				m_pControl->m_pCanvas->setValid(false);
		}

		void CRegistrationService::NotifyOnSetFocus(const MessageFocus &m)
		{
			if (m.FocusedBy != FocusedBy::LeftMouse)
			{
				m_pControl->m_eFocusedElement = CRegistration::Element::InputFirstName;
				m_pControl->getInputFirstName()->setFocused(true);
				m_pControl->getInputLastName()->setFocused(false);
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				m_pControl->Repaint(false);
			}
		}

		void CRegistrationService::NotifyOnKillFocus()
		{
			m_pControl->CloseCalendar();
			m_pControl->m_eFocusedElement = CRegistration::Element::None;
			m_pControl->getInputFirstName()->setFocused(false);
			m_pControl->getInputLastName()->setFocused(false);
			if (m_pControl->m_pCanvas)
				m_pControl->m_pCanvas->setValid(false);
			m_pControl->Repaint(false);
		}

		void CRegistrationService::NotifyOnKillCaptureMouse()
		{
			CancelDown(false);
		}
	#pragma endregion

	#pragma region Paint notifications
		void CRegistrationService::NotifyOnPaint(const MessagePaint &m, bool &draw_children)
		{
			if (!m.LastPass)
				m_pControl->RenderControl(m_pControl->getForm()->getRenderer());
			else if (m_pControl->m_bCalendarOpened)
				m_pControl->RenderCalendar(m_pControl->getForm()->getRenderer());
		}
	#pragma endregion

	#pragma region Keyboard input notifications
		void CRegistrationService::NotifyOnKeyDown(const MessageKey &m, bool &processed)
		{
			bool changed{ false }, numlock{ Application->Keyboard->isToggled(Key::NumLock) }, ctrl, alt, shift;
			Application->Keyboard->getControlKeys(ctrl, alt, shift);
			switch (m_pControl->m_eFocusedElement)
			{
			case CRegistration::Element::InputFirstName:
				if (m_pControl->getInputFirstName()->NotifyOnKeyDown(m.Key, ctrl, alt, shift, numlock))
					changed = true;
				break;
			case CRegistration::Element::InputLastName:
				if (m_pControl->getInputLastName()->NotifyOnKeyDown(m.Key, ctrl, alt, shift, numlock))
					changed = true;
				break;
			case CRegistration::Element::Date:
				if (m_pControl->m_bCalendarOpened && m_pControl->getCalendar()->NotifyOnKeyDown(m.Key, ctrl, alt, shift, numlock))
					changed = true;
				break;
			}
			if (changed)
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				if (m_pControl->m_pCanvasCalendar)
					m_pControl->m_pCanvasCalendar->setValid(false);
				m_pControl->Repaint(false);
			}
		}

		void CRegistrationService::NotifyOnKeyUp(const MessageKey &m, bool &processed)
		{
			bool changed{ false }, numlock{ Application->Keyboard->isToggled(Key::NumLock) }, ctrl, alt, shift;
			Application->Keyboard->getControlKeys(ctrl, alt, shift);
			if (!ctrl && !alt && !shift)
			{
				switch (m.Key)
				{
				case Key::Up:
					switch (m_pControl->m_eFocusedElement)
					{
					case CRegistration::Element::InputLastName:
						m_pControl->m_eFocusedElement = CRegistration::Element::InputFirstName;
						m_pControl->getInputLastName()->setFocused(false);
						m_pControl->getInputFirstName()->setFocused(true);
						break;
					case CRegistration::Element::Date:
						m_pControl->m_eFocusedElement = CRegistration::Element::InputLastName;
						m_pControl->getInputLastName()->setFocused(true);
						break;
					default:
						m_pControl->m_eFocusedElement = CRegistration::Element::Date;
						m_pControl->getInputFirstName()->setFocused(false);
						break;
					}
					if (m_pControl->m_bCalendarOpened)
						m_pControl->CloseCalendar();
					if (m_pControl->m_pCanvas)
						m_pControl->m_pCanvas->setValid(false);
					if (m_pControl->m_pCanvasCalendar)
						m_pControl->m_pCanvasCalendar->setValid(false);
					m_pControl->Repaint(false);
					return;
				case Key::Down:
					switch (m_pControl->m_eFocusedElement)
					{
					case CRegistration::Element::InputFirstName:
						m_pControl->m_eFocusedElement = CRegistration::Element::InputLastName;
						m_pControl->getInputFirstName()->setFocused(false);
						m_pControl->getInputLastName()->setFocused(false);
						break;
					case CRegistration::Element::InputLastName:
						m_pControl->m_eFocusedElement = CRegistration::Element::Date;
						m_pControl->getInputLastName()->setFocused(false);
						break;
					default:
						m_pControl->m_eFocusedElement = CRegistration::Element::InputFirstName;
						m_pControl->getInputFirstName()->setFocused(true);
						break;
					}
					if (m_pControl->m_bCalendarOpened)
						m_pControl->CloseCalendar();
					if (m_pControl->m_pCanvas)
						m_pControl->m_pCanvas->setValid(false);
					if (m_pControl->m_pCanvasCalendar)
						m_pControl->m_pCanvasCalendar->setValid(false);
					m_pControl->Repaint(false);
					return;
				case Key::F4:
					if (m_pControl->m_eFocusedElement == CRegistration::Element::Date)
					{
						m_pControl->m_bCalendarOpened ? m_pControl->CloseCalendar() : m_pControl->OpenCalendar();
						if (m_pControl->m_pCanvas)
							m_pControl->m_pCanvas->setValid(false);
						if (m_pControl->m_pCanvasCalendar)
							m_pControl->m_pCanvasCalendar->setValid(false);
						m_pControl->Repaint(false);
						return;
					}
					break;
				}
			}
			switch (m_pControl->m_eFocusedElement)
			{
			case CRegistration::Element::InputFirstName:
				if (m_pControl->getInputFirstName()->NotifyOnKeyUp(m.Key, ctrl, alt, shift, numlock))
					changed = true;
				break;
			case CRegistration::Element::InputLastName:
				if (m_pControl->getInputLastName()->NotifyOnKeyUp(m.Key, ctrl, alt, shift, numlock))
					changed = true;
				break;
			case CRegistration::Element::Date:
				if (m_pControl->m_bCalendarOpened && m_pControl->getCalendar()->NotifyOnKeyUp(m.Key, ctrl, alt, shift, numlock))
					changed = true;
				break;
			}
			if (changed)
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				if (m_pControl->m_pCanvasCalendar)
					m_pControl->m_pCanvasCalendar->setValid(false);
				m_pControl->Repaint(false);
			}
		}

		void CRegistrationService::NotifyOnChar(const MessageChar &m, bool &processed)
		{
			bool changed{ false }, numlock{ Application->Keyboard->isToggled(Key::NumLock) }, ctrl, alt, shift;
			Application->Keyboard->getControlKeys(ctrl, alt, shift);
			switch (m_pControl->m_eFocusedElement)
			{
			case CRegistration::Element::InputFirstName:
				if (m_pControl->getInputFirstName()->NotifyOnChar(m.Char, ctrl, alt, shift, numlock))
					changed = true;
				break;
			case CRegistration::Element::InputLastName:
				if (m_pControl->getInputLastName()->NotifyOnChar(m.Char, ctrl, alt, shift, numlock))
					changed = true;
				break;
			case CRegistration::Element::Date:
				if (m_pControl->m_bCalendarOpened && m_pControl->getCalendar()->NotifyOnChar(m.Char, ctrl, alt, shift, numlock))
					changed = true;
				break;
			}
			if (changed)
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				if (m_pControl->m_pCanvasCalendar)
					m_pControl->m_pCanvasCalendar->setValid(false);
				m_pControl->Repaint(false);
			}
		}

		void CRegistrationService::NotifyOnDeadChar(const MessageChar &m, bool &processed)
		{
			bool changed{ false }, numlock{ Application->Keyboard->isToggled(Key::NumLock) }, ctrl, alt, shift;
			Application->Keyboard->getControlKeys(ctrl, alt, shift);
			switch (m_pControl->m_eFocusedElement)
			{
			case CRegistration::Element::InputFirstName:
				if (m_pControl->getInputFirstName()->NotifyOnDeadChar(m.Char, ctrl, alt, shift, numlock))
					changed = true;
				break;
			case CRegistration::Element::InputLastName:
				if (m_pControl->getInputLastName()->NotifyOnDeadChar(m.Char, ctrl, alt, shift, numlock))
					changed = true;
				break;
			case CRegistration::Element::Date:
				if (m_pControl->m_bCalendarOpened && m_pControl->getCalendar()->NotifyOnDeadChar(m.Char, ctrl, alt, shift, numlock))
					changed = true;
				break;
			}
			if (changed)
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				if (m_pControl->m_pCanvasCalendar)
					m_pControl->m_pCanvasCalendar->setValid(false);
				m_pControl->Repaint(false);
			}
		}
	#pragma endregion

	#pragma region Mouse input notifications
		void CRegistrationService::NotifyOnMouseHover(const MessagePosition &m)
		{
			if (m_pControl->UpdateHoveredElement(m_pControl->FormToLocal(m.Position)))
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				if (m_pControl->m_pCanvasCalendar)
					m_pControl->m_pCanvasCalendar->setValid(false);
				m_pControl->Repaint(false);
			}
		}

		void CRegistrationService::NotifyOnMouseLeave()
		{
			if (m_pControl->UpdateHoveredElement(m_pControl->FormToLocal((PointF)m_pControl->getForm()->ScreenToClient(Application->Mouse->getPosition()))))
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				if (m_pControl->m_pCanvasCalendar)
					m_pControl->m_pCanvasCalendar->setValid(false);
				m_pControl->Repaint(false);
			}
		}

		void CRegistrationService::NotifyOnMouseMove(const MessageMouse &m, bool &processed)
		{
			bool changed{ m_pControl->UpdateHoveredElement(m_pControl->FormToLocal(m.Position)) };
			if (m_pControl->m_eDownElement != CRegistration::Element::None)
			{
				switch (m_pControl->m_eDownElement)
				{
				case CRegistration::Element::InputFirstName:
					if (m_pControl->getInputFirstName()->NotifyOnMouseMove(m_pControl->FormToLocal(m.Position) - m_pControl->getInputFirstName()->getPosition(), m.Left, m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					{
						changed = true;
						processed = true;
					}
					break;
				case CRegistration::Element::InputLastName:
					if (m_pControl->getInputLastName()->NotifyOnMouseMove(m_pControl->FormToLocal(m.Position) - m_pControl->getInputLastName()->getPosition(), m.Left, m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					{
						changed = true;
						processed = true;
					}
					break;
				}
				if (changed)
				{
					if (m_pControl->m_pCanvas)
						m_pControl->m_pCanvas->setValid(false);
					if (m_pControl->m_pCanvasCalendar)
						m_pControl->m_pCanvasCalendar->setValid(false);
					m_pControl->Repaint(false);
				}
				return;
			}
			switch (m_pControl->m_eHoveredElement)
			{
			case CRegistration::Element::InputFirstName:
				if (m_pControl->getInputFirstName()->NotifyOnMouseMove(m_pControl->FormToLocal(m.Position) - m_pControl->getInputFirstName()->getPosition(), m.Left, m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					changed = true;
				break;
			case CRegistration::Element::InputLastName:
				if (m_pControl->getInputLastName()->NotifyOnMouseMove(m_pControl->FormToLocal(m.Position) - m_pControl->getInputLastName()->getPosition(), m.Left, m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					changed = true;
				break;
			case CRegistration::Element::Calendar:
				if (m_pControl->getCalendar()->NotifyOnMouseMove(m_pControl->FormToLocal(m.Position) - m_pControl->m_sCalendarRect.LeftTop, m.Left, m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					changed = true;
				break;
			}
			if (changed)
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				if (m_pControl->m_pCanvasCalendar)
					m_pControl->m_pCanvasCalendar->setValid(false);
				m_pControl->Repaint(false);
			}
		}

		void CRegistrationService::NotifyOnLeftMouseButtonDown(const MessageMouse &m, bool &processed)
		{
			if (m_pControl->m_eDownElement == CRegistration::Element::None && !m.Middle && !m.Right && !m.Ctrl && !m.Alt && !m.Shift)
			{
				bool changed{ false };
				switch (m_pControl->m_eHoveredElement)
				{
				case CRegistration::Element::InputFirstName:
					if (m_pControl->getInputFirstName()->NotifyOnMouseLeftDown(m_pControl->FormToLocal(m.Position) - m_pControl->getInputFirstName()->getPosition(), false, false, false, false, false))
						changed = true;
					m_pControl->m_eDownElement = CRegistration::Element::InputFirstName;
					m_pControl->m_eFocusedElement = CRegistration::Element::InputFirstName;
					m_pControl->getInputLastName()->setFocused(false);
					m_pControl->getInputFirstName()->setFocused(true);
					if (m_pControl->m_bCalendarOpened)
						m_pControl->CloseCalendar();
					break;
				case CRegistration::Element::InputLastName:
					if (m_pControl->getInputLastName()->NotifyOnMouseLeftDown(m_pControl->FormToLocal(m.Position) - m_pControl->getInputLastName()->getPosition(), false, false, false, false, false))
						changed = true;
					m_pControl->m_eDownElement = CRegistration::Element::InputLastName;
					m_pControl->m_eFocusedElement = CRegistration::Element::InputLastName;
					m_pControl->getInputFirstName()->setFocused(false);
					m_pControl->getInputLastName()->setFocused(true);
					if (m_pControl->m_bCalendarOpened)
						m_pControl->CloseCalendar();
					break;
				case CRegistration::Element::Calendar:
					m_pControl->getInputLastName()->setFocused(false);
					m_pControl->getInputFirstName()->setFocused(false);
					m_pControl->m_eFocusedElement = CRegistration::Element::Date;
					if (m_pControl->getCalendar()->NotifyOnMouseLeftDown(m_pControl->FormToLocal(m.Position) - m_pControl->m_sCalendarRect.LeftTop, m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
						changed = true;
					break;
				case CRegistration::Element::Date:
					m_pControl->getInputLastName()->setFocused(false);
					m_pControl->getInputFirstName()->setFocused(false);
					m_pControl->m_eFocusedElement = CRegistration::Element::Date;
					if (m_pControl->m_bCalendarOpened)
						m_pControl->CloseCalendar();
					break;
				case CRegistration::Element::DateArrow:
					m_pControl->getInputLastName()->setFocused(false);
					m_pControl->getInputFirstName()->setFocused(false);
					m_pControl->m_eFocusedElement = CRegistration::Element::Date;
					m_pControl->m_bCalendarOpened ? m_pControl->CloseCalendar() : m_pControl->OpenCalendar();
					changed = true;
					break;
				default:
					if (m_pControl->m_bCalendarOpened)
					{
						changed = true;
						m_pControl->CloseCalendar();
					}
				}
				if (changed)
				{
					if (m_pControl->m_pCanvas)
						m_pControl->m_pCanvas->setValid(false);
					if (m_pControl->m_pCanvasCalendar)
						m_pControl->m_pCanvasCalendar->setValid(false);
					m_pControl->Repaint(false);
					if (m_pControl->m_eDownElement != CRegistration::Element::None)
						m_pControl->getForm()->CaptureMouse(m_pControl, true);
				}
			}
			else
				CancelDown(true);
		}

		void CRegistrationService::NotifyOnLeftMouseButtonUp(const MessageMouse &m, bool &processed)
		{
			bool changed{ false };
			if (m_pControl->m_eDownElement != CRegistration::Element::None)
			{
				switch (m_pControl->m_eDownElement)
				{
				case CRegistration::Element::InputFirstName:
					if (m_pControl->getInputFirstName()->NotifyOnMouseLeftUp(m_pControl->FormToLocal(m.Position) - m_pControl->getInputFirstName()->getPosition(), m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
						changed = true;
					break;
				case CRegistration::Element::InputLastName:
					if (m_pControl->getInputLastName()->NotifyOnMouseLeftUp(m_pControl->FormToLocal(m.Position) - m_pControl->getInputLastName()->getPosition(), m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
						changed = true;
					break;
				}
				CancelDown(true);
				if (changed)
				{
					if (m_pControl->m_pCanvas)
						m_pControl->m_pCanvas->setValid(false);
					m_pControl->Repaint(false);
				}
				processed = true;
				return;
			}
			switch (m_pControl->m_eHoveredElement)
			{
			case CRegistration::Element::InputFirstName:
				if (m_pControl->getInputFirstName()->NotifyOnMouseLeftUp(m_pControl->FormToLocal(m.Position) - m_pControl->getInputFirstName()->getPosition(), m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					changed = true;
				break;
			case CRegistration::Element::InputLastName:
				if (m_pControl->getInputLastName()->NotifyOnMouseLeftUp(m_pControl->FormToLocal(m.Position) - m_pControl->getInputLastName()->getPosition(), m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					changed = true;
				break;
			case CRegistration::Element::Calendar:
				if (m_pControl->getCalendar()->NotifyOnMouseLeftUp(m_pControl->FormToLocal(m.Position) - m_pControl->m_sCalendarRect.LeftTop, m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					changed = true;
				break;
			}
			if (changed)
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				if (m_pControl->m_pCanvasCalendar)
					m_pControl->m_pCanvasCalendar->setValid(false);
				m_pControl->Repaint(false);
			}
		}

		void CRegistrationService::NotifyOnLeftMouseButtonDoubleClick(const MessageMouse &m, bool &processed)
		{
			CancelDown(true);
			bool changed{ false };
			switch (m_pControl->m_eHoveredElement)
			{
			case CRegistration::Element::InputFirstName:
				if (m_pControl->getInputFirstName()->NotifyOnMouseLeftUp(m_pControl->FormToLocal(m.Position) - m_pControl->getInputFirstName()->getPosition(), m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					changed = true;
				break;
			case CRegistration::Element::InputLastName:
				if (m_pControl->getInputLastName()->NotifyOnMouseLeftUp(m_pControl->FormToLocal(m.Position) - m_pControl->getInputLastName()->getPosition(), m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					changed = true;
				break;
			case CRegistration::Element::Calendar:
				if (m_pControl->getCalendar()->NotifyOnMouseLeftUp(m_pControl->FormToLocal(m.Position) - m_pControl->m_sCalendarRect.LeftTop, m.Middle, m.Right, m.Ctrl, m.Alt, m.Shift))
					changed = true;
				break;
			}
			if (changed)
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				if (m_pControl->m_pCanvasCalendar)
					m_pControl->m_pCanvasCalendar->setValid(false);
				m_pControl->Repaint(false);
			}
		}

		void CRegistrationService::NotifyOnRightMouseButtonDown(const MessageMouse &m, bool &processed)
		{
			CancelDown(true);
		}

		void CRegistrationService::NotifyOnRightMouseButtonUp(const MessageMouse &m, bool &processed)
		{
			CancelDown(true);
		}

		void CRegistrationService::NotifyOnRightMouseButtonDoubleClick(const MessageMouse &m, bool &processed)
		{
			CancelDown(true);
		}

		void CRegistrationService::NotifyOnMiddleMouseButtonDown(const MessageMouse &m, bool &processed)
		{
			CancelDown(true);
		}

		void CRegistrationService::NotifyOnMiddleMouseButtonUp(const MessageMouse &m, bool &processed)
		{
			CancelDown(true);
		}

		void CRegistrationService::NotifyOnMiddleMouseButtonDoubleClick(const MessageMouse &m, bool &processed)
		{
			CancelDown(true);
		}
	#pragma endregion

	#pragma region Clipboard notifications
		void CRegistrationService::NotifyOnPasteString(const MessagePasteString &m)
		{
			bool changed{ false };
			switch (m_pControl->m_eFocusedElement)
			{
			case CRegistration::Element::InputFirstName:
				if (m_pControl->getInputFirstName()->NotifyOnPasteString(m.Text))
					changed = true;
				break;
			case CRegistration::Element::InputLastName:
				if (m_pControl->getInputLastName()->NotifyOnPasteString(m.Text))
					changed = true;
				break;
			}
			if (changed)
			{
				if (m_pControl->m_pCanvas)
					m_pControl->m_pCanvas->setValid(false);
				m_pControl->Repaint(false);
			}
		}
	#pragma endregion
	}
}

The constructor is pretty simple. In it we just call parent class constructor and store widget class instance. We need to use widget class properties and methods in the service so we need to have pointer to widget class. In the service we almost always need to access to private properties and methods as so the service should be made a friend to widget. That is what we did earlier in widget class declaration.

In the CancelDown() helper method we revert changes made when mouse button was down. We need to revert changes not only when button is up but in another cases as well. For example, when application is being closed or use click another mouse button and so on. So we need to do the same reverting many times and thus we created a method for it to avoid a lot of code duplications. The method also has an argument indicating whether it should also release mouse capture (we will capture mouse when left mouse button is down). We shouldn't release capture if we call this method from mouse capture release notification and should release capture in all other cases. That is what this argument for. All the method does is send cancel mouse down notification to the built-in control that was previously notified about mouse down on it. We store which built-in control is that in the m_eDownElement member of the widget class. And in the end if release mouse capture is requested and widget captures mouse input we release it.

We do nothing special in the NotifyOnAttach() notification. We only transfer the notification to built-in controls but we do that only when our widget is on a form. Widget can be placed on another widget and that parent widget can be not on a form. When this happens, widget still gets attach notification but in this case we better ignore it as it is often no use to do anything when widget cannot be visible. So we handle attach notification only when widget is attached to a form, directly or indirectly (attach event called whenever widget or any of its parents is attached to another widget or a form).

As mentioned earlier we need to free all platform resources we created when we receive FreeResources notification. That is what we do in the NotifyOnFreeResources() method. We release both textures if they were created and transfer notification to built-in controls (also if they are created).

The NotifyOnTranslateChange() just close calendar. We did it for simplicity. We could have handle possible size and position change due to new texts usage as well as handle mouse pointer change relatively to a new dimensions and position of the calendar.

In the NotifyOnParentStyleChange() and NotifyOnStyleChange() style change notifications we simply update widget properties from style by calling its UpdateFromStyle() method and repaint the widget. We didn't call the Repaint() method because we know that a form definitely need to be repainted when style is changed and thus form should call repaint itself. Also we just close the calendar instead of handling its layout change. We again did it for simplicity. We need all updates only if widget style exists. Because if style was just removed we left everything as is and no update is required.

We do the same for font change notifications NotifyOnParentFontChange() and NotifyOnFontChange() except for the check if font exists as the default font always exists (in case default form or default control class implementations are used; and we do use default control class implementation).

When widget gets input focus, it receives NotifyOnSetFocus() notification which also has information about the method the widget got focus. We will handle mouse clicks later and in the NotifyOnSetFocus() we just skip this method of getting focus. So, when our widget gets focus in the way other than by mouse click we set the property indicating which part of our widget is focused to the first name input, inform first name input about it gets focus and, just in case, inform last name input that it has no focus anymore. Finally, as usually we repaint the widget. When widget lose focus, we get NotifyOnKillFocus() notification and we there close calendar (just in case it was opened), indicate that nothing has focus, inform both inputs they have no focus anymore and, as usual, repaint the widget.

In the Paint notification we draw widget if we got the notification in the first stage of drawing (as you might know there are two drawing stages in Nitisa) and in the second stage we draw calendar if it's opened of course.

Keyboard input notifications are all similar. Generally speaking in them we transfer keyboard input notification to corresponding focused built-in control. Some keys add special meaning to another keys if they are used together. These keys are Ctrl, Alt and Shift. Additionally there are keys which can be turned on and off and depending on it they also change the meaning of other keys. That is such keys as NumLock, CapsLock and ScrollLock. So, in order to process input successfully, built-in control need to know not only the key was down or released but also states of those special keys (at least the ones the built-in input handles). Some notifications, like some of the mouse notifications, have information about control keys, others, like keyboard notifications, don't. Fortunately there is a simple way to get information about key states. It can be done via methods of Application->Keyboard member with is an instance of IKeyboard interface. In keyboard notifications we use it to get key information about additional key states to transfer that information to proper built-in control. When we transfer notification to a built-in control it returns true if something is changed and, thus, we need to repaint the widget. We collect that information in changed variable and use it to check whether repainting is required or not.

Additionally in the NotifyOnKeyUp() notification we check whether the released key is Up, Down or F4. Depending on the key we either set focus to another (previous or next) part of our widget or toggle calendar if focused part if date of birth and F4 is the key was released.

When mouse pointer moves from outside to widget we get NotifyOnMouseHover() notification. When mouse pointer leaves widget area we get NotifyOnMouseLeave() notification. In these methods we just update which element of a widget is under the mouse pointer. To do that we use widget class UpdateHoveredElement() method and we need to know exact mouse pointer position in widget coordinate space for that. Mouse hover notification method has information about mouse pointer position in form coordinate space, so we change it to widget coordinate space by widget's method FormToLocal(). The mouse leave notification has no information about mouse pointer location. Fortunately we can get it using Application->Mouse member which is a pointer to IMouse interface. Its method getPosition() returns mouse pointer coordinates in screen coordinate space which we should first change to form coordinate space by form's method ScreenToClient() and then from form to widget coordinate space using again widget's method FormToLocal(). When UpdateHoveredElement() method changes something it means we should repaint the widget and that is what we do in the if body.

In the NotifyOnMouseMove() notification method (which is called each time mouse pointer moves) we update hovered element at the beginning as it might change as the mouse pointer is now in a new position. Then, if mouse was down (and not yet release) over a built-in text input, we transfer notification to corresponding text input and if text input processes the notification (returns true), we set processed also to true which is information for a form that the notification was processed by a widget and need no further processing. This flag has only meaning when mouse input capture takes place. As you will see a little bit later we capture mouse input when user downs mouse button over a built-in text input. If there is no down element we just transfer move notification to the element which under the mouse pointer now. As usually we repaint widget only when something is changed.

When user down left mouse button on the widget, it receives NotifyOnLeftMouseButtonDown() notification. Together with left mouse button another mouse buttons and control keys may be down. We check all these in the first line of the method as we interested only in a pure left mouse button down event. Also we check there whether there is no down element yet. In case of our criteria is not met we just initiate mouse down cancel which you can see in the last line of the method. If the criteria is met, we do following. If current element under the mouse pointer is the first name built-in text input, we transfer notification to it, mark it as down element as well as focused one, inform last name text input it is not focused and inform first name input it is focused, additionally we close calendar if it is opened. We do absolutely the same if the element under the mouse pointer is the last name built-in text input. If hovered element is the calendar, we inform both built-in text inputs they are not focused, mark date of birth as focused element and transfer notification to calendar. If hovered part is the date of birth part, we inform both built-in text inputs they are not focused, mark date of birth as focused element and close calendar if it's opened (because the mouse button down was outside the calendar). If hovered part is the calendar show/hide arrow, we inform both built-in text inputs they are not focused, mark date of birth as focused element and toggle calendar visibility. In all other cases we just hide the calendar if it is shown. If any change happened we repaint the widget and (!) if down happened on any of two built-in text inputs, we capture mouse input. We need that for correct processing such features as selecting part of text by down and drag of mouse pointer. If we don't capture mouse the mouse move event won't happen if mouse pointer is outside the widget area and thus we couldn't update selection on the text in the input.

When user releases left mouse button we need to send corresponding notification to proper built-in control. If button was down over one of the built-in text inputs we need to send release notification exactly to the same text input where mouse was down independently on the current mouse position as from the down on the built-in text input that built-in text input is completely responsible for all mouse events. That is what we do in the if body at the beginning of the NotifyOnLeftMouseButtonUp() notification method. If mouse button was not down over a text input then we just send release notification to the widget part below the mouse pointer.

In the next NotifyOnLeftMouseButtonDoubleClick() method all we do is transmit double click notification to the built-in control currently under the mouse pointer and repaint widget if any change is happened.

In the rest mouse input notification methods we simply revert changes done when mouse left button was down (if there were any).

And the final notification in the service is NotifyOnPasteString(). This notification happens when user attempts to paste a string from clipboard using, for example, Ctrl+V key combination. All we do in this method is transfer notification to proper text input (the text input which is now active/focused).

Conclusion

We now have the widget completely finished. Lets test it using the test application we created at the beginning of the tutorial. To do that add following widget class forward declaration to the FormMain.h class right inside the nitisa namespace.

namespace coolwidgets
{
    class CRegistration;
}

After this add coolwidgets::CRegistration *m_pRegistration; widget instance pointer declaration to the private section of the form class. Go to FormMain.cpp and add #include "Nitisa/Core/LockRepaint.h" to the beginning of the file and following code to the constructor body of the form.

CLockRepaint lock{ this };
m_pRegistration = new coolwidgets::CRegistration(this);
m_pRegistration->getTransform()->Translate(100, 100, 0);

Here we use repaint locking to prevent double form repainting when creating the widget (the first one would have happened when we created the widget and added it to the form and the second one would have happened when we changed widget's position). You can build and run test application and see how widget works. You can move mouse pointer over its parts to see if cursor shape really changes. You can click on first name text input or last name text input and see if proper input becomes active. You can open calendar and see how it works when you click on a date inside it or in its arrows or even somewhere else (like form's header). You even can add another widgets (for example, from Standard Package) to the form right below the registration widget and see that if you click on that widget when calendar is hidden, that widget gets your input but if calendar is shown and over that widget the calendar processes input. And so on... You may think up many of your own test scenarios for our widget.

Below you can see a screenshot of running test application.

Running test application

Despite we make many simplifications (which we even didn't describe as they are not significant for tutorial project) this tutorial turned out to be quite a long. But that is not because of creating widgets is to hard. It's just because of we wrote a lot of explanations to all the parts of widgets development and partly because of we selected not the simplest widget to create. Although there are still some aspects of widget development we didn't describe here, information from this tutorial is more than enough to develop most of the widgets you may want to make.

In the next part of the tutorial series we will show how to add our Registration widget to the Form Builder.

You can find projects CoolWidgets and TestApp with lots of comments 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. To run the TestApp application from Visual Studio you need to make it StartUp Project.