Expert Delphi
上QQ阅读APP看书,第一时间看更新

Forms applications

It is time to start building our first fully functional mobile app The Game of Memory. In this chapter, we will only create the main architecture. Our app is going to be very simple. There will be two forms--one main form will contain a grid of squares, which will serve as the game board, and the second form will contain the game settings. This simple architecture will help us understand the structure of a typical Delphi forms applications.

Close the Greeter program if it is still open. Create a new Multi-Device Application - Delphi program. Select the Blank Application template and click on Save All. Save the file that contains the main form of the application as uFormMain and the project as GameOfMemory. Change the Name property of the application form to FormMain. Add another form to the project:

In the File | New menu, select Multi-Device Form - Delphi. In the dialog to select the form type, keep the default HD selection. The other option is 3D. We will get to building 3D user interfaces later in the book. HD stands for high definition and is just a more fancy way of specifying that we just want a regular 2D form. Save the file as uFormSettings and change the Name property of the new form to FormSettings.

Our project contains two forms now. Where is the program file of our app? Click on the View Source option in the Project menu, or in the context menu in Project Manager. You should now see the following code. This is the main program of our GameOfMemory app:

program GameOfMemory; 
uses 
  System.StartUpCopy, 
  FMX.Forms, 
  uFormMain in 'uFormMain.pas' {FormMain}, 
  uFormSettings in 'uFormSettings.pas' {FormSettings}; 
 
{$R *.res} 
 
begin 
  Application.Initialize; 
  Application.CreateForm(TFormMain, FormMain); 
  Application.CreateForm(TFormSettings, FormSettings); 
  Application.Run; 
end. 

The main program file in our project is managed automatically by the IDE, and in most cases, there is no need to edit it manually. This is a very typical project file.

The first line contains the program heading. The lines following it contain the uses clause. Next, there is a compiler directive that links the resource file of the project into the program. What follows is the block of statements executed when the program runs. The project file ends, like all the source code files, with a period.

The uses clause lists all the units incorporated into the program. These units may, in turn, have their own uses clauses. The logic of a program is defined between reserved words, begin and end. In this program, executable statements are simply method calls to the global Application object of the project. The block can also contain declarations of constants, types, variables, procedures, and functions. These declarations must precede the statement part of the block.

The Delphi Code Editor has a built-in hyper-navigation functionality to quickly jump to other units. For example, we may want to see where the Application global object variable reference is declared. If we move the mouse cursor over Application somewhere in the code, we will see a hint that tells us in which unit a given symbol is defined, as shown in the following screenshot:

If you press the Ctrl key at the same time, the symbol turns into an underlined hyperlink that you can click to quickly jump to the unit where a given symbol is declared. Alternatively, you can right-click on the symbol in the editor and select the Find Declaration option. Take a look at the following screenshot:

The blue arrows in the IDE toolbar let you move back and forth through the history of your navigation in the Code Editor.

Currently, at the startup of our program, both the application forms are created. In fact, we do not need to create the Settings form when the application starts. It may so happen that the end user will just play the game and close the application, without going into Settings. Another important consideration in mobile apps is the application startup time. You want to keep it to the minimum. The call to create the Settings form, depending on the processor speed of your mobile device, may delay the moment when the end user sees the screen of the app by a couple of milliseconds. We can just manually delete the line of code in the Code Editor that contains Application.CreateForm(TFormSettings, FormSettings), but there is a more elegant way of doing so. In the Project menu, click on Options at the bottom of the menu. In the Project Options dialog, select the Forms node and move FormSettings from the list of "Auto-create forms" to the list of Available forms. Take a look at the following screenshot:

Note that the line of code responsible for creating the Settings form disappeared from the main program block.

Now, we are going to implement basic navigation between the forms and, at the same time, have a closer look at the structure of the Object Pascal unit.

The main form of the application is displayed at application startup, and its lifetime is the same as the application. Let's add a toolbar to the main form of the app and a speed button in the top-right corner, which will display the Settings form. In the Settings form we will also add a speed button on the left-hand side of the toolbar with the left arrow image for going back to the main application form.

Make sure that the FormMain unit is open in Form Designer. If you are in the Code Editor, you can switch to Form Designer by clicking on the Design tab at the bottom of the screen. Press the Ctrl and . keys at the same time to focus the IDE insight and just start typing the TToolbar that we want to add to the form. After the first three letters, the TToolbar should be at the top of the list. Just press Enter to add it to the main form, as shown in the following screenshot:

It will automatically align to the top of the form. In the same way, using the IDE Insight, add the TSpeedButton component on the toolbar. Change its Align property to Right, so it always stays in the top-right corner of the screen regardless of its size or orientation. Change the Name property of the speed button to spdbtnSettings. Now, we need to give it a nice icon. We can do it by changing its StyleLookup property.

By default, when we create a new multi-device form, it uses the default Windows style. What we really want is to see how the form will look on a mobile target, be it iOS or Android. Change the selection from Windows to iOS in the combobox above Form Designer. Refer to the following screenshot:

Now, if we open the StyleLookup property of the speed button in Object Inspector, we should be able to select from one of the built-in iOS styles for a given control. Select the drawertoolbutton style from the list. If we switch to Android, the look and feel of the form will change, including the styling of the toolbar and also the icons available for styling the speedbutton. Refer to the following screenshot:

Save the form. Now go to the Settings form. Change Style in the preceding combobox the form to iOS. Following the same steps, add a toolbar and a speed button. Change the Name property of the speed button to spdbtnBack, its StyleLookup property to arrowlefttoolbutton, and Align property to Left.

Now we will implement the functionality to navigate between these forms. In the main form, when we click on the Settings button, we want to call the Show method of the Settings form. However, FormMain does not know about FormSettings. We need to add FormSettings to the uses clause of FormMain. We could do it manually, but the IDE again can help us with it. Make sure that the uFormMain file is open in the Code Editor and click on the Use Unit... option in the File menu, as shown in the following screenshot:

In the Use Unit... dialog, select FormSettings and keep the default selection to add this unit to the uses clause of the implementation section of the FormMain unit. This dialog will list all the other units in the current project that are not yet used by the currently selected unit. Take a look at the following screenshot:

The next step is to do the same thing in the FormSettings. Add FormMain to the implementation section of the uses clause of FormSettings. Double-click on the Back speed button and enter one line of code to show the main form of our application:

unit uFormSettings; 
 
interface 
 
uses 
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, 
  FMX.Controls.Presentation; 
 
type 
  TFormSettings = class(TForm) 
    ToolBar1: TToolBar; 
    spdbtnBack: TSpeedButton; 
    procedure spdbtnBackClick(Sender: TObject); 
  private 
    { Private declarations } 
  public 
    { Public declarations } 
  end; 
 
var 
  FormSettings: TFormSettings; 
 
implementation 
 
{$R *.fmx} 
 
uses uFormMain; 
 
procedure TFormSettings.spdbtnBackClick(Sender: TObject); 
begin 
  FormMain.Show; 
end; 
 
end. 

This is a fairly simple Object Pascal unit. Every unit file begins with a unit heading, which is followed by the interface and implementation sections. After the implementation section, there could be optional initialization and finalization sections with instructions to be executed before and after the main application logic.

The interface section of the unit starts with the reserved word, interface, and continues until the beginning of the implementation section. The interface section declares constants, types, variables, procedures, and functions, which are visible to other units in the project. The interface section does not contain any executable code. The declaration of a procedure or function includes only its heading. The block of the procedure or function follows in the implementation section. The interface declaration for a class includes declarations of all its members, including the ones that are private to the class and not accessible to the code outside the declaring unit. The interface section may include its own uses section, which must appear immediately after the interface keyword.

The implementation section of a unit starts with the implementation keyword and continues until the beginning of the initialization section or, if there is no initialization section, until the end of the unit marked with the end keyword followed by a period. The implementation section contains declarations of constants, types, variables, procedures, and functions, which are visible only to the declaring unit. This introduces better organization to the program structure through encapsulation and the separation of concerns. The implementation section of the unit contains the executable code defined in either standalone functions and procedures or belonging to classes and in this case called methods. Only those functions and procedures that additionally have their headings declared in the interface section of the unit are visible to the code residing in other units. The implementation section of the unit may have its own uses clause section, which follows immediately after the implementation reserved word.

A given Object Pascal unit may contain a reference to other units only once, either in its implementation section or its interface section. If you are unsure where to put a unit reference, always use it in the implementation part of the unit. Sometimes, you have to put a unit reference in the interface section of the unit. For example, our uFormSettings unit declares the TFormSettings class type, which inherits from the TForm class, which is defined in the FMX.Forms unit, so this unit has to be listed in the interface section of the unit.

If unit A depends on unit B, and unit B depends on unit A, then they are mutually dependent. This is the case between the two units in our GameOfMemory project. If the two units are mutually dependent, at least one of them should list another in its implementation's, and not interface's, uses clause.

The initialization section is optional. It starts from the initialization keyword and continues until the beginning of the finalization section, or if there is no finalization section, until the end of the unit. The initialization section contains statements that are executed at the program startup. The order in which the units are listed in uses clauses is important because it is what determines the order in which initialization statements are executed.

The finalization section is also optional and can only appear in units that have an initialization section. It starts from the reserved word, finalization, and continues until the end of the unit. The finalization section contains statements that are executed when the program terminates. The finalization sections are executed in the opposite order of initializations.

The uFormSettings unit contains the declaration of the TFormSettings type. Type declarations follow the reserved word, type. After the declaration of the form's type, there is a variable declaration, FormSettings, of the TFormSettings type. Variable declarations start with the var keyword. When you define a variable, you must also state its type. The Object Pascal language is strongly typed. The more complex your programs begin, the more you will appreciate the fact that it is not the job of a compiler to guess the type, but it is you, the programmer, who decides about types. The Delphi compiler works in a top-down fashion and, typically, just makes one go through all the dependent units in your project. This also means that the TFormSettings type needs to be declared before it can be used to declare the type of the variable. Because the FormSettings variable is defined in the interface section of the unit, it can be used outside the unit, for example, in the main program file.

Inheritance is a very important concept in object-oriented programming. Our Settings form has everything that a standard multi-device form has. You can inspect the declaration of the TForm class in the FMX.Forms unit. A class declaration defines the name of the class and the class it inherits from in the brackets after the class keyword. The class declaration is divided according to visibility. Different class members can be private, then they are visible to the defining class only. It is also possible to have members defined as protected. This means that a given member--variable field, constant, or a method--is also visible to the descendant classes. The two other important visibility specifiers are public and published. The first one means that the class members are visible to all the other units in the project. Published has public visibility, but, additionally, published class members are also accessible at the design-time in the IDE.

The three lines right after the class declaration of the form contain two field declarations for the toolbar and for the speedbutton. There is also a method, or more specifically an event handler, for the OnClick event of the button. These declarations do not have an explicit visibility specifier, and they are treated as published. This part of the form class is managed by the IDE. Every time you add a component to the form or create an event handler, the corresponding declaration is added automatically. Also, the interface uses clause is updated by the IDE every time a different component is added to the form in Form Designer. The form's class declaration also has placeholders for optional private and public declarations that the programmer may want to add.

The implementation section of the unit starts with the $R compiler directive that instructs the compiler to link into the unit the form file that is managed by the IDE and contains component properties set by Form Designer and Object Inspector. What follows is the implementation of the TFormSettings.spdbtnBackClick(Sender: TObject) event handler. Its name is made of two parts separated by a period. The first part is the name of the class and the second part is the name of the method. In brackets, there is one Sender parameter that is passed to the method and, in this case, is a reference to the spdbtnBack control of the form. The Show method in the body of the event handler has been inherited from the TForm class and, as such, is available in the descendant TFormMain class reference defined in the other form.

One last thing is to implement the functionality to show the Settings form in the OnClick event of the main form. Remember that we have removed the TFormSettings from the list of autocreated forms. This means that we are now responsible for creating an instance of this class before we can manipulate it in code.

Enter the following code in the OnClick event handler of the spdbtnSettings speedbutton:

implementation 
 
{$R *.fmx} 
 
uses uFormSettings; 
 
procedure TFormMain.spdbtnSettingsClick(Sender: TObject); 
begin 
  if FormSettings = nil then 
    FormSettings := TFormSettings.Create(Application); 
 
  FormSettings.Show; 
end; 
 
end. 

The global FormSettings variable stores the reference to an instance of the TFormSettings class. We only need to create the Settings form once in code. This is a common lazy creation pattern, where an object is created just before it is used for the first time in code. If the end user never clicks on the Settings button, this object is never created. Here, we are entering a whole new world of different expressions and instructions of the Object Pascal language. In short, what happens here is that if the FormSettings variable is nil, then we assign to it the reference to the newly constructed instance of the TFormSettings type. Create is a special constructor method that may perform the initialization of an object, but most importantly, it allocates memory for the object. There are also destructors that are responsible for freeing the object's memory. The TForm class, which is an ancestor to TFormSettings, is, in turn, inherited from the TComponent class. This is where the Create(AOwner: TComponent) constructor is defined. Here, we specify that the global Application object is the owner of the newly created form and it will be responsible for calling the form's destructor when the application is terminated.

This is another feature of Object Pascal. Not only do you need to define the types of variables, but you are also responsible for managing memory. Once you get some memory through a call to a constructor, you should also think about when it is released. In fact, in the case of Delphi mobile compilers, there is an automatic reference counting mechanism in place that will automatically call a class destructor when the object reference is no longer accessible in code. However, it is always a good practice to call destructors and think about memory management as if there were no automatic reference counting.