I would like to tell you about new approach in components development in Firemonkey, which is used for visual component, which support several implementation in one platform. We are gonna talk about advantages and opportunities of new approach for creating visual component.
Contents
Introduction
Overview of new approach, we will begin from example. Let’s look at the TCalendar. It’s a visual component for displaying date or calendar and providing way of choosing date by user. This control for implementation uses TListBox with multicolumn mode for displaying days in current month and additional buttons TButton and TPopupBox for choosing month, year and navigation between adjacent months. There are not nothing complex.
TCalendar fills TListBox with values of days based on current date and sets own event handlers on clicking on buttons and choosing day, year, month.
Windows, OSX and Android have the similar way for choosing date by User. But iOS give us a new way for choosing date by “barrels”.
And there is an important issue: “How to combine in one control TCalendar several way for displaying date and principal different ways for choosing date”
The easiest way is combine two implementation in one component TCalendar. It is naturally would bring to the considerable complication of source code. Because control will have all fields and data for two implementation, which are not required for correct working of one of these.
Second way is creating cross-platform service. Each implementation of Calendar will hide by specific service implementation of service. We use this way in some controls lik: TWebBrowser, Pickers, TMediaPlayer, TBannerAd and etc. Good way, but concept of FireMonkey services doesn’t allow to have several implementations of service at the same time. What about, if user would like to use “classical” approach (OSX, Windows, Android) for displaying date and “barrel” for choosing date like a standard iOS application Calendar?
It means, that we shall provide in ideal:
- Existence of two independent implementations of calendar;
- Allowing to create any count of implementations.
I think, that the main problem of components development is need to create new component for IDE. If we would like to add new functionality or to correct default behaviour or view, we need to create new component:
- To create new component based on existed. To extend functionality
- To register it in IDE (Now we have in Tool Palette two controls: default and new)
- To use new component in our project instead of default.
As result, we need to install a lot of additional packages in IDE and not to forget provide a packages of this components, when we give our project customers of another developer in team. Also we can not simply rollback to use default control, Because we already use a new name of control in source code and form resources.
Note. At least each developer Delfi did the button in due time ;-) Joke
Therefore, try to reduce it or avoid. We will try to make a possibility to create extension of existed ‘default’ component without registration component in IDE in Tool palette. And here appears new approach, which means a separation of control like a TCalendar on:
- Data (Model)
- Ways (Presentations) of displaying data from Model and organization of their input for the user
As you could already understand, it will bear very a strong resemblance MVC (Model – View – Controller), but is applicable to FireMonkey control.
We extract from TCalendar data into separated class FMX.Calendar.TCalendarModel and calls it as a model of calendar’s data. Model includes current Date, Settings of displaying and several event handlers.
Look at declaration below, There are nothing complex in this model class:
type { TCalendarModel } TCalendarModel = class(TDataModel) public const DefaultCalDayOfWeek = TCalDayOfWeek.dowLocaleDefault; private FDate: TDate; FFirstDayOfWeek: TCalDayOfWeek; FTodayDefault: Boolean; FWeekNumbers: Boolean; FOnChange: TNotifyEvent; FOnDateSelected: TNotifyEvent; FOnDayClick: TNotifyEvent; procedure SetDate(const Value: TDate); procedure SetFirstDayOfWeek(const Value: TCalDayOfWeek); procedure SetTodayDefault(const Value: Boolean); procedure SetWeekNumbers(const Value: Boolean); public constructor Create; override; property DateTime: TDate read FDate write SetDate; property FirstDayOfWeek: TCalDayOfWeek read FFirstDayOfWeek write SetFirstDayOfWeek; property TodayDefault: Boolean read FTodayDefault write SetTodayDefault; property WeekNumbers: Boolean read FWeekNumbers write SetWeekNumbers; property OnChange: TNotifyEvent read FOnChange write FOnChange; property OnDateSelected: TNotifyEvent read FOnDateSelected write FOnDateSelected; property OnDayClick: TNotifyEvent read FOnDayClick write FOnDayClick; end;
We will talk about TDataModel a bit later.
We are extracting “classic” showing calendar in another class (Presentation) FMX.Calendar.Style.TStyledCalendar. We will name this class like a “Styled” presentation. “Styled” means, that this presentation uses concept of FireMonkey styles. Pay attention that now this class contains date choice buttons, a TListBox for display and a choice of date and a field of a choice of year and month, but not TCalendar.
OK, What about TCalendar? Now it contains Model and Presentation. So if we would like to create another view of TCalendar, for example a “barrel”, we create another presentation and use UIDatePicker. FMX.Calendar.iOS.TiOSNativeCalendar on iOS. It’s a implementation of native calendar with using native iOS control UIDatePicker.
I will tell even more that the native presentation TiOSNativeCalendar is in fact completely native control, that is UIDatePicker. Thus working with TiOSNativeCalendar you work for with a native control UIDatePicker.
On this stage, we already have a calendar’s model (TCalendarModel), two implementations (TiOSNativeCalendar and TStyledCalendar) of presentations and calendar control (TCalendar). The rest part is a understanding how parts communicate with each other. And also, how TCalendar chooses and loads concrete presentation.
Communication “Control -> Presentation”, “Control -> Model”, “Model <->Presentation”
All interactions between parts happen by means of use of a Delphi messages (TObject.Dispatch). The choice of messages for interaction between parts is chosen not incidentally and dictated by two important advantages:
- Protocol. User can intercept message, which are actual for him, and emulates concept of protocol in ObjectiveC. If we compare using messages and interfaces, then interfaces requires implementation of all methods of interface even what can not make sense in this or that implementation. But messages – no. For example, Native calendar doesn’t support showing numbers of week WeekNumbers и TodayDefault. But styled presentation – supports. So if we will use interface as a way for communication between control and presentation, we need to include into interface two notification methods about changes of two options of settings (WeekNumber and TodayDefault). And of course implements these methods in Native presentation also.
- Requirements to a basic class of representations are completely removed. It means, that presentation can be any class (For example the TObject). It’s especially actually when we use TOCLocal class (Mac, iOS) and TJavaLocal (Android) as a base for native presentation. These classes provides a way for linking Delphi side with native API objects. One important limitation of this classes is they don’t allow mix another interfaces (not from a Operation System API). So, for implementation of our service interface we need to create chain Control -> Adapter (Implementer of Service interface) -> Native Control.
FMX.Presentation.Messages.TMessageSender – It’s a base class, supports convenient working with messages and allows to send message with a data any type. Major features are:
- Sending message to Receiver with value of any type of data;
- Sending message with request and receiving back result of performing from Receiver.
- Possibility of turning off/on sending messages.
- Possibility of creating Receiver of messages into or set it outside of TMessageSender.
For example, if we would like to send data to Receiver, it is enough to me to write:
SendMessage<TDateTime>(MM_DATE_CHANGED, Value);
And if I want to obtain some data, so:
PresentationProxy.SendMessageWithResult<TSizeF>(PM_GET_RECOMMEND_SIZE, Result);
Example, if user choose date in presentation of calendar, then presentation should update date in Model of calendar. But when we set date in model, Model sends notification about it to Presentation. For avoiding it, Model has possibility to turn off sending notification on this time:
Model.DisableNotify; try Model.DateTime := Date; finally Model.EnableNotify; end;
It’s a main purposes of TMessageSender.
Model -> Presentation
When model change data, it notifies presentation about it by sending message with corresponding code. If we change date in Calendar through TCalendar.Date, it changes value of date in Model, that in turn sends the notice to representation of MM_DATE_CHANGED.
procedure TCalendarModel.SetDate(const Value: TDate); begin if not SameDate(FDate, Value) then begin FDate := Value; SendMessage<TDateTime>(MM_DATE_CHANGED, Value); end; end;
After it, presentation catch this message and perform displaying of current date.
TStyledCalendar = class(TStyledPresentation) private // .... { Messages } procedure MMDateChanged(var AMessage: TDispatchMessageWithValue<TDateTime>); message MM_DATE_CHANGED; //...
Native presentation for iOS:
TiOSNativeCalendar = class(TiOSNativeControl) private //... protected { Messages From Model} procedure MMDateChanged(var AMessage: TDispatchMessageWithValue<TDateTime>); message MM_DATE_CHANGED; // ...
Presentation -> Model
Presentation receives model through message PM_SET_MODEL. So it has directly access to Model.
Control -> Presentation
What to do, if control requires performing of action on data processing? For example, User would like to programmatically open drop down list in TComboBox. So TComboBox should tell about it to presentation, because TComboBox doesn’t know about implementation of Drop Down list and even doesn’t have it. For this task, control can send a notification to presentation by message. Control need to use extension of TMessageSender class TPresentationProxy for sending messages.
TPresentationProxy – It’s a mediator between presentation and control. Control communicates with presentation by PresentationProxy. Control send message through TPresentationProxy. Also PresentationProxy performs connection control with presentation: sends to presentation a model and control.
Each presentation has the own TPresentationProxy. For Example, styled presentation of TCalendar has a TStyledCalendarProxy и TiOSCalendarProxy for native presentation. Each class of a proxy creates in itself already necessary representation:
{ TStyledCalendarProxy } TStyledCalendarProxy = class (TPresentationProxy) protected function CreateReceiver: TObject; override; end; // .... { TStyledCalendarProxy } function TStyledCalendarProxy.CreateReceiver: TObject; begin Result := TStyledCalendar.Create(nil); end;
CreateReceiver – creates presentation and uses it as Receiver in TMessageSender.
Thus, we will sum up the intermediate result:
- Controls stores data in Model;
- Presentation responds for way of displaying of data of control and providing way for inputting data by user;
- Control owns model and presentation;
- When model change data, model notifies about it to presentation by messages;
- Control can send notification and receives data from presentation by messages;
- Control knows about class of model class;
- Control doesn’t know about presentation (doesn’t know a class of presentation)
- Контрол ничего не знает о своем представлении. There is no binding to a specific class;
- Presentation receives model. when control loads presentation.
The rest part is understanding of choosing and loading presentation to control.
TPresentedControl – base of presentation approach
TPresentedControl – is a base for using concept of model and presentation. This class provides:
- Creating model.
- Automatically choosing and loading of presentation by string identifier.
- Performing initialization of presentation.
- Provides communication with presentation through TPresentationProxy.
- Also implements transfer primary data about monitoring to representation. In case of change of properties. For example, visibility switching, change of a line item, change of the size and тд.
TPresentedControl has next methods for working with presentation:
- DefinePresentationName – getting string identifier of presentation;
- LoadPresentation – searching, creating and loading presentation by string name;
- InitPresentation – first initialization of presentation on control side;
- UnloadPresentation – unloading presentation.;
- HasPresentationProxy – presentation is loaded or not?
- PresentationProxy – Access to presentation proxy;
- ControlType – what representation needs to be loaded.. Platform – native, Styled – style. Influences on the generation name of representation in DefinePresentationName.
TPresentedControl has next methods for working with model:
- DefineModelClass – returns class of control’s model. By default – TDataModel. For calendar – TCalendarModel;
- Model – access to model;
- GetModel<T: TDataModel> – receives model with specified class.
DefinePresentationName responds for generation of presentation name. By default, it generates name next ways:
- Gives class name of control and removes first char T.
- Adds suffix:
- ControlType = Style: ‘-style’
- ControlType= Platform: ‘-native’
Successors can override it and specify any presentation name and including to use already existing presentation from other control.
Presentation Factory or choosing arbitrary representation
All presentation are stored in factory FMX.Presentation.Factory.TPresentationProxyFactory. Factory has next methods:
- Registration of presentations;
- Replace default fmx presentation on another (Extension for 3D-party);
- Checking accessibility of presentation by string name;
- Getting instance of presentation by presentation string name.
Control requests presentation by string identifier . Actually, the factory registers not representations, and their proxy of TPresentationProxy. The specific proxy of specific presentation itself creates already necessary presentation and establishes with it connection.
Sequence diagram shows, how TPresentedControl loads presentation:
Conclusion
we considered the main parts of new approach and got acquainted with the general concept in this article. In the next article i’m going to tell you: how to add into TEdit new functionality of auto-completion of input by using this approach. We will also be convinced that this functionality will be available to regular TEdit, without the need for creation of the component on the basis of TEdit.
Hi,
Is there a way to have a code example, please ? I will test it with XE 7:
Regards,
Hello,
I have a next article with practical application (“New approach of development of FireMonkey control «Control — Model — Presentation». Part 2. TEdit with autocomplete“), but on Russian language. This article has a project sample. I’m going translate it on Monday.
If you would like to look at sample, you can download sample here
Thank you
Excellent document! 🙂
Thank you.
Hi Brovin,
I would like to ask why FireMokney still not support writing in RightToLeft Languages, trying to typing in Arabic language in any edit controls under Android or iOS will not show the text probley, under Windows it show the text correctly, but the cursor not positions write in the place of typing (when setting Bidimode = bdRightToLeft).
Hello,
I think, this question better address for out product manager Marco Cantu
Thank you
Thanks for share this great post.
Thank you, welcome.
Dear Yaroslave,
Very good stuff, i download your demo source it is works good on Delphi XE8 but it’s fatal error on Delphi D10 seattle. Please help bro, i use it on my software production.
Thank you 🙂
regards
Hello, Thank you,
Did you download special Sample for XE10 or you use sample for XE8?
Thank you
Dear Yaroslave,
It is a very useful article! But i use FMX seattle “C++ builder”,
Could you help me to solve this problem.
On the other hand,
The official website says that calendar’s cell color can be changed by “TryFindDayItem”. Any idea?
Thank you so much!
Hello Jason,
I wrote article about it in Russia: “Calendar with hightlight days” with demo sample about using it (TryFinDayItem)
Try to use transaltor, if it’s nothelp, i will translate it in English.
Thank you
Dear Yaroslav,
Nice introduction ! Thank you for sharing.
I’ve tested the XE10 sample under Android (Android 4.4.4). I’ve encountered a few issues :
– typing A for exemple will show “Apple” but hitting the Return will not replace “A” with “Apple”
– Autosuggestion list doesn’t works after a few test. Nothing is displayed in the combobox.
Do you have an idea why I’m getting these issues ?
Thank you
Forgot to tell you that I’m working with Delphi Berlin 10.1 Update 2 version.