Delphi XE Starter Essentials - Programacao Delphi

156 Pages • 44,211 Words • PDF • 4.4 MB
Uploaded at 2021-09-24 12:16

This document was submitted by our user and they confirm that they have the consent to share it. Assuming that you are writer or own the copyright of this document, report to us by using this DMCA report button.


Delphi XE Starter Essentials Delphi XE Starter Essentials February 2011

Author: Bob Swart (aka Dr.Bob) Bob Swart Training & Consultancy (eBob42)

Delphi XE Starter Essentials

Table of Contents

Table of Contents 1. Delphi XE Starter IDE

1

Delphi and C++Builder ....................................................................... 1 Installation ........................................................................................ 1 Run Delphi “as Administrator” ............................................................. 2 Welcome Page ................................................................................... 3 Project Targets and Object Repository .................................................. 6 Options Dialog ................................................................................... 7 Delphi IDE New Menu Items ................................................................ 8 File Open From Version Control… ............................................. 8 View Configuration Manager .................................................... 9 The “old” Component Palette ..................................................... 9 Run without Debugging............................................................. 10 Project Options .................................................................................. 10 Build Configurations ................................................................. 11 Warnings as Errors ................................................................... 11 Resource Compiler ................................................................... 12 Project Manager ................................................................................. 13 Build Configurations ................................................................. 14 STRICT Build Configuration ....................................................... 14 Object Repository............................................................................... 15 Form Designer ................................................................................... 16 Edit Copy .............................................................................. 16 Quality Central................................................................................... 17 Summary .......................................................................................... 18 Exercises........................................................................................... 18 Exercise #1 ............................................................................. 18 Exercise #2 ............................................................................. 18

2. Language Enhancements

19

Delphi XE Compiler and Language Enhancements .................................. 19 Delphi 2010 Enhancements ....................................................... 20 Custom Attributes .................................................................... 21 Enhanced RTTI......................................................................... 22 Scoped Enumerators................................................................. 23 IS and AS keywords ................................................................. 24 Class Constructors & Destructors ............................................... 24 Delayed Dynamic Link Libraries ........................................................... 25 Example DLL............................................................................ 25 Implicit ................................................................................... 26 Explicit.................................................................................... 27 Delay Load .............................................................................. 28 DelayedLoadHook ..................................................................... 29 DelayedHandler........................................................................ 30 Delphi 2009 Language Enhancements .................................................. 32 Type Aliases ............................................................................ 32 Compiler Defines...................................................................... 32 Compiler Directive.................................................................... 33 Record Alignments ................................................................... 33 Compiler Warnings ................................................................... 33 Unicode Strings.................................................................................. 34 Exit .................................................................................................. 34

Bob Swart ([email protected])

- ii -

February 2011

Delphi XE Starter Essentials

Table of Contents

Generics............................................................................................ 34 Generic Stack .......................................................................... 35 Generic Methods ...................................................................... 37 Anonymous Methods........................................................................... 38 Anonymous Exit ....................................................................... 39 Useful Anonymous Methods....................................................... 39 Generics.Defaults ..................................................................... 41 Combining Anonymous Methods and Generics ............................. 42 Delphi 2007 Language Enhancements .................................................. 44 Unit namespaces...................................................................... 44 Strict Private and Protected ....................................................... 44 Final Methods .......................................................................... 45 Sealed Classes ......................................................................... 46 Nested Type Declarations .......................................................... 47 Class data ............................................................................... 47 Class properties ....................................................................... 48 Ampersand Character ............................................................... 48 For ... in ... do ......................................................................... 48 Function Inlining ...................................................................... 49 Operator Overloading ............................................................... 49 Summary .......................................................................................... 52 Exercises........................................................................................... 52 Exercise #1 ............................................................................. 52 Exercise #2 ............................................................................. 52

3. RTL and VCL Enhancements

53

XE RTL Enhancements ........................................................................ 53 FormatSettings ........................................................................ 53 Binary Streams ........................................................................ 55 RegularExpressions .................................................................. 55 Windows 7 Support ............................................................................ 56 Themes................................................................................... 56 Fonts ...................................................................................... 57 Taskbar buttons ................................................................................. 57 Progress.................................................................................. 58 ImageList –icon ....................................................................... 60 Direct2D............................................................................................ 62 DirectWrite .............................................................................. 64 New VCL Components ......................................................................... 64 TButtonedEdit .......................................................................... 64 TCategoryPanel ........................................................................ 64 TCategoryPanelGroup ............................................................... 65 TLinkLabel ............................................................................... 65 TCustomHint............................................................................ 65 TBalloonHint ............................................................................ 65 VCL Enhancements............................................................................. 66 TControl .................................................................................. 66 TActionManager ....................................................................... 67 TButton................................................................................... 67 TApplication............................................................................. 68 TBitmap .................................................................................. 68 TComboBox ............................................................................. 68 TCustomListView...................................................................... 68 TDBImage ............................................................................... 68 TEdit/TMaskEdit/TLabeledEdit.................................................... 68 TGraphic ................................................................................. 70

Bob Swart Training & Consultancy

- iii -

www.drbob42.com

Delphi XE Starter Essentials

Table of Contents

TImage ................................................................................... 70 TImageList .............................................................................. 70 TLabel..................................................................................... 70 TListView................................................................................. 70 THeaderControl ........................................................................ 70 THeaderSection........................................................................ 70 TMemo.................................................................................... 70 TPanel..................................................................................... 70 TPopupActionBar ...................................................................... 71 TProgressBar ........................................................................... 71 TRadioGroup............................................................................ 71 TScreen .................................................................................. 71 TTrayIcon................................................................................ 71 TTreeView ............................................................................... 72 Ribbon Controls.................................................................................. 72 Actions and TActionList ....................................................................... 74 Using TActionList...................................................................... 74 Action! .................................................................................... 78 Problems? ............................................................................... 79 ActionBands ...................................................................................... 79 ActionManager ......................................................................... 79 CustomizeDlg........................................................................... 80 ActionBars............................................................................... 80 Customise Action ..................................................................... 81 Action! .................................................................................... 81 Further Customizations ............................................................. 82 Returning to Ribbon Controls ............................................................... 82 Ribbon Styles........................................................................... 84 Touch and Gestures............................................................................ 85 Biolife Fields ............................................................................ 85 Touch and Action ..................................................................... 86 Gestures ................................................................................. 86 Action! .................................................................................... 87 Custom Gestures...................................................................... 88 Touch Keyboard ....................................................................... 91 Deployment ............................................................................. 92 COM Enhancements............................................................................ 93 ActiveX Wizards ....................................................................... 93 Automation Demo .................................................................... 94 RIDL Files................................................................................ 95 Importing Type Libraries ........................................................... 98 Summary .......................................................................................... 99 Exercises........................................................................................... 99 Exercise #1 ............................................................................. 99 Exercise #2 ............................................................................. 99 Exercise #3 ............................................................................. 100 Exercise #4 ............................................................................. 100

4. Unicode

101

Unicode............................................................................................. 101 Unicode Transformation Formats................................................ 101 UTF-8 ..................................................................................... 101 UTF-16.................................................................................... 101 UTF-32.................................................................................... 102 Windows API............................................................................ 102 Notepad .................................................................................. 102

Bob Swart ([email protected])

- iv -

February 2011

Delphi XE Starter Essentials

Table of Contents

Unicode and Fonts.................................................................... 102 Delphi 2009+ and Unicode .................................................................. 103 AnsiString Encoding ................................................................. 103 String Assignments and Conversions .......................................... 104 UTF-16 Surrogate Characters .................................................... 105 UTF-16 Number of Characters.................................................... 105 Unicode Files and BOM.............................................................. 106 TEncoding ............................................................................... 106 Console or Text File I/O ............................................................ 106 TStrings / TStringList................................................................ 107 Output Logging ........................................................................ 108 TCharacter .............................................................................. 109 Character Sets ......................................................................... 109 Extending TCharacter ............................................................... 109 Unicode Support Routines ......................................................... 110 Explicit Unicode Conversions ..................................................... 110 Unicode Migration............................................................................... 112 SizeOf in Bytes ........................................................................ 112 Length in Items ....................................................................... 112 Number of Characters............................................................... 112 FillChar ................................................................................... 113 Move....................................................................................... 113 TStream Read and Write ........................................................... 114 Windows API............................................................................ 114 Chr -> Char............................................................................. 115 Character Literals ..................................................................... 115 Integer and Float To AnsiString ................................................. 115 UpperCase............................................................................... 116 Globalisation and localisation............................................................... 117 Globalisation and SysUtils ......................................................... 118 Localisation using ResourceStrings ............................................. 118 Not for runtime packages .......................................................... 119 Summary .......................................................................................... 119 Exercises........................................................................................... 120 Exercise #1 ............................................................................. 120 Exercise #2 ............................................................................. 120

5. Delphi XE and Subversion

121

Subversion ........................................................................................ 121 Local Repository....................................................................... 121 Remote Repository ................................................................... 124 Overview of SVN Projects .......................................................... 130 Summary .......................................................................................... 130

Bob Swart Training & Consultancy

-v-

www.drbob42.com

Delphi XE Starter Essentials

Table of Contents

A. Answers to Exercises

131

1. Delphi for Win32 IDE ...................................................................... 131 Exercise #1 ............................................................................. 131 Answer to Exercise #1 .............................................................. 131 Exercise #2 ............................................................................. 132 Answer to Exercise #2 .............................................................. 132 2. Language Enhancements................................................................. 133 Exercise #1 ............................................................................. 133 Answer to Exercise #1 .............................................................. 133 Exercise #2 ............................................................................. 134 Answer to Exercise #2 .............................................................. 134 3. RTL & VCL Enhancements................................................................ 138 Exercise #1 ............................................................................. 138 Answer to Exercise #1 .............................................................. 138 Exercise #2 ............................................................................. 139 Answer to Exercise #2 .............................................................. 140 Exercise #3 ............................................................................. 141 Answer to Exercise #3 .............................................................. 141 Exercise #4 ............................................................................. 142 Answer to Exercise #4 .............................................................. 142 4. Unicode ......................................................................................... 146 Exercise #1 ............................................................................. 147 Answer to Exercise #1 .............................................................. 147 Exercise #2 ............................................................................. 147 Answer to Exercise #2 .............................................................. 147

This Delphi XE Starter Essentials manual covers the fundamentals and the new features in Delphi XE Starter (compared to Delphi 7 Personal). There are some more advanced topics that fell beyond the scope of this “starter essentials” manual which will be covered in other places (see http://www.eBob42.com/courseware for details). -

Delphi XE DataSnap Development Delphi XE XML, SOAP & Web Services Delphi Prism XE Development Essentials

-

Delphi for Win32 Database Development (currently covering Delphi 2007) Delphi for Win32 VCL for the Web Development (also for Delphi 2007)

The information in this courseware manual is © 2011 by drs. Robert E. (Bob) Swart of Bob Swart Training & Consultancy. All Rights Reserved. The information in this courseware manual is presented to the best of my knowledge at the time of writing. However, in case of errors or omissions, I welcome your feedback or comments (by e-mail) as Bob Swart Training & Consultancy cannot be held responsible for any damage that results from using the information in this manual or the example source code snippets. Thanks in advance for your understanding.

Bob Swart ([email protected])

- vi -

February 2011

Delphi XE Starter Essentials

1. Delphi XE Starter IDE

1. Delphi XE Starter IDE In this section you'll get an overview of the IDE features in Delphi XE Starter.

Delphi and C++Builder Delphi and C++Builder are different personalities of the same IDE. With the 2007 editions, this IDE was called RAD Studio, and also included a Delphi for .NET personality. With Delphi 2009, the .NET edition was replaced by Delphi Prism for .NET, and the RAD Studio IDE kept the combined Delphi and C++Builder personalities. In this courseware manual, I will cover only Delphi and not C++Builder, so I will refer to the IDE as Delphi instead of the combined RAD Studio IDE.

Installation The installation of Delphi XE Starter consists of at least two steps: the Delphi XE Starter IDE and the Help and Documentation, each with a separate installer. An optional third step consists of the download and installation of InterBase 2009. The product includes a free key for InterBase 2009 Developer Edition.

You can also download a trial edition of ER/Studio, as well as an ODBC driver for InterBase which is required for ER/Studio to work with InterBase. Finally, the Partner and Additional Products include a download link for the Remote Debugger (in case you want to debug an application running on another machine, especially helpful if the Delphi IDE itself is not installed on that other machine), as well as the Embarcadero RAD Studio XE Partner Software, CollabNet Subversion Server, and InstallAware Express, CodeGear Edition.

Bob Swart Training & Consultancy

-1-

www.drbob42.com

Delphi XE Starter Essentials

1. Delphi XE Starter IDE

The Delphi XE Starter installer shows the features included in this edition of Delphi:

The database features include InterBase Express, which is why you should download and install InterBase 2009. The installation also includes the Borland Database Engine, which includes support for dBASE and Paradox database tables, but I would recommend using InterBase and InterBase Express instead (even for local database tables). Although not explicitly mentioned, one third-party library is included with Delphi XE Starter Edition, and that’s Indy 10. The online help and documentation installer offers the choice to include the Microsoft SDK Help. This is disabled by default, since it requires a lot of disk space. The thirdparty help does not only include Indy 10 help, but also details for other third-party products that are included with the Professional and higher editions of Delphi.

Run Delphi “as Administrator” During installation of Delphi XE, the last step offered you the ability to automatically have the IDE check for updates – both of Delphi XE and the on-line Help for Delphi XE. This is a useful feature, but if you run Delphi on Windows Vista or Windows 7, it only works if you run Delphi with the “Run as Administrator” option. Otherwise, you will not see any automatic update messages from the IDE. This issue is caused by the fact that the application checking for available updates is called “setup.exe1”, and Windows Vista and Windows 7 do not allow applications with “setup” in their name to run without special user permission. 1 The Check for Updates is running C:\ProgramData\{7DE921C9-42C8-4DA3-8A44-043C3349FD1D}\Setup.exe /update /showui as can be seen if you run “Check for Updates” directly from the Embarcadero RAD Studio XE program group.

Bob Swart ([email protected])

-2-

February 2011

Delphi XE Starter Essentials

1. Delphi XE Starter IDE

As a workaround, you can start Delphi XE with the “Run as Administrator” option, which will also allow you to make changes to some files that are stored in the C:\Program Files directory. The easiest way to make this change is to located the “Delphi XE” option on the start menu (in the Embarcadero RAD Studio XE group), right-click on it and select option. In the dialog that follows, go to the Compatibility tab and check the “Run this program as an administrator” option. Note that this will cause the User Account Control (UAC) dialog to appear, in order to confirm that you want to allow the following program (Delphi XE) to make changes to this computer, each time you start Delphi XE from now on. Which is OK, so just click on OK, and the error message will be gone at least.

Welcome Page When you start Delphi XE you will first see the so-called Welcome Page. Initially, the Welcome Page shows a list with the recent projects, project groups, and favorites, with the ability to manage favorites, group them in favorite groups even, to quickly allow you to locate and open up the project or project group you need.

However, the main part of the Welcome Page can also be configured to show the recent news from Embarcadero Developer Network and the option to view other resources like RSS feeds. If you select News Read News, you see the Embarcadero Blogs Master Site Feed, but can also pick another NewsFeed to view.

Bob Swart Training & Consultancy

-3-

www.drbob42.com

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

In order to extend the collection of RSS feeds that the CodeGear RAD Studio is using, (and add your own personal favorites) you have to edit the menuRSSFeeds.xml file from \Program Files\Embarcadero\RAD Studio\8.0\Welcomepage\xml\ and just add other RSS feeds to this list. Editing this file can be done with Delphi itself, of course, although you need to have started Delphi with the “Run as Administrator” option in order to be able to save the file (in the \Program Files\... directory tree). Just do File Open, click on Computer (on the left side of the dialog), select your drive and then the Program Files\Embarcadero\RAD Studio\8.0\Welcomepage\xml directory, and then open up the menuRSSFeeds.xml file. Scroll down to line 123 where, as part of the English Community Blogs channel, there’s an entry for my Dr.Bob Examines column, as follows: English Community Blogs … Dr. Bob Swart http://www.drbob42.com/rss.xml You can extend this entry with a link to my more frequently updated Drs.Bob’s Delphi Notes weblog, as follows: Bob Swart - Drs.Bob Examines http://www.drbob42.com/rss.xml Bob Swart - Drs.Bob's Delphi Notes http://www.drbob42.com/weblog.xml And while you’re at it, note that I’m not a real Dr. but only a drs., so I’ve changed that part as well on line 123.

Bob Swart ([email protected])

-4-

February 2011

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

Note that if you didn’t start Delphi XE with the “Run as Administrator” option, you may get the following error when trying to save the menuRSSFeeds.xml file:

In that case, you have to exit Delphi, and restart it using the “Run as Administrator” option, in order to be able to write in the C:\Program Files directory tree. Delphi XE offers the ability to install and start the IDE in multiple languages, and as a result, the xml directory where we’ve just edited the menuRSSFeeds.xml is only the “original root” directory, and contains up to four subdirectories: de (for German), en (for English), fr (for French) and ja (for Japanese). Note that you will only have the directories for the languages that you selected when Delphi XE was installed (by default English only). Depending on the language in which you start the IDE, you will need to make the changes to the menuRSSFeeds.xml file in that particular directory. Using the steps taken earlier, and with the “Run as Administrator” option applied, you can now modify the correct version of the menuRSSFeeds.xml file. This will finally ensure that you can view my standard weblog posts every time you start the Delphi IDE (and there’s no need to restart the IDE for this amended list to have effect by the way – just do a News Clear Feed Cache if it doesn’t show up right away).

Of course, you can also just visit my two Delphi blogs at http://www.bobswart.nl/blog and http://www.drbob42.com/examines manually, or subscribe to the RSS feeds.

Bob Swart Training & Consultancy

-5-

www.drbob42.com

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

Project Targets and Object Repository Delphi XE Starter Edition offers a number of different project types: from regular Console Application, to a Control Panel Applet, a Dynamic Link Library, a Package, an MDI or SDI Application, and the VCL Forms Application. Also supported from the ActiveX category are an ActiveX Library and a Type Library project.

Notably not supported are the Windows Service application and the Resource DLL Wizard. Also missing are DataSnap Server, Multitier, VCL for the Web, WebBroker, WebServices, WebSnap and XML categories. For these functionalities, the Professional or Enterprise editions are required. Also not supported by Delphi XE Starter Edition are the Design Projects, the Profiling or Unit Test support, and the Web Documents.

Bob Swart ([email protected])

-6-

February 2011

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

Options Dialog The Options dialog (do Tools Options to open it) has been enhanced in some places, and the ordering of the pages has changed slightly. The Delphi XE Starter Edition will show only a subset of options compared to the Professional and Enterprise editions. The Options dialog can now be resized (there is a minimum size, however), and you can also move the mouse cursor just to the right of the vertical scrollbar to grab the splitter and resize the treeview. These changes are persistent, so they will be remembered next time you start Delphi. There are a few enhancements to the Debugger Options page in the Tools Options. New is the “Prompt to rebuild projects modified while debugging” option, checked by default (to mimic the “old” behavior). Also new is the option to specify which views to close after debugging.

The option for “Multiple evaluators” in the Embarcadero Debuggers page is new, and is used to specify that we want to use the C++Builder or Delphi evaluator when loading modules written in that environment. Another minor change in the Debugger Options is the way the Native OS Exceptions are organized. No longer in a single list, the Win32 exceptions now appear as child nodes of the Windows OS Exceptions, opening the dialog up for Linux and Mac OS exceptions in later versions of Delphi.

Bob Swart Training & Consultancy

-7-

www.drbob42.com

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

Delphi IDE New Menu Items There are a number of new Delphi IDE menu items. Some have been covered before, and some will be covered in later sections, but I want to mention them all (to help you find the exact menu item in case you cannot find them right away). A minor change in the IDE menu, for example, is the Edit Menu option “Toggle Form/Unit” or F12, which has now been renamed to “Show Code/Designer” to be more clear about the effect.

File

Open From Version Control…

One of the new features of Delphi XE is the support for Subversion – a version control system, now integrated in the IDE. There are a number of places where we can see the Subversion integration, and one of the places is in the File Open menu, with the specific File Open From Version Control… menu item. This will be covered in more detail in the last section of this courseware manual. The other place where you can see the integration with Subversion is in the Project Manager: when you right-click on a project you see the option to add it to Version Control, and once added, a special Subversion menu will appear as well.

Bob Swart ([email protected])

-8-

February 2011

Delphi XE Starter Essentials

View

1. Delphi for Win32 IDE

Configuration Manager

The Configuration Manager used to be part of the Project menu, but can now be found in the View menu.

We can use this dialog to set the active configurations for one or all of the projects, and also view the dependencies (if any) and possible option sets.

The “old” Component Palette Delphi developers who are still used to Delphi 7, can now get the old Component Palette back in place. If you right-click just under the Delphi menu, where the toolbars are located, you can select the “Component” option which will show the old Component Palette. You may want to move the other toolbars around a bit, but the result can be very familiar.

Note that you even get the search box at the right, offering the same functionality as in the new Tool Palette.

Bob Swart Training & Consultancy

-9-

www.drbob42.com

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

Run without Debugging Previous versions of Delphi had a little green arrow button to Run an application with the debugger enabled. However, some people do not want to use the debugger every time they run a Delphi application. The Run menu introduced the Run Run without Debugging option some versions ago, but Delphi XE now also offers two Run buttons: the green triangle means “Run without Debugging” and the green triangle with a red little bug on top of it means “Run (with Debugging)”. This means that people who do not want to use the debugger can use a single button to run the application without debugging now.

Project Options The Project Options dialog, available as Project Options or Project Default Options – Delphi (if no project has been loaded), has been modified and can now also be resized.

There is a very important change in Delphi XE compared to Delphi 2010 and earlier: the Output Directory is now defined (at the “Base” build configuration level) to no longer be the current directory, but to be .\$(Config)\$(Platform) Where $(Config) will be replaced with “Debug” or “Release” or another name of the active build configuration, and $(Platform) will be replaced with Win32. Yes, this means that we may be able to switch to other platforms in later editions of Delphi, just as we can switch from one build configuration to another. The immediate consequence, however, is that new projects compiled with Delphi XE with the Debug build configuration being the default, will produce the executable in the Debug\Win32 subdirectory, and no longer in the same directory as the source code.

Bob Swart ([email protected])

- 10 -

February 2011

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

This can actually be a great help if you ever need to deploy updates of your applications and want to ensure that you ship only Release versions and no Debug versions (just make sure that the timestamp is right and you’re not shipping an outdated Release build). Note that existing project, or .dpr files without .dproj, will have an empty Output directory value, so these projects will still produce the output in the current directory.

Build Configurations Talking about Build Configurations…. the first thing that should be noticed in the screenshot above is the Build Configuration combobox at the top of the dialog, which initially offers a choice between Base, Debug and Release. These are the different Build Configurations we can select. Previously, we could only select Debug and Release, but in Delphi 2009 and later they both inherit from a set of Base configuration options. And we can even create our own custom Build Configurations, as I’ll show in a minute (when covering the Project Manager).

Warnings as Errors Another useful feature – especially when combined with the Build Configuration option – is to mark one or more warnings as errors. If you want all warnings to be an error, then just select “as errors” in the Output Warnings drop-down combobox:

In earlier versions of Delphi, we could disable (or enable) warnings, but with Delphi 2009 and later we have three options for each warning: on, off or error, as the screenshot of the Project Options dialog below shows (where I want to turn the new Delphi XE warning “Unsupported language feature” into a real error):

Bob Swart Training & Consultancy

- 11 -

www.drbob42.com

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

This will turn any occurrence of the warning into an error instead, making it impossible to do a successful build of the project until this particular issue is fixed. In the next sections, I’ll cover some more warnings that you may also want to turn into errors. Personally, I believe that all warnings should be removed from a project, unless there is a very good reason. That’s why I always define a third build configuration, called “STRICT”, derived from DEBUG which has all warnings turned to errors. This will be demonstrated shortly, when we cover the Project Manager in some more detail.

Resource Compiler By default, the Resource Compiler options page shows that Delphi uses the BRCC32 resource compiler (which still shows Inprise when you run it from the command-line) which doesn’t support Unicode characters in resource files, so is not a good idea if you have Unicode characters that you want to use. Microsoft’s RC Resource Compiler contains support for Unicode, as well as Windows Vista (or higher) specific resource types such as icons with alpha channel. You can use the Resource Compiler page to select the resource compiler to use for your projects.

Bob Swart ([email protected])

- 12 -

February 2011

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

The default choice is still Borland Resource Compiler, but you can change that to Microsoft SDK Resource Compiler (if you want that change to be for all projects, then don’t forget to check the “Default” option).

Project Manager The Delphi XE Project Manager has a new look, with the toolbar on top extended with new buttons. The Activate, New and Remove buttons were already present in previous versions, but the Views, Sync, Expand and Collapse buttons are new. In Delphi 2009, these buttons showed their captions by default, but in Delphi 2010 and Delphi XE we only get the buttons themselves.

You can get the labels by right-clicking on the Project Manager toolbar and enabling the “Text labels” option. The Views button can be used to switch the Project Manager from a Directory (nested), to a Directory (flat) or a List view. This can help to organize the contents of the project inside the Project Manager. The Directory (nested) layout is the default view, where each directory is a node that can be expanded or collapsed. The Directory (flat) layout shows the complete path for each file, and finally the List just shows all units without directory nodes.

Bob Swart Training & Consultancy

- 13 -

www.drbob42.com

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

Build Configurations The Build Configurations node for each project can be opened to show the Debug and Release debug configuration options, with the active one selected. As a result, it’s now really easy to switch from one Build Configuration to another, just by double-clicking on the one you want in the Project Manager. This is also the place to add new Build Configurations, like a Strict Build Configuration where all warnings are turned into errors.

STRICT Build Configuration To create a new Build Configuration, we just have to use an existing project (unfortunately, this cannot be done without a project, since the Build Configurations can only be edited for projects). To create a new build configuration, we have to use the Project Manager and open the Build Configurations node of a project. Our new Strict Build Configuration will be derived from the Debug Build Configuration, so we should select the Debug Build Configuration and right-click on it and select “New Configuration”.

This will start with a dialog were we can enter the name of the new Build Configuration:

The new Build Configuration will appear under its parent node in the Project Manager. If you’re not happy about the name, you can always rename it. You can also rename the Debug and Release Build Configurations if you want (but not the “root” Base Build Configuration), and you can delete all Build Configurations if you want – again except for the Base Build Configuration.

Bob Swart ([email protected])

- 14 -

February 2011

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

By default, it will inherit everything from the Debug (parent) Build Configuration, but you can right-click on it to Edit the specific properties for the Strict Build Configuration. This will result in the Project Options dialog, but this time for the Strict specific Build Configuration only (although you can still select another Build Configuration to edit its options). For the Strict Build Configuration, I’d like to set the “Output warnings” option to “as errors”, in order to enforce that all warnings are resolved.

Make sure to click on the Default checkbox in the lower left corner in order to add the Strict Build Configuration to the default project, so it will be available to all future new projects as well from now on. Another potential Build Configuration is the Unicode Build Configuration, where you turn only some warnings into errors - the new string data conversion warnings into errors (especially the one where Unicode data is converted to ANSI String data with potential data loss, which is an error that should always be solved in my view).

Object Repository The Delphi XE Object Repository is enhanced with a description pane in the lower right corner, to explain in a bit more detail what a particular wizard will produce for us. Note that the description is shown for the item that is highlighted (by the mouse over event),and not by the current selected item.

Bob Swart Training & Consultancy

- 15 -

www.drbob42.com

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

Form Designer The form designer has a few enhancements. One of them is regarding the position of the form on the screen. As you know, we cannot drag the form itself around, but we can use the little rectangle in the lower right corner of the form designer to do that for us. First of all, we can click on the rectangle to make it a bit bigger, so it’s easier to drag the white rectangle (representing the form) around on the gray rectangle (representing the screen). Note that this only works if you set the Position property to poDesigner of course, for a value of poDesktopCenter (or any of the “Center” options), the effect of the position preview is ignored. The new feature in Delphi XE is the fact that you get a little tooltip window to display the coordinates of the form. The downside is that you get the tooltip “Form Screen Position” (without the actual position) if you place your mouse over it, and the actual position will only be shown if you change it (i.e. if you drag the form around). So this feature will only give you the required information when you change it…

Edit

Copy

Once an application is created, and opened in the forms designer, another enhanced feature is made available: the Edit Copy menu. This will now put two things in the clipboard: both an image of the form or data module, and a textual DFM representation of the form (or one or more components if you selected these). This is a very useful new feature, especially for authors. As an example, if you have an empty form and place an edit and a button on it, and do Edit Copy, then the clipboard will contain both the image of the form with the edit and button (ready to be pasted inside Word for example).

Apart from this bitmap, the clipboard will also contain the Unicode as well as plain text representation of the DFM structure for the entire form or just the selected components. Which can also be pasted in Word as illustration for the image of the form. object Edit1: TEdit Left = 120 Top = 26 Width = 121 Height = 21 TabOrder = 1 Text = 'Edit1' end Especially when only one or two components of a full form are relevant (to show their DFM contents), this becomes very handy to use.

Bob Swart ([email protected])

- 16 -

February 2011

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

Quality Central Let’s talk about bugs and quality… I’m sure you’ve encountered a bug in Delphi once or twice or a few times in your development life. What did you do with it? Be unhappy because it wasn’t fixed in the next release? Tell it to your friends and co-workers? Or did you report the bug back to the Delphi Team, in the hope that they could fix it? The later is the best option, and the Delphi XE IDE contains an ideal way to report bugs, issues and even feature requests quick and easy. By the way, did you know that Delphi XE contains almost 1000 bug-fixes? You can get an overview of the 977 fixes at http://edn.embarcadero.com/article/40817 or in the file http://edn.embarcadero.com/article/images/40817/Delphi_XE_and_CBuilder_XE_QC_Bu g_Fix_List.pdf See http://blogs.embarcadero.com/chrispattinson/2010/08/27/38971 for the blog post of Chris Pattinson, the Director of Quality Assurance at Embarcadero, who writes about Quality Reviews and Metrics (and make sure to read the section on Modeling for the QA Audits and Metrics that are included in Delphi XE itself as well). The tool to report bugs is called Quality Central, and you can get to it from the Delphi IDE itself. Just start Delphi, and use the Tools Quality Central menu option to start the Quality Central Windows Client (note that you have to login first using your EDN account details). Using the Quality Central Windows Client, you can search for bugs (to see if other people have maybe reported the same issue, and if a workaround may exist), or you can report new bugs yourself. You can also check the progress of any bugs that you have reported yourself. In my case, when I run the Quality Central Windows Client, I can search for all bugs that I’ve reported, and view their status.

Bob Swart Training & Consultancy

- 17 -

www.drbob42.com

Delphi XE Starter Essentials

1. Delphi for Win32 IDE

An interesting, but not fatal, issue is QC #88020, which is about the Delphi XE Search Path in the Project Options dialog. This search path contains the same path twice. $(BDSLIB)\$(Platform)\release;$(BDSUSERDIR)\Imports;$(BDS)\Imports;$(BDSC OMMONDIR)\Dcp;$(BDS)\include;C:\Program Files\Raize\CS4\Lib\RSXE;;$(BDS)\RaveReports\Lib;$(BDSLIB)\$(Platform)\release;$(BDSUSERDIR)\Im ports;$(BDS)\Imports;$(BDSCOMMONDIR)\Dcp;$(BDS)\include;C:\Program Files\Raize\CS4\Lib\RS-XE;;$(BDS)\RaveReports\Lib This is the case on multiple machines (Win7 as well as XP), so it's probably not something I did but a real bug. When you click on the ellipsis to edit the paths, no duplicates show up. But if you use the tooltip or just copy the contents, it contains the duplicates. It’s not a big issue, and can be resolved easily (it won’t happen if you manually add a custom path to the list for example), but still it has been promoted to the internal RAID database and I assume it will be fixed in a future update. The bottom line is this: if you report a bug or issue, then there is a chance it will be looked at and fixed. If you don’t report a bug, that chance is much lower. At least let your reseller know about a bug if you’re not willing to report it yourself (I don’t mind reporting bugs, but make sure you have an example that makes it easy to reproduce). Note that if Delphi itself doesn’t start, and you want to report that, you can also start the Quality Central Windows Client by using the QualityCentral.exe from the QualityCentral directory in the C:\Program Files\Embarcadero\RAD Studio\8.0 directory. Or start the QCClientDownload.exe first, which will check to see if there is a new version of the client available to download. Note that for installation or registration issues, you could also contact your local reseller or Embarcadero Support at http://www.embarcadero.com/support

Summary In this section, I’ve given a brief tour through the Delphi XE IDE and the new features in this new release of Delphi XE. I’ve also discussed some of the debugger enhancements, including the new debugger visualizers.

Exercises The following exercises are optional, but can be used to practice the material of this first section. In order to make the exercises, you will need a machine with Delphi XE installed and configured.

Exercise #1 Create a custom Build Configuration called UNICODE and set the options to your favorite settings, but include the fact that all Unicode string conversions with potential data loss are turned into errors.

Exercise #2 Start the Quality Central Windows Client from the IDE, and search for all error reports and feature requests made by “Bob Swart” for Delphi or Delphi Prism. Feel free to vote for certain error reports, and don’t forget to add your own QC report when you find a bug (or contact your local reseller – or me - to verify the issue and/or report it for you).

Bob Swart ([email protected])

- 18 -

February 2011

Delphi XE Starter Essentials

2. Language Enhancements

2. Language Enhancements In this section, we'll cover a number of the Delphi language enhancements for Win32 (compared to Delphi 2007) including a few compiler enhancements, generics, anonymous methods and the new exit construct.

Delphi XE Compiler and Language Enhancements Delphi XE did not really introduce a lot of new compiler and language features, although the RTL and VCL source code were made “ready” to support Linux as well as Mac OS X and 64-bits Windows next to the current 32-bits Windows target. Evidence for this can be found in several places in the RTL and VCL source code. One of the notable things is the restructure of the VCL source code. We used to have the source\Win32 directory structure, but now there is no mention of Win32 in the source directories. There are also a few new compiler defines. For example in the unit AnsiStrings.pas, we can see the following code: {$IFDEF POSIX} {$WARN IMPLICIT_STRING_CAST OFF} {$WARN IMPLICIT_STRING_CAST_LOSS OFF} {$ENDIF} uses SysUtils, StrUtils, {$IFDEF MSWINDOWS} Windows, {$ENDIF} {$IFDEF POSIX} PosixString, {$ENDIF} SysConst; So apart from MSWINDOWS (and no longer specific only WIN32) we have a POSIX compiler define as well. Later in the same unit, we can see a MACOS and LINUX define as well, in the implementation of the AnsiCompareFileName function for example: function AnsiCompareFileName(const S1, S2: AnsiString): Integer; {$IFDEF MSWINDOWS} begin Result := CompareStr(AnsiLowerCase(S1), AnsiLowerCase(S2)); end; {$ENDIF MSWINDOWS} {$IFDEF MACOS} var Buf1, Buf2: array[0..NAME_MAX + 1] of AnsiChar; begin if StringToFileSystemString(AnsiLowerCase(S1), Buf1) and StringToFileSystemString(AnsiLowerCase(S2), Buf2) then Result := strcoll_l(Buf1, Buf2, UTF8CompareLocale) else Result := -1; end; {$ENDIF MACOS} {$IFDEF LINUX} begin Result := AnsiCompareStr(S1, S2); end; {$ENDIF LINUX}

Bob Swart Training & Consultancy

- 19 -

www.drbob42.com

Delphi XE Starter Essentials

2. Language Enhancements

To continue on the cross-platform theme, we can now find a TimeStamp field inside the TSearchRec structure, containing a TDateTime value that we can use to make it easier to use FindFirst and FindNext on whatever platform we want. The old field “Time” is now marked as deprecated, and we should use TimeStamp instead. type TSearchRec = record private function GetTimeStamp: TDateTime; public {$IFDEF MSWINDOWS} Time: Integer platform deprecated; {$ENDIF MSWINDOWS} {$IFDEF POSIX} Time: time_t platform; {$ENDIF POSIX} Size: Int64; Attr: Integer; Name: TFileName; ExcludeAttr: Integer; {$IFDEF MSWINDOWS} FindHandle: THandle platform; FindData: TWin32FindData platform; {$ENDIF MSWINDOWS} {$IFDEF POSIX} Mode: mode_t platform; FindHandle: Pointer platform; PathOnly: string platform; Pattern: string platform; {$ENDIF POSIX} property TimeStamp: TDateTime read GetTimeStamp; end; Obviously, the implementation of the GetTimeStamp function is also using a lot of IFDEFs to make it transparent for us. I leave it as exercises for the reader to check out the implementation of TSearchRec.GetTimeStamp in the unit SysUtils.pas Other changes in Delphi XE include the value of the $STRINGCHECKS compiler directive (see also the Unicode section for more details). The $STRINGCHECKS directive is now ignored by the Delphi compiler, and treated as if it’s in {$STRINGCHECKS OFF} mode. The {$ALIGN} compiler directive is extended with an option for 16-byte alignment, so we can now use {$ALIGN 1}, {$ALIGN 2}, {$ALIGN 4},{$ALIGN 8},and {$ALIGN 16}. There is also a new {$CODEALIGN} compiler directive now, for the alignment of code inside a procedure, function or method. The possible code alignment values are 0, 1, 2, 4, 8, and 16.

Delphi 2010 Enhancements Delphi 2010 introduces a number of new features, including custom attributes, enhanced RTTI, scoped enumerators, new behavior for the IS and AS keywords, class constructors and destructors, and the new delayed keyword for loading DLLs. The Delphi RTL also has been enhanced by including FastCode routines to replace the original slower counterparts. This means that Win32 applications can benefit from increased performance by simply recompiling them with Delphi 2010, although executables are generally also larger when compiled with Delphi 2010 (compared to Delphi 2009 or earlier).

Bob Swart ([email protected])

- 20 -

February 2011

Delphi XE Starter Essentials

2. Language Enhancements

Custom Attributes We can define custom attributes now, derived from TCustomAttribute. The attributes are classes, and can be used to “decorate” other classes using the [ ] syntax also known from the .NET Framework. As an example, here’s a DocAttribute class (note that we generally do not use the “T”-prefix when defining attributes), which contains a property URL to define additional information we can use on classes, methods and parameters. unit uAtributes; interface type DocAttribute = class(TCustomAttribute) private FURL: string; public constructor Create(const URL: string); property URL: string read FURL write FURL; end; implementation constructor DocAttribute.Create(const URL: string); begin FURL := URL end; The syntax of using attributes is similar to that in the .NET Framework, using []. So, for an example class and method, we can define the attribute as follows: unit uClasses; interface uses uAttributes; type [Doc('http://www.drbob42.com/Delphi2010')] TMyDocumentedClass = class public [DocAttribute('http://www.bobswart.nl/')] procedure HelloWorld; end; implementation { TMyDocumentedClass } procedure TMyDocumentedClass.HelloWorld; begin // implementation doesn’t matter for this example end; end. Note that in Delphi XE, if you do not have the unit in the uses clause that defines the custom attribute, or if you make a typing mistake in the name of the custom attribute, then the compiler will not give you an error message, but rather a warning to tell you that it encountered an unsupported language feature. Ignoring the attribute!! This a the reason why it may be a good idea to turn the specific “Unsupported Language Feature” warning into an error, see page 19 for more details.

Bob Swart Training & Consultancy

- 21 -

www.drbob42.com

Delphi XE Starter Essentials

2. Language Enhancements

Enhanced RTTI RTTI is enhanced in Delphi 2010, and responsible for some of the size increase of executables compiled with Delphi 2010. More information about the enhanced RTTI can be found in the Rtti.pas unit from the RAD Studio\8.0\source\Win32\rtl\common directory. One of the things we can do with the new RTTI functionality is to get a list of “known” (linked-in) types, and enumerate these. The starting point of all new RTTI operations is the TRttiContext, a class defined in the Rtti unit. The TRttiContext has a number of useful functions, such as the GetTypes function. As first example, we can produce the list of known classes in a VCL application, by calling the GetTypes method and placing the resulting types in a Listbox: procedure TFormWorkshop.btnRTTIClick(Sender: TObject); var ctx: TRttiContext; T: TRttiType; begin ListBox1.Clear; ListBox1.Sorted := True; ctx := TRttiContext.Create; try for T in ctx.GetTypes do ListBox1.Items.Add(T.ToString); finally ctx.Free end end; We get an impressive list of classes, including a lot that we do not explicitly use, but which are registered or used in initialization sections of units for example. It can be very insightful to view the list of classes that are already used (and linked in) with an almost empty VCL forms application. One interesting observation can also be that the class TMyDocumentedClass does not appear in this list. Not even if we add the uAttributes and uClasses units to the uses clause. The “problem” is that the smart linker will ensure that the TMyDocumentedClass – since it’s not actually used – will not be part of the executable. One way to “trick” the smart linker, is to create an instance (and free it right away), so the type TMyDocumentedClass is needed: TMyDocumentedClass.Create.Free; Apart from class types, we can also get list of attributes, and see if any attributes are used by the types in the RTL and VCL: procedure TFormWorkshop.btnRTTIClick(Sender: TObject); var ctx: TRttiContext; T: TRttiType; A: TCustomAttribute; begin Memo1.Clear; ctx := TRttiContext.Create; try TMyDocumentedClass.Create.Free; for T in ctx.GetTypes do for A in T.GetAttributes do Memo1.Lines.Add('Type: ' + T.ToString + ' attribute: ' + A.ToString) finally ctx.Free end end; It appears that there are no attributes used in the RTL or VCL types just yet (at least none that I could find), although they are now used for DataSnap developers.

Bob Swart ([email protected])

- 22 -

February 2011

Delphi XE Starter Essentials

2. Language Enhancements

We can perform a similar check, and get a list of all methods for the known types, including the custom attributes, as follows: procedure TFormWorkshop.btnRTTIClick(Sender: TObject); var ctx: TRttiContext; T: TRttiType; A,MA: TCustomAttribute; M: TRttiMethod; begin ListBox1.Clear; ListBox1.Sorted := True; Memo1.Clear; ctx := TRttiContext.Create; try for T in ctx.GetTypes do ListBox1.Items.Add(T.ToString); TMyDocumentedClass.Create.Free; // use my type… for T in ctx.GetTypes do begin for A in T.GetAttributes do begin Memo1.Lines.Add('Type: ' + T.ToString + ' attribute: ' + A.ToString); for M in T.GetMethods do begin Memo1.Lines.Add(''); Memo1.Lines.Add('Type: ' + T.ToString + ' method: ' + M.ToString); Memo1.Lines.Add('from parent class: ' + M.Parent.ToString); for MA in M.GetAttributes do Memo1.Lines.Add('Method: ' + M.ToString + ' attribute: ' + MA.ToString) end end end finally ctx.Free end end; Note that all these examples also nicely demonstrate the for-in loop in a useful way.

Scoped Enumerators Enums can currently be used with or without scope. With the compiler directive {$SCOPEDENUMS ON} we can enforce the usage of the scope. procedure TFormWorkshop.btnEnumClick(Sender: TObject); {$SCOPEDENUMS ON} type TMyEnum = (een, twee, drie, vier); var E: TMyEnum; begin E := een; // this will no longer compile with SCOPEDENUMS ON E := TMyEnum.een; end; Note that defining {$SCOPEDENUMS ON} may break existing code, as statements that use enumerators without the scope prefix will then no longer compile!

Bob Swart Training & Consultancy

- 23 -

www.drbob42.com

Delphi XE Starter Essentials

2. Language Enhancements

IS and AS keywords We can use IS and AS to turn an interface back into the original class instance. As an example, we can define an interface IMyInterface and a class TMyClass that implements this interface. type IMyInterface = interface function HelloWorld: String; end; TMyClass = class(TInterfacedObject, IMyInterface) public X: String; function HelloWorld: string; end; function TMyClass.HelloWorld: string; begin Result := 'Hallo ' + X; end; We can then add a procedure that takes an interface as argument, and changes a public field from the class that implements this interface. procedure ChangeComponentBehindInterface(Intf: IMyInterface; NewX: String); begin (Intf as TMyClass).X := NewX end; Using this method, and demonstrating that we actually modify the underlying class that implements the interface, is as follows: var A: IMyInterface; begin A := TMyClass.Create; // A.X := 'start'; ChangeComponentBehindInterface(A, 'Bob'); writeln(A.HelloWorld); end. It can be very helpful in practice to get the implementing class from an interface, for example in the SOAP world.

Class Constructors & Destructors Previous versions of Delphi already introduced class methods – methods that can be called without the need to construct an instance of the class itself. Delphi 2010 now also introduces class constructors and destructors. We can use class constructors and destructors to create for example a singleton class. Based on an idea by Allen Bauer. unit SingletonUnit; // http://blogs.embarcadero.com/abauer/2009/05/29/38888 interface

Bob Swart ([email protected])

- 24 -

February 2011

Delphi XE Starter Essentials

2. Language Enhancements

type TSingleton = class private class var FSingle: TSingleton; class constructor Create; class destructor Destroy; public procedure DoSomething; class property Instance: TSingleton read FSingle; end; implementation class constructor TSingleton.Create; begin FSingle := TSingleton.Create; end; class destructor TSingleton.Destroy; begin FSingle.Free; end; procedure TSingleton.DoSomething; begin end; // usage: TSingleton.Instance.DoSomething end.

Delayed Dynamic Link Libraries Traditionally, Dynamic Link Libraries (DLLs) can be loaded in two different ways: implicit or explicit. However, Delphi 2010 introduced a new way: the delayed loading of DLLs, which offers the best of both worlds and more, as we’ll see.

Example DLL The example DLL should be as simple as possible, yet introducing another nice feature that perhaps not all of the readers may know: the concept of function overloading in DLLs, and how to export overloaded functions correctly. The example I’m going to use here is a DLL with two “Add” methods, one for adding integers, and one for adding doubles. Since the function names are the same, we must decorate them with the overload keyword. In order to export them, we must make sure each function is exported with a unique name, so in case of the Add for doubles, I’ll export it by name “AddDouble”. The source code for the library eBob42 is as follows: library eBob42; function Add(X,Y: Integer): Integer; overload; stdcall; begin Result := X + Y end; function Add(X,Y: Double): Double; overload; stdcall; begin Result := X + Y end;

Bob Swart Training & Consultancy

- 25 -

www.drbob42.com

Delphi XE Starter Essentials

2. Language Enhancements

exports Add(X,Y: Integer) name 'Add', Add (X,Y: Double) name 'AddDouble'; end. When compiling this source code, we’ll end up with a eBob42.DLL that we can import and use in different ways: implicit, explicit or with the new delayed technique, offered by Delphi 2010 and Delphi XE.

Implicit The implicit import of functions from DLLs is the easiest way to use a DLL. All you have to do is repeat the function definition, including the calling convention (plus overload when needed), and add the “external” keyword plus the name of the DLL where the function can be found. For an overloaded function, we should also include the name keyword again, followed by the correct name under which the function was exported. All in all, not very complex, and for the eBob42.DLL, the Implicit import unit could be implemented as follows: unit eBob42Implicit; interface const DLL = 'eBob42.DLL'; function Add(X,Y: Integer): Integer; overload; stdcall external DLL; function Add(X,Y: Double): Double; overload; stdcall external DLL name 'AddDouble'; implementation end. The biggest disadvantage of using the implicit import of DLLs technique is the fact that you’ll get an error message when trying to load an application that requires a DLL, and that DLL cannot be found. In that situation, the application will be unable to start, so the error message is a fatal one, and without the DLL, the application itself is useless.

This is a bad situation, because the application cannot be started as long as the eBob42.DLL is missing.

Bob Swart ([email protected])

- 26 -

February 2011

Delphi XE Starter Essentials

2. Language Enhancements

Explicit The main alternative for implicit import is the explicit loading and import of functions from a DLL. This takes more code, but it allows us to give a nice error message when the DLL cannot be loaded and/or when a function from the DLL cannot be found, without keeping the application itself from running. So even without the DLL being present, the application can be used (albeit without the functionality from the DLL). An example of an explicit import unit, where we explicitly need to load the DLL using LoadLibrary and get a handle to the functions using GetProcAddress, is as follows: unit eBob42Explicit; interface const DLL = 'eBob42.DLL'; var Add: function(X,Y: Integer): Integer; stdcall = nil; AddDouble: function(X,Y: Double): Double; stdcall = nil; implementation uses Windows; var eBob42Handle: Cardinal; initialization eBob42Handle := LoadLibrary(DLL); if eBob42Handle = 1000000 then if (value mod 1000000) = 0 then Result := Num2Word(value div 1000000) + 'miljoen' else Result := Num2Word(value div 1000000) + 'miljoen ' + Num2Word(value mod 1000000) else if value >= 1000 then if (value mod 1000) = 0 then Result := Num2Word(value div 1000) + 'duizend' else Result := Num2Word(value div 1000) + 'duizend ' + Num2Word(value mod 1000) else if value >= 100 then if (value mod 100) = 0 then Result := Num2Word(value div 100) + 'honderd' else Result := Num2Word(value div 100) + 'honderd' + Num2Word(value mod 100) else case (value div 10) of 5,6,7,9: if (value mod 10) = 0 then Result := Num2Word(value div 10) + 'tig' else Result := Num2Word(value mod 10) + 'en' + Num2Word(value div 10) + 'tig'; 8: if value = 80 then Result := 'tachtig' else Result := Num2Word(value mod 10) + 'entachtig'; 4: if value = 40 then Result := 'veertig' else Result := Num2Word(value mod 10) + 'enveertig'; 3: if value = 30 then Result := 'dertig' else Result := Num2Word(value mod 10) + 'endertig'; 2: if value = 20 then Result := 'twintig' else Result := Num2Word(value mod 10) + 'entwintig'; 0,1: case value of 0: Result := 'nul'; 1: Result := 'een'; 2: Result := 'twee'; 3: Result := 'drie'; 4: Result := 'vier'; 5: Result := 'vijf'; 6: Result := 'zes'; 7: Result := 'zeven'; 8: Result := 'acht'; 9: Result := 'negen'; 10: Result := 'tien';

Bob Swart Training & Consultancy

- 97 -

www.drbob42.com

Delphi XE Starter Essentials

11: 12: 13: 14: 15: 16: 17: 18: 19: end end

Result Result Result Result Result Result Result Result Result

3. RTL and VCL Enhancements

:= := := := := := := := :=

'elf'; 'twaalf'; 'dertien'; 'veertien'; 'vijftien'; 'zestien'; 'zeventien'; 'achttien'; 'negentien'

end; We can compile the NumberToWords project, resulting in a NumberToWords.ocx file which we can register using the Run ActiveX Server – Register or Run ActiveX Server – Register for Current User option from the IDE.

The last option will obviously only register the ActiveX Server for the Current User (which also requires less permissions from Windows). Then, create a client application, add the AutoXE_TLB.pas unit to the uses clause, and write the following code to test the Num2Word function from the Number2Words interface in the AutoXE automation object: procedure TForm6.Button3Click(Sender: TObject); var Number2Words: INumber2Words; begin Number2Words := CoNumber2Words.Create; ShowMessage(Number2Words.Num2Word(42)) end; This is in fact no different from earlier implementations of COM and Automation Objects in Delphi.

Importing Type Libraries when you open an older project the upgrade process will automatically convert the .tlb to a .ridl file. If for some reason that process fails or you want to do it by hand, you can also call the tlibimp command-line tool to convert a .tlb to a .ridl. Make sure to use the Delphi XE tlibimp.exe from the RAD Studio\8.0\bin directory, and not an old version from Delphi 2007 for example (if the RAD Studio\5.0\bin directory comes first in the PATH).

Bob Swart ([email protected])

- 98 -

February 2011

Delphi XE Starter Essentials

3. RTL and VCL Enhancements

We can call tlibimp with the –I command-line parameter in order to produce a .ridl file for a .tlb file, for example: tlibimp –I stdvcl40.tlb Which will produce a StdVCL.ridl file (just as an example). With the RIDL system the _TLB.pas file won't be regenerated each and every time you do something. What currently happens is the _TLB.pas file is updated whenever it is older than the .ridl file, just like any other build system would do with a .dcu or .obj.

Summary In this section, I’ve covered the RTL and VCL enhancements – new as well as enhanced components and classes have been covered. We’ve also covered the new Touch and Gesture functionality, plus the COM and ActiveX enhancements in Delphi 2009 –found in the icons as well as the wizard, but more notably in the .ridl files that now contain the main source for the definitions.

Exercises The following exercises are optional, but can be used to practice the material of this first section. In order to make the exercises, you will need a machine with Delphi XE installed and configured.

Exercise #1 The initial interface ITaskbarList can be used to add, delete, and activate tabs in the taskbar. ITaskbarList = interface(IUnknown) [SID_ITaskbarList] function HrInit: HRESULT; stdcall; function AddTab(hwnd: HWND): HRESULT; stdcall; function DeleteTab(hwnd: HWND): HRESULT; stdcall; function ActivateTab(hwnd: HWND): HRESULT; stdcall; function SetActiveAlt(hwnd: HWND): HRESULT; stdcall; end; Experiment with the ActivateTab and SetActiveAlt methods (who only activate the tab on the taskbar, and not the window itself, by the way).

Exercise #2 The final interface, ITaskbarList4, can be used to set some tab options. type STPFLAG = Integer; const STPF_NONE STPF_USEAPPTHUMBNAILALWAYS STPF_USEAPPTHUMBNAILWHENACTIVE STPF_USEAPPPEEKALWAYS STPF_USEAPPPEEKWHENACTIVE

= = = = =

0; $1; $2; $4; $8;

type ITaskbarList4 = interface(ITaskbarList3) [SID_ITaskbarList4] function SetTabProperties(hwndTab: HWND; stpFlags: STPFLAG): HRESULT; stdcall; end; Experiment with these options, and figure out what their effect is.

Bob Swart Training & Consultancy

- 99 -

www.drbob42.com

Delphi XE Starter Essentials

3. RTL and VCL Enhancements

Exercise #3 Delphi 2010 and XE contain a special TTouchKeyboard component. One which shows the keyboard layout for the current locale. Experiment with the TTouchKeyboard and use it in numerical layout.

Exercise #4 Manipulate the TTouchKeyboard and reconfigure some of the keys. Use Chris Bensen’s blog at http://chrisbensen.blogspot.com/2009/12/hacking-ttouchkeyboard-part-i.html As example exercises, first remove all keys from the numerical layout except for the digits and the dot. (i.e. remove the left arrow, the slash, multiply, minus, plus and return keys). As second exercise, modify the virtual keyboard in Standard layout by removing the “Fn” key, and changing the second Alt key (at the right of the spacebar) into a € character that will insert the € character into the input stream.

Bob Swart ([email protected])

- 100 -

February 2011

Delphi XE Starter Essentials

4. Unicode

4. Unicode Delphi 2009 has full Unicode support, from the IDE, RTL to the VCL. In this section, I will explain what Unicode is, and how we can see traces of Unicode in our existing Delphi applications.

Unicode Unicode is a standard for the definition and identification of characters and symbols in all written languages, by assigning a unique value to each character or symbol.. The Unicode Consortium defines which number (code point) represents which character or symbol. In 1991 we got Unicode version 1.0.0, which was extended to 1.0.1 and 1.1 according to the ISO-10646 standard. Originally, Unicode 1.1 was limited to 64K characters and symbols, which meant 2 bytes were enough to encode all characters. Unfortunately, the 64K was also insufficient to support all written languages in the world. As a result, Unicode 2.0, from June 1996, extended the number of code points to $10FFFD characters and symbols (1.114.109 to be exact). At the time of writing, Unicode 5.0 is current, for which the Unicode Consortium has already defined 101.203 characters in the standard (so we’ve got space for another 900.000 characters or symbols).

Unicode Transformation Formats Unicode data (the code point values) can be presented in different formats, such as UTF8, UTF-16 or UTF-32. We can also optionally compress Unicode data. UTF stands for Unicode Transformation Format, and each UTF defines the mapping between a code point to a unique series of bytes that represent this code point. So where Unicode itself defines the character or symbol that belongs to a code point, the UTF defines the physical representation (in file on disk or in memory for example).

UTF-8 Using UTF-8 we get between 1 and 4 bytes for each Unicode character. This is an encoding where we never know in advance how much (storage) bytes are needed to contain a string. Although we can predict the minimum number of bytes: which is the same as the number of characters for a 7-bit ASCII data stream. The standard 7-bits ASCII characters are the same in UTF-8, which means there is a great level of compatibility between ‘normal’ characters. Apart from these standard ASCII characters, UTF-8 supports all 1 million Unicode characters using a UTF-8 specific coding. UTF-8 is mainly used on the internet for web pages for example (since it produces smaller files compared to the UTF-16 and UTF-32 formats).

UTF-16 Using UTF-16, we get 2 or 4 bytes for each Unicode character. This encoding is easier and faster to process than UTF-8, and compatible with UCS2 (the original 2-byte Unicode 1.x 2-byte character encoding), which uses 2 bytes for each character, but is not enough for the full Unicode 2.x character set. Using UTF-16, the value ranges from $D800-DBFF and $DC00-DCFF are used to specify so-called surrogate pairs. Using these surrogate pairs, we can map Unicode code points of $10000 and higher (in the range $10000 to $10FFFD). This is done by subtracting $10000 from the value, leaving a value in the range 0 to $FFFFD, which can be represented in 20 bits. These 20 bits are split in two pairs of 10 bits each, added to the $D800 resp. $DC00 pairs. So for the Unicode code point $1D11E the UTF-16 surrogate pair is calculated as follows: first subtract $10000, which leaves $D11E, which is 00001101000100011110 in 20 bits, split in $34 and $11E. $34 is added to $D800, and $11E is added to $DC00 resulting in $D834 for the most significant surrogate, and $DD1E for the least significant surrogate. We’ll get back to this special character example in a minute. Note that the Unicode code points $D800 to $DFFD will not be assigned a valid character by the Unicode standard (to avoid problems with UTF-16), so the individual surrogate

Bob Swart Training & Consultancy

- 101 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

characters are never mapped to actual characters themselves (but should always be used as a pair). The main disadvantage of UTF-16 is the fact that the ‘normal’ characters are also represented by 2 bytes instead of 1 byte (like with UTF-8), so any ANSI String will be at least twice as big (in storage space) using UTF-16. Which is a bit of a waste for source code and data in database that doesn’t use many special characters. The UTF-16 format is used by Windows and Java, and is the default format used by Delphi 2009.

UTF-32 Using UTF-32 we always get exactly 4 bytes for each Unicode character. This is the easiest encoding, but also the one resulting in the largest storage space – four times as big as before for the standard 7-bit ASCII characters. The biggest advantage is the fact that storage space is increased by a factor four compared to ANSI data. UTF-32 is mainly used in the UNIX world.

Windows API Since Windows NT and 2000, the Windows API supports two sets of APIs: one for ANSI (A) and one for Unicode characters (with W for Wide, using UTF-16). It’s important to realize that Windows 95, 98 or ME do not support Unicode. And as a result, Delphi 2009 is not able to produce applications for Windows 95, 98 or ME (so it’s really time to move to at least Windows 2000 or XP). Delphi up to version 2007 is still using the ANSI version of the Windows API for the Win32 personality. The .NET side is different, since the .NET Framework itself supports Unicode (and so does Delphi for .NET). This has in fact been a big help, since the VCL for .NET has been Unicode enabled and made sure the entire VCL was prepared for Unicode already (before the work on Delphi 2009 was started).

Notepad In order to produce some sample Unicode text files, we can use Notepad. The File Save As dialog gives us the option to generate and save ANSI, UTF-8, and/or UTF-16 files. The UTF-16 files are usually about twice as big as the ANSI files, and UTF-8 will be in the middle (closer to ANSI if you don’t use too many special characters). Delphi Source Files It may be a little know fact that apart from using Notepad, we can also use Delphi 2007 for Win32 (I didn’t try any older versions of Delphi) to produce Unicode source files. The Code Editor defaults to saving files in the ANSI format, but also offers the option to saving them in UTF-8 format, as soon as we place any Unicode characters inside. As an example, I’ve created some sample Unicode strings in a Delphi unit, as follows:

These strings can be used in the Wide Windows API functions, like MessageBoxW for example. MessageBoxW(HWnd(0), WStr2, IJ, MB_OK);

Unicode and Fonts Not all Windows fonts support the special Unicode characters, which is especially something to keep in mind when migrating legacy applications from ANSI to Unicode Strings. When a Unicode character is not supported by the font being used, you'll get a . Note that this is different from a ? that you will get when square instead, like converting a Unicode character to an ANSI character (in which case information has been lost).

Bob Swart ([email protected])

- 102 -

February 2011

Delphi XE Starter Essentials

4. Unicode

A square does not indicate lost information, but merely a missing font glyph for the specified code point. The latter can be solved by selecting a font which supports the required Unicode code points. Klingon (U+FB80-F8FF) alphabetical glyphs are also part of Unicode, and contain 26 characters and 10 digits. See http://www.evertype.com/standards/csur/klingon.html for more details. Klingon characters take only 2 bytes in UTF-16, and require no surrogate bytes! Code2000 is an example of a shareware font which supports Unicode characters for Klingon. See http://www.code2000.net/code2000_page.htm for more information on the Code2000 font. Code2001 is a freeware font that supports plane 1 of the Unicode specification. See http://www.code2000.net/code2001.htm for more information and the free download archive for this font (I’ll use it in some of the examples later).

Delphi 2009+ and Unicode Where previous versions of Delphi used a String type based on ANSI Character types of only 1 byte long, Delphi 2009 introduced a new string type based on Unicode data, with WideChar elements of 2 bytes long. Delphi 2009 and later are fully Unicode based, and define a new type called UnicodeString which is the new equivalent for the String type. Previously, String was synonymous with AnsiString (a type which is also still available, just like AnsiChar and PAnsiChar). Delphi 2009-XE Character types are Char, AnsiChar and WideChar, where Char defaults to WideChar. In previous versions of Delphi, a Char would be equivalent to an AnsiChar. In order to ensure existing code compiler without changes in behavior, change Char to AnsiChar (as well as PChar to PAnsiChar). The most important Delphi 2009, 2010 and Delphi XE String types are: UnicodeString, WideString, AnsiString, UTF8String (a AnsiString with UTF-8 encoding) and ShortString. The default String type is equivalent to UnicodeString, which consists of WideChar characters (like WideString), but is reference counted and memory managed by Delphi (instead of by Windows itself), so a lot faster than a WideString3. Type AnsiString UTF8String String (= UnicodeString) WideString

Element AnsiChar AnsiChar Char (= WideChar) WideChar

Elem. Size 1 byte 1 byte 2 bytes 2 bytes

Misc Ref Counted Ref Counted Ref Counted

The default encoding of the [Unicode]String in Delphi is UTF-16, which is also the default for Windows itself.

AnsiString Encoding Where a Unicode string is by default encoded as UTF-16, an AnsiString can also be encoded according to a so-called code page. Delphi 2009 has extended the definition for AnsiStrings to allow us to define strings with an affinity for a given codepage by using brackets: type CyrillicString = type Ansistring(1251); // ANSI-Cyrillic WesternEuropeString = type Ansistring(1252); // Western Europe Latin-1

3 The difference is almost two orders of magnitude: string concatenation is about 85 times as fast using String or UnicodeString compared to using WideString.

Bob Swart Training & Consultancy

- 103 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

There is a special codepage 65001 which produces an AnsiString with the UTF-8 encoding, also called the UTF8String. type UTF8String = type Ansistring(65001); // UTF-8 This is a very powerful string type, as will be demonstrated in more detail when writing to Text files or the console output.

String Assignments and Conversions We can assign a UnicodeString to a WideString and a UTF8String (which is an AnsiString using UTF-8 Encoding, and hence also contains Unicode data) without losing information. We can even assign data from an AnsiString to a UnicodeString, no matter what the original codepage from the AnsiString is. But if you assign a UnicodeString back to an AnsiString, you may lose data in the conversion (special Unicode characters or code points may be unavailable in the ANSI codepage and returned as ? characters). That’s why the compiler contains four new warning messages: two for implicit or explicit conversions from AnsiString to UnicodeString, which can be ignored. var A: AnsiString; S: String; U: UTF8String; begin A := 'This is a simple ANSI String'; S := A; // Implicit cast U := A; // Implicit cast (2x) end; Note that the assignment from A to U will actually result in two warnings: first you’ll get a warning about an implicit cast from AnsiString to string, and then another warning from the implicit cast from string to UTF8String. So the compiler is doing two conversions here: first from AnsiString to String, and then from String to UTF8String. In no situation should this lead to data loss, so the warning may be ignored in my view. Two other warnings concern the implicit or explicit conversion from a Unicode string to an AnsiString, which is likely to lose data, and an important warning (which can be turned into an error, as I’ve demonstrated in the first section of this courseware manual, and which can be seen in the Code Editor screenshot fragment below).

The assignment from String to UTF8String will have no data loss (just using a little less space using UTF8), but the assignment to AnsiString will have lost all Chinese characters, returning the default ? character for these. The length of the String as UTF-16 Unicode String is 24: 17 normal WideChar elements and 7 Chinese elements. The UTF8 representation of the string is using 38 elements, i.e. 38 bytes (so shorter than the 24 WideChar items that take 48 bytes). The 38 elements come from 17 normal AnsiChar elements and 7 Chinese characters that are represented using UTF-8 as 3 bytes each, resulting in 21 bytes for the Chinese characters or 38 bytes in total.

Bob Swart ([email protected])

- 104 -

February 2011

Delphi XE Starter Essentials

4. Unicode

UTF-16 Surrogate Characters Most UTF-16 characters are simple WideChar characters. However, as mentioned before, there are also surrogate characters (also WideChar items) that are used to produce a special Unicode character on a different plane (than the default plane 0). Plane here refers to the high $10000 bit of the Unicode code point. A nice example of a Unicode code point beyond $10000 is the G Clef, which is code point $1D11E and the UTF-16 surrogate pair $D834 and $DD1E. There are a number of ways to define the Clef inside a string. As an example, let’s put the Clef between [ and ] square brackets. We can produce the two UTF-16 surrogate bytes by calling the ConvertFromUtf32($1D11E) function. But we can also declare a constant Clef by coding the opening and closing brackets as well as the two surrogate bytes themselves directly: const // surrogate bytes Clef = #$5B + #$D834 + #$DD1E + #$5D; Alternately, we can put the actual character between the two brackets. Note that this will not be without problems. const Clef2 = '[

]'; // pasted Clef character

First of all, the above clef will only be visible if you use the Code2001 font (or another font that’s capable of showing the $1D11E code point), which the IDE does not offer as part of the font choices. Furthermore, the IDE will only “see” two WideChar elements, and doesn’t know that there’s only one visible character on screen. So when you move with your cursor over the opening quote or bracket then the closing item will be displayed, but in other cases, a space will be shown after the square for the clef. This bring another interesting topic to the attention: the number of elements vs. the number of printable characters. The number of elements of a string can be retrieved by calling the Length function. So Length(Clef) = Length(Clef2) = 4, meaning there are 4 WideChar elements in both Clef constants.

UTF-16 Number of Characters If we do not want to know the number of elements, but rather the number of printable characters in a UTF-16 string, we need to check the surrogates and count the surrogate pair as one printable character (instead of two). This is implemented in the following UTF16Length function, which returns the number of printable characters: function UTF16Length(S: String): Integer; var i: Integer; begin Result := 0; for i:=1 to Length(S) do if not IsSurrogate(S[i]) then Result := Result + 1 else // Surrogate if (i > 1) and IsSurrogatePair(S[i],S[i-1]) then Result := Result + 1 end;

Bob Swart Training & Consultancy

- 105 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

Calling UTF16Length on Clef or Clef2 returns 3: the opening and closing brackets are counted as well as the surrogate pair (being equal to a single printable character).

Unicode Files and BOM A Unicode file starts with 2 or 3 bytes to specify the format of the file and the byte-order. As a consequence, this sequence of bytes is also called the Byte-Order-Mark or BOM. For UTF-8, the BOM consists of three bytes: 239 = $EF = ï 187 = $BB = » 191 = $BF = ¿ For UTF-16, the BOM consists of two bytes, usually as follows (little endian): 255 = $FF = ÿ 254 = $FE = þ For UTF-16 big endian, the order of the BOM is reversed. When we create and write data to files in the Unicode format (either in UTF-8 or UTF-16), we need to write the BOM first, before writing any Unicode data – in the right format as well, of course. Fortunately, we do not have to do this manually, but we can use some support classes added to Delphi 2009, like the TEncoding class.

TEncoding In order to determine the encoding of Unicode data, Delphi 2009 defines a TEncoding class with members for the different encodings: ASCII, UTF7, UTF8, Unicode (= UTF16), BigEndianUncode and Default. The Default encoding, or TEncoding.Default, is equal to the current codepage that the application is running on. You cannot change the default encoding while the application is running. TEncoding is used as encoding specifier when saving a TStrings or TStringList to disk, or when loading these from disk, as we’ll see shortly. But before we see that, let’s check normal file I/O using writeln statements.

Console or Text File I/O First the bad news: neither console nor Text file I/O support reading Unicode strings. And writing also only supports AnsiStrings. This means that as soon as you call write or writeln, the contents of a (Unicode) string will be converted to AnsiString when needed, and written to the output. This means that any Text file I/O needs to be rewritten using streams or other techniques. However, since a UTF8String is also an AnsiString (with the 65001 code page specified), there is a good workaround for writing to console output provided you set the console codepage to UTF8 and use a font that can display the Unicode characters (that’s Lucida Console for example): program ConsoleUTF8; {$APPTYPE CONSOLE} uses Windows, SysUtils; begin SetConsoleOutputCP(65001); write(AnsiChar(239), AnsiChar(187), AnsiChar(191)); // UTF-8 BOM Writeln(Output, UTF8String('[???????????? ???????]')); end. This will produce Cyrillic characters on the standard output. Note that Lucida Console cannot display all Unicode characters – Chinese and the Clef are not shown, but at least Cyrillic characters display without problems.

Bob Swart ([email protected])

- 106 -

February 2011

Delphi XE Starter Essentials

4. Unicode

Note that I’m also writing the BOM to the output in case you want to save the console output to a text file and read it afterwards. That way, you can set the font afterwards and also see the Chinese or Clef characters without problems. Provided they were written as UTF8. This is also the basis for writing UTF8 data to Text files: printing UTF8Strings on a file which starts with the UTF-8 BOM: program UnicodeTextFile; {$APPTYPE CONSOLE} uses Windows, SysUtils; var F: Text; begin Assign(F, 'output.txt'); Rewrite(f); write(f, AnsiChar(239), AnsiChar(187), AnsiChar(191)); // UTF-8 BOM writeln(f, UTF8String('[???????????? ???????]')); Close(f); end. Since UTF8String is an AnsiString, we can combine the code above with writeln of normal strings, which will be converted to AnsiStrings, as long as we keep away from high-ascii characters (since these would indicate the start of a UTF8 special character byte sequence). program UnicodeTextFile; {$APPTYPE CONSOLE} uses Windows, SysUtils; var F: Text; begin Assign(F, 'output.txt'); Rewrite(f); write(f, AnsiChar(239), AnsiChar(187), AnsiChar(191)); // UTF-8 BOM writeln(f, UTF8String('[???????????? ???????]')); writeln(f, 'This is a UTF-16 String which will be written as AnsiString'); Close(f); end. As long as we convert UTF-16 Unicode Strings to UTF8 before writing to Text files, and don’t forget to use the UTF-8 BOM as prefix, this will work fine for writing files with Unicode UTF-8 output.

TStrings / TStringList Apart from the UTF-8 testfile trick just covered, the easiest way to produce text output that supports the TEncoding formats, is using the SaveToFile method of a TStrings or TStringList. The SaveToFile method has been extended with a second argument, specifying the encoding. begin Memo1.Lines.SaveToFile('Memo1.txt', TEncoding.UTF8); By default, the second argument uses TEncoding.Default, which is the default ANSI Code Page of the machine. This means that by default, the SaveToFile will not produce Unicode output, but ANSI output instead (in other words: the previous behavior of the application,

Bob Swart Training & Consultancy

- 107 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

but any explicit Unicode characters or data will be lost, unless the SaveToFile gets a second argument value using a TEncoding field other than Default, ASCII or UTF7). Note that the corresponding LoadFromFile does not take a second argument of type TEncoding, since the encoding should be determinable from the BOM in the first few characters of the file: Memo1.Lines.LoadFromFile('Memo1.txt'); end;

Output Logging Using this knowledge, I’ve could replace a Unicode-enabled text file based logging unit by a TStringList based version, implemented as follows: unit Logging; interface uses SysUtils; procedure Log(const Str: String); var LogFilename: String = 'logfile.txt'; LogEncoding: TEncoding; implementation uses Classes; var Logfile: TStringList; procedure Log(const Str: String); begin Logfile.Add(Str) end; initialization Logfile := TStringList.Create; LogEncoding := TEncoding.UTF8 finalization Logfile.SaveToFile(LogFilename, LogEncoding); Logfile.Free end. The variables LogFileName and LogEncoding can be assigned at any time, since they will only be used when the application is closed and the logfile is written to disk. The global procedure Log takes a String argument and simply appends this string to the private Logfile TStringList. Logging this way may take up a lot of memory as the logfile grows in memory, and you don’t see the output until the application terminates (nicely!), but I’ve already used it in a number of cases as a quick-and-dirty way to produce some logging output. Of course, apart from this very simple logging unit, you can also use a more sophisticated tool like CodeSite from Raize Software – see www.raize.com for details. The good news is that CodeSite Express is included with Delphi XE now, and for more details see also the last section of this courseware manual that covers CodeSite Express among others.

Bob Swart ([email protected])

- 108 -

February 2011

Delphi XE Starter Essentials

4. Unicode

TCharacter Another Unicode support class in Delphi 2009 is the TCharacter class. This is actually a sealed class which only consists of static class functions to check whether a character is a Digit, a Letter, etc. The TCharacter class is the “solution” to eliminate the compiler warnings when you combine Chars with Char sets (sets can only contain AnsiChar values, so the expression is changed which results in a compiler warning). var C: Char; begin … // assign some Char value to C if C in ['a'..'z','A'..'Z'] then And the set expression should be replaced by a call to IsLetter from the TCharacter class, as follows: if TCharacter.IsLetter(C) then

Character Sets While this works for this particular case, sometimes we need a test that doesn’t already exists in the TCharacter class, like the following: var C: Char; begin … // assign some Char value to C if C in ['a','e','i','o','u'] then There is no IsVowel function in TCharacter, but the compiler warning itself actually already suggests the CharInSet method from SysUtils, so in this case we can change the code as follows: var C: Char; begin … // assign some Char value to C if CharInSet(C, ['a','e','i','o','u']) then Note that CharInSet will fail for any character contants higher than #127. So ultimately, this is still only a comparison with low-ANSI characters. In order to solve this problem, Peter Below has written a unit WideCharSetU.pas that can be download from the Embarcadero Developet Network at http://cc.embarcadero.com/Item/26658 The WidecharSetU unit supplied in this archive aims to solve the above problem by implementing the equivalent of a set of widechar. The syntax in using these is necessarily a bit different from using the classic sets, since there is no build-in compiler support for my "widechar set" types and the language also does not support operator overloading on interface types (which Peter choose as the base for the implementation).

Extending TCharacter Although the TCharacter class is a sealed class, we can still extend it with an IsVowel method by using class helpers, as follows: type TMyChar = class helper for TCharacter class function IsVowel(C: Char): Boolean; // Vowel end;

Bob Swart Training & Consultancy

- 109 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

The implementation uses the newly mentioned CharInSet function as follows: class function TMyChar.IsVowel(C: Char): Boolean; begin Result := CharInSet(C, ['a', 'e', 'o', 'i', 'u']) // Vowel end; And now we can replace the CharInSet call with a simple call to TCharacter.IsVowel as follows: var C: Char; begin … // assign some Char value to C if TCharacter.IsVowel(C) then With no more compiler warnings. Note that the unit that defines the IsVowel method must be added to the uses clause of any other unit where you want to use this functionality.

Unicode Support Routines There are a number of Unicode support routines that will come in handy in several situations. Both are implemented in the System unit: function StringElementSize(const S: UnicodeString): Word; overload; function StringElementSize(const S: RawByteString): Word; overload; {$IFNDEF MSWINDOWS} function StringElementSize(const S: WideString): Word; overload; {$ENDIF !MSWINDOWS} StringElementSize returns the typical size for an element (code point) in a given string. For an AnsiString, StringElementSize returns 1, and for a UnicodeString or WideString, StringElementSize will return 2. Note that for UTF8String, the result of StringElementSize is also 1, although printable characters can be encoded using 1 to four bytes in total. function StringCodePage(const S: UnicodeString): Word; overload; function StringCodePage(const S: RawByteString): Word; overload; {$IFNDEF MSWINDOWS} function StringCodePage(const S: WideString): Word; overload; {$ENDIF !MSWINDOWS} StringCodePage returns the codepage for a string, and SetCodePage can be used to set the code page of a string. SetCodePage takes an AnsiString, the CodePage and an optional Convert parameter which controls whether the contents (payload) of the string should also be converted to the specified codepage.

Explicit Unicode Conversions Unicode strings can be converted from the Unicode code point value to the UTF-16 format using ConvertFromUtf32 and ConvertToUtf32 (to and from UTF-16 from UTF-32) defined in the Character unit: function ConvertFromUtf32(C: UCS4Char): string; inline; function ConvertToUtf32(const S: string; Index: Integer): UCS4Char; overload; inline; function ConvertToUtf32(const S: string; Index: Integer; out CharLength: Integer): UCS4Char; overload; inline; function ConvertToUtf32(const HighSurrogate, LowSurrogate: Char): UCS4Char; overload; inline;

Bob Swart ([email protected])

- 110 -

February 2011

Delphi XE Starter Essentials

4. Unicode

UCS4Char is equivalent to a LongWord and holds a Unicode code point value. These four functions are inline because they actually call the class functions with the same name of the TCharacter class. The system unit also defines methods to convert an entire string from UCS4 to UTF-16 and back: function UnicodeStringToUCS4String(const S: UnicodeString): UCS4String; function UCS4StringToUnicodeString(const S: UCS4String): UnicodeString; The old conversion routines from UCS4 to WideString and back are also still available, but should no longer be used (since the new Unicode String type is more efficient than the WideString type): function WideStringToUCS4String(const S: WideString): UCS4String; function UCS4StringToWideString(const S: UCS4String): WideString; In order to migrate to and from UTF8, we can use function UnicodeToUtf8(Dest: PAnsiChar; MaxDestBytes: Cardinal; Source: PWideChar; SourceChars: Cardinal): Cardinal; overload; function Utf8ToUnicode(Dest: PWideChar; MaxDestChars: Cardinal; Source: PAnsiChar; SourceBytes: Cardinal): Cardinal; overload; There are also two deprecated functions: function UnicodeToUtf8(Dest: PAnsiChar; Source: PWideChar; MaxBytes: Integer): Integer; overload; deprecated; function Utf8ToUnicode(Dest: PWideChar; Source: PAnsiChar; MaxChars: Integer): Integer; overload; deprecated; Much easier to use is the UTF8Encode function, which is overloaded to take WideString, UnicodeString and AnsiString arguments. function UTF8Encode(const WS: WideString): RawByteString; overload; function UTF8Encode(const US: UnicodeString): RawByteString; overload; function UTF8Encode(const A: RawByteString): RawByteString; overload; There is also a deprecated UTF8Decode function: function UTF8Decode(const S: RawByteString): WideString; deprecated 'Use UTF8ToWideString or UTF8ToString'; As the deprecated message says, we should use UTF8ToWideString instead. Or use some of the other UTF8ToXXX routines: function UTF8ToWideString(const S: RawByteString): WideString; inline; function UTF8ToUnicodeString(const S: RawByteString): UnicodeString; overload; function UTF8ToUnicodeString(const S: ShortString): UnicodeString; overload; function UTF8ToUnicodeString(const S: PAnsiChar): UnicodeString; overload; function function function function

UTF8ToString(const UTF8ToString(const UTF8ToString(const UTF8ToString(const

Bob Swart Training & Consultancy

S: S: S: S:

RawByteString): string; inline; overload; ShortString): string; inline; overload; PAnsiChar): string; inline; overload; array of AnsiChar): string; overload;

- 111 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

Unicode Migration There are a number of code constructs and assumptions that we must keep in mind when recompiling existing or legacy applications with Delphi 2009. Before we examine the actual migration of methods, we will first examine SizeOf and StringElementSize, Length and Number of printable character properties of strings.

SizeOf in Bytes SizeOf(AnsiChar) is 1, and SizeOf(WideChar) is still 2, but SizeOf(Char) is now 2 in Delphi 2009 and no longer 1. The SizeOf(String) is always 4, since it returns the size of the pointer to the string. Length(String) will return the number of characters (elements) in the string, and not the number of bytes. To get the number of bytes that the string uses, we must add the size of each individual element, which is WideChar for a String or UnicodeString. The function StringElementSize can help in this regard. So, in order to determine the number of bytes that a String buffer contains, we have to evaluate the following expression: SizeOf String == Length(String) * StringElementSize(String) Which will be used in some of the migration examples later in this section.

Length in Items The Length function returns the number of elements of a string, which can lead to unexpected results if you expect Length to return the number of printable characters. Length(ShortString) = number of AnsiChars Length(AnsiString) = number of AnsiChars Length(String) = number of WideChars (not number of printable characters) Length(UTF8String) = number of bytes (not number of printable characters) Length(UCS4) = number of USC4 characters When you call Length on a UTF8String or UnicodeString, then the result is the number of elements (AnsiChar/Byte for UTF8String and WideChars for UnicodeString), but this is not equal to the number of printable characters which can be less in case of special UTF-8 or UTF-16 characters.

Number of Characters Although SizeOf and StringElementSize can be used in combination with Length to determine the size of the string buffer, we sometimes also want to know the number of printable characters. For AnsiStrings and UCS4String, this is the same as the number of elements, but for UTF-8 or UTF-16 strings, we need something special. It’s actually not easy to calculate the number of printable characters in a UTF-8 string, so the easiest way may be to convert the UTF-8 string to a UTF-16 string and call the function UTF16Length that I defined earlier: function UTF16Length(S: String): Integer; var i: Integer; begin Result := 0; for i:=1 to Length(S) do if not IsSurrogate(S[i]) then Result := Result + 1 else // Surrogate if (i > 1) and IsSurrogatePair(S[i],S[i-1]) then Result := Result + 1 end; The number of printable characters can be important if you need to know how wide the visual controls must be in certain situations.

Bob Swart ([email protected])

- 112 -

February 2011

Delphi XE Starter Essentials

4. Unicode

FillChar The FillChar routine fills a string or buffer with bytes, which means that we need to multiply the length of the string with the size of the string elements in order to fill the complete size of the string. var Buffer: array[0..255] of Char; begin FillChar(Buffer, Length(Buffer) * SizeOf(Buffer[0]), 0); // using new function StringElementSize FillChar(Buffer, Length(buffer) * StringElementSize(Buffer), 0); Note that we can also use the SizeOf(Buffer) in this case: FillChar(Buffer, SizeOf(buffer), 0); Note that FillChar fills the buffer with Byte values now, and no longer with Char values. A better named function is FillMemory, defined in the Windows unit: procedure FillMemory(Destination: Pointer; Length: DWORD; Fill: Byte); begin FillChar(Destination^, Length, Fill); end; ZeroMemory, also defined in the Windows unit, is another function which will clear a section of memory with 0. procedure ZeroMemory(Destination: Pointer; Length: DWORD); begin FillChar(Destination^, Length, 0); end; As you can see, both FillMemory and ZeroMemory use the FillChar function.

Move Like FillChar, Move expects the count to be specified in bytes and not characters. So if you need to move one string buffer to another, you again need to multiply the length of the buffer with the size of the individual buffer elements. type BufArray = array[0..255] of Char; var BufferIn: BufArray; BufferOut: BufArray; begin Move(BufferIn, BufferOut, Length(BufferIn) * SizeOf(BufferIn[0]); Move(BufferIn, BufferOut, Length(BufferIn) * StringElementSize(BufferIn); Like FillChar, we can also use the SizeOf the buffer in this case: Move(BufferIn, BufferOut, SizeOf(BufferIn)); Like FillMemory and ZeroMemory, the Windows unit also defines MoveMemory and CopyMemory functions which also operate in bytes: procedure MoveMemory(Destination: Pointer; Source: Pointer; Length: DWORD); begin Move(Source^, Destination^, Length); end;

Bob Swart Training & Consultancy

- 113 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

procedure CopyMemory(Destination: Pointer; Source: Pointer; Length: DWORD); begin Move(Source^, Destination^, Length); end;

TStream Read and Write When using streams, we need to read or write the number of bytes that the string buffer occupies, which can be determined by the length of the string multiplied again by the StringElementSize. For reading a string from a stream, we first read the length (integer) followed by the number of bytes the string buffer occupies: var Stream: TStream; Len: Integer; Str: SomeString; begin Stream.Read(Len, sizeof(integer)); SetLength(Str, Len); Stream.Read(Pointer(Str)^, Len * StringElementSize(Str)); When writing the string back to the stream, we first write the length followed by the number of bytes that the string buffer occupies: var Stream: TStream; Len: Integer; Str: SomeString; begin Len := Length(Str); Stream.Write(Len, sizeof(integer)); Stream.Write(Pointer(Str)^, Len * StringElementSize(Str)); This will work if Str is of any possible type String: AnsiString, String, UTF8String or WideString.

Windows API As mentioned before, the Windows API consists of two version: the ANSI and the Wide versions, which can be noticed when you example the MessageBox API for example: MessageBoxA takes the ANSI String arguments (PAnsiChar actually), while MessageBoxW expects Wide or Unicode Strings (PWideChar). GetWindowText GetWindowText is an example of the Windows API where the function expects the number of elements instead of the number of bytes. So unlike the Delphi functions, we should pass the Length of the Buffer here, instead of the number of bytes the Buffer occupies: var Buffer: Array[0..255] of Char; begin GetWindowText(Edit1.Handle, Buffer, Length(Buffer)); Note that this code will also work in older versions of Delphi where Char is one byte, and the GetWindowText function maps to the GetWindowTextA ANSI edition instead of the GetWindowTextW Unicode edition of the Windows API.

Bob Swart ([email protected])

- 114 -

February 2011

Delphi XE Starter Essentials

4. Unicode

GetProcAddress The GetProcAddress Windows function is special, since there is only an “ANSI” edition of this function available. Nevertheless, using Delphi we can call GetProcAddress while passing a Unicode String. This is actually a nice feature of the Windows unit, which defines an overloaded version of GetProcAddress that just converts the Unicode String argument back to an AnsiString before calling the actual Windows API version.

Chr -> Char If you use Chr to convert an ordinal value to a character, you must ensure that the result is written to a Char and not an AnsiChar. In order to produce an AnsiChar we can explicitly cast the ordinal value to AnsiChar instead of using Chr.

Character Literals Character literals between #128 and #255 – the so-called high ASCII values – have a specific value depending on the ANSI code page. As a result, we should not use the #128..#255 character literals, but use the actual characters themselves which will end up as Unicode characters in the source code. {$HIGHCHARUNICODE OFF} With the HIGHCHARUNICODE set to OFF, we can define the high-ASCII values without them turning into Unicode characters. {$HIGHCHARUNICODE OFF} var C: AnsiChar; begin C := #128; Without turning HIGHCHARUNICODE OFF, we would get a warning that the #128 character, actually WideChar constant #$0080, would have to be narrowed from WideChar to AnsiChar.

Integer and Float To AnsiString SysUtils defines the well-known IntToStr and FloatToStr, which return a Unicode String. However, if you want to use AnsiStrings in your application, you may want to use versions of these routines that return an AnsiString. Since these didn’t exist, yet, I’ve written a set of routines to help us converting integers and floats to AnsiStrings, based on the built-in Str function. unit Convert; interface function IntToAnsiStr(X: Integer; Width: Integer = 0): AnsiString; function FloatToAnsiStr(X: Extended; Width: Integer = 0; Decimals: Integer = 0): AnsiString; overload; function FloatToAnsiStr(X: Double; Width: Integer = 0; Decimals: Integer = 0): AnsiString; overload; function FloatToAnsiStr(X: Single; Width: Integer = 0; Decimals: Integer = 0): AnsiString; overload; implementation function IntToAnsiStr(X: Integer; Width: Integer = 0): AnsiString; begin Str(X: Width, Result); end;

Bob Swart Training & Consultancy

- 115 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

function FloatToAnsiStr(X: Extended; Width: Integer = 0; Decimals: Integer = 0): AnsiString; begin Str(X: Width: Decimals, Result); end; function FloatToAnsiStr(X: Double; Width: Integer = 0; Decimals: Integer = 0): AnsiString; begin Str(X: Width: Decimals, Result); end; function FloatToAnsiStr(X: Single; Width: Integer = 0; Decimals: Integer = 0): AnsiString; begin Str(X: Width: Decimals, Result); end; end. Note that the overloaded FloatToAnsiStr function uses default Width and Decimal arguments (with a default value of 0). Also note that the Str function always uses the dot as decimal separator in the string result, and doesn’t consider the DecimalSeparator value. If you want to use the DecimalSeparator, you may want to change the functions as follows: function FloatToAnsiStr(X: Double; Width: Integer = 0; Decimals: Integer = 0): AnsiString; const Dot: AnsiChar = '.'; begin Str(X: Width: Decimals, Result); if DecimalSeparator '.' then begin Decimals := AnsiPos(Dot, Result); if Decimals > 0 then Result[Decimals] := AnsiChar(DecimalSeparator) end end; This will use the DecimalSeparator, narrowed back to AnsiChar (and I’m assuming nobody will use a Unicode Character which doesn’t “map” back to an AnsiChar as DecimalSeparator, but if you do, you could just stick to the normal FloatToStr anyway).

UpperCase There's an overloaded version of function UpperCase in the SysUtils unit as well as the AnsiStrings unit. To start with the unit AnsiStrings, the definition of UpperCase is as follows: {* AnsiStrings *} function UpperCase(const S: AnsiString): AnsiString; overload; function UpperCase(const S: AnsiString; LocaleOptions: TLocaleOptions): AnsiString; overload; inline; The definition of UpperCase in the SysUtils unit is using String instead of AnsiString, and is defined as follows: {* SysUtils *}

Bob Swart ([email protected])

- 116 -

February 2011

Delphi XE Starter Essentials

4. Unicode

function UpperCase(const S: string): string; overload; function UpperCase(const S: string; LocaleOptions: TLocaleOptions): string; overload; inline; Unfortunately, there is also a function called AnsiUpperCase inside SysUtils, which has the following signature: function AnsiUpperCase(const S: string): string; overload; Note that it works on Strings and not AnsiStrings! The comments (and help?) of AnsiUpperCase say the following: { UpperCase converts all ASCII characters in the given string to upper case. The conversion affects only 7-bit ASCII characters between 'a' and 'z'. To convert 8-bit international characters, use AnsiUpperCase. } { AnsiUpperCase converts all characters in the given string to upper case. The conversion uses the current user locale. } This should be "Unicode characters" instead of 8-bit, as the Windows API CharUpperBuff is now called (the W-version in Delphi 2009 and higher)... To remedy this potential confusion, I suggest that there will be a new function introduced, UnicodeUpperCase (not WideUpperCase, since that one already exists for WideStrings). Same thing for the AnsiLowerCase (calling CharLowerBuff API), AnsiCompareStr (calling CompareString API), AnsiSameStr, AnsiCompareText (calling CompareString API), AnsiSameText, AnsiCompareFileName (calling CompareStr), AnsiLowerCaseFileName (calling AnsiLowerCase), AnsiUpperCaseFileName (calling AnsiUpperCase), and AnsiPos (calling StrPosLen). UnicodeUpperCase should implement the call to CharUpperBuffW while AnsiUpperCase should be moved to the AnsiStrings unit, calling the CharUpperBuffA function. I also suggest a number of UnicodeXXX functions to call the "W"-APIs and a change to the AnsiXXX functions to call the "A"-APIs. The latter is already implemented: the AnsiStrings unit contains the AnsiXXX functions. So the only "strange" thing is the names of the AnsiXXX functions inside the SysUtils unit, which should IMHO be UnicodeXXX functions instead. The reason for not changing these names from AnsiXXX to UnicodeXXX is probably to avoid breaking existing code. On the other hand, it would be a good idea for people to explicitly wonder about which function to use...

Globalisation and localisation Globalisation and localisation are two different but related terms that are sometimes confused with each other. Globalisation, also known as internationalization, means that your application can handle international data using different character encodings and date, time, number, currency formats. It’s the cornerstone for the next step, localisation, which is the act of actually translating an application using a particular language or locale. An application is said to be localizable when it’s ready for translation to a particular locale. In this section, I’ll globally cover globalization, but will focus in more detail on localisation details: the acts of actually producing different translations of an application.

Bob Swart Training & Consultancy

- 117 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

Globalisation and SysUtils Delphi supports globalization in a number of ways. First of all, there’s the SysUtils unit which uses the system locale to determine the date (short and long), time, number and currency formatting settings. Based on these format strings, your application will display and accept only certain formats. This is often a pain in the rear end if your endusers expect to be able to enter numerical values using the comma as decimal separator, but the current system setting is using a dot. Or you need to display the date in the yyyy/mm/dd format (for example because that’s the easiest way to sort items by date), or in a format dictated by the DBMS that your client is using. Fortunately, although SysUtils does a great job of finding out the regional options set by Windows, you can always override them in your application, usually in the begin-end block of your project or in a unit initialization section, for example as below: initialization SysUtils.ShortDateFormat := 'YYYY/MM/DD'; SysUtils.DecimalSeparator := '.'; end. Of course, if your users want to use both a dot and a comma to input numerical data, you may have to override the KeyPress method, changing a dot into a comma (or vice versa, based on the value of the SysUtils.DecimalSeparator variable). You can either override the KeyPress method of the VCL control, or set the KeyPreview property of the VCL form to True, and implement the FormKeyPress as shown below: procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char); begin if Key in ['.',','] then Key := SysUtils.DecimalSeparator end; This code will ensure that both the comma and the dot can be used, regardless of the regional settings (unless something other than a comma or dot is used, which seems unlikely, but feel free to contact me if you know of a valid example otherwise).

Localisation using ResourceStrings Resourcestrings are another feature supported by Delphi offering globalization support for both Win32 and .NET. Using resourcestrings, we can translate an application as well as components that are used in that application. An example of resourcestrings used by Delphi can be found in Consts.pas. This unit contains only resourcestring definitions, for error messages as well as button captions, etc. These resourcestrings are used by several VCL components. And in order to translate these VCL components (even when already compiled into .dcu or .dcuil files), we only have to redeclare the resourcestring in another unit, using the same name for the resourcestring identifier, but giving it a different value. As an example, let’s assume I want to modify the caption on the OK button in a dialog to the Dutch string “Akkoord”. The first thing I need to do for this is make a copy of the Consts.pas unit, and add it to my project. Then, it’s a simple matter of translating the resource strings in the Consts.pas unit, to the specific language I want, as demonstrated in the code snippet below for the SMsgDlgInformation and SMsgDlgOK resourcestrings. unit Consts; interface resourcestring SMsgDlgInformation = 'Informatie'; SMsgDlgOK = 'Akkoord';

Bob Swart ([email protected])

- 118 -

February 2011

Delphi XE Starter Essentials

4. Unicode

Since the new unit has the same name as the original unit Consts, it will override the definitions of the strings for the source code that will be compiled as part of my application. That can also be done with regular strings. The neat thing is that the new resource string values will also be used by the already compiled VCL units and components, for example the Dialogs unit (which contains the MessageDlg method). As a result, the call to MessageDlg('Hallo Resourcestrings', mtInformation, [mbOK], 0); will produce a localized version:

Resourcestrings are not hardcoded into the project, but added as resource to the executable, which allows us to override them in the way I’ve demonstrated. This ensures that we can always translate (resource)strings, even when they are used by already compiled code. The only downside of using resourcestrings is that you can only translate them to a single language. If you need to produce multiple translations of an application, then you need to produce multiple different copies of the Consts.pas unit (and all the other units that include resourcestrings, by the way). Each of these new resourcestring units can be used to produce another localized application. However, for each language, you’ll end up with a unique copy of the Resourcestring units, and a separate project executable for that particular language. This means that you can start an executable for a specific language, but cannot switch from one language to another (not without starting another executable, containing the Resourcestrings for that language).

Not for runtime packages There’s one caveat when using resourcestrings: they only work in a non-runtime package setting. You cannot use the resourcestring technique in combination of runtime packages, since the Consts unit will be compiled into the package, and the package will use the resourcestrings from its own .dll, and not from the application .exe.

Summary In this section, I’ve covered Unicode and explained how Delphi 2009 and later work with Strings and Characters in a Unicode world. We’ve seen how AnsiString and UnicodeStrings behave themselves, how to do file I/O and what to take care off when doing Unicode conversions or migrations. We’ve also covered some globalization and localisation techniques using Delphi XE. I’ve mentioned SysUtils and the regional settings, as well as Resourcestrings and their use (and the fact that they won’t work in combination with runtime packages). I’ve also demonstrated the use of the Translation Editor for VCL for Win32 applications, and shown how to switch from one language to another (and back to the original default language, even without a resource DLL).

Bob Swart Training & Consultancy

- 119 -

www.drbob42.com

Delphi XE Starter Essentials

4. Unicode

Exercises The following exercises are optional, but can be used to practice the material of this first section. In order to make the exercises, you will need a machine with Delphi XE installed and configured.

Exercise #1 Write a little application that verifies that the file is a UTF-8 or UTF-16 file based on the BOM. Hint: use the TBinaryReader to verify that the file starts with the UTF-16 BOM.

Exercise #2 Since UTF-8 files generally require less space than UTF-16 files, write a little application that for a given filename, verify that it’s indeed a UTF-16 file, and then read the contents and write it back as a UTF-8 file. Report the number of bytes the new file is smaller than the original file.

Bob Swart ([email protected])

- 120 -

February 2011

Delphi XE Starter Essentials

5. Delphi XE and Subversion

5. Delphi XE and Subversion Delphi XE includes third-party tools that already ship with Delph like Indy and IBX. Delphi XE also includes a number of third-party tools that you can access using the “Download Partner / Additional Products” button from the installer. These include a download link for the Remote Debugger (in case you want to debug an application running on another machine, especially helpful if the Delphi IDE itself is not installed on that other machine), as well as the Embarcadero RAD Studio XE Partner Software, CollabNet Subversion Server, and InstallAware Express, CodeGear Edition. In this section, I want to cover Subversion and the Subversion integration into the Delphi XE IDE.

Subversion Subversion by CollabNet (see http://www.collab.net/) is a standard for version control and Software Configuration Management (SCM). Subversion is now integrated into the RAD Studio XE IDE, so you can use Subversion features directly. However, before we can use Subversion from the IDE, we should first create a repository to store the files. This can be done in two ways: either a local repository (for your own projects), or a remote repository, where multiple developers can connect to.

Local Repository Since I do not expect all Delphi XE developers to start their own remote SVN repositories, I will start with creating a local repository first. For this, I’m using TortoiseSVN, which you can download from http://tortoisesvn.tigris.org/ offering a Subversion client implemented as a Windows shell extension (so we can create repositories using Windows Explorer for example). Once TortoiseSVN is installed, you can create a new directory on your local drive (in a location where you want to save the repositories – not the actual project files), and inside that directory use Windows Explorer to set up your repository. As an example, I’ve created a directory C:\Repositories on my local machine, right-clicked in it, and selected the TortoiseSVN Create repository here option.

Bob Swart Training & Consultancy

- 121 -

www.drbob42.com

Delphi XE Starter Essentials

5. Delphi XE and Subversion

This will give you dialog that the repository has been created successfully, and will show a number of administration files in that directory. You should no longer manually add or remove files from this directory now.

The next step we need to do is obtain the URL for this local repository. Right-click again, and this time select the Repo-browser.

Bob Swart ([email protected])

- 122 -

February 2011

Delphi XE Starter Essentials

5. Delphi XE and Subversion

The repository will still be empty, but at least we can see the repository URL, which is file:///C:/Repositories We can now start Delphi XE, and use the repository URL to add an existing project to the local SVN repository. As an example, I can add my Matrix project to the local repository. So, while the project is opened, right-click on the project node in the Project Manager and select “Add to Version Control”. In the dialog that follows, we need to specify the URL of the Repository. This is the URL we saw earlier in the repo-browser, followed by a name for the specific project. This name does not have to be the name of the Delphi project, but it must be some kind of subdirectory name that can be used. In our case, I can save the Matrix project in the repository URL file:///C:/Repositories/MatrixProject for example.

We then need to click in Import to actually add the file to the local repository. In the Subversion tab of the messages pane, you will see the files that have been added and sent to the local repository. From this moment on, we can use the Subversion context menu of the project or individual files of the project to Commit (send local changes to the repository), Update (get the latest version from the local repository) and Browse Repository (to get an overview of the files in the local repository). Unfortunately, there are no options to Lock or Unlock a file, which can be done using Checkout (lock) and Checkin (unlock). This feature request was added to Quality Central as QC report #87736 (feel free to vote!), and can be read in more detail on my weblog at http://www.bobswart.nl/Weblog/Blog.aspx?RootId=5:4332 Note that the Lock and Unlock functionality are especially important when working with remote repositories.

Bob Swart Training & Consultancy

- 123 -

www.drbob42.com

Delphi XE Starter Essentials

5. Delphi XE and Subversion

Remote Repository To setup a remote SVN repository, we need some kind of SVN server, for which I’ve used the free Standard Edition of VisualSVN Server 2.1.3 that you can download from http://www.visualsvn.com/server/ for free. The installer includes both Apache 2.2.13 and Subversion 1.6.12, so you must be careful (for port conflicts) if you already have IIS on your web server, which I do on my old Windows Server 2003 machine.

I’ve installed both the VisualSVN Server and the Management Console, which gives me all the options I need to manage the SVN repositories on the server.

Bob Swart ([email protected])

- 124 -

February 2011

Delphi XE Starter Essentials

5. Delphi XE and Subversion

As location for the repositories I’ve specified c:\Repositories – a location not shared or used by other applications. As port I had to specify a different port than the default port 443 (since that one was in use by IIS already), so I picked port 8443 to use https:// The only problem is that I had to use a so-called self-signed certificate, which means people will get a warning when they try to connect to my Delphi SVN repositories at https://www.bobswart.nl:8443/

Finally, I did not want to use Windows authentication, but rather use Subversion authentication, which means I can define users, groups and repositories with rights for these users and groups. Given a group “Delphi” and two users “Bob” and “Courseware”, I can define some SVN repositories, including one called “XE” with the project group and source files for the individual projects of this Delphi XE Development Essentials courseware manual.

Bob Swart Training & Consultancy

- 125 -

www.drbob42.com

Delphi XE Starter Essentials

5. Delphi XE and Subversion

The readers of my Delphi XE Development Essentials manual will get the credentials for the Courseware user, which is able to read but not write to the material (that is only offered to use Bob).

With the projects in place, let’s now demonstrate how to open an example project from the XE repository. But first, I should demonstrate how to add a project to the repository, and especially how to add a complete project group. In this case, the project group with the AQtime project and the ProfileProject all at once. Inside the Project Manager, I had to right-click on the Project Group itself, and select the Add to Version Control option. This may give you a warning if not all files are located in the same root directory, in which case you must make sure to save the file to the right place first. When you get all files in the list of files to be committed, then you can specify the URL of the repository. For a local repository, this could be a file:/// URL, but now we have to use a “real” URL which is based at https://www.bobswart.nl:8443/ on my server. For the XE repository, we need to add the svn and XE part to it, and then we also need to add the name of the project or project group to ensure that these files end up in their own place in the XE repository (I need to add the files for the other sections of this courseware manual as well). In short, the full URL of the repository is https://www.bobswart.nl:8443/svn/XE/7 for the project group of this section. We can optionally add an Import Comment, which I’ve set to “These are the source files for the section on Delphi XE Third-Party Tools of Bob Swart's Delphi XE Development Essentials courseware manual.”

Bob Swart ([email protected])

- 126 -

February 2011

Delphi XE Starter Essentials

5. Delphi XE and Subversion

Then, I only need to click on the Import button to send all files over to the XE repository. However, we now get a dialog where the Subversion SSL Server certificate information is shown, including a question if we want to accept it:

Bob Swart Training & Consultancy

- 127 -

www.drbob42.com

Delphi XE Starter Essentials

5. Delphi XE and Subversion

This dialog is followed by the Subversion login dialog. Here, I can specify my username and password to get read/write access to the project, while you (the reader) later need to specify the information to get read-only access to the projects in this repository.

Finally, I will get the message to tell me the import was successful, and the local files (i.e. the working copy) are now locked.

Let’s now move to another machine, and open the repository (as a reader). On a different machine, I can start Delphi XE and do File Open from Version Control. The dialog that follows can be used to specify the SVN URL as well as the local destination:

Bob Swart ([email protected])

- 128 -

February 2011

Delphi XE Starter Essentials

5. Delphi XE and Subversion

Unfortunately, there is no way to specify the credentials, so I’ve had to ensure on the SVN server that “Everyone” has at least read-only access to the repositories. After all files are downloaded and added to the local destination directory, we get a question which project to open from the project group, or the project group itself.

The default choice is to open the entire project group, which is fine of course. This will open the project group with the two projects. We can now right-click on the nodes in the Project Manager and select the Subversion options, like Commit (from the local directory to the server – not allowed, since we only have read-only access), Update (to get updates from the server), Show Log (to show the actions as well as any comments that were added, like the “These are the source files for the section on Delphi XE Third-Party Tools of Bob Swart's Delphi XE Development Essentials courseware manual.” comment), Clean, and Browse Repository (where we can see the files in the connected repository:

Bob Swart Training & Consultancy

- 129 -

www.drbob42.com

Delphi XE Starter Essentials

5. Delphi XE and Subversion

Note that you cannot save changes to my repository, since you can only read the files (and not write them). But you can open the projects from the repository, save them on your local disk and work with them. And whenever I post updates to these projects, you can retrieve the updates.

Overview of SVN Projects The Delphi XE sample projects themselves are also available on SVN. Project samples (previously called Demos) are installed by both the Delphi XE and the C++Builder XE installer. The samples are also checked into Subversion at SourceForge: https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio _XE. You can check this location for additions or updates to the samples. Finally, the Open Tools API integration of SVN in Delphi XE itself is also available in at https://radstudioverins.svn.sourceforge.net/svnroot/radstudioverins/branches/radstudioxe DUnit2 is also available as Open Source project in the format of an SVN repository. You can connect to it using https://dunit2.svn.sourceforge.net/svnroot/dunit2/trunk

Summary In this section, I’ve covered some of the third-party tools that are now included with Delphi, including AQtime, CodeSite and the SVN integration. I’ve also explained how to get the project with the example source code from the SVN repository and load them into Delphi XE itself.

Bob Swart ([email protected])

- 130 -

February 2011

Delphi XE Starter Essentials

A. Answer to Exercises

A. Answers to Exercises This appendix contains the answers to the exercises in this courseware manual. It is highly recommended that students attempt to solve the exercises without consulting the answers first.

1. Delphi for Win32 IDE In this section, I’ve given a brief tour through the Delphi XE IDE and the new features in this new release of Delphi XE. I’ve also discussed some of the debugger enhancements, including the new debugger visualizers.

Exercise #1 Create a custom Build Configuration called UNICODE and set the options to your favorite settings, but include the fact that all Unicode string conversions with potential data loss are turned into errors.

Answer to Exercise #1 First, start a dummy project, and then in the Project Manager right-click on the Build Configuration node and select “New Configuration…”. Give it the name “UNICODE”.

This will result in a new Bulid Configuration on the same level as the Release and Debug build configurations. Alternately, you could first have selected either the Debug or the Release build configurations and then add a new one, to result in the new build configuration being a child of the selected build configuration (i.e. a child of the Release or Debug build configurations). Note that the new UNICODE build configuration will inherit the project options from its direct ancestor (either Base, Release or Debug). We should now ensure that the Unicide String conversion with potential data loss warnings are turned into an error. To do this, do Project Options, go to the Hints and Warnings node, and make sure that you have selected the UNICODE Build Configuration in the drop-down list. Make sure to turn both the Explicit and Implicit warning into an Error:

Bob Swart Training & Consultancy

- 131 -

www.drbob42.com

Delphi XE Starter Essentials

A. Answer to Exercises

Finally, make sure to save this new UNICODE build configuration with the “Default” option checked, so it will also be available for other Delphi projects from now on.

Exercise #2 Start the Quality Central Windows Client from the IDE, and search for all error reports and feature requests made by “Bob Swart” for Delphi or Delphi Prism. Feel free to vote for certain error reports, and don’t forget to add your own QC report when you find a bug (or contact your local reseller – or me - to verify the issue and/or report it for you).

Answer to Exercise #2 Do Tools Quality Central to get the login dialog. After you’ve logged on, you can click on the toolbar button with the question mark icon to start a new query. You can then fill in the Project (Delphi or Delphi Prism for example), the First Name (Bob for example) and the Last Name (Swart for example) and click on the yellow lightning bolt to execute the search. In order to get a better view of the reports, you can also do View Report List to get a nice list of the reported issues:

Bob Swart ([email protected])

- 132 -

February 2011

Delphi XE Starter Essentials

A. Answer to Exercises

2. Language Enhancements In this section, I’ve covered a number of the Delphi XE, 2010, 2009 and 2007 language enhancements for Win32 including the new Exit, Generics and Anonymous Methods (and the combination of the two) new in Delphi 2009, and a whole list of improvements that were added in Delphi 2007. I’ve also included a minimum description of the Generics.Defaults unit which is included with Delphi 2009. The Generics.Collections unit is also interesting to examine.

Exercise #1 The implementation of the GetTimeStamp function is also using a lot of IFDEFs to make it transparent for us. Check out the implementation of TSearchRec.GetTimeStamp in the unit SysUtils.pas

Answer to Exercise #1 In the SysUtils.pas unit from Delphi XE, the TSearchRec record is defined as follows, using IFDEFs for MSWINDOWS and POSIX. { Search record used by FindFirst, FindNext, and FindClose } TSearchRec = record private function GetTimeStamp: TDateTime; public {$IFDEF MSWINDOWS} Time: Integer platform deprecated; {$ENDIF MSWINDOWS} {$IFDEF POSIX} Time: time_t platform; {$ENDIF POSIX} Size: Int64; Attr: Integer; Name: TFileName; ExcludeAttr: Integer; {$IFDEF MSWINDOWS} FindHandle: THandle platform; FindData: TWin32FindData platform; {$ENDIF MSWINDOWS} {$IFDEF POSIX} Mode: mode_t platform; FindHandle: Pointer platform; PathOnly: string platform; Pattern: string platform; {$ENDIF POSIX} property TimeStamp: TDateTime read GetTimeStamp; end; The implementation of GetTimeStamp uses the same IFDEFs: { TSearchRec } function TSearchRec.GetTimeStamp: TDateTime; begin {$IFDEF MSWINDOWS} Result := InternalFileTimeToDateTime(FindData.ftLastWriteTime); {$ENDIF} {$IFDEF POSIX} Result := InternalFileTimeToDateTime(Time); {$ENDIF POSIX} end;

Bob Swart Training & Consultancy

- 133 -

www.drbob42.com

Delphi XE Starter Essentials

A. Answer to Exercises

Exercise #2 Implement TVector and TMatrix record types which support operator overloading for vector and matrix arithmetic.

Answer to Exercise #2 If you’ve read the manual it its entirely, you already know that such an implementation can be found in section 6 as the big example for Unit Tests. The definition of TVector and TMatrix is as follows: unit VectorMatrix; interface uses SysUtils; type Point = Double; // or Integer; Vector = Array of Point; Matrix = Array of Array of Point; type EVectorSizeMismatch = class(Exception) end; EMatrixSizeMismatch = class(Exception); type TMatrix = record private FData: Matrix; public // conversion class operator Explicit(const X: TMatrix): String; // unary operators class operator Negative(const X: TMatrix): TMatrix; // comparison operators class operator Equal(const X,Y: TMatrix): Boolean; class operator NotEqual(const X,Y: TMatrix): Boolean; // binary operators class operator Add(const X,Y: TMatrix): TMatrix; class operator Subtract(const X,Y: TMatrix): TMatrix; class operator Multiply(Factor: Point; const X: TMatrix): TMatrix; class operator Multiply(const X: TMatrix; Factor: Point): TMatrix; class operator Multiply(const X,Y: TMatrix): TMatrix; end; type TVector = record private FData: Vector; public // conversion // class operator Implicit(const X: Array of Point): TVector; class operator Implicit(const X: Vector): TVector; class operator Explicit(const X: TVector): String; // unary operators class operator Negative(const X: TVector): TVector;

Bob Swart ([email protected])

- 134 -

February 2011

Delphi XE Starter Essentials

A. Answer to Exercises

// comparison operators class operator Equal(const X,Y: TVector): Boolean; class operator NotEqual(const X,Y: TVector): Boolean; // binary operators class operator Add(const X,Y: TVector): TVector; class operator Subtract(const X,Y: TVector): TVector; class operator Multiply(Factor: Point; const X: TVector): TVector; class operator Multiply(const X: TVector; Factor: Point): TVector; class operator Multiply(const X,Y: TVector): TMatrix; end; The implementation of the TVector methods is as follows: { TVector class operator TVector.Implicit(const X: var i: Integer; begin SetLength(Result.FData,High(X)+1); for i:=0 to High(X) do Result.FData[i] end; } class operator TVector.Implicit(const X: var i: Integer; begin SetLength(Result.FData,High(X)+1); for i:=0 to High(X) do Result.FData[i] end;

array of Point): TVector;

:= X[i] Vector): TVector;

:= X[i]

class operator TVector.Explicit(const X: TVector): String; var P: Point; begin Result := '('; for P in X.FData do Result := Result + FloatToStr(P) + ','; if Length(Result) > 1 then Result[Length(Result)] := ')' else Result := Result + ')' end; class operator TVector.Negative(const X: TVector): TVector; var i: Integer; begin SetLength(Result.FData, High(X.FData)+1); for i:=0 to High(X.FData) do Result.FData[i] := -X.FData[i] end; class operator TVector.Equal(const X,Y: TVector): Boolean; var i: Integer; begin if High(X.FData) High(Y.FData) then raise EVectorSizeMismatch.Create('TVector.Equal'); Result := True; for i:=0 to High(X.FData) do Result := Result AND (X.FData[i] = Y.FData[i]) end;

Bob Swart Training & Consultancy

- 135 -

www.drbob42.com

Delphi XE Starter Essentials

A. Answer to Exercises

class operator TVector.NotEqual(const X,Y: TVector): Boolean; begin Result := not (X = Y) end; class operator TVector.Add(const X,Y: TVector): TVector; var i: Integer; begin if High(X.FData) High(Y.FData) then raise EVectorSizeMismatch.Create('TVector.Add'); SetLength(Result.FData,High(X.FData)+1); for i:=0 to High(X.FData) do Result.FData[i] := X.FData[i] + Y.FData[i] end; class operator TVector.Subtract(const X,Y: TVector): TVector; begin Result := X + (-Y) end; class operator TVector.Multiply(Factor: Point; const X: TVector): TVector; var i: Integer; begin SetLength(Result.FData,High(X.FData)+1); for i:=0 to High(X.FData) do Result.FData[i] := Factor * X.FData[i] end; class operator TVector.Multiply(const X: TVector; Factor: Point): TVector; begin Result := Factor * X end; class operator TVector.Multiply(const X,Y: TVector): TMatrix; var row,col: Integer; begin SetLength(Result.FData, High(X.FData)+1, High(Y.FData)+1); for row:=0 to High(X.FData) do begin for col:=0 to High(Y.FData) do Result.FData[row][col] := X.FData[row] * Y.FData[col] end end; And the implementation of the TMatrix methods is as follows: { TMatrix } class operator TMatrix.Explicit(const X: TMatrix): String; var row: Integer; begin Result := '('; for row:=0 to High(X.FData) do Result := Result + String(TVector(Vector(X.FData[row]))) + ','; if Length(Result) > 1 then Result[Length(Result)] := ')' else Result := Result + ')' end;

Bob Swart ([email protected])

- 136 -

February 2011

Delphi XE Starter Essentials

A. Answer to Exercises

class operator TMatrix.Negative(const X: TMatrix): TMatrix; var row,col: Integer; begin SetLength(Result.FData,High(X.FData)+1); for row:=0 to High(X.FData) do begin SetLength(Result.FData[row], High(X.FData[row])+1); for col:=0 to High(X.FData[row]) do Result.FData[row][col] := - X.FData[row][col] end end; class operator TMatrix.Equal(const X,Y: TMatrix): Boolean; var row,col: Integer; begin if High(X.FData) High(Y.FData) then raise EMatrixSizeMismatch.Create('TMatrix.Equal'); for row:=0 to High(X.FData) do if High(X.FData[row]) High(Y.FData[row]) then raise EMatrixSizeMismatch.Create('TMatrix.Equal'); Result := True; for row:=0 to High(X.FData) do for col:=0 to High(X.FData[row]) do Result := Result AND (X.FData[row][col] = Y.FData[row][col]) end; class operator TMatrix.NotEqual(const X,Y: TMatrix): Boolean; begin Result := not (X = Y) end; class operator TMatrix.Add(const X,Y: TMatrix): TMatrix; var row,col: Integer; begin if High(X.FData) High(Y.FData) then raise EMatrixSizeMismatch.Create('TMatrix.Add'); for row:=0 to High(X.FData) do if High(X.FData[row]) High(Y.FData[row]) then raise EMatrixSizeMismatch.Create('TMatrix.Add'); SetLength(Result.FData,High(X.FData)+1); for row:=0 to High(X.FData) do begin SetLength(Result.FData[row], High(X.FData[row])+1); for col:=0 to High(X.FData[row]) do Result.FData[row][col] := X.FData[row][col] + Y.FData[row][col] end end; class operator TMatrix.Subtract(const X,Y: TMatrix): TMatrix; begin Result := X + (-Y) end;

Bob Swart Training & Consultancy

- 137 -

www.drbob42.com

Delphi XE Starter Essentials

A. Answer to Exercises

class operator TMatrix.Multiply(Factor: Point; const X: TMatrix): TMatrix; var row,col: Integer; begin SetLength(Result.FData, High(X.FData)+1, High(X.FData[0])+1); for row:=0 to High(X.FData) do for col:=0 to High(X.FData[row]) do Result.FData[row,col] := Factor * X.FData[row,col] end; class operator TMatrix.Multiply(const X: TMatrix; Factor: Point): TMatrix; begin Result := Factor * X end; class operator TMatrix.Multiply(const X,Y: TMatrix): TMatrix; // NxM * MxO = NxO var row,col: Integer; i: Integer; begin for row:=0 to High(X.FData) do if High(X.FData[row]) High(Y.FData) then raise EMatrixSizeMismatch.Create('TMatrix.Multiply'); SetLength(Result.FData, High(X.FData)+1, High(Y.FData[0])+1); for row:=0 to High(X.FData) do begin for col:=0 to High(Y.FData[0]) do begin Result.FData[row,col] := 0; for i:=0 to High(X.FData[row]) do Result.FData[row,col] := Result.FData[row,col] + X.FData[row,i] * Y.FData[i,col] end end end; See section 6 for more questions about TVector and TMatrix.

3. RTL & VCL Enhancements In this section, I’ve covered the RTL and VCL enhancements – new as well as enhanced components and classes have been covered. We’ve also covered the new Touch and Gesture functionality, plus the COM and ActiveX enhancements in Delphi 2009 –found in the icons as well as the wizard, but more notably in the .ridl files that now contain the main source for the definitions.

Exercise #1 The initial interface ITaskbarList can be used to add, delete, and activate tabs in the taskbar. Experiment with the ActivateTab and SetActiveAlt methods (who only activate the tab on the taskbar, and not the window itself, by the way).

Answer to Exercise #1 Create a VCL Forms Application, and place at least two buttons on the form (I’ve placed four, so I can also implement and demonstrate the DeleteTab and AddTab functions from the ITaskBarList interface again. In the FormCreate, call the CreateComObject passing CLSID_TaskbarList as argument, casting the result as ITaskbarList to extract the interface, and call the HrInit function.

Bob Swart ([email protected])

- 138 -

February 2011

Delphi XE Starter Essentials

A. Answer to Exercises

The implementation of the four buttons is as follows: procedure TFormWorkshop.btnDeleteTabClick(Sender: TObject); var FormHandle: THandle; begin if not Application.MainFormOnTaskBar then FormHandle := Application.Handle else FormHandle := Application.MainForm.Handle; TaskbarList.DeleteTab(FormHandle); end; procedure TFormWorkshop.btnAddTabClick(Sender: TObject); var FormHandle: THandle; begin if not Application.MainFormOnTaskBar then FormHandle := Application.Handle else FormHandle := Application.MainForm.Handle; TaskbarList.AddTab(FormHandle); end; procedure TFormWorkshop.btnActiveTabClick(Sender: TObject); var FormHandle: THandle; begin if not Application.MainFormOnTaskBar then FormHandle := Application.Handle else FormHandle := Application.MainForm.Handle; TaskbarList.ActivateTab(FormHandle); end; procedure TFormWorkshop.btnSetActiveAltClick(Sender: TObject); var FormHandle: THandle; begin if not Application.MainFormOnTaskBar then FormHandle := Application.Handle else FormHandle := Application.MainForm.Handle; TaskbarList.SetActiveAlt(FormHandle); end; You can now click on each of the four buttons to see the effect (on Windows 7 or higher).

Exercise #2 The final interface, ITaskbarList4, can be used to set some tab options. type STPFLAG = Integer; const STPF_NONE STPF_USEAPPTHUMBNAILALWAYS STPF_USEAPPTHUMBNAILWHENACTIVE STPF_USEAPPPEEKALWAYS STPF_USEAPPPEEKWHENACTIVE

= = = = =

0; $1; $2; $4; $8;

This means that we can set the Thumbnail for the application as “always” or only when the application is active (i.e. the current active tab), and we can allow a “peek” always, of only when active.

Bob Swart Training & Consultancy

- 139 -

www.drbob42.com

Delphi XE Starter Essentials

A. Answer to Exercises

type ITaskbarList4 = interface(ITaskbarList3) [SID_ITaskbarList4] function SetTabProperties(hwndTab: HWND; stpFlags: STPFLAG): HRESULT; stdcall; end; Experiment with these options, and figure out what their effect is.

Answer to Exercise #2 This time, we have to include the definitions for all ITaskbarList interfaces, and extract them all in the FormCreate, as follows: procedure TFormWorkshop.FormCreate(Sender: TObject); begin TaskbarList := CreateComObject(CLSID_TaskbarList) as ITaskbarList; TaskbarList.HrInit; Supports(TaskbarList, IID_ITaskbarList2, TaskbarList2); Supports(TaskbarList, IID_ITaskbarList3, TaskbarList3); Supports(TaskbarList, IID_ITaskbarList4, TaskbarList4); end; Using the same application from the previous exercises, we can add five more buttons and implement them passing the flag 0, $1, $2, $4 or $8, as follows: procedure TFormWorkshop.btnNoneClick(Sender: TObject); var FormHandle: THandle; begin if not Application.MainFormOnTaskBar then FormHandle := Application.Handle else FormHandle := Application.MainForm.Handle; TaskbarList4.SetTabProperties(FormHandle, STPF_None); end; procedure TFormWorkshop.btnThumbAlwaysClick(Sender: TObject); var FormHandle: THandle; begin if not Application.MainFormOnTaskBar then FormHandle := Application.Handle else FormHandle := Application.MainForm.Handle; TaskbarList4.SetTabProperties(FormHandle, STPF_USEAPPTHUMBNAILALWAYS); end; procedure TFormWorkshop.btnTHumbActiveClick(Sender: TObject); var FormHandle: THandle; begin if not Application.MainFormOnTaskBar then FormHandle := Application.Handle else FormHandle := Application.MainForm.Handle; TaskbarList4.SetTabProperties(FormHandle, STPF_USEAPPTHUMBNAILWHENACTIVE); end; procedure TFormWorkshop.btnPeekAlwaysClick(Sender: TObject); var FormHandle: THandle; begin if not Application.MainFormOnTaskBar then FormHandle := Application.Handle

Bob Swart ([email protected])

- 140 -

February 2011

Delphi XE Starter Essentials

A. Answer to Exercises

else FormHandle := Application.MainForm.Handle; TaskbarList4.SetTabProperties(FormHandle, STPF_USEAPPPEEKALWAYS); end; procedure TFormWorkshop.btnPeekActiveClick(Sender: TObject); var FormHandle: THandle; begin if not Application.MainFormOnTaskBar then FormHandle := Application.Handle else FormHandle := Application.MainForm.Handle; TaskbarList4.SetTabProperties(FormHandle, STPF_USEAPPPEEKWHENACTIVE); end; According to MSDN, you should see the following effect now. STPF_NONE: No specific property values are specified. The default behavior is used: the tab window provides a thumbnail and peek image, either live or static as appropriate. STPF_USEAPPTHUMBNAILALWAYS: Always use the thumbnail provided by the main application frame window rather than a thumbnail provided by the individual tab window. Do not combine this value with STPF_USEAPPTHUMBNAILWHENACTIVE; doing so will result in an error. STPF_USEAPPTHUMBNAILWHENACTIVE: When the application tab is active and a live representation of its window is available, use the main application's frame window thumbnail. At other times, use the tab window thumbnail. Do not combine this value with STPF_USEAPPTHUMBNAILALWAYS; doing so will result in an error. STPF_USEAPPPEEKALWAYS: Always use the peek image provided by the main application frame window rather than a peek image provided by the individual tab window. Do not combine this value with STPF_USEAPPPEEKWHENACTIVE; doing so will result in an error. STPF_USEAPPPEEKWHENACTIVE: When the application tab is active and a live representation of its window is available, show the main application frame in the peek feature. At other times, use the tab window. Do not combine this value with STPF_USEAPPPEEKALWAYS; doing so will result in an error.

Exercise #3 Delphi 2010 and XE contain a special TTouchKeyboard component. One which shows the keyboard layout for the current locale. Experiment with the TTouchKeyboard and use it in numerical layout.

Answer to Exercise #3 The TTouchKeyBoard has two modes that can be toggled using the Layout property. If you set Layout from the default “Standard” to “Numerical”, then you get a Numerical keyboard layout. Like the Standard keyboard, this mode also has a minimum size:

Bob Swart Training & Consultancy

- 141 -

www.drbob42.com

Delphi XE Starter Essentials

A. Answer to Exercises

Exercise #4 Manipulate the TTouchKeyboard and reconfigure some of the keys. Use Chris Bensen’s blog at http://chrisbensen.blogspot.com/2009/12/hacking-ttouchkeyboard-part-i.html As example exercises, first remove all keys from the numerical layout except for the digits and the dot. (i.e. remove the left arrow, the slash, multiply, minus, plus and return keys). As second exercise, modify the virtual keyboard in Standard layout by removing the “Fn” key, and changing the second Alt key (at the right of the spacebar) into a € character that will insert the € character into the input stream.

Answer to Exercise #4 I’ve written a unit HackKeyboardLayout that is able to perform these two tricks. Based on the blog post from Chris Bensen, I’ve defined the following support class and methods: type THackVirtualKeys = class private FKeys: TList; FTopMargin: Integer; FBottomMargin: Integer; function GetItem(Index: Integer): TVirtualKey; procedure SetItem(Index: Integer; const Value: TVirtualKey); public constructor Create; destructor Destroy; override; procedure Add(var AKey: TVirtualKey); function Count: Integer; property Items[Index: Integer]: TVirtualKey read GetItem write SetItem; default property TopMargin: Integer read FTopMargin write FTopMargin; property BottomMargin: Integer read FBottomMargin write FBottomMargin; end; THackVirtualKeyboard = class(TList); THackKeyboardLanguages = class(TList); THackTouchKeyboard = class(TTouchKeyboard); { THackVirtualKeys } procedure THackVirtualKeys.Add(var AKey: TVirtualKey); begin FKeys.Add(AKey); end; function THackVirtualKeys.Count: Integer; begin Result := FKeys.Count; end; constructor THackVirtualKeys.Create; begin FKeys := TList.Create; FTopMargin := 0; FBottomMargin := 0; end;

Bob Swart ([email protected])

- 142 -

February 2011

Delphi XE Starter Essentials

A. Answer to Exercises

destructor THackVirtualKeys.Destroy; begin FKeys.Free; inherited; end; function THackVirtualKeys.GetItem(Index: Integer): TVirtualKey; begin Result := FKeys[Index]; end; procedure THackVirtualKeys.SetItem(Index: Integer; const Value: TVirtualKey); begin FKeys[Index] := Value; end; As welll as one function HackKeyboard that implements my wishes, using a local procedure RemoveKeyByCaption, implemented as follows: procedure RemoveKeyByCaption(Layout: TVirtualKeyLayout; const Caption: string); var Row, Col: Integer; Keys: THackVirtualKeyboard; Key: TVirtualKey; begin Keys := THackVirtualKeyboard(Layout.Keys); for Row := 0 to Keys.Count - 1 do begin for Col := 0 to Keys[Row].Count - 1 do begin Key := Keys[Row][Col]; if Key.Caption = Caption then begin Keys[Row].FKeys.Delete(Col); if Keys[Row].Count > 0 then begin if Col = 0 then begin Key := Keys[Row][0]; Key.LeftMargin := 0; Keys[Row][0] := Key; end; if Col = Keys[Row].Count then begin Key := Keys[Row][Col - 1]; Key.RightMargin := 0; Keys[Row][Col - 1] := Key; end; end; Exit; // done, don’t look any further end; end; end; end; With this procedure at hand, we can remove the keys from the numerical keyboard:

Bob Swart Training & Consultancy

- 143 -

www.drbob42.com

Delphi XE Starter Essentials

A. Answer to Exercises

var Layout: TVirtualKeyLayout; Stream: TResourceStream; TempStream: TStream; LayoutName: String; Keys: THackVirtualKeyboard; MyVK: TVirtualKey; begin if TouchKeyboard.Layout 'Standard' then begin LayoutName := 'NUMPADKEYBOARD'; Stream := TResourceStream.Create(HInstance, LayoutName, RT_RCDATA); try Layout := TVirtualKeyLayout.Create; try Layout.LoadFromStream(Stream); Keys := THackVirtualKeyboard(Layout.Keys); Keys[3][3] := Keys[0][3]; Keys[1].FKeys.Delete(3); Keys[3].FKeys.Delete(3); Keys.Delete(0); // delete first row Layout.KeyboardName := 'CustomNum'; Layout.KeyboardType := 'Num'; TempStream := TMemoryStream.Create; try Layout.SaveToStream(TempStream); TempStream.Position := 0; TKeyboardLayouts.LoadFromStream(TempStream); TouchKeyboard.Layout := 'Num'; THackTouchKeyboard(TouchKeyboard).CreateKeyboard('en'); TouchKeyboard.Redraw finally TempStream.Free end; finally Layout.Free end; finally Stream.Free; end; end end; The result can be seen below (note that you only get this effect at run-time, not at design time):

Bob Swart ([email protected])

- 144 -

February 2011

Delphi XE Starter Essentials

A. Answer to Exercises

For the standard layout, the code is a bit more complex, since we not only have to remove one key, we also have to modify the caption and the behaviour of another one: var Layout: TVirtualKeyLayout; Stream: TResourceStream; TempStream: TStream; LayoutName: String; Keys: THackVirtualKeyboard; MyVK: TVirtualKey; begin if TouchKeyboard.Layout = 'Standard' then begin LayoutName := 'STANDARD102KEYBOARD'; Stream := TResourceStream.Create(HInstance, LayoutName, RT_RCDATA); try Layout := TVirtualKeyLayout.Create; try Layout.LoadFromStream(Stream); RemoveKeyByCaption(Layout, 'Fn'); Layout.KeyboardName := 'CustomFoo'; Layout.KeyboardType := 'Foo'; Keys := THackVirtualKeyboard(Layout.Keys); MyVK := TVirtualKey.Create; MyVK.Caption := '€'; SetLength(MyVK.ComboKeys, 3); MyVK.ComboKeys[0].Vk := 17; MyVK.ComboKeys[0].ScanCode := 0; MyVK.ComboKeys[1].Vk := 18; MyVK.ComboKeys[1].ScanCode := 0; MyVK.ComboKeys[2].Vk := -1; MyVK.ComboKeys[2].ScanCode := Keys[0].FKeys[6].ScanCode; // 5 Keys[4].SetItem(3, MyVK); TempStream := TMemoryStream.Create; try Layout.SaveToStream(TempStream); TempStream.Position := 0; TKeyboardLayouts.LoadFromStream(TempStream); TouchKeyboard.Layout := 'Foo'; THackTouchKeyboard(TouchKeyboard).CreateKeyboard('nl'); TouchKeyboard.Redraw finally TempStream.Free end; finally Layout.Free end; finally Stream.Free; end; end end; The most tricky part of this code is the assignment of the ComboKeys – producing a set of keystrokes that will result in the € character in the keyboard buffer. The keystroke is Alt Gr + 5, where the Alt Gr key is done using the Vk codes 17 and 18, followed by the scancode for the “5” (which is stored in the Keys[0].FKeys[6].ScanCode key already).

Bob Swart Training & Consultancy

- 145 -

www.drbob42.com

Delphi XE Starter Essentials

A. Answer to Exercises

The result, with the working € character next to the spacebar, can be seen in the following screenshot::

Note that some cleaning up is needed when you close the application, otherwise you may get a warning that you have some memory leaks left if you compile your application with the ReportMemoryLeaksOnShutdown option set to True.

Note that this memory leak only occurs for a numerical layout, so there’s probably some memory leak in my code on page 273. The memory leak is reported when you add the following: {$IFDEF DEBUG} ReportMemoryLeaksOnShutdown := True; {$ENDIF} but only when you close the application – it’s not a growing memory leak.

4. Unicode In this section, I’ve covered Unicode and explained how Delphi 2009 and later work with Strings and Characters in a Unicode world. We’ve seen how AnsiString and UnicodeStrings behave themselves, how to do file I/O and what to take care off when doing Unicode conversions or migrations. We’ve also covered some globalization and localisation techniques using Delphi XE. I’ve mentioned SysUtils and the regional settings, as well as Resourcestrings and their use (and the fact that they won’t work in combination with runtime packages). I’ve also demonstrated the use of the Translation Editor for VCL for Win32 applications, and shown how to switch from one language to another (and back to the original default language, even without a resource DLL).

Bob Swart ([email protected])

- 146 -

February 2011

Delphi XE Starter Essentials

A. Answer to Exercises

Exercise #1 Write a little application that verifies that the file is a UTF-8 or UTF-16 file based on the BOM. Hint: use the TBinaryReader to verify that the file starts with the UTF-16 BOM.

Answer to Exercise #1 We can use a TBUtton and a TOpenDialog component to locate a file, and then use an instance of the TBinaryReader to get the first few bytes from the file and compare it to the COM of UTF-8 or UTF-16. Finally using the TBinaryReader.BaseStream, we can get the size of the actual file and report that as well. procedure TForm3.Button1Click(Sender: TObject); const UTF8: Array[0..2] of Byte = ($EF, $BB,$BF); UTF16: Array[0..1] of Byte = ($FF, $FE); var BinaryReader: TBinaryReader; Size: Integer; Bytes: Array[0..2] of Byte; begin if OpenDialog1.Execute then begin BinaryReader := TBinaryReader.Create(OpenDialog1.FileName); if BinaryReader.BaseStream.Size > 2 then try Bytes[0] := BinaryReader.ReadByte; Bytes[1] := BinaryReader.ReadByte; if (Bytes[0] = UTF8[0]) and (Bytes[1] = UTF8[1]) then begin Bytes[2] := BinaryReader.ReadByte; if Bytes[2] = UTF8[2] then ShowMessage('UTF-8 file: ' + IntToStr(BinaryReader.BaseStream.Size) + ' bytes.') end else if (Bytes[0] = UTF16[0]) and (Bytes[1] = UTF16[1]) then ShowMessage('UTF-16 file: ' + IntToStr(BinaryReader.BaseStream.Size) + ' bytes.'); BinaryReader.Close; finally BinaryReader.Free end; end; end;

Exercise #2 Since UTF-8 files generally require less space than UTF-16 files, write a little application that for a given filename, verify that it’s indeed a UTF-16 file, and then read the contents and write it back as a UTF-8 file. Report the number of bytes the new file is smaller than the original file.

Answer to Exercise #2 First, we should rewrite the code that checks if a file it UTF-16 of UTF-8 to become more generic, checking for any BOM and returning the file size if successful, or -1 if the file is not of the correct type.

Bob Swart Training & Consultancy

- 147 -

www.drbob42.com

Delphi XE Starter Essentials

A. Answer to Exercises

function CheckBOM(const FileName: String; const BOM: TBytes): Integer; var BinaryReader: TBinaryReader; Check: Boolean; i: Integer; begin Result := -1; BinaryReader := TBinaryReader.Create(FileName); if BinaryReader.BaseStream.Size >= High(BOM) then try Check := True; for i:=0 to High(BOM) do if BinaryReader.ReadByte BOM[i] then Check := False; if Check then Result := BinaryReader.BaseStream.Size; BinaryReader.Close; finally BinaryReader.Free end; end; With this support function, the copying of a UTF-16 file to a UTF-8 file is now easy, and can be implemented as follows, using a TStringList as carrier: procedure TForm3.Button2Click(Sender: TObject); var Str: TStringList; OrignalSize, NewSize: Integer; begin if OpenDialog1.Execute then begin OrignalSize := CheckBOM(OpenDialog1.FileName, TEncoding.Unicode.GetPreamble); if OrignalSize > 0 then try Str := TStringList.Create; Str.LoadFromFile(OpenDialog1.FileName); Str.SaveToFile(OpenDialog1.FileName, TEncoding.UTF8); NewSize := CheckBOM(OpenDialog1.FileName, TEncoding.UTF8.GetPreamble); ShowMessage(Format('Old Size: %d -> New Size: %d' , [OrignalSize,NewSize])); finally Str.Free end; end; end;

The information in this courseware manual is © 2011 by drs. Robert E. (Bob) Swart of Bob Swart Training & Consultancy. All Rights Reserved. The information in this courseware manual is presented to the best of my knowledge at the time of writing. However, in case of errors or omissions, I welcome your feedback or comments (by e-mail) as Bob Swart Training & Consultancy cannot be held responsible for any damage that results from using the information in this manual or the example source code snippets. Thanks in advance for your understanding.

Bob Swart ([email protected])

- 148 -

February 2011
Delphi XE Starter Essentials - Programacao Delphi

Related documents

156 Pages • 44,211 Words • PDF • 4.4 MB

466 Pages • 163,257 Words • PDF • 2.9 MB

1,194 Pages • 368,639 Words • PDF • 19.3 MB

135 Pages • 35,241 Words • PDF • 641.3 KB

1,200 Pages • 301,805 Words • PDF • 9 MB

32 Pages • 6,719 Words • PDF • 476.9 KB

121 Pages • 21,272 Words • PDF • 4.4 MB

22 Pages • 1,060 Words • PDF • 1.3 MB

128 Pages • PDF • 23.5 MB

712 Pages • 270,643 Words • PDF • 7.1 MB

21 Pages • 414 Words • PDF • 3.2 MB