Professional Documents
Culture Documents
October 2011
Americas Headquarters 100 California Street, 12th Floor San Francisco, California 94111
EMEA Headquarters York House 18 York Road Maidenhead, Berkshire SL6 1SF, United Kingdom
Asia-Pacific Headquarters L7. 313 La Trobe Street Melbourne VIC 3000 Australia
EXECUTIVE SUMMARY
FireMonkey is a brand new application platform for building stunning visual applications in Delphi and C++Builder. FireMonkey uses native graphics libraries to abstract from the underlying OS. It offers powerful HD (High Definition) and 3D graphics, flexible styles, support for rich GUIs, and a new data binding model for business applications. In this whitepaper, I will introduce you to FireMonkey and guide you through its key features while building simple real-world applications for Windows, Mac, and iOS. The whitepaper, which is meant for developers already familiar with Delphi or C++Builder, will help you get started and understand the role of the various technologies that are part of FireMonkey, while building business applications. We will also see how you can connect to data from a database, a middle-tier DataSnap server (also built in Delphi), or a cloudhosted database. Overall, I am going to touch on a number of different technologies, because FireMonkey is not simply a collection of components, but a sophisticated framework providing a foundation for the future of Embarcadero RAD Studio. In fact, while VCL will continue as a Windows-only library, FireMonkey is at the core of the new crossplatform architecture of Delphi and C++Builder, along with native compilers for different operating systems. Notice that differently from other frameworks, FireMonkey applications are native applications on each platform. Native to the CPU because the application source code is compiled and native to the GPU because rendering code is compiled as well. This is a very important feature at the core of the FireMonkey library.
AN INTRODUCTION TO FIREMONKEY
So what does FireMonkey mean to a Delphi or C++Builder developer? FireMonkey is a CPU/GPU powered application platform for businesses, enabling developers to rapidly build visually engaging, data rich HD and 3D applications.
An abstract application platform. In the past, there have been user interface
libraries that would map to the corresponding elements of the underlying operating system. In Delphis VCL, for example, a TButton component wraps a Windows button control. FireMonkey, instead, has an abstract notion of a button, and can
Embarcadero Technologies
-1-
apply different styles to it to make it look like the native element of different platforms, or go for a totally custom user interface style.
A native platform. Different from other libraries that abstract the user interface,
FireMonkey is bound directly to native graphics libraries, like OpenGL or DirectX, offering the best possible performance in terms of using the GPU or the target computer. Also, compared to any other current solution, the code of your application is native, compiled to the CPU. This also means that you have the ability to call native APIs on a given platform. Furthermore, you do not need to distribute or install additional libraries on the target computer, as you can build a single executable, which includes both your code and the required library code. This largely simplifies the deployment, compared to most other similar solutions.
Embarcadero Technologies
The visual controls in FireMonkey can use the brand new LiveBindings architecture to display data coming from many different sources, including database tables. Creating very rich user interfaces driven by data has never been so simple and powerful. Why should I need HD and 3D Graphics? To create the best looking and most modern applications, you need to stretch the user interface capabilities, and go beyond the limitations of native GUIs. FireMonkey gives you a solid HD structure, but also full support for 3D user interfaces, and the capability of merging both models in a single application. But is it flexible? Also, given its clean object-oriented and component-based architecture (much like VCL, which was the first of such libraries predating both Java and .NET models), FireMonkey makes it easy to customize the components you interact with, up to the point that you or third party vendors can build custom components to extend the library. And unlike most other solutions, you get the source code of the library to learn the internal tricks and to debug your applications tracing into the library calls.
Specifically the GPU is used for 3D, for filters/effects, and for 2D drawing. The GDI+ software fallback is only available for 2D. While a VCL application can generally run on some rather old Windows computers, FireMonkey requires a modern computer with a
Embarcadero Technologies
-3-
decent graphics chip (Basic GPU - Any vendor DirectX 9.0 class or better- Pixel Shader Level 2). If you are not running a FireMonkey application on a native platform, but using a virtual machine or a remote desktop, you might run into issues. Windows 7 and Vista remote desktops should work fine, while WinXP remote desktops should work for 2D but not for 3D. Virtual machine and virtual execution environments (like Citrix) might pose a more significant challenge, as most of them do not support a GPU and not everything in FireMonkey can fall back to GDI+. This is not a FireMonkey specific issue, as other application platforms suffer the same problems.
Embarcadero Technologies
-4-
There are, of course, common features, like the complete abstraction from the platform native user-interface controls, and Silverlight/WPF uses the GPU in a way similar to FireMonkey. Also the definition of the UI in a separate file and the use of external styles, also used to adapt the abstract UI to the specific platforms is a common trait, even if the implementation is quite different. The most significant differences are in the core underlying platform. For Silverlight/WPF the core platform is .NET. This is a managed platform available only on limited operating systems (Windows and Windows Phone, if we dont include the cross-platform Mono engine, which doesnt actually support WPF). While the entire .NET library is huge to install, it is widely available on recent versions of Windows. Silverlight has a smaller footprint, but you still need to install it on the target PC (and in the version the application is based on). A significant problem of WPF applications is their start-up time, also due to the Just-In-Time compiler technology .NET is based on. The Flex platform is based on its custom runtime, available on many platforms, except on iOS. This is a typical scripting environment, with slower execution time than most compiled languages, and limited in the code architecture. While in terms of graphic, Flash and Flex have been favored by the graphic designers community, code developers have been far from enthusiastic about adopting this platform, generally favoring the clean architecture of .NET. FireMonkey departs from these platforms on a few key points: First, the code is compiled and native to the CPU and to the operating system. There is no runtime, interpreter, JITter, or anything like that. Just plain compiled code, very fast to execute. Second, there is no library to deploy or install. The FireMonkey platform is compiled into the actual application, making it extremely easy to deliver your applications on end-users computers and devices, with no limitation (both from a technical point of view and a political one). Third, the FireMonkey platform can be adapted to different GPU libraries, like DirectX and OpenGL, and it could be ported to many different operating systems, provided there is a Delphi language compiler for it. While the first version of FireMonkey supports Win32, Win64, Mac OSX, and iOS, in the future Embarcadero plans to port it to several other platforms. Being cross-platform is in FireMonkeys nature, from the ground up, and unlike other players, Embarcadero has no hidden platform agenda and is more likely to port the platform to additional operating systems..
Embarcadero Technologies
-5-
To summarize, FireMonkey offers maximum CPU and GPU performance in a truly crossplatform and open environment, with compiled and native applications, and with no need to install a virtual machine, interpreter, or runtime on the users computers and devices. FireMonkey native and optimized rich business applications are already running on Windows, Mac OS, iPad/iPhone hardware and will possibly run on a variety of operating systems quite soon.
Embarcadero Technologies
-6-
Figure 1: The initial FireMonkey user interface of the demo, before doing any change to the controls
3. To customize this form a little bit, we can change some of the properties of the components (like the caption of the button and that of the main form). While the form has the familiar Caption property, to change the caption of the Button you have to use its Text property, which is not what a Delphi VCL developer would expect, but something more in line with other frameworks. I have also renamed the form and the various components to make the code easier to read. What is very similar to VCL is that the various properties you set are saved in a specific form description file and dont generate source code. This file is similar to the DFM file of a VCL application, but has the FMX extension, which is specific to FireMonkey. To see it, right click on the form in the designer and pick the View as Text menu item. Here is the content of my FireMonkey (FMX) file, in which I have removed some less important properties but left all that show a difference from its DFM counterpart:
object PizzaForm: TPizzaForm Caption = 'Build My Pizza, 101' Transparency = False Visible = False StyleLookup = 'backgroundstyle' object lbToppings: TListBox Position.Point = '(256,32)' Width = 305.000000000000000000 Height = 289.000000000000000000 TabOrder = 2 Embarcadero Technologies -7-
Discover FireMonkey, The Next Generation Business Application Platform end object edTopping: TEdit Position.Point = '(24,32)' Width = 201.000000000000000000 Height = 22.000000000000000000 TabOrder = 0 ReadOnly = False Password = False end object btnAddTopping: TButton Position.Point = '(24,72)' Width = 201.000000000000000000 Height = 22.000000000000000000 OnClick = btnAddToppingClick TabOrder = 1 StaysPressed = False IsPressed = False Text = 'Add Topping' end end
For everyone with some Delphi or C++Builder experience the differences should be quite obvious and striking. The Left and Top properties are replaced by a Position property, which is a point with two coordinates; Width and Height properties use floating point values, the form has a Transparency property and there is a StyleLookup property describing its style, and much more all within very few lines of very simple code. As an aside, coordinates in FireMonkey work much like in VCL, with X increasing to the right, Y increasing downwards, and Z increasing away from the screen. FireMonkey uses an abstract coordinate system that is mapped to that of the underlying graphics library (for example, OpenGL has the Y axis increasing upwards, but we dont have to worry and can just use FireMonkeys rules). As you can see above, there is also an event handler attached to the button, a familiar OnClick event handler. The event handler has the following code to add the content of the edit control to the list box:
procedure TPizzaForm.btnAddToppingClick(Sender: TObject); begin lbToppings.Items.Add(edTopping.Text); end;
Embarcadero Technologies
-8-
The resulting program running on Windows, as seen in Figure 2, shows the ingredients for a classic Pizza Margherita.
At first glance, this application appears very similar to a VCL application without any obvious differences. However, this is actually a very different program both in terms of cross-platform support and of user interface features. Let me start with cross-platform.
Embarcadero Technologies
-9-
Figure 3: To add the OS X Mac platform to an existing Delphi project, select the Add Platform command in the Project Manager, then pick the given platform in the following Select Platform dialog box.
The next step, if you have not already done so, is to configure the IDE for deploying and debugging the program on a Mac. For this step, you first need to install the Platform Assistant application on the target computer, and start it there. The Platform Assistant is available from the RAD Studio XE2 installation directory. This tool extends the RAD Studio IDE to the target platform, allowing you to automatically deploy an application on it and run in under the debugger. To accomplish this, the deployment includes the application but also a large file with symbolic information (this file has the RSM extension). What you can do in the Project Manager is to assign a remote profile to the target platform, a profile you can later modify in the Environment Options, as you can see in Figure 4:
Embarcadero Technologies
- 10 -
With my new profile configuration and the Platform Assistant running on my Mac, I can simply select the platform, click the familiar Run button in the IDE or press the classic F9 key, and the program will run on my Mac (see Figure 5):
Embarcadero Technologies
- 11 -
The Mac application behaves exactly as its Windows counterpart, although with the lookand-feel of the the Mac platform. There are other techniques you can use to adapt the application to the platform, rather than having a common user interface across platforms, but this is a more advanced topic than I can cover now.
TOPPINGS TO IOS
Now that we have seen how the RAD Studio IDE can cross-compile (and even crossdebug) your application on Mac OS X, we can move to a different type of multi-platform support available for FireMonkey. We will build an application for iOS, the operating system of Apples most recent devices: iPod, iPhone, and iPad. In this case, we cannot just recompile an existing application, but we rather have to create a new project with different settings. The starting point is to open the New Items dialog box of the RAD Studio IDE and pick FireMonkey HD iOS Application (notice this is available only for Delphi, not for C++). The design time form will have a different border to reflect the device it is going to be deployed to. By default the size reflects the iPhone size, but you can adjust it to the size of other iOS devices. Now after adding the usual edit box, button, and list box to the form,
Embarcadero Technologies
- 12 -
you can adjust their visual appearance (picking larger fonts, adjust position to fully utilize the iOS devices available space) to look like Figure 6 on the left. Add the usual code for the buttons OnClick event handler, and we can run a local instance of the application, as seen in Figure 6 on the right.
Figure 6: The iOS designer in the Delphi IDE (on the left) and a local version of the application running on Windows (on the right)
The running application is not being executed in an iOS emulator, as that is only available in the Xcode development platform on the Mac. This is a FireMonkey Windows application using a different form size, which can still be useful for some initial local testing and debugging. How do we compile this as an iOS program? To make this easier, the RAD Studio IDE provides the iOS FireMonkey tools, which you install separately from the product install. A one-time setup is performed on the development Mac, in addition to the standard Xcode installation. The FireMonkey-iOS.dmg disk image file installed with RAD Studio under the
Embarcadero Technologies - 13 -
FireMonkey-iOS directory should be transferred to the Mac. Open it and run the two installer packages inside: 1. fpc-2.4.4.intel-macosx.pkg installs Free Pascal 2.4.4 under /usr/local/. 2. FireMonkey-iOS-XE2.pkg. - copies Free Pascal 2.5.1 source to /Developer/Embarcadero/fpc/ - copies FMI source required for apps to /Developer/Embarcadero/fmi/ - builds and installs Free Pascal 2.5.1 under /usr/local/ - generates, builds, and installs required iOS SDK headers under
/Developer/Embarcadero/fpc/
This installs and configures your Mac correctly for the external open source compiler (FPC, Free Pascal Compiler) and the FMI (FireMonkey library, the FMX library ported for iOS). For detailed installation instructions, see: http://docwiki.embarcadero.com/RADStudio/XE2/en/FireMonkey_Platform_Prerequisites# iOS_Development_Setup The first step we need to do is to run the Export Project to Xcode utility (alternatively, the command-line program dpr2xcode.exe is available in RAD Studio XE2 bin folder). This will create an Xcode compatible project file. This process only needs to be executed again if the project structure changes, for example, because you have added some new units to your project.
Embarcadero Technologies
- 14 -
Figure 7: The source code of the PizzaToppings application in Xcode (on the Mac)
Now we can move to the Mac, run Xcode, and locate the project (which should likely be on a shared folder, so that you can easily work in the IDE, save your changes, and recompile in Xcode). As you open the project in Xcode (see Figure 7) you can open the various files and edit them, although it is recommended to edit the source in the RAD Studio IDE. You can also pick a device or one of the available simulators as a target. For this demo, I have selected the iPhone simulator. Now build and run the program (a much slower operation, compared to RAD Studio), and you will see the PizzaToppings FireMonkey project written in Delphi running in the actual iPhone simulator on the Mac, as shown in Figure 8.
Embarcadero Technologies
- 15 -
Figure 8: The PizzaToppings application running in the iPhone emulator, after compiling it with Xcode on a Mac. This is a Delphi FireMonkey application.
While moving a Delphi application to iOS can be a lot of fun, you have to consider what is available on the platform. Since the compiler used in Xcode is FreePascal, you must use the language features available in FreePascal (which lacks some of the recent extensions to the Delphi language) and run time library that comes with that compiler. What you get on both environments is the FireMonkey application platform, with the exclusion of things that don't make sense or don't exist on iOS, like an Open Dialog.
to deliver to these platforms a rich user experience. Thats what Ill start exploring in this section. The goal, of course, is not to build the best possible user interface, but to guide you step by step to understand some of the key features of FireMonkey while building a nice looking interface.
That translates to a shadow at a 3 pixels distance, a 45 degrees angle, and a given color, opacity, and softness. For the edit box I used a ReflectionEffect component, while I dragged a CrumpleTransitionEffect to the list box - like if the list was written on a slightly crumpled piece of paper. You can see the design time form with these effects in Figure 9. Notice that there is another significant difference from VCL. Since any control is automatically a control container, you can add other controls inside it. This is true for a button, which can host other internal controls. And it is also true for list box items: each of them is an individual sub control and can host other controls. Here I have added a tomato item, and added an image with an X symbol to remove it. The parent control of this image is the list box item itself, as you can see in the Structure View:
Embarcadero Technologies
- 17 -
Figure 9: The design time structure of a more complex version of the Build My Pizza application, with several effects and an image inside a list box item.
When you look at the Structure View inside Figure 9, you can notice another significant feature of FireMonkey that was not available in VCL. I have added a ScaledLayout control to the form, aligned to the client area, and I have added all other controls to it rather than to the form itself. This results in an application that can automatically scale all of its controls as you resize the main form. So you could run two instances of this program, enlarge one and shrink the other, and youll be able to obtain an image like in Figure 10.
Figure 10: If you resize the main form of the Build My Pizza application, it will size its internal components to the same ratio. Here are a large and a small version.
Embarcadero Technologies - 18 -
This behavior is obtained without writing any code. Next, we need to implement the removal of the first item of the list box when the image with the X is pressed:
lbToppings.Items.Delete(0);
The other new button, Crumple, enhances this effect by changing the transition Progress. This is a value that goes from 0 to 100, indicating a percentage (the image gets really crumpled above 50 percent):
CrumpleTransitionEffect1.Progress := CrumpleTransitionEffect1.Progress + 1;
These two lines plus the one used to add the content of the edit box to the list is all the code of the application, which, as you can guess, runs also on a Mac (in this case I have enhanced the crumple effect) as you can see in Figure 11.
Figure 11: A very crumpled version of the list of toppings, running on a Mac.
Embarcadero Technologies
- 19 -
STYLES IN FIREMONKEY
In the next revision of the pizza toppings application I am going to introduce styles. This is one of the core features of FireMonkey, and it deserves a bit of an introduction. The basic idea is that every visual control can be painted in different styles. There are some default styles you can pick and that partially depend on the target platform. But you can also define custom styles, and you can do so in multiple ways. When you pick a style, you are not picking a different set of colors or lines: you can really change the individual elements making up a control. For example, a button has a couple of rectangles and a text element, as you can see by opening the default button style in the Style Editor (see Figure 12):
In other words, the style of a control in FireMonkey is actually a collection of primitive elements, like shapes, effects, backgrounds and textual elements. This means we can add
Embarcadero Technologies - 20 -
new elements to the style, for example an image to turn a plain button into one with an image (like a VCL BitBtn control). The simplest way to customize a control is to select an existing style for it. Of course, you should generally pick a button style for a button, or you will alter the controls behavior significantly. You can also create a new custom style. One approach to style customization is to implement a new complete set of graphical primitives the controls use for painting, so that you actually generate a new complete style (a technique often referred to as skinning). This is quite complex and out of the scope for this paper. The second approach is to customize the layout of an individual control (an instance) or the default layout of all the controls of a given type within an application. In both cases, this customization is performed using the Style Editor right in the RAD Studio IDE. This is the technique I will focus on in the next example. In this second approach, customizing a style involves picking some predefined settings for the controls properties, but also changing its shape by using another control as a base element. Another very specific feature is the ability of adding sub-controls. For example, you can define a style for a button that includes an image. Once youve given this style a name, any other button using the same style will have also an image control inside itself, and the ability to become a graphical button. This ability of creating nested controls with styles is a very significant element of FireMonkey architecture. While FireMonkey has a very rich set of controls, you can expand it infinitely simply by combining the controls with the available styles and also through freely nesting the controls.
Embarcadero Technologies
- 21 -
To make the button look almost circular I set the XRadius and YRadius properties of each of the three rectangles to 55 (given that the width and height of the control will be set to 110). Now the button will change its user interface quite significantly, becoming a round button, as you can see in Figure 13.
Figure 13: A FireMonkey round button at design time, along with the other controls of the PizzaToppingsStyle application. The more complex the user interface control, the more flexibility you have in customizing it. Rather than working on the angles of the rectangles, I could have created a completely custom style with ellipses. But I have decided for different customizations. In this revision of the demo, I have replaced the edit for entering the pizza toppings with a combo edit control that can be used for selecting an item from a predefined list, while still letting the user enter a custom spice or ingredient (hoping the Pizza Shop has it!). Notice that the plain combo box in FireMonkey can be used only to select items from the drop down list, while the companion combo edit controls has both selection and editing capabilities. What you achieve in VCL by replacing a setting of the control (mapped to a specific behavior of the operating system), you can achieve here by picking a modified control. Now, before you ask, the reason is that modified controls likely have a very specific style including the edit element, thus the need for separating the two controls rather than having a setting.
Embarcadero Technologies
- 22 -
There we have our basic combo edit box. However, we want the program to show an image for the topping in our list, so we will do further modification to load a JPG image of our topping from the executable files folder.
Figure 14: A custom style for list box items, including an image and some text
Embarcadero Technologies
- 23 -
Notice that styles are saved in a textual format in the FMX file, just like component properties, but converted to a pure string format. Here is the description of the custom style (again with some omissions to avoid a uselessly long listing):
object StyleBook1: TStyleBook Resource.Strings = (
end
' object TLayout' ' StyleName = '#39'imagelabel'#39 ' Align = alScale' ' object image: TImage' ' StyleName = '#39'image'#39 ' end' ' object text: TText' ' StyleName = '#39'text'#39 ' Font.Size = 22.000000000000000000' ' end' ' end' 'end')
Now in the code I can create a list box item and customize its image and text subcomponents, by referring to the style and the style sub-elements by name:
var listItem: TListBoxItem; itemText: TText; itemImage: TImage; begin
// force the items style and set the text listItem.Resource := 'imagelabel';
itemText := listItem.FindStyleResource ('text') as TText; if Assigned (itemText) then itemText.Text := strTopping; itemImage := (listItem.FindBinding('image') as TImage); if Assigned (itemImage) then begin itemImage.Bitmap := Image1.Bitmap; listItem.Height := Image1.Bitmap.Height; end else ShowMessage ('Image binding element not found');
For Delphi developers used to VCL, this code should be quite surprising, since you would need a custom control for adding graphical elements to a list! This code, in fact, creates a list box as seen in Figure 15.
Embarcadero Technologies
- 24 -
Figure 15: The graphical list box with the topping names and images at run time
Embarcadero Technologies
- 25 -
Discover FireMonkey, The Next Generation Business Application Platform Delay = 0.00 Duration = 0.50 Trigger = 'IsVisible=true' StartValue = -200.00 StopValue = 32.00 PropertyName = 'Position.X' end end
With these design time settings (and no extra code), the combo edit will first appear offscreen because it will be using the starting position value of -200 as defined by the FloatAnimation control. It will then slide into position within half a second. A more sophisticated approach, albeit a slightly more complex one, would be to perform the Inverse animation enabling the StartFromCurrent property. This way you wont have to specify the final combo position in the animation, but only in the control itself. A more professional approach could likely use a fade-in animation, instead of a slide-in one, but here I wanted to focus on a rather simple scenario. The program uses another custom animation, actually two animations applied to the same control at once. The form has an image control, in which I load the image for the pizza topping as you press the button. The image has a slide animation like the previous one, but also a rotate animation, which goes from 0 to 720 degrees (two complete rounds) to the control rotation angle, saved in the RotationAngle property. Both animations are programmatically started in the buttons OnClick event handler, which also makes adjustment to one of the animation parameters:
procedure TFormToppings.btnAddClick(Sender: TObject); begin strTopping := cbToppings.ListBox.Selected.Text; Caption := 'Adding ' + strTopping; Image1.Bitmap.LoadFromFile(strTopping + aniRotateImage.Start; aniMoveImage.StopValue := listToppings.Position.X + 20; aniMoveImage.Start; end; '.jpg');
The pizza topping is added to the list at the end of the animation (which basically moves the image behind the list box, while rotating it), handling its OnFinish event handler, with the code I showed in the previous section. The event handler also clears the image and resets its position.
Embarcadero Technologies
- 26 -
Figure 16: The deployment pane for the PizzaToppingsStyle project, with the images used by the program
Now we can recompile the application and run it on the Mac. Since the files are available and given that the file access code is operating system independent, everything works smoothly, as you can see in Figure 17.
Embarcadero Technologies
- 27 -
Figure 17: The PizzaToppingsStyle application running on the Mac (during the animation)
Embarcadero Technologies
- 28 -
Figure 18: A FireMonkey 3D example with lights and objects at design time and runtime.
While this can be extremely useful for specific applications, it is not what you will generally use in a business program. A different way of approaching the librarys 3D support, though, is using it for moving and floating bi-dimensional objects like images, videos, and entire 2D forms, in the three-dimensional space. This is what Im going to demonstrate with the next demo, which is a 3D menu for our Pizza Shop, in which you can literally turn the pages with the available pizzas. The key difference in this application is the fact that the base class of the main form is now TForm3D:
type TFormMenu = class(TForm3D)
The page turning effect is obtained by using two 3D images (that is, images that can float in three dimensions) one in the opposite direction of the other, and turn the two images at the same time using two animations. The algorithm is based on changing the Y value of the Rotation property, using two float animations with the InterpolationBack sequence, so
Embarcadero Technologies
- 29 -
that at the beginning and at the end of the movement there is a limited movement in the opposite direction, like with a real world page movement. As you can see in the code below, the second image is turned by 180 degrees, and its animation is also shifted:
object Image3D1: TImage3D Width = 500.00 Height = 350.00 Depth = 0.0099 Opacity = 1.00 Projection = pjScreen TwoSide = False OnClick = TurnPage Align = alCenter Quanternion = '(0,0,0,1)' object FloatAnimation1: TFloatAnimation AnimationType = atInOut Enabled = True Duration = 2.00 Interpolation = itBack StartValue = 1.00 StopValue = 179.00 PropertyName = 'RotationAngle.Y' end end object Image3D2: TImage3D RotationAngle.Point = '(0,180,0)' Width = 500.00 Height = 350.00 Depth = 0.0099 Opacity = 1.00 Projection = pjScreen TwoSide = False OnClick = TurnPage Align = alCenter Quanternion = '(0,1,0,-2.71E-20)' object FloatAnimation3: TFloatAnimation AnimationType = atInOut AutoReverse = True Enabled = True Duration = 2.00 Interpolation = itBack StartValue = -180.00 PropertyName = 'RotationAngle.Y' end end
Every time a user clicks on the BufferLayer3D hosting the images or on the images themselves, the following code is executed.
procedure TFormMenu.TurnPage(Sender: TObject); begin LoadNewImage; // in the non-visible 3D image control FloatAnimation1.StartValue := Image3D1.RotationAngle.Y;
Embarcadero Technologies
- 30 -
Discover FireMonkey, The Next Generation Business Application Platform FloatAnimation1.StopValue := Image3D1.RotationAngle.Y + 180; FloatAnimation3.StartValue := Image3D2.RotationAngle.Y; FloatAnimation3.StopValue := Image3D2.RotationAngle.Y + 180; FloatAnimation1.Start; FloatAnimation3.Start; end;
Finally, you can also animate the right side of the form with a 3D effect, which includes a Layer3D control hosting some regular 2D controls. Using such hosting 3D layer, in fact, is how a standard 2D object can participate in a 3D application. This is the object structure:
object FormMenu: TFormMenu object Layer3D1: TLayer3D object AniIndicator1: TAniIndicator object Panel1: TPanel object Label1: TLabel object Label2: TLabel
To move this control Im not using design time animation objects, but Im using methods that define the animations at runtime:
procedure TFormMenu.FlipSide(Sender: TObject); begin Layer3D1.AnimateFloat('RotationAngle.Y', 360, 2, TAnimationType.atInOut, TInterpolationType.itBack); Layer3D1.AnimateFloat('Position.Z', 500, 1); Layer3D1.AnimateFloatDelay('Position.Z', 0, 1, 1); end;
The final effect is partially visible in Figure 19, but you really need to see a video to understand it.
Figure 19: A couple of snapshots of the moving 3D images of the Pizza Menu 3D Demo (although you really have to see a video to appreciate it)
Embarcadero Technologies
- 31 -
Embarcadero Technologies
- 32 -
Discover FireMonkey, The Next Generation Business Application Platform Name = 'Description' DataType = ftString Size = 256 end item Name = 'Price' DataType = ftCurrency end item Name = 'Image' DataType = ftGraphic end> StoreDefs = True End
As usual in Delphi, this application has a DataSource hooked to the dataset, but from this point things are different. By the way, you can see a snapshot in design time of all of the components used in the main form of the application in Figure 20.
Figure 20: The visual controls and the non-visual components of the FmxPizzaMenuDB program at design time in the RAD Studio form editor.
Embarcadero Technologies
- 33 -
The DataSource is referenced from a BindScopeDB component, which is the conduit between the new data binding model and the old data-aware architecture or, to put it in a simpler way, the conduit between data binding and a TDataSet:
// listing: tdata source + bind scope DB object dsPizza: TDataSource DataSet = cdsPizzaMenu end object BindScopeDB1: TBindScopeDB DataSource = dsPizza End
The actual associations take place in a BindingList container, which holds the various data bindings for the current application. You can see the list of bindings Ive defined in Figure 21.
As you can see in Figure 21, the program connects three edit boxes and a memo control to four different fields, plus an ImageControl tied to a graphic field. In the FMX code below you can see that there are three different types of links involved: three instances of the TBindDBEditLink class, one of the TBindDBMemoLink class, and one of the
Embarcadero Technologies
- 34 -
TBindDBImageLink. The binding list definition in the FMX file is long, but it is worth looking at it, since this is the real code of the application:
object BindingsList1: TBindingsList UseAppManager = True object DBEditLinkID: TBindDBEditLink Category = 'DB Links' DataSource = BindScopeDB1 FieldName = 'ID' EditControl = edID end object DBEditLinkName: TBindDBEditLink Category = 'DB Links' DataSource = BindScopeDB1 FieldName = 'Name' EditControl = edName end object DBEditLinkPrice: TBindDBEditLink Category = 'DB Links' DataSource = BindScopeDB1 FieldName = 'Price' EditControl = edPrice end object DBImageLinkImage: TBindDBImageLink Category = 'DB Links' DataSource = BindScopeDB1 FieldName = 'Image' ImageControl = imgImage end object DBMemoLinkDescription: TBindDBMemoLink Category = 'DB Links' DataSource = BindScopeDB1 FieldName = 'Description' MemoControl = meDescription end end
The only additional code required is a line to open the ClientDataSet as the program starts (in the OnCreate event handler of the form), plus the following code to load a new image in the database (which automatically makes it available to the image control tied to the field);
procedure TFormPizzaMenu.btnLoadImageClick(Sender: TObject); begin if OpenDialog1.Execute then begin cdsPizzaMenu.Edit; (cdsPizzaMenu.FieldByName('Image') as TGraphicField). LoadFromFile(OpenDialog1.FileName); cdsPizzaMenu.Post; end; end;
The output of the application looks like that in Figure 22, with a nice pizza Margherita on display. Notice the navigator at the top of the form, a TBindNavigator control (with
Embarcadero Technologies
- 35 -
standard images) that you can fully customize in terms of user interface and in terms of the available actions.
Of course we could do exactly the same with a dataset mapped to a relational database, using dbExpress or any other database access technology available in Delphi. Since these database access components often end up populating a ClientDataSet, the user-interface portion of a similar application will remain the same. We are actually going to see this in a different scenario - the client side of a multi-tier application built with DataSnap.
MULTI-TIER PIZZA
The database application I just built uses local data for data source. And as I have mentioned, it can be easily turned into a client/server application talking to a relational database. I am, however, going to skip the client/server step, in order to move to the more complex architecture - the development of a multi-tier application based on the DataSnap technology! This is a multi-tier technology that has been part of Delphi for over 10 years,
Embarcadero Technologies - 36 -
and was significantly enhanced in the last few versions of the product to fully embrace Internet-based communication by natively supporting TCP/IP, HTTP, and HTTPS. Here Ill just sketch the steps you have to follow, as an in-depth analysis of DataSnap is beyond the scope of this whitepaper. First, we have to build a server, this is a Windows application (but could as well be a Windows service or a console program) that you could run on a custom Windows server or deploy to a server in the cloud, as Ill show later in this paper. The server is not tied to FireMonkey and will equally work with different client libraries. In the second step, however, Ill build a client (modifying the previous example), and it will be a FireMonkey client.
Embarcadero Technologies
- 37 -
The resulting application will have a few parts: an unnecessary main form (although I will actually use it by adding a list box to it with pending orders from the clients application to show interaction between them); a server container data module with the DSHTTPService component, defining the connectivity, plus some other structural DataSnap components; and a server methods unit, which is the unit I will customize for this demo. The steps we have to follow to build this program are quite simple. First, we need to expose a dataset. This is accomplished by adding a dataset component to this last data module. It could be a dataset for accessing a relational database, but for simplicity I will use a local ClientDataSet. The second component we need is a DataSetProvider, which will expose the dataset to the client applications. These are the key properties of the two components:
object DataSetProvider1: TDataSetProvider DataSet = ClientDataSet1 end object ClientDataSet1: TClientDataSet FileName = PizzaMenu.cds' End
This is all it takes to expose a dataset. However, the server has a second feature, a server side method used to accept pizza orders. The client will invoke this method passing the name of the pizza and the customer name (not a real world scenario, but enough to let you understand the approach). For this second step we need to add a method to the server class, called TServerMethods1 by default (after removing the methods generated by the wizard):
type TServerMethods1 = class(TDSServerModule) ClientDataSet1: TClientDataSet; DataSetProvider1: TDataSetProvider; public function OrderPizza (PizzaName, CustomerName: string): Integer; end;
The OrderPizza method takes two string parameters and returns an integer indicating how many pending orders there are. For this simple demo, the program will add the orders to a list box in the main form, and the user (working on the server) can select the order to process and remove them from the pending list. I will not list the code of this demo, as it is unrelated to FireMonkey, thus not the topic of this whitepaper.
Embarcadero Technologies
- 38 -
Embarcadero Technologies
- 39 -
Figure 24: You can turn any FireMonkey (or VCL) application into a DataSnap client by using the DataSnap Client Module wizard. Here is the last page of the wizard.
The wizard generates two files, one hosting the client classes (the proxy classes we can use to access server methods) and the other hosting the client module, a data module with a ready to use remote connection component. To access the remote table, we can add a second component to this data module, a DataSnap Provider Connection:
object DSProviderConnection1: TDSProviderConnection ServerClassName = 'TServerMethods1' SQLConnection = SQLConnection1 End
The component refers to the SQLConnection component configured by the wizard to refer to the specific DataSnap server; and to the name of the class of the server we want to refer to, in the example the default 'TServerMethods1'. After adding these new units to the previous example, the database pizza menu, we can modify the main form. Basically, we have to remove the reference to a local file from the ClientDataSet component, and add this initialization code (that Ive added to the OnClick event handler of a button, rather than on form creation, as in the latter case an error will prevent the program from starting):
procedure TFormPizzaMenu.btnConnectClick(Sender: TObject); begin cdsPizzaMenu.RemoteServer := ClientModule1.DSProviderConnection1; cdsPizzaMenu.ProviderName := 'DataSetProvider1'; cdsPizzaMenu.Open; end;
As you can see in the code above, the ClientDataSet must refer to the provider connection and indicate the specific data table, using the name of the provider component used on the server. The result of running this code is a form similar to the previous version, but now accessing remote data rather than a local file. A sample output is in Figure 25.
Embarcadero Technologies
- 40 -
Figure 25: Pizza data and images now coming from a remote DataSnap server
To finish the DataSnap client, we have to be able to place an order. As the user types in the customer name edit box, the Place Order button gets enabled. Pressing it executes the following code:
procedure TFormPizzaMenu.btnOrderClick(Sender: TObject); var nInLine: Integer; begin nInLine := ClientModule1.ServerMethods1Client.OrderPizza( cdsPizzaMenu.FieldByName('Name').AsString, edCustomerName.Text); ShowMessage ('You are number ' + IntToStr (nInLine) + ' in the line'); end;
The code above refers to the proxy object (ServerMethods1Client) surfaced by the client data module, and calls the OrderPizza method directly, receiving a response, as you can see in Figure 26. Now an animation of the position in the line over time could be a nice addition!
Embarcadero Technologies
- 41 -
Figure 26: The order for the pizza showing up on the server (which, of course, will be running on a different computer).
Embarcadero Technologies
- 42 -
Discover FireMonkey, The Next Generation Business Application Platform begin SQLConnection1.Params.Values['HostName'] := strIP; end;
Since the program uses the ClientDataSet component, which is implemented in the midas.dll library, we also need to add the corresponding library (libmidas.dylib) to the list of files to deploy. A sample deployment structure is shown in Figure 27:
Figure 27: To deploy a DataSnap client to the Mac you need to include the libmidas.dylib library file.
With the flexible IP and extra file deployed, we can now run the DataSnap client on the Mac (see Figure 28). This is a very significant extension to the DataSnap architecture as a whole.
Embarcadero Technologies
- 43 -
Figure 28: The DataSnap client running on the Mac, with data provided by a DataSnap server running on Windows
Embarcadero Technologies
- 44 -
server you own. If we really want to create a truly cloud-based application, we should rather distribute the database data and pizza images to cloud-based storage. In the next example, Im going to use Microsofts Azure Table and Blob services, but I could have equally used Amazon S3 and simple tables as both services are now well integrated in Delphi. Delphi includes some components for Azure, but also and moreover a number of lower level classes, which is where the real power is. These classes provide ready-to-use methods for performing the various operations and deal with the complicated security requirements of the Azure platform. The core non-visual component is the one used to configure the connection to the service, called TAzureConnectionInfo. This component is a handy starting point in which to enter the user name and password of your account. Needless to say you need to have an Azure account (with information added to a separate INI file) to test this demo. This demo uses a pre-populated non-relational database table with the pizza menu data. I have populated the database using the VCLPizzaMenu application not discussed here, as it does not relate to FireMonkey. However, the code for the application will be included in the companion code part of the whitepaper download. The client application is not a direct extension to the previous pizza menu program, as we do not have a dataset to bind our user interface to. Still the user interface has similar visual controls, plus a list box with the list of menu entries. The program initialization code is located in the OnCreate event of the main form. The code (below) reads the Azure account settings from an INI file in the users documents folder and initializes two private fields of the form: TableService and BlobService:
procedure TFormPizzaMenu.FormCreate(Sender: TObject); var fIni: TMemIniFile; begin fIni := TMemIniFile.Create(GetHomePath + PathDelim + 'azure.ini'); try AzureConnectionInfo1.AccountName := fIni.ReadString('azure', 'AccountName', ''); AzureConnectionInfo1.AccountKey := fIni.ReadString('azure', 'AccountKey', ''); finally FreeAndNil (fIni); end; Caption := Caption + ' - ' + AzureConnectionInfo1.TableURL; TableService := TAzureTableService.Create(AzureConnectionInfo1); BlobService := TAzureBlobService.Create(AzureConnectionInfo1); end;
Embarcadero Technologies
- 45 -
By clicking on a button, the program retrieves the list of pizzas in the menu, by reading the two key fields of an Azure table:
procedure TFormPizzaMenu.btnGetListClick(Sender: TObject); var rowsList: TList<TCloudTableRow>; aRow: TCloudTableRow; begin rowsList := TableService.QueryEntities(tablename); try ListBox1.Clear; for aRow in rowsList do begin ListBox1.Items.Add(aRow.GetColumn(XML_ROWKEY).Value + '=' + aRow.GetColumn(XML_PARTITION).Value); end; finally rowsList.Free; end; end;
By the way, I could have kept the table data (rowsList) in memory, rather than fetching the data for each individual entry, but I wanted to show a more complete example. So now we have a list of entries in the list box. As a user double clicks on an item, the program extracts the row and partition key and uses them as the ID of the individual record (or row) to fetch from the database table. The second step is to get the image from the Blob service, using the partition key name (in this case the record ID) as a file name, since this was the rule I used when uploading those images. This is the code used to fetch the data and populate the user interface for an individual entry, with the result shown in Figure 29:
procedure TFormPizzaMenu.ListBox1DblClick(Sender: TObject); var aRow: TCloudTableRow; memStream: TMemoryStream; begin
if Assigned (ListBox1.Selected) then begin aRow := TableService.QueryEntity(tablename, ListBox1.Items.ValueFromIndex [ListBox1.Selected.Index], ListBox1.Items.Names [ListBox1.Selected.Index]); if Assigned (aRow) then begin edId.Text := aRow.GetColumn(XML_PARTITION).Value; edName.Text := aRow.GetColumn(XML_ROWKEY).Value;; meDescription.Text := aRow.GetColumn('description').Value; edPrice.Text := aRow.GetColumn('price').Value; memStream := TMemoryStream.Create; BlobService.GetBlob(tablename,
Embarcadero Technologies
- 46 -
Discover FireMonkey, The Next Generation Business Application Platform aRow.GetColumn(XML_PARTITION).Value + '.jpg', memStream, ''); memStream.Position := 0; imgImage.Bitmap.LoadFromStream(memStream); MemStream.Free; end; aRow.Free; end; end;
Figure 29: The user interface of the Pizza Menu Cloud application, after fetching one row (or record).
Some of your VCL code can be ported. Some of the visual controls have a similar role and similar property and method names. For example, some of the initial demos had code identical to that of a VCL application. There are notable differences, though, even for basic features. For example, control positions are not expressed with Left and Top, but with a single Position property with X and Y sub-values. For 3D objects, however, there is also a Z axis. I mentioned the key elements of the coordinate system in the initial section, Building The Pizza Application. All control positions and sizes are expressed in floating point values, rather than integer ones. The size of fonts uses a different scale, which can cause confusion. For most FireMonkey controls, the textual information is stored in the Text property, rather than in the Caption property generally used by VCL. The notable exception is the form object, which uses the Caption property in both libraries. There are other differences I can point out. Rather than continuing with such a list, however, I would rather try to migrate a focused example based on a ListView control.
Figure 30: The VCL version of the PizzaTweets application, to be ported to FireMonkey.
Embarcadero Technologies
- 48 -
The application is clearly split into two parts: there is a data processing back-end, implemented using a data module, and the user interface. The data processing is based on a separate data module, which in turn uses Delphi XML interface mapping technology to process the data retrieved by calling the Twitter REST web service. The communication is done using the Indy HTTP client component (IdHTTP) and the XML processing is based on Delphis XMLDocument component, which can map different XML engines. At the end, the selected tweets are exported to HTML using a PageProducer component. The interaction between the user interface and the data processing takes place in two different ways. On one side, the program uses some global records to define a data structure for the PageProducer. On the other side, when populating the user interface the program uses an anonymous method passed to the data module to refer to the user interface in a very flexible way. Since this is a more modern and interesting approach, I am only going to describe this second approach here. This is the code used by the data module to fetch the information and process it (using the XMLDocument and an interface):
procedure TTweetsDataModule.GetData(const strTopic: string; processEntry: TProcessEntry); var strResult: string; feed: IXMLFeedType; I: Integer; begin
strResult := IdHTTP1.Get('http://search.twitter.com/search.atom?q=' + strTopic + '&rpp=100&page=1'); XMLDocument1.LoadFromXML(strResult); XMLDocument1.Active := True; feed := Getfeed (XMLDocument1); for I := 0 to feed.Entry.Count - 1 do begin processEntry (feed.Entry[I]); end; end;
The main form invokes the function passing the anonymous method, which can connect the data to the user interface, keeping the two clearly separate:
procedure TTweetsForm.btnLoadClick(Sender: TObject); var
Embarcadero Technologies
- 49 -
Discover FireMonkey, The Next Generation Business Application Platform item: TListItem; strTitle: string; begin ListData.Clear; TweetsDataModule.GetData (edTopic.Text, procedure (iEntry: IXMLEntryType) begin item := ListData.Items.Add; item.Caption := copy (iEntry.Id, Length ('tag:search.twitter.com,2005:') + 1, maxint); item.SubItems.Add(iEntry.Author.Name); strTitle := iEntry.Content.Text; item.SubItems.Add(strTitle); item.SubItems.Add(iEntry.Published_); item.SubItems.Add(iEntry.Link.Items[1].Href); // image item.SubItems.Add(iEntry.Link.Items[0].Href); // status item.SubItems.Add(iEntry.Author.Uri); end); end;
Using this architecture with a clear separation of the data processing part from the user interface will help us move this application to FireMonkey. In fact, I can create a very similar form and reuse most of the code. The data module and backend processing is even the same unit shared by the two projects!
Embarcadero Technologies
- 50 -
Discover FireMonkey, The Next Generation Business Application Platform TabOrder = 0 Header = 'Author' end
... // and so on
After creating the user interface, we have to change the code that manages it. For example, the code used to load the elements in the interface (based on an anonymous method) now becomes:
procedure TTweetsFormFmx.btnLoadClick(Sender: TObject); var rowNo: Integer; strTitle: string; begin if edTopic.Text = '' then raise Exception.Create('Missing search element'); rowNo := 0; TweetsDataModule.GetData (edTopic.Text, procedure (iEntry: IXMLEntryType) begin
StringGrid1.Cells[colAuthor.Index, rowNo] := iEntry.Author.Name; StringGrid1.Cells[colID.Index, rowNo] := copy (iEntry.Id, Length ('tag:search.twitter.com,2005:') + 1, maxint); StringGrid1.Cells[colText.Index, rowNo] := iEntry.Content.Text; StringGrid1.Cells[colPublished.Index, rowNo] := iEntry.Published_; StringGrid1.Cells[colImage.Index, rowNo] := iEntry.Link.Items[1].Href; StringGrid1.Cells[colStatus.Index, rowNo] := iEntry.Link.Items[0].Href; StringGrid1.Cells[colUri.Index, rowNo] := iEntry.Author.Uri;
The user interface of the FireMonkey version of the PizzaTweets application is visible in Figure 31.
Embarcadero Technologies
- 51 -
Figure 31: The user interface of the FireMonkey version of the Pizza Tweets application, based on a StringGrid rather than a ListView, but with a lot of shared code
Now the advantage of this version compared to the VCL one is that we can make the program look much nicer, thanks to the features of FireMonkey, and also move it to the Mac platform.
END-PAPER ASSESSMENT
Now that you have an idea of what FireMonkey is, have seen demos and quite a bit of source code, Ill get back to the core features and reasons for using it. First, the FireMonkey platform delivers the next incarnation of Delphis visual development experience, the first architecture to ever combine object-oriented programming and visual development. This is the next incarnation because rather than tying to a specific platform
Embarcadero Technologies
- 52 -
(like Windows), FireMonkey uses the power of modern GPUs to deliver fast native performance on multiple platforms. Using styles, effects, animations, freely combining controls, and redefining their internal layout make FireMonkey a very nice foundation for third party controls vendors. In fact, some interesting FireMonkey controls are already starting to appear. In this first version, the cross-platform experience is fully available for Windows and Mac OS X, with a subset of functionality available on iOS. This foundation is likely to be extended in the future to other platforms, as there is nothing inherent to it that is tied to Windows, or to a desktop PC. In fact you can already build an iOS application with rich graphics using FireMonkey today which is a clear indication of the future path. Although I havent specifically focused on this issue in my demos, performance is a key factor. If you look at how smooth the 3D menu animation is even on rather old hardware, you get an idea of the power of using both the CPU and the GPU in a native way. As I explained in the introduction, at the moment there is no other library with this power. FireMonkey applications start right away, perform fast, and have no dependency on third party libraries or large frameworks to be installed on the target computer. Of course, this paper is meant to be only an introduction to FireMonkey, as there is a lot more to learn about it. The introduction of FireMonkey is certainly going to be a revolution for Delphi, although VCL is still here to stay for time to come. But FireMonkey clearly goes beyond Delphi and offers a new application development platform for the developer community at large.
Embarcadero Technologies
- 53 -
Embarcadero Technologies, Inc. is the leading provider of software tools that empower application developers and data management professionals to design, build, and run applications and databases more efficiently in heterogeneous IT environments. Over 90 of the Fortune 100 and an active community of more than three million users worldwide rely on Embarcaderos award-winning products to optimize costs, streamline compliance, and accelerate development and innovation. Founded in 1993, Embarcadero is headquartered in San Francisco with offices located around the world. Embarcadero is online at www.embarcadero.com.
Embarcadero Technologies
- 54 -