You are on page 1of 163

Code Faster in Delphi

Alister Christie
00000001

Copyright
Copyright © 2020 by Alister Christie

All rights reserved. No part of this publication may be reproduced, distributed, or


transmitted in any form or by any means, including photocopying, recording, or
other electronic or mechanical methods, without the prior written permission of
the publisher, except in the case of brief quotations embodied in critical reviews
and certain other noncommercial uses permitted by copyright law. For
permission requests, email alister@LearnDelphi.tv.

ISBN: 9798686719118
First Edition

Dedication
For Alex, the best reason for this book being so late.

001
00000010
Table of Contents
Copyright................................................................................................1
Dedication..............................................................................................1
Table of Contents..................................................................................2
Foreword................................................................................................6
Preface....................................................................................................7
Acknowledgements...............................................................................8
Introduction..........................................................................................9
Conventions Used in this Book..............................................................................9
Scope.......................................................................................................................10
Code Samples..........................................................................................................10
Code Faster by Typing Faster..............................................................11
Touch Typing..........................................................................................................11
Getting Started with Touch Typing.......................................................................13
Know Thy Keyboard Shortcuts.............................................................................15
The Delphi Code Editor.......................................................................16
Keyboard Shortcuts...............................................................................................16
CodeInsight............................................................................................................26
Code Templates......................................................................................................27
MultiPaste...............................................................................................................31
The Editor Toolbar.................................................................................................32
IDE Insight..............................................................................................................33
Structure View........................................................................................................34
The Class Explorer.................................................................................................36
Code History...........................................................................................................37
Macros....................................................................................................................39
Surround................................................................................................................40
SyncEdit..................................................................................................................41
The Delphi Form Designer.................................................................42
Keyboard Shortcuts...............................................................................................42
Quick Edits.............................................................................................................44
Quick Actions.........................................................................................................45
Add Control and Add Component........................................................................45
Object Inspector.....................................................................................................46
Structure View........................................................................................................47
The Component Palette........................................................................................49
Editing the Form’s Source.....................................................................................51

002
00000011
Editing the Clipboard............................................................................................52
Aligning Controls...................................................................................................52
Position....................................................................................................................... 53
Alignment (and Size)............................................................................................... 53
VCL Guidelines.......................................................................................................... 56
Windows Magnifier.................................................................................................. 57
Customising the IDE...........................................................................58
IDE Layout..............................................................................................................58
Unpinning and Undocking......................................................................................58
Desktop Speedsettings............................................................................................. 61
Changing the ToolBar and ToolButtons.............................................................63
Welcome to the Dark Side.....................................................................................64
Write Your Own IDE Plugin..................................................................................66
Further Learning...................................................................................................... 70
Language Features...............................................................................71
Interfaces................................................................................................................71
Further Learning...................................................................................................... 73
Generics..................................................................................................................74
Generic Collections.................................................................................................. 76
Anonymous Methods............................................................................................77
Variable Capture....................................................................................................... 80
Anonymous Threads..............................................................................................81
Further Learning....................................................................................................... 81
Inline Variables and Type Inferencing................................................................82
Know the RTL......................................................................................84
Measuring Time....................................................................................................84
Generic Collections...............................................................................................86
TDictionary................................................................................................................ 88
Further Learning...................................................................................................... 90
Parallel Programming..........................................................................................90
No Parallel Example................................................................................................. 91
Background Thread Example.................................................................................93
Multiple Tasks Example......................................................................................... 94
Parallel For Example................................................................................................ 97
Further Learning...................................................................................................... 99
Regular Expressions.............................................................................................99
IP Address Validation............................................................................................. 100
IsMatch...................................................................................................................... 101
Match........................................................................................................................ 102
Matches..................................................................................................................... 103
Replace...................................................................................................................... 104
Summary.................................................................................................................. 104
003
00000100
Further Learning.................................................................................................... 105
Enhanced RTTI.....................................................................................................105
Reading Properties................................................................................................. 105
Writing Properties.................................................................................................. 107
Further Learning.................................................................................................... 108
FireDAC.................................................................................................................108
TFDConnection....................................................................................................... 109
Adding a TFDQuery.................................................................................................. 111
But There’s More..................................................................................................... 113
Further Learning..................................................................................................... 113
Tools and Plugins..............................................................................114
Third-Party Tools................................................................................................114
cnWizards / cnPack..............................................................................................114
Structural Highlighting......................................................................................... 114
Tab Order................................................................................................................... 117
Component Prefix Wizard.....................................................................................119
ModelMaker Code Explorer................................................................................120
Live Documentation................................................................................................ 121
Class Browser........................................................................................................... 122
Tip of the Day.......................................................................................................... 124
The MMX Toolbar................................................................................................... 124
Navigator..............................................................................................................125
Bookmarks............................................................................................................128
CodeSite................................................................................................................129
Further Learning..................................................................................................... 131
GExperts................................................................................................................131
Clipboard History.................................................................................................... 131
File Favorites........................................................................................................... 132
AutoCorrect.............................................................................................................. 132
Backup Project......................................................................................................... 133
Other Non-Delphi Specific Tools.......................................................................135
Third-Party Libraries..........................................................................................135
Metaprogramming............................................................................136
Case study - BDE Replacement............................................................................136
Find and Replace...................................................................................................137
In the IDE.................................................................................................................. 137
Turbo GREP.............................................................................................................. 137
Delphi AST............................................................................................................140
DFM Parser............................................................................................................141
reFind....................................................................................................................143
Mida Converter.....................................................................................................145
cnWizards Property Corrector............................................................................146
004
00000101
GExperts Replace Components...........................................................................147
Your Physical Environment.............................................................149
Hardware..............................................................................................................149
Keyboard.................................................................................................................. 149
Mouse........................................................................................................................ 149
Computer................................................................................................................. 149
Screens...................................................................................................................... 150
Chair.......................................................................................................................... 150
Desk........................................................................................................................... 150
Other Considerations...........................................................................................151
Environmental......................................................................................................... 151
Interruptions............................................................................................................ 151
Multitasking............................................................................................................ 152
Sharpening the Saw...........................................................................153
Where to go when you are Stuck.........................................................................153
Google is Your Friend............................................................................................. 153
Asking Questions.................................................................................................... 154
Stack Overflow......................................................................................................... 154
Recommended Reading.......................................................................................154
Social Networks....................................................................................................155
Facebook................................................................................................................... 155
LinkedIn.................................................................................................................... 155
Twitter...................................................................................................................... 155
Meetup...................................................................................................................... 155
YouTube.................................................................................................................... 156
StackOverflow......................................................................................................... 156
Delphi-PRAXiS........................................................................................................ 156
Becoming Known as an Expert...........................................................................156
What’s Improved Productivity Worth................................................................157
As an Employer........................................................................................................ 157
As an Employee....................................................................................................... 158
Self Employed.......................................................................................................... 158
Diminishing returns on investment...................................................................158
Further Learning.................................................................................................... 159
Final Words and Conclusion............................................................160

005
00000110
Foreword
I’ve been around Delphi since the very beginning. I was lucky enough to be
invited onto the beta after being a long-time member (on CompuServe!) of the
Borland Pascal community. Somewhere in my basement is still a copy of
“Wasabi” -- an early code name for Delphi -- on floppy disks. Can you imagine
delivering Delphi on floppies today?

Anyway, Delphi was a revelation. Real, compiled code done in a RAD way. I was at
the launch event on February 14, 1995, in San Francisco. People were ten deep in
the Borland booth watching demos. There was a considerable commotion as
people waited to get a free CD with a pre-release copy on it. Jaws dropped at the
evening demo when Delphi gracefully recovered from a General Protection Fault.
It made a huge splash, and soon was one of the dominant tools in the
marketplace.

Why? One word: Productivity.

You could simply build things faster with Delphi. Start adding the flood of native,
built-in-Delphi components that soon followed, and you were running circles
around the C++ and VB developers. You could build great-looking, sophisticated,
data-accessing Windows applications with ease. Many business applications
were made, many companies were founded, and many ISVs released outstanding
products -- all because of Delphi.

Delphi was productive because it was designed that way. It was designed to make
it easy to use, easy to build components for it, easy to use those components, and
generally easy to do things that used to be really hard. The people who designed
Delphi used it themselves, and so were driven to make it productive and capable.
After all – they benefitted just like we all did.
And that’s what Alister’s book is all about: productivity. How do you use Delphi
to be better, faster, stronger? How do you get what is in your head out and into
the Code Editor faster? How do you leverage what the tool provides to make
things better? What is Code Insight all about? Read this book and you’ll know.
You’ll learn about keyboard shortcuts and productivity hacks and useful RTL
classes and a whole lot more. It’s a long stream of productivity goodness.

I’m a book author too. I know how much work it takes to write and publish a
book. Take it from me. This is one of the good ones.

Nick Hodges
Boyertown, PA
June 2020

006
00000111

Preface
I like a book title that has a double meaning, and this is one of them.

Firstly, this book is aimed at making you faster at programming. If you read and
absorb the contents of this book, it will certainly do just that. Secondly is the
implication that programming in Delphi is faster - which I fully agree with. Very
powerful and useful applications can be built in Delphi without too much effort -
and often much less effort than in other languages or tools.

To get the most out of this book you will only need a basic knowledge of Delphi,
however, this book will be useful to both beginner and expert alike. No matter
how long you’ve been using Delphi, you're going to have a whole lot of “I can’t
believe I didn’t know that Delphi does that!”, and “I’ve wasted so much time in
the past doing this manually!”, and, “where was this book years ago?”. Yeah, I
hear you. You’ve got it now, and that’s what counts.

This book will not teach you the fundamentals of how to program.
This book will not teach you the fundamentals of the Delphi language.

This book WILL teach you to Code Faster in Delphi.

This book will make you faster at coding, but not better at programming in Delphi
- at least not directly - that’s my next book, “Code Better In Delphi”. By writing
more code in a shorter space of time, you will, of course, get better in less time
because you’ll hopefully have more time to think and spend much less time
pounding keys like a monkey.

007
00001000
Acknowledgements
There are always a great many people to thank for helping to get a book
published, and the book you are reading is no different. While all of the content is
original, it still takes many eyes to spot mistakes, omissions, and just to make the
text easy to read. Thank you to these people who have helped make this book
possible.

Joachim Dürr
Primož Gabrijelčič
Malcolm Groves
Michael Riley
Harry Stahl
Jackie Thomas-Teague
Shane van de Vorstenbosch
Danny Wind
And of course, Nick Hodges for writing such a lovely foreword.

008
00001001

Introduction
Coding can be fun, and the faster you can code the more fun you are going to
have. This book is all about making you more efficient in the Delphi IDE, starting
with something that is not even related to Delphi specifically - touch typing. If
you’ve been hunting and pecking for a while now, I hope you’ll be convinced to
put in the effort to make this change to drastically improve your output.

Next, we move onto getting the most out of the Code Editor and Form Designer by
taking advantage of shortcuts, tips and tricks - when put together will
significantly improve your productivity. I have included a shortcut table which
will give you the perfect place to find ways to make your life easier and help stop
you from doing it the hard way.

I’ll show you how you can customise the IDE to make things easier to find and
faster to reach. Next, we examine some language features that can make your
code easier to write and more expressive. I’ll reveal some powerful features of
the Delphi Run-Time Library (RTL) that can allow you to do some amazing things
with very little code.

Sometimes the Delphi IDE needs a little help, and we take a look at tools and
plugins that can offer some fantastic labour saving features. Why not build some
software to write your code for you? This is what we look at doing in the
Metaprogramming section - the art of using tools or writing code to write or
modify code.

Good programmers should be in good physical shape too. You need your fingers
to input code, and they are connected to the rest of your body - hopefully that’s
no surprise. So I’ll share with you some tips on not getting bent out of shape by
your physical environment.

Conventions Used in this Book


 Code is in a mono-spaced font: Value := GetValue(42);
 Shortcut keys in italics: Ctrl+C or Escape. In some instances, I’ve placed
square brackets around a key to make it easier to read, such as [;] or Ctrl+
[+]
 Menu items and commands are in bold: Edit|Copy or Save all
 References to controls are in Bold: Button1
 Instructions to type something are quoted: Give the variable the type of
“string”

009
00001010
Scope
In this book I’m going to cover a vast number of topics - many should be (and
are) entire books in their own right, which means that I can only cover them
briefly here. I hope this will give you enough of a taste for you to check out the
resources in the Further Learning sections. Some may even be expanded on in
future books of mine.

I invite you to let me know what you want to know more about and sign up for my
newsletter on LearnDelphi.tv to receive several bonus tips not covered in this
book. On my website you will also find a large number of free video learning
resources.

Code Samples
There are a few code samples included in this book, to get their full listing you
can download the source code via Git. You can find the code samples on
BitBucket:
https://bitbucket.org/alisterchristie/books
If you have Git installed, you can clone the repository:
git clone https://bitbucket.org/alisterchristie/books.git
This repository includes the source for all my books - you will be most interested
in the CodeFasterInDelphi sub-directory.
If you want to maximise your learning, you are best to manually type out the code
samples so that you can understand them more fully.

010
00001011

Code Faster by Typing Faster

Touch Typing
Chickens hunt and peck, not software developers.

One thing developers do is type significant amounts of text. To write code quickly
you need to be able to touch type, that is to say, you type without looking at the
keyboard. This is an important skill, really important. It allows you to keep
looking at the screen - rather than continually swapping your attention between
the keyboard and your source code.

One cool and creepy trick you can do: when someone requires your attention,
continue to finish typing whatever statement you are working on while looking
directly at them - this alone makes it worth learning to touch type.

Touch typing can take many years to develop. Find out how fast you can type by
going to TypingTest.com (I type at about 70 words per minute or wpm, which is
about six characters per second). You probably want to type at a speed of 45 wpm
or better (41 wpm is the average ‘computer typist’ speed, but if you want
something to aspire to, the world record is 212 wpm). Handwriting is about 30
wpm so even if you are an average typist you can out-type your colleagues who
are still handwriting their code. I can’t think of a good reason for you to write
code with paper and a pen, so you can add this to your list of useless facts.
Although if you discuss this in a large enough group, you inevitably have
someone say something that begins with “In my day…”, mentions something
about ‘punch cards’, and ends with “walking miles barefoot through the snow”.

Keyboards are important, and yes my mechanical keyboard allows me to type


slightly quicker and more accurately than a squishy membrane keyboard, but
your mileage may vary. I spent a bit of time in a real bricks and mortar computer
store trying out various keyboards - and I’ve been using a keyboard that has
something similar to CHERRY MX Brown switches. You can go down a serious
rabbit hole learning about the relative merits of different keyboard switches
(such as volume, speed, travel, clickiness and RGB-ness). Some people are very
opinionated about the type of keyboard they use. I’ve used ergonomic keyboards,
cheap keyboards and expensive keyboards, but I would recommend that if you
can touch type, invest in a good mechanical keyboard. They’re also suitable for

011
00001100
gaming - at last, a business justification for upgrading your tech to play first-
person shooters.

Typing.io is specifically designed to test your skills at typing in a programming


context. This is important as programming tends to use lots of less commonly
used symbols, for comments, array indexing, etc. As with any new skill, initially,
your productivity will suffer as you concentrate on remembering where the keys
are located, however, over time you will greatly surpass the speed of your “hunt
and peck” contemporaries. There are a vast number of resources online and just
Googling “how do I speed up my typing” will give you a good start.

I learnt to touch-type at school (at around 11 years of age) on large electric


typewriters when living in Canada. I’ve never had to use an electric typewriter in
my career. However, the skill has served me exceptionally well in the intervening
decades and probably will continue to be a valuable skill for a few decades to
come. I’m still waiting for my future brain interface that will finally be faster
than reaching for the keyboard, but for now, touch typing is the quickest way of
entering text.

There have been a few other keyboard innovations. For one thing, the QWERTY
layout is not the most efficient. It wasn’t designed for typing efficiency, instead,
it was designed so that the old manual mechanical keyboards wouldn’t lock up by
moving the commonly used keys away from each other - effectively designed to
slow down typing speed. Different keyboard layouts such as Dvorak allow you to
type even faster (speed typing records are set on this layout), however, if you
change your keyboard layout to Dvorak, swapping between yours and other
people’s keyboards it can do your head in. If you want to try it though, you can
change the settings on your computer without needing to buy a new keyboard.
What the keys say on your keyboard won’t matter, you aren’t looking at the keys
anyway, right?

Caps Lock

Shift Shift

Dvorak keyboard (From Wikipedia, public domain image)

012
00001101
Near where I live there are parking meters that require you to type in your license
plate details and the keyboards are in alphabetical order. This sounds simple in
principle, but I find myself having to hunt down the keys, it takes forever just to
enter six characters. I wonder if the rest of the population, like me, would find a
QWERTY layout easier?

Another observation on keyboards. I live on a lifestyle block (Hobby Farm, or


Smallholding) where we have WWOOFers and HelpX visitors stay with us for a
New Zealand rural experience. If you don’t know what those things are, they are
travellers who get food and accommodation while they help out on the farm.
They are quite often German or French, and when they hand me their phone to
type in the wifi password I often mistype. It takes serious concentration not to
assume an English QWERTY layout - even when using an on-screen keyboard
and there are no physical keys - both the French and German keyboard layouts
are different from English ones, AZERTY and QWERTZ respectively.

° ! " § $ % & / ( ) = ? `
ˆ 1 2 ² 3 ³ 4 5 6 7 { 8 [ 9 ] 0 } ß \ ´
Q W E R T Z U I O P Ü *
@ € + ~
A S D F G H J K L Ö Ä '
#
> Y X C V B N M ; : _
< | µ , . -
Strg (Win) Alt Alt Gr (Win) (Menu) Strg

The German QWERTZ keyboard layout (Wikipedia, released under the Creative
Commons Attribution-Share-Alike License 3.0)

Getting Started with Touch Typing


If you look at your keyboard, you should notice some raised bumps on the F and J
keys on your keyboard (and the 5 on the numeric keypad). These bumps allow
you to find these keys without looking at the keyboard and indicate where you
place your index fingers (your pointing, or trigger fingers, the ones next to your
thumbs). Your left index finger on the F and right index finger on the J. Line up
your other fingers on the keys either side of your index fingers. Your fingers
should rest on the ASDF for the left hand and JKL; for your right hand. Your
thumbs should naturally rest on the space bar.

This is called the home position, and it’s where your fingers return when you are
not typing. In the beginning, after you type a single letter your finger should

013
00001110
return to its home key, but as you get faster your fingers dance over the keyboard,
and return to the home keys only when you pause to think.

Touch Typing fingering

Each key will be controlled by a specific finger, the colour coded diagram above
shows which finger you should use for each key (if you are reading this in colour).
You will notice that your pinkies (little fingers, on the yellow keys) have to reach
for far more keys (including Shift, Ctrl, etc.), particularly your right little finger
which is responsible for many symbols that are quite commonly used in
programming.

To practice, you need to type text without looking at the keyboard, which can be
very challenging in the beginning. You can place a towel over your hands and
keyboard to prevent you from looking if you find yourself cheating. By cheating,
you only cheat yourself (or some other equally trite saying, but really, you’re not
harming me or anyone else - so cheat away if you desire). Take whatever steps
necessary to move through the frustration rather than be defeated by it. Initially,
you have to think where each letter is located and which finger to use, later you
are automatically able to type the letter just by thinking of it. When you become
skilled, you will just think of the word, and the characters appear magically on
the screen - almost as quickly as it will when we get that brain implant interface
mentioned earlier. It is this level you want to be able to achieve. This will be the
point where you realise how much you need to improve your spelling because you
can no longer blame it on your erratic typing technique. What is life if not the
opportunity for constant self-improvement?

The thing that still challenges me is the symbol keys that I have to reach with my
right pinkie finger as many of these are quite a stretch, this is where I find myself
glancing at the keyboard - or worse yet lifting my hand and using one of my other
fingers. Don’t do it Alister, don’t do it! And neither should you.

014
00001111
Know Thy Keyboard Shortcuts
You should avoid reaching for your mouse as much as possible. Each time you
have to take your hands away from the keyboard, you are missing out on valuable
keystrokes. This isn't ergonomic advice about Occupational Overuse Syndrome /
Repetitive Strain Injury (OOS/RSI), just what makes you an efficient software
developer. Of course, you should take regular breaks, and build up your wrist
strength by doing some real exercise - no, gaming on your computer doesn't
count as exercise.

Keyboard shortcuts are almost always faster than reaching for the mouse and
click navigating menus, so take time to become familiar with them. Initially, you
may find yourself reaching for the mouse, but as you are about to click on a menu
item notice the keyboard shortcut listed beside it. Cancel your mousing, and use
the keyboard shortcut instead. This initially makes you slower as you are both
reaching for the mouse then using the shortcut. It will get better. Eventually, you
will learn the shortcut and not reach for the mouse at all. Touch typing also gives
you the added advantage of not needing to take your eyes off the screen.

You can find a list of keyboard shortcuts below in the Delphi Code Editor and
Form Designer sections. You can also change the shortcuts if, for instance, you
prefer Visual Studio or Emacs emulation. However, I recommend sticking with
the default setting, especially if, at some point in your career, you work in a team.
If you do any peer programming sharing a keyboard, you may find your sanity
being tested, as all the key combinations that you’ve spent years internalising do
the wrong thing entirely. Make sure your teammates also aren’t the sort to go
reinventing the wheel on this either, or tears may be shed.

Some IDE plugins can also redefine/overwrite some of the default shortcuts (and
those of each other). Frustration may ensue, so install them at your peril
(actually you will probably want to install at least one as they can improve
productivity significantly, but more on this later). Examples of plugins that
overwrite shortcuts are GExperts, cnWizards and MMX (Model Maker Code
Explorer). You may want to reconfigure these shortcuts back to the defaults if
you like to have them.

The actual keyboard shortcuts are covered later in chapters The Delphi Code
Editor (next) and The Delphi Form Designer.

015
00010000
The Delphi Code Editor
As a Delphi developer, you don’t have much choice here, other than the particular
version of Delphi. Different versions of Delphi have differing levels of stability
(Delphi 2005 bad, Delphi 2007 better. Codenamed versions even better - Seattle,
Berlin, Tokyo, Rio, and Sydney, at time of writing). Generally speaking, IDE
stability has been improving over successive versions, so you are usually better
off with a newer version of Delphi. General performance and start-up times can
also be worthy of consideration, some versions were quite slow, while others
received improvements. Over time additional features and shortcuts have been
added, these can also significantly improve your productivity as long as you take
advantage of them. As a rule, you will almost always be better off with the latest
version of Delphi.

In this section, I’m going to look at some of the features and tools available
within the IDE, many of these are extremely underutilised. But first, let’s have a
look at some keyboard shortcuts (actually it’s going to be LOTS of keyboard
shortcuts).

Keyboard Shortcuts
Some of these shortcuts have been in Delphi since Version 1, and some are very
recent. Many I use without even thinking about them, others I use less often, but
are still beneficial. I’ve included as many keyboard shortcuts as I could find, this
could be pretty close to a definitive list. Some are not used very frequently but
can save lots of effort (e.g. Extract Method), others get used all the time
(Copy/Paste). I’ve taken these from the Delphi DocWiki1, Fandom Wiki2 and other
sources.

Hopefully you will be able to execute your most commonly used ones without
looking at the keyboard because we are going for speed here.

Shortcuts for the Form Designer are covered in a later chapter, although many are
the same.

1 http://docwiki.embarcadero.com/RADStudio/en/Default_Keyboard_Shortcuts
2 https://delphi.fandom.com/wiki/Default_IDE_Shortcut_Keys

016
00010001
Shortcut Name Description

Editing Commands

Ctrl+C Copy, Cut, and You should certainly know these ones, they
Ctrl+X Paste are your basic editing commands - but I’ve
Ctrl+V seen too many people use the mouse to use
these from the Edit menu.

Ctrl+Insert Copy, Cut, and These are alternative keystrokes used by


Shift+Del Paste Delphi and other applications, which date
Shift+Insert back to 1987. They can be used in the version
control window (e.g. Subversion Commit) as
the conventional shortcuts don’t work there.

Ctrl+Z Undo If you never make mistakes then you don’t


Alt+Backspace need this one, but for us mere mortals…

Ctrl+Shift+Z Redo Typically used if you have pressed Undo too


many times (or held it down too long) - i.e. to
undo the Undo command.

Ctrl+Y Delete line I use this shortcut all the time, particularly
when removing blank lines.

Ctrl+Shift+Y Delete to end of It’s slightly faster than pressing Shift+End,


Ctrl+Q+Y line Delete.

Ctrl+T Delete to end of Deletes from the cursor to the end of the
word current word.

Ctrl+Mouse Drag on Copy Text Using this or the Shift alternative is an easy
Selected Text way of copy / cut / paste text, without
affecting the clipboard (I’ll often use these if
I have something in the clipboard already).

Shift+Mouse Drag Shift Text See above.


Selected on Text

Ctrl+/ Comment Line Adds or removes // at the beginning of the


line, then moves to the next line. Allows you
to comment out a number of lines fairly
quickly. You can also highlight a block of text
and comment it all at the same time. It can
also be used to remove comments.

Ctrl+Space Code See the section on CodeInsight, this one is a


Completion major time-saver, so it gets its own section.

Ctrl+Shift+Space Parameter Displays the parameters for a procedure.


insight

Ctrl+Alt+Shift+P Synchronise Use this to synchronise the procedure


Prototypes parameters between the interface section and

017
00010010
its implementation (works both ways). This
shortcut is super handy when you change the
parameters of a procedure.

Ctrl+Shift+C Class Use this to create an implementation of a


Completion class from the signature of its interface (or in
reverse).

Ctrl+Backspace Delete back Works like backspace, but deletes entire


word words/identifiers.

Ctrl+J Code Templates Brings up the code templates selection


popup, or executes a code template if it’s only
one that matches. Also, see the section on
Code Templates.

Ctrl+Shift+J SyncEdit Allows you to alter all matching identifiers in


a selected block of text. See the section on
SyncEdit.

Ctrl+Shift+G Insert GUID Use this to create a new GUID, particularly


useful for interfaces.

Ctrl+Shift+I Indent Block Indents a selected block of code. You can also
Tab select the text and press Tab, which is a
Ctrl+K+I newer addition and has all but replaced this
shortcut.

Ctrl+Shift+U Unindent Block The reverse of above.


Shift+Tab
Ctrl+K+U

Ctrl+Shift+R Record Macro Allows you to record a number of keystrokes


as a macro. This can be very useful if you
have a large number of repetitive keystrokes
to perform. Macros get their own section
below.

Ctrl+Shift+P Playback Macro Playback the macro you’ve recorded.

Ctrl+H Show Hint Displays the popup that appears when you
hover the mouse over an identifier. When
you are hovering with the mouse the hint will
disappear, but with the shortcut it is more
persistent.

Ctrl+Shift+T Add To-Do Pops up the Add / Edit To-Do item.


Item

Ctrl+S Save File Saves the current file that is being edited.

Ctrl+Shift+S Save All Save all files that have changes pending.

Insert Toggle Insert / This toggles the Insert / Overwrite mode in

018
00010011
Overwrite the editor. I find this annoying, and don’t use
it - see the example below in Write Your Own
IDE Plugin.

Ctrl+M Same as Enter

Ctrl+N New Line Adds a newline after the cursor, but stays on
the current line.

Ctrl+D Format Source This will format the current file or selected
text based on the configuration in Tools|
Options|Language|Formatter|Delphi

Ctrl+I Insert Tab Inserts a Tab character. Your keyboard is


almost guaranteed to have a Tab key these
days, might be useful if it’s broken?
Otherwise, why use two keys when one will
do?

Ctrl+O+O Insert compiler This will insert compiler options at the top of
options the unit currently in effect.

(, [, and { Smart Surround If you have some text selected, typing an


opening bracket will automatically place a
closing bracket at the end of the selection.

Selection

Double-click Select identifier

Ctrl+W Select A really cool shortcut, the first time you press
identifier, then it, whatever identifier under the cursor is
expand selected. If you press it repeatedly, the
selection will be expanded to ever bigger
scope until the entire file is selected. Outside
of Delphi Ctrl+W is ‘close window’ - you’ve
been warned.

Shift+Keyboard Select Do this with combinations of navigation


Navigation shortcuts, e.g. Ctrl+Shift+END to select to the
end of the file.

Ctrl+A Select All Selects the contents of the current file in the
editor or the contents of an edit box.

Highlight with Mouse Select text Obvious, but I mention it anyway.

Alt+Highlight with Column Handy for deleting a column of text, or


Mouse or Selection adjusting an indent by a single character.
Shift+Alt+Left, Right, Can also use PageUp and PageDown if you
Up, Down want very tall columns.

Ctrl+Alt+Shift+Home/ Column Column selects to the first/last line of the file.


End Selection

019
00010100
Ctrl+O+C Block selection Toggles block selection on, i.e. it behaves like
mode Alt is held down when making text selection
(e.g. Shift+Arrow).

Ctrl+K+T Select word Selects the current word under the cursor.

Ctrl+O+L Toggle line When you toggle this on, Shift+Up/Down will
selection mode select entire lines.

Ctrl+O+K Switch to If you are in block or line selection mode, this


regular will switch you back
selection

Ctrl+Q+B Beginning of If you have a block of text selected, it will


block take you to the beginning of it. This is handy
if your cursor is at the end of the block, and
you want to expand your selection upwards.
Use this shortcut then Shift+Up or Shift+Left.

Ctrl+Q+K End of block Similar to above, but moves the cursor to the
end of the selection block.

Refactoring

Ctrl+Shift+E Rename It’s hard to name something correctly. If you


change your mind, use this to rename
something project-wide.

Ctrl+Shift+M Extract Method Extracts a selected block of code into its own
method, where possible. Sorts out
parameters and local variables. Doesn’t like
With statements (but you shouldn’t be using
these anyway) or Break / Exit statements.

Ctrl+Shift+V Declare A frequently used refactoring. Click within an


Variable undeclared variable and the IDE will create it
and determine its type where possible.

Ctrl+Shift+D Declare Field Similar to Declare Variable, but will add a


field to a class or record.

Ctrl+Shift+A Add Uses Place the cursor over code that is missing a
unit, and this will find and add the unit to the
uses section. Can be a bit (i.e. very) slow on
larger projects.

Ctrl+Shift+X Change Do this on a method or procedure and it will


parameters bring up a dialog that allows you to add,
remove, or change parameters. I prefer the
Change Prototypes (Ctrl+Alt+Shift+P)
shortcut these days.

Navigation Commands

020
00010101
Ctrl+Shift+Num Set Bookmark Where Num is a number from 0 to 9. Sets a
bookmark on a particular line of code. I use
bookmarks frequently to help navigate code.

Ctrl+Num Goto Bookmark Returns to a preset bookmark.

Ctrl+Left/Right Navigate by Much faster than just Left and Right


word character by character.

Alt+G Go to line Handy if you have and know a particular line


Ctrl+O+G number number you need to go to directly.

Ctrl+Click Go to I use this all the time to navigate to where


Alt+Up Declaration things are declared. Use the keyboard
shortcut and you won’t need the mouse.

Alt+Left Navigate Back Navigate back through the stack of locations


where you’ve used Ctrl+Click.

Alt+Right Navigate Return to somewhere you have navigated


Forward back from.

Ctrl+Enter Open file at Execute this when the cursor is inside a unit
Ctrl+O+A. cursor name in the uses to open it.

Ctrl+Up/Down Scroll Screen Keep the cursor stationary but scroll the
window.

Ctrl+Home/End Beginning / End Shifts the cursor to the very beginning or end
of File of a file.

Ctrl+Shift+Down Jumps between implementation and


interface. Works on methods and uses. UP
also does the same.

Ctrl+TAB Next Tab Will ‘tab’ forwards between open files with
the IDE.

Ctrl+Shift+TAB Previous Tab As above, but in reverse.

Alt+[ Match bracket If the cursor is on a bracket, this will navigate


Alt+] to the other bracket in the pair.

Ctrl+Q+L Toggle Ctrl+Alt Toggles the Ctrl+Alt+Navigation scope to


navigation either be limited by the current class or
current unit (effects the two shortcuts
below).

Ctrl+Alt+Wheel or Navigate Will put the cursor at the beginning of the


Ctrl+Alt+Up, Procedures next/previous procedure.
Ctrl+Alt+Down

Ctrl+Alt+Home Navigate Navigate to the first/last procedure.


Ctrl+Alt+End Procedures

021
00010110
Ctrl+Q+W Next item in Handy if you’ve just compiled, this will allow
message view you to navigate through several hints,
warnings, or errors in the message view one
at a time.

Ctrl+Q+D End Move the cursor to the end of the line.

Ctrl+Q+S Home Move the cursor to the beginning of the line.

Ctrl+Q+C Ctrl+End Move the cursor to the end of the file.

Ctrl+Q+R Ctrl+Home Move the cursor to the beginning of the file.

Ctrl+PageUp Cursor to Move the cursor to the first line in the


Ctrl+Q+E window top window.

Ctrl+PageDown Cursor to Move the cursor to the last line in the


Ctrl+Q+X window bottom window.

Ctrl+Q+T Window top to Move the editor window so that the first line
cursor is the current cursor position.

Ctrl+Q+U Window bottom Moves the editor window so that the last line
to cursor is the current cursor position.

Ctrl+Q+P Previous Use this to return the cursor to its previous


position position, e.g. after Ctrl+Home or PageDown.
Press it again to go back.

Code Folding

Ctrl+Shift+K+O Toggle On / Off Will enable or disable code folding in the IDE,
disabling it gives you a tiny bit extra
horizontal space if you don’t fold.

Ctrl+Shift+K+A Expand All Any collapsed folds in the current unit will
expand.

Ctrl+Shift+K+E Collapse Press repeatedly to fold additional scope.


Current

Ctrl+Shift+K+U Expand Current The reverse of above.

Ctrl+Shift+K+T Toggle Current Folds or unfolds the current structure.

Ctrl+Shift+K+R Collapse All Everything defined within any [$region ‘My


Regions Region’}...{$endregion} will be collapsed

Ctrl+Shift+K+P Collapse Nested These are the procedures within procedures.


Procedures This can be handy if you want to navigate to
the first line of a method when there are lots
of nested procedures.

Ctrl+Shift+K+M Collapse Collapse all the methods in the current unit.

022
00010111
Methods

Ctrl+Shift+K+C Collapse Collapse all the class definitions in the


Classes current unit.

Ctrl+Shift+K+G Collapse to Collapse down to interface and


Primary Groups implementation.

Ctrl+Shift+K+N Collapse Will effectively collapse the entire file. Not


Namespace / sure if this is particularly useful.
Unit

Searching

Ctrl+F Find Bring up the Find toolbar on the bottom of


Ctrl+Q+F the edit window. The text to find will default
to any select text or identifier under the
cursor.

Ctrl+Shift+F Find in Files Brings up the Find in Files dialog, it has Lots
of options for searching files in the current
project, or files on disc.

Ctrl+R Replace Shows a dialog that allows you to search for a


Ctrl+H pattern and replace it with something else.
Ctrl+Q+A

Ctrl+Shift+Enter Find References I love this; it will find all the references
within the current project of a given
identifier.

F3 or Ctrl+L Find Next Find the next occurrence of the previous


search.

Ctrl+E Incremental This will highlight any occurrences of the


Search text you type.

Ctrl+G Find Original For instance, do this on TForm and it will


Symbol take you to the declaration of TForm.

F6 or Ctrl+. IDE Insight Focuses the IDE Insight edit box, allowing
you to search the IDE.

IDE Commands

Ctrl+Shift+B Show Buffer A list of all the open files in the Editor.
List

F5 Toggle Sets or removes a breakpoint.


Breakpoint

Alt+F7, Alt+F8 Previous, Next Go to the previous/next error message in the


Message Messages View.

023
00011000
F1 Help I still use this occasionally, but usually
Google knows more.

Shift+F10 Context Popup Displays the popup context menu. It’s like
right-clicking - or pressing the context menu
key - if you have one. If you do, it will likely
be beside the right Ctrl key.

F4 Run to cursor Place the cursor on a line of code, and


execution will stop when that line is reached.
Allows you to use a breakpoint without
creating one.

F7 Step into When debugging is paused, this will step into


the current line of code.

F8 Step over When debugging is paused, this will step over


the current line of code.

Shift+F7 Trace Into Will trace into the next line of code (very
similar to Step into)

Shift+F8 Run until Finishes execution of the current method or


return procedure and breaks again.

F9 Run under Compile and run the current project, or


debugger continue if your application is paused.

Ctrl+F2 Program reset If your application is currently being


debugged, this will terminate it.

Shift+Ctrl+F9 Run Without Just as it says on the box.


Debugging

Alt+F5 Inspect While debugging brings up the Debug


Inspector window.

Ctrl+F7 Evaluate/ While debugging it allows you to change the


Modify value of a variable.

Ctrl+F5 Add Watch Adds a watch for the current symbol when
debugging.

Ctrl+F12 View Units Shows the Search for Units dialog.

Ctrl+Alt+P Tool Palette Navigate directly to the tool pallet search


window, this will allow you to add controls to
your form, or units to your application
without having to reach for the mouse.

Alt+F12 Form / Text Toggles between the graphical form


representation and its text counterpart.

Alt+F11 Use unit Allows you to add a unit from the current

024
00011001
application to the interface or
implementation uses section of a unit.

Ctrl+F11 Open Project Brings up the “open file” dialog for projects.

Ctrl+Alt+F11 Project Places focus in the Projects window


Manager
Window

F11 Object Toggle between the currently selected control


Inspector and the object inspector.

F12 Design / Code Toggles between the source code of a form


View and its GUI

Ctrl+F9 Compile Project Compiles (Compile units that have changed).

Shift+F9 Build Project Builds (Compile every unit).

Shift+F11 Add to Project Shows an open file dialog that allows you to
add a file to the current project.

Ctrl+F1 Topic Search Same as F1, although it should open the Topic
Search within Help.

Ctrl+F10 Activate Main Can also be done by tapping the Alt key.
Menu

Tool Windows

Alt+0 Window List If you have multiple edit windows open (by
Alt+[zero] right-clicking in the editor and selecting New
Edit Window or View|New Edit Window), this
will show you a list of your open windows.
Having multiple edit windows is a great way
to get the IDE to do “strange” things.

Shift+Alt+F11 Structure Opens and moves focus to the Structure tool


Window window.

Ctrl+Shift+B Buffer List Shows the Buffer List dialog, shows all open
files.

Ctrl+Alt+B Breakpoints Opens and moves focus to the Breakpoints


Window tool window.

Ctrl+Alt+S Call Stack Opens and moves focus to the Call Stack tool
Window window (a Debugging window).

Ctrl+Alt+W Watch List Opens and moves focus to the Watch List tool
Window window.

Ctrl+Alt+L Local Variables Opens and moves focus to the Local Variables
Window tool window (a Debugging window).

025
00011010
Ctrl+Alt+T Threads Opens and moves focus to the Threads tool
Window window (a Debugging window).

Ctrl+Alt+C CPU Window Opens and moves focus to the CPU view (only
available while debugging).

Ctrl+Alt+F FPU Window Opens and moves focus to the FPU tool
window (only available while debugging).

Ctrl+Alt+D Disassembly Opens and moves focus to the Disassembly


window tool window (only available while
debugging).

Ctrl+Alt+R Registers Opens and moves focus to the CPU Registers


window tool window (only available while debugging)

Ctrl+Alt+K Stack window Opens and moves focus to the CPU Stack tool
window (only available while debugging)

Ctrl+Alt+E, Memory Open one of four memory view windows.


Ctrl+Alt+(2-4) windows 1-4

Ctrl+Alt+V Event Window Opens and moves focus to the Events tool
window (for Debugging).

Ctrl+Alt+M Modules Opens and moves focus to the Modules tool


Window window (for debugging)

Shift+F12 Forms Window Shows a dialog with all the forms in your
application.

Ctrl+Shift+F11 Project Options Opens the Project Options dialog

Keyboard shortcuts that are of the form Ctrl+K+X (Ctrl plus two letters) can either
be: hold down Ctrl, then hold down K and press X or, hold down Ctrl, press K, then
press X or ever Ctrl+K followed by Ctrl+X.

CodeInsight
CodeInsight is built into the IDE and can help autocomplete the code you are
currently typing. CodeInsight can be extremely helpful in cutting down the
amount of typing you need to do. As fast as you can type, not typing is even
faster. CodeInsight can be invoked automatically (default), or manually by
pressing Ctrl+Space.

026
00011011

In the diagram, you can see CodeInsight recommending a list of identifiers that
begin with (or in Delphi 10.4 or later contain) “Roll”. It’s also worth noting that
you can resize the code complete window - I make mine quite tall, which allows
me to see more suggestions without having to scroll. You can select a suggestion
in the list by scrolling with the cursor keys (PageUp and PageDown also work),
then pressing Enter to select the one you want, or you can reach for the mouse
(which you already know is bad - I’ll let you slap your own wrist if you find
yourself doing it).

You can configure its options in Tools|Options|User Interface|Editor Options|


Code Insight - or you can type “code insight” into the IDE Insight box (Ctrl+. Or
F6) and find it there. There are a bunch of things to configure there. I find the
defaults just fine, although you might like to enable Auto Invoke if you work with
small projects - on more substantial projects it can be quite painful as it can
cause long delays as it tries to work out the auto-completion list.

In Delphi 10.4 Sydney CodeInsight received a substantial upgrade, it now uses the
Language Server Protocol (LSP) and should make the code editor much more
responsive.

Code Templates
Continuing on the theme of letting the IDE write code for you, Code Templates
are macros that let you type a few keystrokes which can expand out into blocks of
code. Place the cursor somewhere convenient then type the letter b, and press
Ctrl+J.

027
00011100
A list of code templates will popup with the begin...end template highlighted. If
you press Enter, the IDE automatically places the begin...end code block with
the cursor located within the block.

You can see the full list of code templates by pressing Ctrl+J on a blank line.
Additionally, you can go into View|Tool Windows|Templates

The Template tool window allows you to manage the templates.

Here is another example. Go to a type section of your unit and type in “classf”
then press Ctrl+J, then Enter. The full outline of a class definition replaces the
template shortcut within your code. From the public section (you should already
be there) type “propgs”, then Space, and it creates an outline for a property with
getter and setter. To name the property type “Test”, then Tab, “string” to give
it a type, and press Enter (or Tab again). You should get the following (I’ve
removed some blank lines):

028
00011101
interface

type
TMyClass = class(TComponent)
private
function GetTest: String;
procedure SetTest(const Value: String);
protected
public
property Test: String read GetTest write SetTest;
published
end;

implementation

{ TMyClass }

function TMyClass.GetTest: String;


begin
end;

procedure TMyClass.SetTest(const Value: String);


begin
end;

That’s quite a bit of code that you generated with only a few keystrokes.

It's certainly worth becoming familiar with the available templates and knowing
how to write your own. You can do this from the Templates window (View|Tool
windows|Templates) by editing an existing template and saving a modified copy.

029
00011110
The Templates tool window (undocked)

Double click a template to use it (or click the Insert/Play button), or use any of the
other buttons to Add, Remove, Edit or Filter the templates. You also can find
these actions via a Right-click.

Here is a listing of a simple template:

<?xml version="1.0" encoding="utf-8" ?>


<codetemplate
xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
version="1.0.0">
<template name="fan" invoke="auto">
<point name="variable">
<text>variable</text>
<hint>variable to be freed</hint>
</point>
<description>
FreeAndNil
</description>
<code language="Delphi" context="methodbody" delimiter="|">
<![CDATA[FreeAndNil(|variable|);]]>
</code>
</template>
</codetemplate>

030
00011111

This script allows you to type “fan”, then space (or Tab or Ctrl+J) and the fan is
replaced with FreeAndNil(variable), with the text variable selected, so you can
just type something to replace it.

The template name attribute is the shortcut text, and the invoke="auto"
indicates that it’s executed by just pressing space (otherwise you need to press
Ctrl+J). There can be multiple “points” which you can tab between, here I have
shown only one. The code section indicates what code should appear in the
editor, with the actual code appearing in the CDATA section.

MultiPaste
MultiPaste is a fantastic tool that is now standard in Delphi. It was originally part
of Castalia but was integrated directly into the IDE with its acquisition by
Embarcadero. It gives much more control over how you paste blocks of text into
your Delphi code. For instance, say we have some SQL we want to add to a
TFDQuery:

select u.*
from users u left join groups g on g.Userid = u.Userid
where u.LastName = 'Smith'
and u.FirstName = 'John'

We could paste this directly into the Code Editor, then add qryGroups.SQL.Add('
to the beginning of each line and '); to the end, plus escape the quote marks.
Which wouldn’t be tedious for this small example, but what if it was a hundred
lines? Then you’d be looking for a better option, right? Fortunately, there is one.
It’s MultiPaste, and it’s available from the Edit menu (Edit|MultiPaste).

031
00100000
As you can see in the MultiPaste dialog, we are prefixing and suffixing each line,
and any quote marks optionally escaped automatically. Just click OK to paste the
adjusted clipboard contents into our code. Because it can escape quotes, it can
even be useful if you want to paste a single line. I love this addition, not one I use
every day, but when I do use it, it’s a big time saver.

The Editor Toolbar

Stretching along the top of the code editor is the editor Toolbar. The first button
(Used Units) provides a list of all the units in the uses list - I’ve never found this
particularly helpful. However, the second button (File Sections) provides a
mechanism to jump to specific points in your code.

032
00100001

In particular, I find going to the uses clauses quite handy at times. The two
combo-boxes in the Toolbar show the Types and Methods (respectively) in the
current unit, click the dropdown or type text in it to filter. The final button,
Project Symbol Search, allows you to search your entire project based on types,
methods, and variables. Here I’m searching for “setting”.

As you can see, it gives you a list of what it has found. Clicking on an item takes
you straight to it in the code.

IDE Insight
IDE Insight is a search box that lets you search IDE for settings, controls, menu
items and various other things. You can find it on the title bar of the IDE window,
and its shortcut is Ctrl+[.] or F6. Once active, you can type in things to search for.
For instance:

033
00100010
Here I’m currently viewing a form in the editor, and I’m searching for “button”.
It has found several controls on the Component Palette, some Components on the
current form and many Preferences. IDE Insight is smart enough to be context-
aware. If you have the Form Designer open, you get a different list than from the
Code Editor. It also includes menu items that have been added by plugins.

IDE Insight has also included as a search box in Project|Options and Tools|
Options allowing you to quickly find rarely used or obscure options without
having to hunt for them.

Structure View
The Structure View shows different items depending on whether you are inside
the Form Designer or Code Editor. From inside the Code Editor, the Structure
View gives you an outline of your code. Double-click on a Structure View item,
and it navigates you straight to that section of your code.

You are likely aware that it shows you errors in your code as part of Error Insight.

034
00100011

Did you know that you can also use it to modify your code?

If you Right-click on a class and select New (or press Insert) you can add a new
field (by typing something like “name: string”), procedure or function
(“function NameValid(name: string): boolean”) or a property (“property name:
string”).

While this can be very handy, the Structure View does have many limitations. You
can declare global variables and functions - but only if you have one already in
your unit. While you can declare a property, it doesn’t allow you to specify getters
and setters. You can rename things, but it’s not for refactoring (it only renames
the declaration, not where it’s used). And while you can create a procedure with
parameters, you can’t change the parameters on an existing method. If you make
a mistake, Undo won’t help you either.

035
00100100
The Class Explorer
The Class Explorer is a tool window that is not visible by default, but you can
show it from View|Tool Windows|Class Explorer

The Class Explorer searches all units listed in your project (DPR) file. It lets you
navigate various declarations by double-clicking on them (or Right-click|Go to
Declaration), alternatively you can Shift+Click on a method and it takes you to its
implementation. The reverse is also true, if the Class Explorer is visible and you
right-click on something in the Code Editor and show it in the Class Explorer
(Right-click|Find in Class Explorer).

You can also easily add fields, properties and methods to classes by right-clicking
on them.

036
00100101

Here I’m declaring a function in the public section of a form. You can see it has
two parameters (arguments) and returns a TMineSweeperCell. Deletion of a
field, property or method is also possible from a Right-click, this can be a quick
way of deleting methods and properties as it removes both the interface and
implementation sections.

Arrangement of the class tree can be in three ways: Container (shown above)
where the ordering is by namespace (unit to class and subclasses). Derived to
Base where sub-nodes form the ancestors of classes (TAnimal would be a
subnode of TDog). And finally Base to Derived where sub-nodes contain
descendants (TDog would be a subnode of TAnimal).

Code History
In the bottom right corner of the Delphi code editor, you can toggle between the
Code and Designer (F12), or a third option History.

In History, you can see different revisions of your source files. Below I’ve
selected a form from my Minesweeper clone application.

It shows the history of this file with the most recent at the top. The first entry is
what is currently stored in memory, followed by the local file on disk. The

037
00100110
revisions with the blue ticks indicate what is stored in version control (Git in this
case) and the green icons are local backup files.

Every time you save a file in Delphi, a backup copy is saved into the __history
subfolder with an additional extension appended (for instance .~2~, the larger
the number, the more recent the file). You can configure how many backups you
want to keep in the Editor Options (Tools|Options|User Interface|Editor
Options|File backup limit). The default is 10, and the maximum is 90. I generally
find that ten is enough, and if I want to go back further, I use version control. I
don’t commit every time I save, so this provides an additional level of safety in
case I make a major mistake that for which Undo is insufficient. This is especially
true in the Form Designer where Undo is limited to the last thing you deleted.

Down the bottom right, you have the option to look at the differences between
the files.

Information is only handy to be able to see the full commit message, but
Differences allow you to compare two revisions and see their differences. I find
this very helpful to be able to compare the current file in memory with what is
saved to disk as a way of double-checking my changes are really what I intended.

If you examine a file that has an associated DFM or FMX file, you can select it to
see what changes have happened on the form or datamodule. This can be helpful
to see what properties have changed and what components were added (or
removed).

Below you can see how a comparison looks.

Here, the “not supported” message has been replaced by some useful code. The
new code, indicated by the + sign, and removed code, indicated by the - sign, is
determined by which revisions you select on the left and right. Here I’ve selected
an old revision on the left and the current revision on the right. If I reversed the

038
00100111
selection (old on the right and current on the left), it would look like I had
removed the code replacing it with the ShowMessage. You navigate the changes
by using the Next (Shift+Ctrl+F8) and Previous (Shift+Ctrl+F7) Difference buttons.

If you need to undo the changes from an entire file, Right-click on the file you
want to go back to and select Revert or select the file and use the Revert button. If
you need to revert a change within a revision, you can either copy and paste the
text you want, or use Beyond Compare via the Show in Difference Viewer button.
You can configure additional difference viewers via Tools|Options|User
Interface|Difference Viewer.

Here I’m configuring Tortoise Merge as an additional external difference viewer.


The Parameters are (which are pretty hard to read from the screenshot):

/base:$(LeftFileName) /mine:$(RightFileName) /basename:$(LeftDisplay)


/minename:$(RightDisplay)

If you want to add it and have TortoiseSVN installed.

Macros
The Delphi IDE has rudimentary macro recording ability, which allows you to
record a series of keystrokes, then play them back. Despite the lack of ability to
save or edit these macros, you can still do some smart things. Usually, this
involves doing repetitive keystrokes, for instance, adding semicolons to the end

039
00101000
of every line would be End, [;], Down repeat. You might get quite quick at this
series of keystrokes, but why not make things easier on yourself?

To record, press Ctrl+Shift+R, type in your keystrokes (like the End, [;], Down from
above), press Ctrl+Shift+R again to stop recording, to playback, press Ctrl+Shift+P.
If you have a large number of lines to apply this to, you can hold down
Ctrl+Shift+P to apply repeatedly.

If you can’t remember the shortcut keys, you can also use the buttons down the
bottom left of the edit window.

Use the round red Record Macro button to start recording, the square Stop
Recording Macro button to stop recording, and the green triangle to Playback
Macro.

Surround
If you highlight a block of text, you can use the Surround command (Right-click|
Surround) to wrap the text in various things. For instance, you can convert a
block of code to a comment by highlighting the text, Right-click|Surround|{.
Which starts the block with a “{” character and ends it with a “}”. You can
achieve a similar thing by highlighting and pressing { (a Smart Surround Key) or
Ctrl+/ on the keyboard (and pressing again to uncomment).

Say you have a block of code that you want to wrap in an if statement, typing this
manually means that you need to type in the if statement, the “begin” at the start
of the block and the “end” where you want it to finish, this can involve
considerable cursor movement. Alternatively, select the block, Right Click|
Surround|ifb, this automatically places the begin and end with the cursor inside
the if statement.

The surround commands are effectively code templates (it’s also possible to write
your own). This means that you can also access them from the code templates
window (View|Tool Windows|Templates) where you can double-click a template
to execute it.

040
00101001
SyncEdit
When you select some code in Delphi, you may have noticed a small icon in the
margin, clicking on it enables Sync Edit Mode (Ctrl+Shift+J).

Once enabled, it allows a dynamic search and replace within the selected code.

Sync Edit Mode enabled on a block of code.

In the image above, you can see that UpdateCaption is selected, and you may
notice that every instance of UpdateCaption has a box around it. If I change one
instance of UpdateCaption, each of the others are changed simultaneously.

Each of the underlined items is an identifier that can be changed with Sync Edit.
You can press the Tab key to move around between each of the identifiers. This
can be a swift way of renaming something in code. However, it’s important to
note that this is just a text-based search and replace and doesn’t understand the
syntax of the code, as you can see from the UpdateCaption comment that will
also change - this is often something you want to happen, but not always.

041
00101010
The Delphi Form Designer

Keyboard Shortcuts
There are a whole new set of keyboard shortcuts that you can use when designing
your forms. However, there are many that are already familiar. Additionally,
many shortcuts work in both the Code Editor and Form Designer (F9 to Run for
example), which are not listed here.

Shortcut Name Description

Ctrl+C Copy, Use these on a single control (and everything it


Ctrl+X Cut, and owns), or selected groups of controls. Be careful
Ctrl+V Paste when pasting between forms as you can lose events
and live bindings.

Selection

Cursor Keys Change selected This will change the focus of the selected control to
control one in the direction pressed.

Tab Next Control This will select the next control by tab order.

Shift+Tab Previous Control This will select the previous control in the tab
order.

Esc Select Parent This will select the parent of the currently selected
Control control, you can press this repeatedly until you
finally reach the form (or frame/datamodule).

Left-Drag+Esc Shift Parent If you left-click and hold on a control, then press
Esc, this will allow you to select the control’s parent
for dragging, allowing you to shift the parent
control (even though you might not be able to click
on it directly).

Left-Click Select Control

Shift+Click Add/Remove This will add or remove a control from your current
control to selection. If you do this to a singularly selected
selection control it will select the main form (or topmost
parent).

Ctrl+Drag Select multiple This will select all the controls that touch the box
controls made by dragging across the form or parent
control.

042
00101011
Drag Move selected If you start dragging on a control, it will move the
control, or select control. If you start on the form background it will
multiple controls work like Ctrl+Drag.

Ctrl+A Select All Selects all the controls in the Form Designer.

Editing Commands

Delete Delete Deletes the currently selected component(s). This


Component is just about the only thing that Undo works with in
the form designer.

Ctrl+Cursor Key Move selected This will shift the selected control(s) by one pixel in
control(s) the direction.

Shift+Ctrl+Curs Move selected This will shift the selected control(s) by eight
or Keys control(s) faster pixels.

Shift+Left/ Change Width This will increase (Right) or decrease (Left) the
Right width of the selected control(s).

Shift+Up/Down Change Height This will increase (Down) or decrease (Up) the
height of the selected control(s).

Ctrl+Z Undo This only really works to undo the last deleted
component.

Other Commands

Enter Toggle to Object This will switch between the Object Inspector and
Inspector or Form selected control.

Ctrl+H Hide non-visual This will hide/show any non-visual components on


components the form.

Double-Click Go to or create If you double click on a control, you will be taken to


default event its default event (e.g. OnClick for a TButton). If
there isn’t one, it will be created.

F12 Swap between Just like it says on the box.


Code Editor and
Form Designer

Alt+F12 Toggle between Use this if you want to modify the form as text.
Form and Form
Source

Right-Click Popup Menu Bring up the context menu for the current selection.
Shift-F10 If you have a menu key on your keyboard you can
also use that (note: keyboard shortcuts don’t
currently work on FMX forms).

Hold Shift or Show VCL If you have control(s) selected, holding down Shift
Ctrl Guidelines or Ctrl will make the VCL Guidelines visible.

043
00101100
There are additional shortcuts specific to other editors such as the Fields Editor
for a dataset which I’ve not listed above.

Fields Editor for a TClientDataSet

Quick Edits
Quick Edits are now available for the VCL and FireMonkey via Right-click|Quick
Edit… (or even faster with Right-click then [Q], or faster still pressing Shift+F10,
[Q] or even Shift+F10+Q, unfortunate the keyboard shortcuts only currently work
for VCL forms not FireMonkey). This gives you access to several common
properties for the selected control. Here is the dialog for a VCL TEdit control.

044
00101101

Here you can quickly change the name and alignment and layout properties.

Quick Actions
If you Right-click on a control, the first few items in the popup menu are the
Quick Actions.

As you can see, we can quickly select Left Justify and Right Justify, and Clear Text
for this Edit box. The Quick Actions also appear at the bottom of the Object
Inspector.

If you have limited screen space, you can Right-click on the Object Inspector,
select Properties and untick both Show quick action panel and Show status bar.
Hiding these will give you some additional height in the object inspector.

Add Control and Add Component


If you Right-click on a container (such as a panel, group box or the form), you get
two other menu items. Add Control allows you to add common controls such as
buttons and labels. Add Component gives you the ability to add an image list or
menu quickly.

045
00101110
Object Inspector

You can access the Object Inspector with the F11 shortcut. This takes you straight
to the search box where you can type in the property you are looking for, such as
“text” on a TEdit (you should see the properties HelpContext, Text, and
TextHint). Press Enter to change focus from the search box to the first property
and use the Up and Down keys to select the property you want. Then just type the
new value of that property. These shortcuts are much faster than reaching for
the mouse (you’ve probably noticed a central theme of “mouse bad, keyboard
good” already).

You can access the Object Inspectors properties from Right-click|Properties (or
Tools|Options|User Interface|Object Inspector).

046
00101111

If you (like myself) want additional screen real estate, unticking the Show quick
action panel, Show description panel, and Show status bar is useful. The Bold
non-default values option is a fast way of identifying which properties you have
adjusted on a control.

Structure View
We’ve already seen the structure view when editing code, but there is also much
you can do with it when designing a form. There are two really common things I
do in the Structure View. The first is to select a control, which selects it in the
object inspector, and the second is to re-parent a control.

Re-parenting is useful, for instance, you might have a button that is currently on
a form, but you want to shift it onto a panel.

047
00110000
You could Cut it from the form and Paste it onto the panel, and this works just
fine (unless you have live bindings). But instead, within the Structure View, drag
the button onto the panel. This is an easy way to shift controls from one container
to another.

Things get tricky when the form is complex, and you want to drag a control
between two containers that can’t both be visible in the Structure View at the
same time. You have several options to accomplish this. You could try to collapse
other controls so that you can get both visible, do some clever dragging as the list
scrolls when you drag a control near the bottom or top. Or make the structure
view larger - which you can do by undocking it from the IDE then maximizing it.

By Right-clicking on a control in the Structure View, you can access its context
menu, including its Quick Edit and any other special menu items. The Structure
View can also be a convenient place to Cut, Copy, Paste and Delete controls.

The Structure View can also provide you access to various other components that
are not selectable on the Form Designer (such as FireMonkey effects and
animations, or control sub-properties).

048
00110001
The Component Palette

Avoid using the mouse to add controls to your form, unless you want to place
them precisely. Instead, use the Ctrl+Alt+P shortcut, then type the name of the
control, then press Enter. This adds the new control as a child or sibling to
whatever control you have currently selected on the form. If you do use the
mouse, you can double-click the control to add it to the form, or drag it onto the
form, or select the control and click where you want it placed, or drag across the
form to define its size and position.

If you want to add many of the same control (5 radio buttons say), you can add
one and copy and paste the remainder, or you can Shift+Click the control in the
palette, then each time you click on the form it adds a control until you press
Escape or select a different control. If the Persist search filter option is enabled,
you can type the control you want then keep pressing Enter to add them.

There are several properties you can configure for the Tool Palette (Right-click|
Properties on the Tool Palette or Tools|Options|User Interface|Palette).

049
00110010
Most of which I pretty much ignore as I use the palette as a way to type text
searches. However, you can disable the captions and have larger buttons to make
the palette similar to Delphi 7.

Component Palette with large buttons and no captions.

You can also adjust the layout of the controls within the palette, just drag the
categories or controls to adjust their order in the list. You can also drag controls
between categories and create new categories (perhaps a favourites category).
050
00110011
Further, you can hide categories (Right-click|Delete on a category) and
individual controls (Right-click|Hide), or bring back either (Right-click|Unhide
Button). If you’ve made a mess with your customisations, you can Right-click|
Reset Palette… to go back to the original settings.

Editing the Form’s Source


You’ll love this extremely powerful technique that allows you to do some pretty
useful things. To access the source of the form Right-click on it and select View
as text (Alt+F12). This gives you the text representation of the form, which is the
“real” representation of the form as it is stored in the DFM file.

Something I do fairly regularly is to change a control’s type. For instance, below


I’ve changed a TButton to a TSpeedButton:

When I return to Edit as form (Alt+F12), I’m immediately confronted by an error


message:

051
00110100
TSpeedButton has no TabOrder property. If I had been diligent, I could have
removed it when I was editing the form’s source. However, clicking Ignore
achieves the same thing.

I’m not done yet as Button2 is still a TButton as far as the PAS file is concerned. If
I compile (Ctrl+F9) I get an additional error dialog. Just clicking Yes updates the
source file and everything compiles just fine, and Button2 is now completely
converted to a TSpeedButton.

Editing the Clipboard


When you copy controls to the clipboard, the details of the control are stored in
the clipboard as text (like they are in the DFM file). You can paste the control into
a text editor, modify it, copy the new text, then paste it back onto the form
designer. Or you could even create controls from scratch; a technique I've used to
great effect many times. For instance, to define the structure of a TFDMemTable, I
could write some code to build up a list of fields based on some other data I had,
then paste those fields into the TFDMemTable field list.

Aligning Controls
In the Form Designer, there are three tool palettes that you can take advantage of
that allow you to change the position and size of controls. They are Position,
Spacing and Align. They are not enabled by default so you may need to right-
click on the toolbar to select them.

052
00110101

Position

The first two buttons are Bring to Front and Send to Back. These change the z-
order of the controls allowing you to change which control is in front (or behind)
when two or more controls overlap. These commands can also be found on a by
Right-clicking the control and selecting Control, or from the IDE main menu,
Edit|Bring to Front and Edit|Send to Back.

The next two buttons (Center vertically and Center horizontally) allow you to
centre one or more selected controls either horizontally or vertically on their
parent.

Alignment (and Size)

This collection of a dozen commands allow you to line up controls in various


ways. You need multiple controls selected for all the alignment commands to be
enabled. Alignment occurs based on the anchor control. The anchor control is
the one with the black dots rather than grey dots surrounding it.

053
00110110
Here we can see that Button2 is the anchor control, and if I click Align left edges
(the first tool button), Button1 shifts right so that its left edge matches Button2
(Button3 and Button4 are already in alignment). If instead, I wanted all the
buttons to line up against Button1, I would click Button1 to make it the anchor
control. The rest of the alignment commands work similarly (aligning right,
vertically centred, top, bottom and horizontally centred).

Additionally, you can bring up the alignment dialog from Edit|Align or Right-
click|Position|Align, the commands in this dialog work just the same as the
buttons. This dialog gives you access to the alignment commands, the spacing
commands (below), and the centre commands (above).

There are three align to grid commands, these all have the hatching pattern in the
background, and are all based on the grid spacing, which you can adjust via
Tools|Options|User Interface|Grid size/Snap tolerance.

054
00110111

The default grid size is 8 pixels which I find perfectly acceptable. You can toggle
the visibility of the grid with the Display grid checkbox and toggle the Snap to
grid checkbox or its corresponding button on the alignment palette. Snap to grid
takes effect when you position or size a control with the mouse. The other two
buttons, Align to grid, adjusts controls so that their top left corner sits on the
grid, and Size to grid adjusts the control width, height and position to fit the grid
(although this doesn’t seem to work in recent versions of Delphi).

The final commands are sizing commands, like the Align commands they work
on multiple selected components based on the anchor control. Make same height
makes all the selected controls the same height as the anchor control. Make
same width does the same for widths. Make same size adjusts both width and
height.

You can also adjust sizing from Edit|Size or Right-click on the control and select
Position|Size

055
00111000
Spacing

This set of eight controls are enabled when you have multiple controls selected
and allows you to adjust the spacing between controls. Four adjust horizontal
spacing, and four adjust vertical spacing.

Space equally horizontally shifts controls so that the left sides of each control are
equally spaced apart. This works great if all the controls are the same size, but if
the controls are of different widths, the gaps between the controls become
uneven.

Increment horizontal space adds one pixel of additional space between controls.
Decrement horizontal space removes one pixel between all the controls. It
continues to work even once the controls overlap each other and keeps going
until their left sides match.

Remove horizontal space removes the space between controls so that they all
touch each other.

These previous three commands all work based around the anchor control, such
that the anchor keeps its position. If you want the space between the controls to
be equal, you can first remove the space between the controls, then increment the
space until you have the spacing you want.

The vertical commands work just the same way as their horizontal counterparts.

Another way of setting controls to be the same size is to have multiple controls
selected then adjust the appropriate property in the Object Inspector. For
instance, if you wanted their left edges aligned, you would set the Left property in
the Inspector.

VCL Guidelines
These were a fantastic addition to the Form Designer (and it would be great to
have an equivalent for FMX). These are the thin blue and pink lines that appear
when you move controls around on your VCL form. The blue lines allow you to
align the edges of controls, and the pink lines indicate when the base of text
elements are aligned.

056
00111001
Here you can see me moving Button1 between Button2 and Button3. The pink
line indicates that all the text is aligned, and the blue lines show that all the tops
and bottoms of the controls line up.

I generally get controls approximately positioned where they should be and use
Ctrl+Arrow to fine-tune their position and Shift+Arrow to alter their size,
adjusting until the desired lines appear. Another trick to check if a control is
aligned is to select it, then hold down Shift or Ctrl which makes the guidelines
appear (no lines indicate that controls may not be correctly aligned).

Windows Magnifier
If you are feeling short-sighted like Mr Magoo, squinting at your form layout
wondering if you need to move that label a pixel to the left, it might be time to
pull out the magnifying glass. The Magnifier app allows you to zoom in on your
screen by a specified amount. You can start the magnifier by pressing Window+
[+] and turn it off by using Window+Escape. You can configure it in Windows
settings (or by clicking the settings button on the app). Once enabled, it makes it
much easier to get that single-pixel positioning on controls that otherwise would
be impossible to do when viewing at 100% (particularly on high-resolution
laptop screens).

057
00111010
Customising the IDE

IDE Layout
The default IDE layout has been pretty standard for quite some time, it has
changed slightly over the years, but for the most part, it has been consistent and
quite usable. However, there are times when the default layout is not suitable.

Here is the default layout


A. Code/Form Editor
B. Structure View
C. Object Inspector
D. Project Window
E. Component Palette
F. Message Window

There are unlimited ways you can rearrange the IDE, but the focus should be on
maximising screen area for things you use most often (in particular the Code
Editor) and minimise for the amount of mouse travel so that tools and commands
that are commonly used together also have proximity.

Before we look at my preferred layout, let’s examine the ways you can customise
the IDE.

Unpinning and Undocking


One way you can make additional space is by closing windows, you can easily do
this by clicking the close button in the top right of each tool window.

058
00111011

The top of the Structure View, showing the pin and close buttons.

The windows can easily be re-opened by using the appropriate menu item or
shortcut-key. If we close the Structure View, we can open it again from View|
Tool Windows|Structure or by its shortcut (Shift+Alt+F11).

An alternative to closing windows is to un-pin them by clicking on the pin button


(next to the close button). Un-pinning collapses the tool window to a tab to the
edge of the IDE. You can pop out the window by moving the mouse over the tab or
using its shortcut key. The window pops back once you either move the mouse
outside the window or change focus to another window. Un-pinning is an
excellent alternative to closing windows, giving you quick access to less
frequently used windows while opening up additional screen real estate for the
other windows.

You can undock a tool window by dragging its title bar until a translucent box
appears. Drag the window to where you want it and release. If you move the
window near a suitable dock-site, it automatically tries to dock, but you can
prevent docking by holding down the Ctrl key.

Object Inspector undocked.

059
00111100
Once undocked and floating, you can shift a tool window around like any other
window. For instance, if you have multiple monitors, you can shift it to a
secondary screen and maximise it.

There are three ways you can dock windows together, side-by-side, top/bottom
or together as tabs. These can be combined to group your windows in more
useful ways.

Here is my preferred layout on my desktop:

A. Code/Form Editor
B. Structure View
C. Object Inspector
D. Project Window
E. Component Palette
F. Message Window

As you can see in the image, I group my windows to the left side and have a large
area for code on the right side with the message window below the other tool
windows to maximise the available height for the Code Editor. The tool windows
are on the left to reduce mouse and eye travel across the IDE so that things are
faster to access. I will also often have the Multi-Device Preview window docked
to the right side if I’m doing any FMX work. My preference is for the IDE to be all
docked together on one screen, and use additional screens for other windows
(email, web, documentation, etc.).

The layout on my laptop (which is pretty old) is different. There isn’t as much
screen height, so the message window has to fit below the code. Play around to
work out what suits your circumstances best.

060
00111101
Once you have arranged the IDE to how you want it, you may want to save that
layout so that should you break it, you can quickly restore it. Here’s how you do
that:

Desktop Speedsettings
The Speedsettings are accessible from a button and Combobox in the title bar of
the IDE.

By default there are four layouts that you can select, they are in order:
1. Classic Undocked. All the windows are undocked, and the component
palette on the toolbar is made visible, I’ve never been a fan of this layout,
but if you have come straight from Delphi 7 or below, this layout might be
more familiar to you.
2. Debug Layout. When you run your application with debugging enabled,
the IDE automatically swaps to this layout. It has different tool windows
visible that are more suited to debugging your application.
3. Default Layout. This is the IDE layout when you have a project open, and
is probably the one you would use most frequently.
4. Startup Layout. When you have no project open, this is the active layout.

You can choose a layout from the Desktop Speedsetting dropdown in the title bar
to be applied to the IDE. The Speedsetting button shows a popup menu.

061
00111110
Once you have arranged the IDE to how you want it to look, use the first option,
Save Desktop. This prompts for a name, enter an existing name to replace that
desktop, or create a new one.

Here I have altered the IDE so that only the editor is visible and I’m saving the
desktop with the name of “Code Only”. I can quickly go back to the default layout
by selecting the Default Layout from the Speedsetting combobox.

Notice how Code Only is now an option. You can create as many layouts as you
like, and even overwrite the defaults.

The next Speedsetting option is Set Desktop, it allows you to change the default
layout for when debugging (Set Debug Desktop...), editing a project (Set Default
Desktop...), or when no project is loaded (Set Startup Desktop...). You can choose
either the Dark, Light or Custom IDE themes or change the syntax highlighting
colour scheme via the Editor menu. These will be discussed further in ‘Welcome

062
00111111
to the Dark Side’. Finally Theme Options... allows you to choose the default
colour schemes for the Light and Dark themes.

Changing the ToolBar and ToolButtons


The main Delphi ToolBar offers quite a lot in terms of customisation. Firstly you
can shift around the tool groups by dragging their tool grips. I recommend you
line up all the tool groups in a single line beside the main menu, if your screen
real estate permits. This gives you slightly more vertical space for the rest of the
IDE. You can Right-click on the toolbar and enable/disable various groups (such
as the Align, Position and Spacing tool groups that we enabled in Aligning
Controls above).

Additionally, you can customise the toolbar at the button level (Right-click|
Customize|Commands)

From here you can drag and drop commands to the toolbar. Some of the cursor
movement is a bit confusing (i.e. not working correctly at time of writing), but if
you drop the button where the cursor is pointing and ignore the little button
preview, everything works fine.

You can reset a tool group (if you mess things up) on the Toolbars tab.

063
01000000
To remove a button, just drag it off the toolbar, and it will disappear. This allows
you to remove all the tool buttons you don’t use to make the IDE less cluttered.
Alternatively you can add some additional controls. One I often add to the Debug
toolbar is Run|Notify on Language Exceptions, which allows you to disable
exceptions in the debugger. This is otherwise hard to get to: Tools|Options|
Debugger|Embarcadero Debuggers|Language Exceptions|Notify on language
exceptions, if you really must know - although IDE Insight can help find it.

Another thing that you might like to do is enable the classic tool palette on the
toolbar (Right-click|Component). If you’ve recently shifted from an old version
of Delphi, you might feel more at home with this rather than the Component
Palette tool window. You will get a bit more horizontal space if you remove the
Component Palette tool window. Personally I prefer the Component Palette tool
window.

The classic Tool Palette

Welcome to the Dark Side


It may seem trivial to customise the font and colour of the IDE, but it can have an
impact on your productivity - particularly if you are working in the evening or in
a dark room (e.g. basement, cave or dungeon, you know, all the usual coding
workspaces).

Darker background colours are not only easier on your eyes, causing less eye
fatigue; they can also help you sleep better. In previous versions of Delphi, it was
quite difficult to swap to a dark theme, now it’s just a couple of clicks, by clicking
on the Speedsetting button, then selecting Dark.

064
01000001

Light vs. Dark theme in the IDE

There are substantially more ways you can customise the editor. To do so have a
look in the Editor properties either by Tools|Properties|User Interface|Editor or
within the editor, Right-click|Properties.

The default font is Courier New, but as far as pre-installed fonts go, I think
Consolas is a better choice for readability. A simple Google of “best programming
font” brings up lots of suggestions and debate about which font to choose.

You also need to decide what point size you want for the font: too small and you
may have difficulty reading the code. Too large, and you limit the amount of code
on the screen at any one time. Use the Ctrl+[Num+] and Ctrl+[Num-] to adjust the
font size in the Code Editor (or the Current editor font size widget at the bottom
of the Code Editor, or Right-click|Font size|Increase/Decrease font size). To
change the font, go to the Display properties (Tools|Options|User Interface|
Editor|Display) and pick the font you want to use. Only mono-spaced fonts are
listed.

If you don’t like the syntax highlighting scheme that Delphi uses, you can create a
custom version on the Color properties (Tools|Options|User Interface|Editor|
Display). I’ve found this to be a bit clunky to edit and maintain, so I’d
recommend using Rodrigo Ruz’s3 Delphi IDE Theme Editor4. It gives you
significantly more themes and allows you to save them to a file. This makes it

3 https://theroadtodelphi.com/
4 https://github.com/RRUZ/delphi-ide-theme-editor

065
01000010
much easier to shift your scheme to different installations of Delphi (either
different versions or on other computers). If you are working with multiple
versions of Delphi, it can be helpful to have a different theme for each version to
help tell them apart.

Write Your Own IDE Plugin


Sometimes you need to make the IDE do something that you can’t find in an
existing plugin. No problem.

In this example, we are going to disable the Insert key in the Code Editor. Usually,
Insert toggles between insert mode and overwrite mode. However, I’ve never had
an instance where overwrite mode has been useful; whenever I’ve enabled it, it’s
been a mistake and has caused me to type over something useful. So, wouldn’t it
be nice if we could disable it? Well, we can. We can build a project that uses the
Open Tools API, which allows us to control the IDE.

Here we are creating a unit that registers a keybinding with the IDE that disables
the Insert key. You can find this example in the DisableInsert\
DisableInsert.dpk project.

066
01000011
unit MyBinding;

interface

procedure Register;

implementation

uses Windows, Classes, SysUtils, ToolsAPI, Vcl.Menus;

type
TLearnDelphiKeyBinding = class(TNotifierObject,
IOTAKeyboardBinding)
private
procedure DoNothing(const Context: IOTAKeyContext; KeyCode:
TShortCut; var BindingResult: TKeyBindingResult);

public
function GetBindingType: TBindingType;
function GetDisplayName: string;
function GetName: string;
procedure BindKeyboard(const BindingServices:
IOTAKeyBindingServices);
end;

var
LearnDelphiKeyBindingIndex : integer = 0;

procedure Register;
begin
LearnDelphiKeyBindingIndex := (BorlandIDEServices as
IOTAKeyBoardServices).AddKeyboardBinding(TLearnDelphiKeyBinding.Cre
ate);
end;

procedure TLearnDelphiKeyBinding.BindKeyboard(const
BindingServices: IOTAKeyBindingServices);
begin
BindingServices.AddKeyBinding([ShortCut(VK_INSERT, [])],
DoNothing, nil);

067
01000100
end;

function TLearnDelphiKeyBinding.GetBindingType: TBindingType;


begin
Result := btPartial;
end;

function TLearnDelphiKeyBinding.GetDisplayName: string;


begin
Result := 'Disable Insert';
end;

function TLearnDelphiKeyBinding.GetName: string;


begin
Result := 'LearnDelphi.DisableInsert';
end;

procedure TLearnDelphiKeyBinding.DoNothing(const Context:


IOTAKeyContext;
KeyCode: TShortCut; var BindingResult: TKeyBindingResult);
begin
BindingResult := krHandled;
end;

initialization
finalization
if LearnDelphiKeyBindingIndex > 0 then
(BorlandIDEServices as
IOTAKeyboardServices).RemoveKeyboardBinding(LearnDelphiKeyBindingIn
dex);

end.

We also need to house this within a package that we can install into the IDE which
looks something like the following (I’ve removed much of the compiler directives
- which get added back if you go into Project|Options).

068
01000101
package DisableInsert;
{$R *.res}
{$DESCRIPTION 'Disable the Insert Key'}
{$DESIGNONLY}
requires
rtl,
designide,
vcl;
contains
MyBinding in 'MyBinding.pas';
end.

Once we have loaded the project, we can install it into the IDE (Right Click|Install
on the project file in the Projects tool window).

You can now test this by going into the Code Editor and pressing the Insert key -
no more overwrite mode.

If you now have a look in the Key Mappings (Tools|Options|User Interface|


Editor|Key Mappings), you now see that Disable Insert can be found in the
Enhancement modules list.

069
01000110
You can also confirm that the package is installed by looking in Components|
Install Packages, you can see it listed as Disable the Insert Key, which is the
package description we used above.

I will confess that the time working out this piece of code will probably not pay
back the time lost to undoing and retyping from being in overwrite mode.
However, it was very satisfying to write, and I don’t end up being angry at my
keyboard anymore. It might also lead to other ideas, hmmm, I have no Menu key
on my laptop - I wonder if I could repurpose the Insert key…

Further Learning
The Open Tools API is rather substantial, and I’m not even scratching the surface
of what’s achievable. If you want to learn more, then it is worth reading Dave
Hoyle book on the subject available at his website DavidHoyle.co.uk5 or the
whitepaper6 by Bruno Fierens.

5 https://www.davidghoyle.co.uk/WordPress/?p=1143
6 https://www.embarcadero.com/images/dm/technical-papers/extending-the-delphi-ide.pdf

070
01000111

Language Features
Delphi has advanced quite a bit since version 7, but many developers using the
latest version of Delphi still write code like it’s 2002. From my experience
working with other developers, I think the three most under-utilized language
features are: Interfaces (introduced in Delphi 3), Anonymous methods (Delphi
2009), and Generics (also Delphi 2009).

There are lots of other language features that are not used as much as they should
be (polymorphism would be one that springs to mind).

These language features allow you to write cleaner and more readable code
(which is a theme of my next book, ‘Code Better in Delphi’), but they can also
allow you to write code faster, so I’m introducing them in this book.

Interfaces
Delphi has supported interfaces for most of its life, however, few developers take
proper advantage of them. They make possible both the Dependency Injection
Pattern and the Dependency Inversion Principle (this is the D in the SOLID
principles). Both of these are extremely powerful techniques that can help make
your code more maintainable. To take advantage of them, you first need to
understand how interfaces work, which I’ll cover briefly.

Declare an interface like this:

type
IAnimal = interface
['{C7982869-9293-41C2-8294-4DCE28623435}']
procedure Speak;
procedure MoveSlow;
procedure MoveFast;
end;

The GUID (Globally Unique IDentifier - shortcut: Ctrl+Shift+G) is required to


identify an interface uniquely, and every interface requires its own GUID
(otherwise it would just be an ID) - strictly you don’t need to add one, but if you
want to be able to use as and is operators then you need the GUID.

Implementing an interface on a class looks like the following:

071
01001000
type
TAnimal = class(TInterfacedObject, IAnimal)
protected
procedure Speak; virtual;
procedure MoveSlow; virtual;
procedure MoveFast; virtual;
end;

TDog = class(TAnimal)
protected
procedure Speak; override;
procedure MoveSlow; override;
procedure MoveFast; override;
end;

I’ve inherited from TInterfacedObject rather than TObject, which I’ve done to
implement the minimum requirements for an interface, which are three special
methods (_AddRef, _Release, and QueryInterface). TInterfacedObject
implements these for you and will look similar to this:

TInterfacedObject = class(TObject, IInterface)


Protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult;
stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;

The actual implementation will depend on which version of Delphi you are using.
_AddRef increments the reference count (FRefCount), _Release decrements it,
and if it reaches zero, _Release also destroys the contents of the interface.

TDog inherits from TAnimal, and thus also implements IAnimal. All the rules of
polymorphism apply in the natural way to interfaces, and interfaces can also
inherit from each other. A given class can implement a whole list of interfaces.
This gives you a kind of multiple inheritance without many of the confusing
headaches that this brings to a language.

072
01001001
Interfaces give you automatic reference counting (ARC), which is a form of
automatic memory management. Each time you assign an interface, the _AddRef
method is called, and each time an interface goes out of scope or is assigned
something else _Release is called. This means that the following code doesn’t
leak memory:

var
Dog : IAnimal;
begin
Dog := TDog.Create;
Dog.Speak;
end;

When the Dog variable gets assigned a new TDog, the reference count for the
object is one, and when the procedure ends, the reference count drops to zero.
This causes the TDog object to be destroyed. Don’t worry, no actual dogs are
harmed in this example.

The lack of try..finally clauses and manual destruction when using interfaces
can greatly reduce the amount of code you need to write, this can make code
easier to maintain and faster to write. There is often a tradeoff with regards to
the difficulty of debugging however.

I’ll mention that you shouldn’t mix code containing the object implementation of
the interface and the interface itself as strange things can happen. For instance,
if you pass a variable of TDog into a procedure that accepts an IAnimal, when that
procedure ends, the IAnimal reference count may become zero, in which case it’s
freed - this is also what your Dog object is pointing to - so you now have a
reference to a freed dog. Off-leash areas only, in accordance with local bylaws,
please.

Further Learning
This section is an intentionally grossly inadequate explanation of interfaces.
Because Interfaces have been around so long, there are plenty of tutorials and
reference material if you Google it. There is also a chapter in Coding in Delphi (by
Nick Hodges, 2014) on interfaces, which I recommend you check out. Actually,
read the whole book, you’ll be a better coder for the experience.

073
01001010
Generics
Most developers don’t need to write code that makes use of generics directly.
Instead, they’ll take advantage of libraries that use them. In particular, the
System.Generics.Collections unit has several prebuilt containers that can
make your code more type-safe and reduce the amount of code you need to write.

So what are generics? Roughly put, “it is a style of programming that allows you
to write algorithms whereby you specify types at a later time, while still ensuring
type safety”. Another way of thinking of this is ‘parameterised types’, just like a
procedure can have a parameter that has a value that you pass in, you can also
specify the type of that parameter elsewhere.

An example. Say we have a procedure that checks an array to see if a particular


integer value is present.

function IsPresentInArray(Value : integer; AnArray : array of


integer) : boolean;
var
I : integer;
begin
for I := low(AnArray) to High(AnArray) do
if Value = AnArray[i] then
Exit(True);
Result := False;
end;

And we can use this function like this:

procedure TForm7.Button1Click(Sender: TObject);


begin
if IsPresentInArray(3, [1,2,3,4,5]) then
ShowMessage('yes');
end;

If we click on Button1, it would indeed say “yes”.

This is a straightforward function, and you might have something similar in a


library. However, what if you now wanted to find a string in an array, or a
floating-point number, or a TBitmap? You would need to write many overloaded
versions of this same function for each of those types - this is lots of repetitive

074
01001011
code. Instead, we can parameterize the type and this is what generics allow us to
do. The code would look something like this:

function IsPresentInArray<T>(Value : T; AnArray : array of T) :


boolean;
var
I : integer;
begin
for I := low(AnArray) to High(AnArray) do
if Value = AnArray[i] then
Exit(True);
Result := False;
end;

If this code actually worked (spoiler - it doesn’t - see below), you would use it in
this way:

if IsPresentInArray<integer>(3, [1,2,3,4,5]) then


ShowMessage('yes');

Or

if IsPresentInArray<string>('yellow', ['red', 'blue', 'green',


'yellow']) then
ShowMessage('yes');

There are two problems here. First, it’s not possible to make a global function or
procedure generic. Second, we can’t simply use ‘=’ for equality. To get around
the first problem, we can use a class (or record). The second issue requires us to
make use of the IComparer interface in the System.Generics.Defaults unit.

type
ArrayUtils = class
class function IsPresentInArray<T>(Value : T; AnArray : array
of T) : boolean;
end;

class function ArrayUtils.IsPresentInArray<T>(Value : T; AnArray :


array of T) : boolean;
var
I : integer;

075
01001100
lComparer: IEqualityComparer<T>;
begin
lComparer := TEqualityComparer<T>.Default;
for I := low(AnArray) to High(AnArray) do
if lComparer.Equals(Value, AnArray[i]) then
Exit(True);
Result := False;
end;

Now we can use our function with whatever type we want. For instance:

if ArrayUtils.IsPresentInArray<TButton>(Button1, [Button1, Button2,


Button3, Button4]) then
ShowMessage('yes');

And (assuming all the buttons have been dropped onto the form), this happily
tells us that Button1 is part of the array. However;

if ArrayUtils.IsPresentInArray<TButton>(Label1, [Button1, Button2,


Button3, Button4]) then
ShowMessage('yes');

won’t even compile, and we get the compiler error message “E2010
Incompatible types: 'TButton' and 'TLabel'”. Thus we now have
compile-time type safety.

In Delphi 10.3 onwards, in some instances the type can automatically be


determined (type inferencing), thus you can omit the <string> so:

ArrayUtils.IsPresentInArray('yellow', ['red', 'blue', 'green',


'yellow']);

is also valid.

Generic Collections
You probably won’t write too much of your own code that uses generics.
However, you may consume much code in the form of libraries and frameworks.
In particular (as mentioned above) you should make use of the
System.Generics.Collections unit and I cover this in the ‘Knowing the RTL’
section.

076
01001101
Anonymous Methods
Anonymous methods are mostly ‘syntactic sugar’, you can achieve much of their
benefit through other means (function pointers, or even just procedures on
classes), but it does give you an additional level of expressiveness that you
shouldn’t ignore. Anonymous methods are, well, just as the name suggests,
methods without names. You can assign them to variables and you can pass them
as parameters. Let’s analyse a contrived and simplistic example.

var
Greet : TProc<string>;
begin
Greet := procedure (value : string)
begin
ShowMessage('Hello ' + value);
end;

Greet('Alister');
end;

First, let's examine the Greet variable; its TProc type is a useful shorthand
defined in the System.SysUtils unit, there are several TProc, TFunc, and a
TPredicate types defined for various numbers of parameters.

type
TProc<T> = reference to procedure (Arg1: T);

Or in our case, using a string, the Greet variable could instead be defined as:

type
TGreet = reference to procedure(value : string);
var
Greet : TGreet;

Greet is a variable that is a reference to a procedure, which takes a string


parameter. You can see, the TProc shorthand is more concise than its full
definition. Back to our original example, we assign a procedure to Greet, and
then call that procedure, passing in a parameter.

Let’s take a look at another, more useful example.

077
01001110
A typical pattern when using anonymous methods is injecting one method into
another.

function ForEveryRecord(DataSet : TDataSet; Func :


TPredicate<TDataSet>) : boolean;
var
bm : TBookmark;
begin
result := True;
bm := DataSet.GetBookmark;
try
DataSet.First;
while not DataSet.EOF do
begin
if not Func(DataSet) then
Exit(False);
DataSet.Next;
end;
finally
DataSet.GotoBookmark(bm);
end;
end;

This procedure iterates over a dataset. You might find yourself doing something
like this quite regularly, and writing the same while loop over and over again. Say
we want to sum a field called cost on a dataset (cdsMonthlyEarnings in this case)
function TMonthEndReport.GetTotalEarnings: Currency;
var
TotalCost : Currency;
begin
TotalCost := 0;
ForEveryRecord(cdsMonthlyEarnings,
function(DataSet : TDataSet) : boolean
begin
result := True;
TotalCost := TotalCost +
DataSet.FieldByName('Cost').AsCurrency;
end
);
result := TotalCost;
end;
078
01001111

If the greeting example looked foreign, this example would look very strange. We
pass a function inline into a procedure, and each record is iterated over, and the
cost field summed.

Why the boolean result on the anonymous method? This can be quite handy if,
for example, we are searching for a particular record with specific characteristics.
If we find one we might want to do some processing, but not iterate over the
remaining records (which would return false). For example:

function TMonthEndReport.RecordIsValid(ds: TDataSet): boolean;


begin
//some processing that returns true for valid, and false
otherwise
result := ds.FieldByName('IsValid').AsBoolean;
end;

function TMonthEndReport.IsEveryRecordValid: boolean;


begin
result := ForEveryRecord(cdsMonthlyEarnings, RecordIsValid);
end;

Here we are passing in a regular function on a class as an anonymous method. If


the function returns false for a particular record, ForEveryRecord would stop
processing on that record and return false, making IsEveryRecordValid return
false.

079
01010000
Variable Capture
Something unique to anonymous methods is variable capture. This is something
that I’ve yet to make use of, but if you are not aware of how it works, it might
catch you out. To explain it I’m going to start with an example:

function VariableCapture : TProc;


var
x : integer;
begin
x := 5;
result := procedure
begin
x := x * 5;
ShowMessage(x.ToString);
end;
end;

This function returns an anonymous method which makes use of a local variable,
the variable x should disappear when the VariableCapture function terminates.
So you might think if the anonymous procedure executes, the x variable points to
something invalid on the stack. It doesn’t.

var
p1, p2 : TProc;
begin
p1 := VariableCapture();
p2 := VariableCapture();
p1;
p1;
p2;
end;

In the above code, we are getting two references to an anonymous method from
our VariableCapture function. We call p1 twice then p2 once, and we get the
following messages 25, 125, and 25. This is because the anonymous method
‘captures’ the local variable, and ‘remembers’ it between invocations, so each
time the p1 procedure is called, it uses the same instance of x. Internally,
anonymous methods are implemented with interfaces, so in the above example,
when they go out of scope, they are freed automatically - along with any captured
variables.
080
01010001

We are only scratching the surface of anonymous methods, but they are very
useful, so get motivated to learn more.

Anonymous Threads
A common use of anonymous methods is to execute something in a background
thread. This allows for an easy way to execute a task without having to worry
about creating a descendant of TThread, which can save quite a bit of coding.

In the example below, we zip up some files in an anonymous thread, and while it
is executing in the background the GUI is not blocked. When it completes, it logs
to a memo indicating that it has finished. This could run as part of a loop, zipping
up the contents of multiple directories simultaneously (to different zip files
obviously).

Include the System.Zip unit in the uses section which contains TZipFile needed
below.

var
ZipFileName : string;
DirectoryToZip : string;
begin
{... setup ZipFileName and DirectoryToZip ...}
TThread.CreateAnonymousThread(
procedure
begin
TZipFile.ZipDirectoryContents(ZipFileName, DirectoryToZip);
TThread.Synchronize(nil,
procedure
begin
memo1.Lines.Add('Zip Complete:' + ZipFileName);
end
);
end
).Start;
end;

Further Learning
See the section on the Parallel Programming Library.
081
01010010
Inline Variables and Type Inferencing
These features were introduced in Delphi 10.3 Rio, however, at the time Error
Insight didn’t support inline variables so they would look like errors even when
you used them correctly. However, in Delphi 10.4 Sydney, Error Insight got a big
overhaul with the introduction of Language Server Protocol (LSP) support, which
makes them usable.

Inline variables allow you to declare variables when you need them and not at the
top of a procedure. Let's take a look at a short snippet of code:

procedure TForm15.Button1Click(Sender: TObject);


var
Files: TArray<string>;
FileName: string;
begin
Files := TDirectory.GetFiles('c:\temp');
for FileName in Files do
ListBox1.Items.Add(FileName);
end;

Here you can see that we are placing a list of all the files in the ‘c:\temp’ folder
into a TListBox. We can change this so that we are declaring the variables inline
like so:

procedure TForm15.Button2Click(Sender: TObject);


begin
var Files : TArray<string> := TDirectory.GetFiles('c:\temp');
for var FileName : string in Files do
ListBox1.Items.Add(FileName);
end;

Now the variables are declared where they are used. This makes little difference
in this simple example, but when writing a much larger method you can create
variables as you need them and don’t need to keep declaring them at the top of a
method. They also have local scope, so that the FileName variable is only valid
within the context of the for loop.

When we add type inferencing (which we encountered briefly in Generics above)


we can rewrite the code to the following:

082
01010011
procedure TForm15.Button3Click(Sender: TObject);
begin
var Files := TDirectory.GetFiles('c:\temp');
for var FileName in Files do
ListBox1.Items.Add(FileName);
end;

Now the compiler ‘infers’ the type from its context, allowing us to write much
more concise code. Less code equals less typing equals better productivity - at
least that’s the theory. Because both these features are new to Delphi, it might
take quite some time to determine if our third example is ‘better’ than the first.
It’s certainly more concise and less cluttered to look at, but is it easier to
maintain? At this stage, I reserve judgement. I suspect it might be the source of
some subtle and hard to find bugs (like the with statement), but this is yet to be
determined.

083
01010100
Know the RTL
The Delphi Run-Time Library is everything leftover when you remove the GUI
code and language features. It includes things like the System unit,
System.SysUtils, System.StrUtils, System.Classes,
System.Generics.Collections, System.RTTI, System.Threading, and so
much more (much of the RTL is in the System namespace). Having a good
knowledge of these libraries can save you reinventing the wheel, and hence save
you lots of effort and time. This section is a teaser to give you a taste of what a
few libraries are capable of. See links to resources as to where you can learn
more.

Measuring Time
Traditionally I’ve done this with GetTickCount, which gives you the number of
milliseconds since the operating system started.

var
ticks: UInt64;
begin
ticks := GetTickCount64;
DoSomething;
ShowMessage('Processing time: ' + (GetTickCount64 -
ticks).ToString + 'ms');
end;

Gives the unexciting message

However, there are some problems with GetTickCount (or in this case,
GetTickCount64). Firstly what if you want sub-millisecond accuracy? Secondly,
this code only works on Windows. Fortunately, we have a simple solution. In the
System.Diagnostics unit there is a type called TStopwatch which solves both
these problems.

084
01010101

var
sw : TStopwatch;
begin
sw := TStopwatch.StartNew;
DoSomething;
sw.stop;
ShowMessage('Processing time: ' +
sw.Elapsed.TotalMilliseconds.ToString + 'ms');
end;

Now we get:

Which is very precise, but how accurate is it? We can determine the precision
from the Frequency property of the stopwatch, which is the number of ‘ticks per
second’ of the operating system. So we can determine the accuracy in
milliseconds from:

ShowMessage('Maximum Accuracy: ' + (1000 / sw.Frequency).ToString +


'ms');

Which will give us:

This dialog indicates a maximum accuracy of one ten-thousandth of a


millisecond (one ten-millionth of a second or 100 nanoseconds) on my current
platform, yours may be better or worse depending on your CPU and operating
system.

085
01010110
Generic Collections
The collections unit contains several classes including TList, TQueue, TStack,
TDictionary and TThreadedQueue along with their object versions
(TObjectList, TObjectQueue, TObjectStack and TObjectDictionary). The
difference between the Object versions and the standard versions is that the
Object version can optionally own the objects. This means that if you remove an
object it will be freed. If you free the collection, every object that it owns is also
freed.

Let’s take TList, for example. The original TList is a list of pointers and allows
you to add and remove pointers. For example

procedure TForm11.btnAddCustomerClick(Sender: TObject);


var
Customer : TCustomer;
CustomerList : TList;
begin
CustomerList := TList.Create;
Customer := TCustomer.Create(1, 'Jim');
CustomerList.Add(Customer);
CustomerList.Add(btnAddCustomer);
// ...
Customer := CustomerList[1];
// ...
end;

If you are observant, you’ll notice that we are adding a button to our customer
list, then later assigning it to a customer. Rather disturbingly this works quite
well as our Customer class is just an integer and a string like so:

TCustomer = class
public
id : integer;
Name : string;
constructor Create(id : integer; Name : string);
end;

When I run this, I get a customer with an id of 52892960 and name


‘btnAddCustomer’, which could be a hard-to-spot source of data corruption. If I

086
01010111
free that customer, it frees the button, and it disappears from the form - all with
zero errors at runtime or compile time.

Historically to fix this you would need to write your own list class
(TCustomerList) that contains a TList internally, but all the methods would be
type-safe. For instance:

procedure TCustomerList.Add(Customer: TCustomer);


begin
List.Add(Customer);
end;

Doing this for all the relevant methods is very tedious, especially when you also
need a TInvoiceList, TPaymentList, and so on. At least the compiler now gives
you an error when we try to add a button to the CustomerList.

If you include the System.Generics.Collections unit, we can rewrite our code


to:

procedure TForm11.btnAddCustomerClick(Sender: TObject);


var
Customer : TCustomer;
CustomerList : TList<TCustomer>;
begin
CustomerList := TList<TCustomer>.Create;
Customer := TCustomer.Create(1, 'Jim');
CustomerList.Add(Customer);
CustomerList.Add(btnAddCustomer);
// ...
Customer := CustomerList[1];
// ...
end;

And this time we get an error on compilation:

[dcc32 Error] Unit11.pas(56): E2010 Incompatible types: 'TCustomer' and


'TButton'

Using TList<TCustomer> gives us the compile-time safety that we desire


without all the hard work. This not only enables you to write code more quickly
and concisely, but also allows you to spot bugs that can be hard to track down

087
01011000
(especially if the button was called bCustomer, and you wanted to add a
CustomerB variable).

TDictionary
I’m going to make a special mention of TDictionary because of how useful it is.

Before the collections unit, there wasn’t an easy-to-use hash table in Delphi.
Hash tables are a fantastic way of storing values based on a key. They are
optimised for retrieval speed, although adding or removing an item can be
expensive. Generally, hash tables require twice as much memory as a plain list.
You especially want to avoid looking up by value as this requires a scan of all the
items.

I’ve used TDictionary as a replacement or cache for a dataset when I needed the
additional performance. I had a calculated field that needed to take a list of codes
and expand them out into a list of descriptions. This caused a vast number of
locate calls on the dataset that contained the codes and descriptions. Replacing
that dataset with a TDictionary resulted in approximately a thousandfold
increase in performance, making data grids much more responsive.

Dictionaries store key/value pairs, and the key is ‘hashed’ to a 32-bit integer,
which is used to determine where to store the value in the dictionary.

Below is code for an application to take the symbol for a chemical element and
return its name.

TfrmElements = class(TForm)
// ...
private
Elements : TDictionary<string, string>;
end;

procedure TfrmElements.FormCreate(Sender: TObject);


begin
Elements := TDictionary<string, string>.Create;
Elements.Add('H', 'Hydrogen');
Elements.Add('He', 'Helium');
Elements.Add('Li', 'Lithium');
Elements.Add('Be', 'Beryllium');
Elements.Add('B', 'Boron');
Elements.Add('C', 'Carbon');

088
01011001
//...
end;

procedure TfrmElements.FormDestroy(Sender: TObject);


begin
Elements.Free;
end;

procedure TfrmElements.btnLookupSymbolClick(Sender: TObject);


var
Name : string;
Symbol : string;
begin
Symbol := edtSymbol.Text;
if Elements.TryGetValue(Symbol, Name) then
ShowMessage(Name + ' has the symbol ' + Symbol)
else
ShowMessage('Could not find an element with the symbol ' +
Symbol);
end;

Below we are confirming that Helium has the symbol He

The performance difference in this example between TDictionary and using a


TStringList is probably not going to be noticeable as there are only just over 100
elements on the Periodic Table. However, on a dataset with a million items using
a TStringList would be painful, yet a TDictionary with 1,000,000 items would
be just as fast as with 100 items.

089
01011010
The other advantage is that you can use any type for the key and value that you
like; for instance simple types such as integers, strings or doubles, or more
complex types such as classes, records, arrays or interfaces. Be aware not all keys
are created equal. For instance, two records with the same contents would not be
considered equal; for this, you need to write a custom comparer to compare their
fields.

Further Learning
The documentation for generic collections is pretty good, so I haven’t felt the
need to reproduce it here.

You can watch more extensive coverage of the collections unit in one of my videos
on LearnDelphi.tv.

Parallel Programming
The System.Threading unit makes writing multi-threaded code much more
straightforward. It’s still challenging. I don’t think it’s ever going to be easy, but
we’ll take whatever simplification we can get. I’m not going to use the boring old
parallel programming example of calculating primes, we’re going to calculate
twin primes. So much more interesting.

Twin primes are primes that are separated by two, for example, 3 and 5 are twin
primes (as are 5, 7 and 11, 13). We are going to calculate every twin prime under
ten million. Our user interface is going to be a memo, some buttons and an
ActivityIndicator (set its animate property to True). You can find this example in
the PrimeExample\PrimeExample.dpr project.

The ActivityIndicator shows us when the GUI is unresponsive (the indicator will
stop spinning).
090
01011011

We need a function that determines if a number is prime:

function TfrmPrimes.IsPrime(const x: integer): boolean;


var
I: Integer;
MaxValue : integer;
begin
MaxValue := Round(Sqrt(x));
for I := 2 to MaxValue do
if (x mod i) = 0 then
exit(False);
result := True;
end;

We can define a procedure to calculate all the twin primes in a given range

procedure TfrmPrimes.TwinPrimes(LowBound, HighBound: integer;


Output: TStrings);
var
I : integer;
begin
for I := LowBound to HighBound do
begin
if IsPrime(i) and IsPrime(i+2) then
OutPut.Add(i.ToString + ', ' + (i+2).ToString);
end;
end;

This is certainly not the fastest algorithm (I could skip even numbers, cache
prime results, take advantage of the properties of twin primes, etc.), but suits our
purposes making the CPU busy.

Now that we are armed with these sophisticated prime calculating algorithms
(cough), we can look at some threading examples, starting with just using the
main thread.

No Parallel Example
For our example where we do nothing in parallel, I’ve written:

091
01011100
procedure TfrmPrimes.btnNoParallelClick(Sender: TObject);
var
sl : TStringList;
sw : TStopwatch;
begin
sw := TStopWatch.StartNew;
sl := TStringList.Create;
TwinPrimes(2, MaxPrime, sl);
sl.Add(sw.Elapsed.TotalMilliseconds.ToString);
ShowResult(sl);
sl.Free;
end;

If I execute this code, it freezes the application for about 10 seconds


(9,709.0126ms for my last run), but it does successfully calculate the 58,980 twin
primes under ten million (MaxPrimes is a constant that’s set to 10,000,000).

To display the primes in the memo, we use the ShowResult procedure:

procedure TfrmPrimes.ShowResult(sl : TStringList);


begin
TThread.Synchronize(nil,
procedure
begin
if cbFinalTwinPrimeOnly.Checked then
begin
mmoTwins.Clear;
if sl.Count <= 2 then
Exit;
mmoTwins.Lines.Add(sl[sl.Count-2]); { final result }
mmoTwins.Lines.Add(sl[sl.Count-1]); { calculation time }
end
else
mmoTwins.Lines.Text := sl.Text;
end
);
end;

This support method passes an anonymous procedure to Synchronize, which


displays our results. Synchronize ensures that we are updating the GUI from the
main thread. Failure to do this may cause the GUI to become corrupted and/or
092
01011101
other strange things to occur. It’s perfectly safe to call Synchronize from the
main thread (which happens in the No Parallel example).

This freezing of the GUI is really annoying as it’s impossible to tell if the
application has crashed or is still calculating. You could somewhat fix this by
adding some calls to UpdateWindow or Application.ProcessMessages during
the calculation (say once for every 10k numbers checked), but this is not a very
good solution. A better thing to do is run the calculation in a background thread.
Once the calculation has completed, it can report back to the GUI. The two easiest
ways of doing this are either with a Task or an anonymous thread. We are going
to use a Task.

Background Thread Example


In the next example below, things get a bit more complicated.

procedure TfrmPrimes.btnBackgroundClick(Sender: TObject);


begin
TTask.Create(procedure
var
sl : TStringList;
sw : TStopwatch;
begin
sw := TStopWatch.StartNew;
sl := TStringList.Create;
TwinPrimes(2, MaxPrimes, sl);
sl.Add(sw.Elapsed.TotalMilliseconds.ToString);
ShowResult(sl);
sl.Free;
end
).Start;
end;

This time we are wrapping our code in a Task, but otherwise, things look pretty
similar. If we look at the CPU utilisation, one of the cores maxes out at 100% for
about 10 seconds.

093
01011110
This calculation takes slightly less time (8,867.0803ms), and unfortunately, the
GUI still freezes for up to a second as 58k lines are copied into the memo. There is
not much that can be done about this if we want to display all the results. The
updating of the memo takes only around one-tenth the total time but is going to
take proportionally more time as we increase the parallelism of our algorithm.

Multiple Tasks Example


One strategy we could employ to speed things up is to break the problem into
chunks and calculate parts of the list across multiple threads. This isn’t a trivial
thing to do, but let’s have a go.

procedure TfrmPrimes.btnParallelClick(Sender: TObject);


begin
TTask.Create(procedure
var
sl : TStringList;
sw : TStopwatch;
begin
sw := TStopWatch.StartNew;
sl := TStringList.Create;
ParallelTasks(sl);
sl.Add(sw.Elapsed.TotalMilliseconds.ToString);
ShowResult(sl);
sl.Free;
end
).Start;
end;

procedure TfrmPrimes.ParallelTasks(Output: TStrings);


var
Tasks : TArray<ITask>;
Lists : TArray<TStringList>;
i : Integer;
begin
SetLength(Lists, TaskCount);
for i := 0 to TaskCount-1 do
Lists[i] := TStringList.Create;

094
01011111
SetLength(Tasks, TaskCount);
for I := 0 to TaskCount-1 do
Tasks[i] := CreateTask(i, Lists[i]).Start;

TTask.WaitForAll(Tasks);
for i := 0 to TaskCount-1 do
begin
OutPut.AddStrings(Lists[i]);
Lists[i].Free;
end;
end;

Our btnParallelClick event should look pretty familiar as it is the same as our
background thread example, with the exception that we are calling
ParallelTasks. ParallelTasks calls CreateTask a constant number of times
(in fact TaskCount times). And in the CreateTask function we return a task.

function TfrmPrimes.CreateTask(x: integer; Output : TStrings):


ITask;
begin
Result := TTask.Create(procedure
var
LowBound, HighBound : integer;
begin
LowBound := (MaxPrime * x div TaskCount) + 2;
HighBound := (MaxPrime * (x+1) div TaskCount) + 1;
TwinPrimes(LowBound, HighBound, Output);
end
);
end;

Execution time using multiple threads is going to vary greatly depending on the
type of CPU you have, how many cores are available, and what other work your
CPU is doing. Additionally, if the debugger is running thread creation is
extremely slow. Running this on my computer with five threads, this executes in
2,550ms (2,900ms with the debugger) which is a four-fold improvement. With
ten threads it takes 1300ms (1950ms with the debugger), you’ll notice the
diminishing returns on the execution time.

If you are executing with the debugger, running it a second time only takes
1300ms. This is because thread creation with the debugger is costly. However we

095
01100000
are using TTask, and the threads are pooled, so they do not need to be created a
second time around, they are instead reused. After a few minutes, you may notice
in the Delphi Event log the application frees the threads automatically to save
resources. You can look at the threads that your application is using in the
Threads window (View|Debug windows|Threads or Ctrl+Alt+T).

Here you can see the 11 worker threads (our ten threads calculating twin primes,
and one background thread to keep the GUI responsive).

You can also monitor the number of threads and CPU usage an application is
using by looking in the Task Manager Details tab.

096
01100001
Parallel For Example
If you thought the Multiple Tasks example was a lot of work, I agree. There is a
much better way to do this, but let’s identify a few problems first:

Ten threads might not be optimal. There might be only two cores, or might be
128 - so we might be wasting effort to create threads that are not required, then
wasting time swapping between them. Or we could create too few threads and
leave CPU cores unused.

Additionally, each thread searches the same sized number space, but it takes
longer to search for larger primes. Thus the last thread created takes the longest
to execute, so it continues to run while other threads sit idle.

Thirdly, the code is confusing to write, and harder to understand - could you go
back and check for me that I didn’t miss finding any twin primes on the
boundaries, or double calculate any? Cheers.

Our solution is going to be to use the TParallel.For function:

procedure TfrmPrimes.btnParallelForClick(Sender: TObject);


begin
TTask.Create(procedure
var
sl : TStringList;
sw : TStopwatch;
begin
sw := TStopWatch.StartNew;
sl := TStringList.Create;
TwinPrimesParallel(2, MaxPrime, sl);
sl.Add(sw.Elapsed.TotalMilliseconds.ToString);
ShowResult(sl);
sl.Free;
end
).Start;
end;

Here we see the exact same construct, but we are calling a new
TwinPrimesParallel procedure:

procedure TfrmPrimes.TwinPrimesParallel(LowBound, HighBound:


integer;

097
01100010
Output: TStrings);
begin
TParallel.For(LowBound, HighBound, procedure(i : integer)
begin
if IsPrime(i) and IsPrime(i+2) then
begin
System.TMonitor.Enter(OutPut);
OutPut.Add(i.ToString + ', ' + (i+2).ToString);
System.TMonitor.Exit(OutPut);
end;
end);
end;

This contains a parallel for loop. You specify both the high and low bounds and
an anonymous procedure that takes an integer as a parameter (either integer of
int64). Our code is similar to a regular for loop, except we are using a monitor to
restrict access to the TStringList to a single thread at a time.

Executing this takes 580ms (or 2,800ms with the debugger). This varies
significantly between machines, especially if you are running the debugger. I ran
this example on a CPU with 14 hyperthreaded cores, so the parallel library created
28 worker threads for calculating primes.

Something worth noting is it still takes time to copy all those twin primes into the
memo. This is something that can’t be improved upon by parallel programming.
The half-second it takes to do the calculation is comparable with updating the
GUI. This illustrates the problem that not all code can be optimised in this way.
Even if you had an excessive number of cores (1024 say), calculating the twin
primes would be extremely quick, but updating the GUI is not going to be much
(if any) faster than if you had one.

There are a large number of overloads for the TParallel.For method

098
01100011

Half of these are just integer vs int64. The optional first stride parameter
allows you to specify how many values for ‘i’ each thread is allocated at a time. If
we specify 100 for the stride, each thread is allocated 100 values to process. When
it is done, it gets another 100 until there are none left to process.

The APool parameter allows you to specify a custom TThreadPool which gives
control over how threads are created and reused. The TParallel.TLoopState
parameter allows you to control the flow within the For loop (for instance to
Break out). The final Sender parameter I’ve not found particularly useful.

Further Learning
Parallel programming is a vast topic, and I've barely scratched the surface. I have
a video on optimising code for generating fractals which use some of these
parallel techniques which you can find on LearnDelphi.tv (Movie #116).

I also recommend the book ‘Delphi High Performance’ by Primož Gabrijelčič


(2018).

Regular Expressions
A regular expression library was added to Delphi XE. Regular expressions are a
powerful mechanism for pattern matching and replacement within strings. They
are not a programming language in themselves (as they are not Turing
Complete), but are instead regarded as a formal grammar. There are several
implementations of regular expressions (or regexes), and Delphi implements the
PCRE variation (Perl Compatible Regular Expressions).

099
01100100
To take advantage of regexes in your application, you need to use the
System.RegularExpressions unit.

Working out and testing your regular expression is usually most easily done
through an external tool. There are some websites that you can use,
regex101.com and regexr.com are good, but there are many others. There are also
some desktop applications you can use (e.g. RegexBuddy - written in Delphi
incidentally).

You can find the code samples in the RegularExpressions\IPAddress.dpr


project.

IP Address Validation
A reasonable use for regular expressions is validating an IP address. A simple
first attempt might look like:

([0-9]{1,3}\.){3}\.[0-9]{1,3}

The square brackets indicate to match any single character inside, so [0-9]
matches any character in that range. In this instance, we could also use the
shorthand \d to match any digit. The curly brackets indicate a count, {1,3} is a
minimum of 1 and a maximum of 3. A single-digit within curly brackets such as
{3} would indicate exactly 3. A full stop is a special character (actually indicates
any character), so we need to escape it with a backslash. And finally round
brackets group things together. So in English, this regular expression says “a
group of one to three numbers followed by a dot repeated three times, then one
group of one to three numbers”.

This doesn’t give us valid IP addresses, as “999.999.999.999” would be valid


using this expression. If we wanted to further restrict each number to a
maximum of 255, we would need to add a little more syntax.

((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|
[01]?[0-9][0-9]?)

Here we have introduced two more bits of syntax. Firstly the ?, which indicates
that the match is optional (zero or one occurrence). Secondly, the vertical bar or
pipe indicates alternatives (boolean or), so (1|2) would match 1 or 2. Explaining
this is a bit harder. 25[0-5] is the number range 250-255. 2[0-4][0-9]
represents 200-249. And [01]?[0-9][0-9]? Is optionally a 0 or 1, followed by

100
01100101
0-9, then optionally 0-9, which effectively gives us our 0-199. Putting these
together gives us the range of numbers 0 to 255.

We are still not quite there yet, as our regular expression matches any IP address
within a string. To prevent this, we need to take advantage of two more special
characters. The caret/hat character or ^ is used to indicate the beginning of a
string (or line), and the dollar sign or $ is used to indicate the end, thus:

^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|
[01]?[0-9][0-9]?)$

This matches exactly an IP address and nothing else. So how do we use this in
Delphi?

IsMatch
Here is our first example:

uses
System.RegularExpressions,
System.UITypes;

procedure TfrmIP.edtIPChange(Sender: TObject);


const
IPRegEx = '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-
5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$';
var
edt : TEdit;
begin
edt := Sender as TEdit;
if TRegEx.IsMatch(edt.Text, IPRegEx) then
edt.Color := TColorRec.Lightgreen
else
edt.Color := TColorRec.Rosybrown;
end;

As you can see we have written an OnChange event for a TEdit, and we are using
our regular expression to change the colour of the TEdit, green for valid and
red(ish) for invalid.

101
01100110
TRegEx is a record with several class methods that you can use directly on TRegEx
as we have done. Or you can ‘create’ a TRegEx and supply a regular expression to
the constructor so that the expression is pre-compiled if you want to use the
same expression repeatedly.

Match
If we want to find a pattern within a string, we can use the Match method. Notice
that we have removed the ‘^’ and ‘$’ characters as we want to search within a
string.

procedure TfrmIP.btnFindIPClick(Sender: TObject);


const
IPRegEx = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|
2[0-4][0-9]|[01]?[0-9][0-9]?)';
var
Match: TMatch;
RegEx : TRegEx;
begin
RegEx := TRegEx.Create(IPRegEx);
if Memo1.SelLength > 0 then {if some text is selected, search
beyond it}
Match := RegEx.Match(Memo1.Text, Memo1.SelStart +
Memo1.SelLength) {find next IP}
else
Match := RegEx.Match(Memo1.Text); {find first IP}

if Match.Success then
begin
Memo1.SelStart := Match.Index-1;
Memo1.SelLength := Match.Length;
end;
end;

Here we are searching within a memo. Each time we click btnFindIP, we search
for the next IP in the memo.

102
01100111

This time we are using the Create method on TRegEx as the Match function with a
starting point is not a class method. If we wanted to know what IP we found, we
could use the Match.Value property.

Matches
If we want to find all the IP addresses in a string, we would use the Matches
method. For instance, if we wanted to clear the memo and only show the IP
addresses, we could use the following.

procedure TfrmIP.btnIPsOnlyClick(Sender: TObject);


const
IPRegEx = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|
2[0-4][0-9]|[01]?[0-9][0-9]?)';
var
Match: TMatch;
Matches: TMatchCollection;
begin
Matches := TRegEx.Matches(Memo1.Text, IPRegEx);
Memo1.Clear;
for Match in Matches do
Memo1.Lines.Add(Match.Value);
end;

Matches returns a TMatchCollection which is a, well, collection of TMatch.

103
01101000
Replace
It might be the case that the IP addresses are sensitive information, and showing
them might pose a security risk (I didn’t use my real IP as you may have guessed,
but if hiding behind an anonymous IP is your idea of security...). An easy way to
redact them would be to use the Replace method.

function TfrmIP.IPMatchEvaluator(const Match: TMatch): string;


begin
result := 'XXX.XXX.XXX.XXX';
end;

procedure TfrmIP.btnMaskIPsClick(Sender: TObject);


const
IPRegEx = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|
2[0-4][0-9]|[01]?[0-9][0-9]?)';
begin
Memo1.Text := TRegEx.Replace(Memo1.Text, IPRegEx,
IPMatchEvaluator);
end;

We pass the IPMatchEvaluator as the last parameter of the Replace method.


This is a function which allows you to work out what string you want to use to
replace the match. This gives a lot of flexibility. Here we’ve replaced it with a
static string, but you might want to do something more dynamic.

Summary
Regular expressions are extremely powerful and are useful in Delphi and many
other languages and tools. I’ve only covered a fraction of the syntax, and
learning more will give you a great deal of flexibility.

104
01101001
Further Learning
There are lots of resources on Regular Expressions. Its Wikipedia7 page has lots
of info. If you want to test your regular expressions, you can use regex101.com or
regexr.com; both also have tutorials and libraries of expressions.

There are also many books on regular expressions, including the ‘Regular
Expressions Cookbook’ (2012), co-authored by Jan Goyvaerts, a fellow Delphi
MVP.

Enhanced RTTI
Run-Time Type Information is a mechanism where information about types can
be determined at run-time based on information stored in the compiled binary.
In Delphi, there are two types of Run-Time Type Information. The original
version, based on published properties, has been around since Delphi 1 (and can
be found in the System.TypInfo unit), and the new far more powerful enhanced
version, introduced in Delphi 2010 (in the System.RTTI unit).

The Enhanced RTTI allows you to access and modify fields and properties on
classes and records. It also allows you to execute methods. Some examples will
probably help illustrate what we can do with RTTI.

Reading Properties
Start by creating a new VCL (or FireMonkey) application and drop a TMemo and
TButton. Add System.RTTI to the uses clause and in the button OnClick event
add the following code (which can be found in the RTTI\RTTIReadProperties
project):

procedure TForm11.btnShowPropertiesClick(Sender: TObject);


var
R: TRttiContext;
Props: TArray<TRttiProperty>;
Prop : TRttiProperty;
begin
memo1.Clear;
R := TRttiContext.Create;
Props := R.GetType(Sender.ClassType).GetProperties;
//also .GetMethods, .GetFields, .GetAttributes etc.

7 https://en.wikipedia.org/wiki/Regular_expression

105
01101010
//List all the properties in the memo
for Prop in Props do
begin
try
Memo1.Lines.Add(
Prop.Parent.Name + '.' +
Prop.Name + ' : ' +
Prop.PropertyType.ToString + ' = ' +
Prop.GetValue(Sender).ToString);
except
on E: Exception do
Memo1.Lines.Add(Prop.Name + ' generated an exception ('
+ E.Message + ')');
end;
end;
end;

This code is very simple, considering what it does. It grabs every property our
button has and adds its Class, Name, Type and Value to the Memo.

So how does this work? TRttiContext and TRttiProperty are both records, so
you don’t need to free them, but you do need to initialise the TRttiContext using
its constructor. From TRttiContext, you can use GetType passing in a TClass
(which is btnShowProperties.ClassType in this case). This gives you the
TRttiType, on which we are calling GetProperties. We now have an array of all
the properties. We iterate over it to extract the name, type and value. Reading the
106
01101011
ComObject property raises an exception which is why we’re catching it. In the
code example, there is also the option to choose between calling GetProperties
(includes properties introduced in ancestors) and GetDeclaredProperties (only
properties introduced, or reintroduced on the current class).

You can now read all the properties for any object in your application at runtime,
even without knowing what the class is (provided RTTI has been enabled for it).

But what about changing the properties? Glad you asked.

Writing Properties
Create a new application and throw some buttons, labels, memos and checkboxes
on it. In the forms OnShow event write (which can be found in the RTTI\
RTTIWriteProperties project):

procedure TfrmRTTIWrite.FormShow(Sender: TObject);


var
R: TRttiContext;
P: TRttiProperty;
I: Integer;
C : TComponent;
begin
R := TRttiContext.Create;
for I := 0 to ComponentCount-1 do
begin
c := Components[i];
P := R.GetType(c.ClassType).GetProperty('Caption');
if p = nil then
P := R.GetType(c.ClassType).GetProperty('Text');
if p <> nil then
p.SetValue(c, TValue.From('*REDACTED*'));
end;
end;

This code fragment goes through all the components the form and checks if there
is a Caption property, if not it checks for a Text property. If it finds one, it sets
it’s value to *REDACTED*. In my application, I used a TFlowPanel to make it
easy to arrange all the controls.

107
01101100
The code is pretty straight forward, except for the TValue where we are using the
SetValue procedure. TValue is like a simple variant, and you can read and write
any type to it. However, you can only get out the same type as you put in (i.e. you
can’t assign a string, then read an integer like you can with a variant). This
means that if there is a component with an integer property called text, we get an
exception as we try to stuff a string into it. In a more general application, you
need to check the type before assignment and end up with case statements like
the following:

case p.PropertyType.TypeKind of
tkInteger, tkInt64: p.SetValue(c, TValue.From(-1));
tkChar, tkWChar: p.SetValue(c, TValue.From('X'));
tkFloat: p.SetValue(c, TValue.From(-1.0));
tkString, tkLString, tkWString, tkUString:
p.SetValue(c, TValue.From('*REDACTED*'));
{ ... }
end;

Further Learning
There are several YouTube videos and Tutorials available (Google: Delphi
Enhanced RTTI), and you can always start with the Delphi documentation8.

FireDAC
FireDAC is genuinely awesome. There are other data access frameworks included
in Delphi, dbGo (ADO), Interbase, DBExpress, and dare I say the BDE. However,
none of these match the power and ease of use of FireDAC. I can’t possibly give
FireDAC the attention it deserves in the small amount of space that I have

8 http://docwiki.embarcadero.com/RADStudio/en/Working_with_RTTI

108
01101101
allocated for it in this book, but I hope to provide you with enough detail to get
you as excited about it as I am.

TFDConnection
There are a vast number of components and units related to FireDAC, and we are
going to cover just two by building a simple SQLite application. We start by
creating a new VCL application and dropping a TFDConnection onto the form.
Double-clicking on the connection component brings up the Connection Editor.

Specify SQLite for the Driver ID, then fill in a Database name and set
LockingMode from Exclusive to Normal. When the locking mode is Exclusive,
only one application can access the database at a time. This can be very annoying
as you probably want the IDE to access the database as well. However, Exclusive
is significantly faster than Normal, so you might want to make use of this when
you are not debugging. Click Test and a new database is automatically created,
and you should get a success message. Click OK to close the dialog.

109
01101110
These properties are also available at runtime, which is probably when you want
to configure them. An easy way of doing this is by providing an
FDConnectionDefs.ini file with your application, or adjusting the properties on
the TFDConnection (or a combination)

We can now write some SQL to create a table. Drop a button onto the form and in
its OnClick event add:

procedure TfrmFireDAC.btnCreateTableClick(Sender: TObject);


begin
FDConnection1.ExecSQL('CREATE TABLE Elements(No INT NOT NULL,
Symbol VarChar(3), Name VarChar(20), PRIMARY KEY (No))');
end;

If you run this and click the button the Elements table is created, clicking it again
results in an exception saying that the Elements table already exists. As an
exercise, you could add a button that does a ‘DROP TABLE Elements’ to remove
the table.

We have a table, but no data so add a button to create some elements, in its
OnClick event add:

procedure TfrmFireDAC.btnAddElementsClick(Sender: TObject);


var
C : TFDConnection;
begin
C := FDConnection1; //Just for shorter lines
C.ExecSQL('DELETE FROM Elements');
C.ExecSQL('INSERT INTO Elements VALUES(1, ''H'',
''Hydrogen'')');
C.ExecSQL('INSERT INTO Elements VALUES(2, ''He'', ''Helium'')');
C.ExecSQL('INSERT INTO Elements VALUES(3, ''Li'', ''Lithium'')');
C.ExecSQL('INSERT INTO Elements VALUES(4, ''Be'',
''Beryllium'')');
C.ExecSQL('INSERT INTO Elements VALUES(5, ''B'', ''Boron'')');
C.ExecSQL('INSERT INTO Elements VALUES(6, ''C'', ''Carbon'')');
//...
end;

Next add a button to look up a symbol:

110
01101111
procedure TfrmFireDAC.btnLookupCClick(Sender: TObject);
var
Name : string;
begin
Name := FDConnection1.ExecSQLScalar('SELECT Name FROM Elements
WHERE Symbol = ''C''');
ShowMessage(Name);
end;

And if we run this (assuming that we’ve added our elements), we get

Which is not very exciting, but still quite powerful.

Adding a TFDQuery
Now drop a TFDQuery and a TDataSource onto the form, and set the DataSet
property on the TDataSource to your TFDQuery (probably FDQuery1). Next, drop
a TDBGrid onto the form and set its DataSource property to your DataSource
(DataSource1).

Double click on the TFDQuery and enter “SELECT * FROM Elements” into the
SQL window and click the Execute button.

111
01110000
You should see your elements in the RecordSet tab, click OK.

Set the Active property on FDQuery1 to True, and you should see the list of
elements in the TDBGrid. Run your application, and you should be able to Edit,
Delete (Ctrl+Delete) and Insert (Insert) data into the Database. When you close and
reopen the application, these changes persist when you next open it.

112
01110001
But There’s More
You can see how effortless it was to write a simple application to talk to an SQLite
database. In fact, FireDAC uses SQLite internally for many amazing features
(such as SQL on in-memory tables). If you are familiar with how datasets work in
Delphi, you should now find it reasonably straightforward to build a much
broader application.

You may need to talk to other databases, and FireDAC has support for quite a few
(some in the Professional version of Delphi, and more in the Enterprise edition).
To talk to a different database, you need to drop the appropriate component
somewhere in your application, for instance, TFDPhysMSSQLDriverLink for
Microsoft's SQL Server.

It is also cross-platform, meaning you can build a mobile version of your


application. One nice feature of SQLite is that the database format is binary
compatible across all platforms, which is excellent for Debugging as you can copy
it backwards and forwards between platforms, for instance, build the database on
Windows and deploy it to Android.

Further Learning
The FireDAC documentation9 is excellent and worth reading. Additionally, you
should read Cary Jensen's book Delphi in Depth: FireDAC (2017), which provides
an excellent introduction into FireDAC.

9 http://docwiki.embarcadero.com/RADStudio/en/FireDAC

113
01110010
Tools and Plugins

Third-Party Tools.
These fall into two categories, IDE plugins and external tools. We can only cover
these superficially as covering some of them in-depth would require a book each.
This is of course, not a definitive list, new tools are popping up all the time (and
occasionally some are abandoned), but the following should hopefully take your
productivity to new heights.

cnWizards / cnPack
cnWizards is currently my favourite IDE plugin. It is an extensive collection of
tools and shortcuts that get installed into the Delphi IDE. You can either find it in
the GetIt Package Manager (Tools|GetIt Package Manager) or download it
manually from cnpack.org.

Structural Highlighting
The thing that I loved most about cnWizards is structural highlighting. However,
since Delphi got this via Embarcadero’s acquisition of Castalia, it’s not quite as
exciting. I think the cnWizards implementation is better as it also highlights the
keywords that are associated with each other, as shown below (with the Delphi
structural highlighting disabled).

114
01110011

As you can see each level of begin and end is in a different colour, as are the if,
then and else keywords. You can see an if, then, and else group highlighted in
yellow as I have the cursor within the if statement and it shows the then and
else that is associated with it. You can also see that it has highlighted the Exit
statements, and has put dotted lines before and after methods.

Additionally, it highlights matching strings under the cursor, shown below.

115
01110100
Here you can see that I have the cursor on the operation parameter, and it is
showing me everywhere that it is used. Also, notice the parenthesis highlighting
indicating the nearest open and close braces. If you want to customise the
options for this you can go to cnPack|IDE Enhancement Settings|Source
Highlight Settings, shown below.

One downside I have noticed is that it can noticeably reduce drawing


performance, which can become apparent if you’re connected via Remote
Desktop. However, I find that the structural highlighting is such a powerful tool
for understanding code at a glance that it’s well worth the performance hit.

My second favourite feature is the Uses Cleaner. Get my next book, ‘Code Better
in Delphi’ to find out why.

cnWizards also adds some additional context menus when you right-click on a
file tab.

116
01110101

There are three additional options at the bottom, the Open “Filename” in
Windows Explorer is something I use every day (although this is also a standard
Delphi feature now). The Shell Context Menu opens the popup that you would
see if you right-clicked on the file in Windows Explorer, and the Copy Full
Path/FileName puts the filename in the clipboard.

Tab Order

The above screenshot shows a bunch of random controls that I’ve added to a
form, and in the top left of each control, you can see a number, which represents
the tab order. The boxes are coloured to indicate parentage. The first component
to get the cursor would be GroupBox1 (the grey 0), and within that, it would be
Edit1 (as it has tab order 0 in yellow). As you can see the tab order is probably not
correct as the tab order goes 0,1,2,3… and some of the controls are not in that
order. Historically you would either fix this in the object inspector, by changing
the TabOrder property or Right-click on the parent control and select Tab Order
- with the dialog shown below.
117
01110110
cnWizards has a handy tool, in the menu cnPack|Tab Orders we can select All
Components of Current Form (with the shortcut key of Ctrl+=), which resets the
tab order. The first time you run anything in cnWizards, you get a tool hint dialog
telling you what you are about to do, which you can disable once you are
confident that you know what you are doing.

The options for the Tab Order wizard can be found in cnPack|Tab Orders|Options

118
01110111

The sort mode labels seem to be inverted (Vertical first seems to sort
horizontally, then vertically - the diagrams are correct).

Component Prefix Wizard


Another wizard I’m rather fond of is the Component Prefix Wizard.

When you drop a control onto a form, it prompts you for a name, while
automatically prefixing the component type for you. I find this encourages me to
name my components properly - when you drop a label on the form, and it has
the name label137, this is the wrong time to realise that you should put more
effort into naming. You can configure what component types it prompts for, as
well as the prefix you want for each component.

119
01111000
There is so much more to cnWizards, some of the tools are highly useful, others I
find annoying, yet others are things on my to-do list to learn (the Script Wizard
looks ridiculously powerful, and I’ve only used it a few times). You can configure
the tools in cnPack|Options, which allows you a simple way of disabling and
configuring many of the tools.

ModelMaker Code Explorer


ModelMaker Code Explorer (MMX for short) is another IDE plugin. It was
originally a commercial product but is currently maintained by Uwe Rabe10 and is
available for free from www.mmx-delphi.de. It has a large number of
productivity enhancements, that once you get the hang of, can significantly
improve the speed at which you write and modify code. It’s quite a substantial
plugin, and as such, we only have space here to cover a fraction of its capabilities.

Once installed you can enable the explorer window via MMX|Code Explorer|
ModelMaker Code Explorer. This opens the explorer window which you can then
dock within the IDE, as shown below.

10 https://www.uweraabe.de/

120
01111001

You can also see a list of the extra shortcuts that MMX provides in the Messages
windows at the bottom of the screenshot. MMX also adds a toolbar above the
editor windows with lots of tools for refactoring your code.

Live Documentation
On the right of the screenshot is the MMX window, the first tool window is the
Live Documentation window (which I’ve expanded below).

121
01111010
I’ve put the cursor in the Cell function in the code window, clicked the magic
wand button (which creates a default XML documentation template) and filled in
some values, then clicked the green tick to apply it. This automatically adds XML
documentation to the code.

This means that if I hover the mouse over anywhere the Cell function is used, the
tooltip window above is shown. The comments work both ways, I can either
modify them in the code or update them in the Live Documentation window.

Class Browser
The next window down is the Class Browser.

122
01111011

This shows class and inheritance relationships and live metrics for the current
unit. Depending on what you have selected here depends on what is shown below
it (the Members view). There are several tool buttons that allow you to do things
such as add a class, record, or interface. You can even sort all the methods in the
unit, which can make it easier to find methods and properties. The view below is
of the Members View when the class is selected.

The first row of buttons allows you to add and delete fields, methods, properties,
etc. of the class, the second row allows you to filter what is visible. As you can

123
01111100
see, each member has icons to enable you to recognise what it is at a glance, and
you can click on a member to navigate to it in the code window.

Tip of the Day


The last window is the Tip of the Day.

This will give you new tips on using MMX, and you can browse them with the
Previous and Next buttons.

The MMX Toolbar


Above the Code Editor, MMX adds a toolbar.

The toolbar is code sensitive, and it changes depending on where you are in the
code. Here you can see that my cursor is in the TfrmGame.Cell method. If I
wanted to rename it, I just click in the drop-down and type a new name (GetCell
for instance), it would then give me the option for updating the method name
across many things including comments and attributes. However, it only seems
to work in the current unit, not project-wide (use Delphi’s Rename refactoring
for this - Ctrl+Shift+E or Refactoring|Rename).

124
01111101

You can also change the visibility of a method with a single click (public, private,
etc.) or change the parameters with the (,,) button.

If you want to find more about MMX, you can go into the About screen (MMX|
Properties|About) and click on the User Manual, which has extensive
documentation about taking best advantage of MMX.

Navigator
The navigator plugin is a highly useful, yet simple to use tool that can be installed
using the GetIt Package Manager (Tools|GetIt Package Manager), search for
‘navigator’ then click its Install button. It was developed initially by Parnassus,
then acquired by Embarcadero.

125
01111110
Once installed, the IDE needs to restart. You can then access the navigator by
pressing Ctrl+G.

Type in what you are looking for in the search box, then press Enter to navigate to
it within the current unit.

The other part of Navigator is the Mini-Map. It replaces the vertical scroll bar
with an overview of your code.

126
01111111

As you can see on the right is a zoomed-out view of the unit, the white area is
what is visible in the code window currently, and if you hover the mouse over it,
you see a preview of the code at that section of the file. Clicking on the Settings
button:

This allows you to adjust the width of the minimap, and also access the settings
window, which integrates with the standard Delphi settings (you can also access
this through Tools|Options|Third Party|Parnassus|Navigator, although I
suspect this will change in a later version)

127
10000000
Bookmarks
Bookmarks is another plugin that was written by Parnassus and acquired by
Embarcadero. It is a much-enhanced version of the standard bookmarking
capability in Delphi. Again you can install it via Tools|GetIt Package Manager.

Bookmarks allow you to set the next available bookmark by just pressing Ctrl+B.

It has a cute animation when you add a bookmark, giving you a much better
visual cue of setting a bookmark. You can still navigate and set bookmarks by
pressing Ctrl+Num and Ctrl+Shift+Num, respectively. It also adds a bookmark
navigation window (View|Tool Windows|Bookmarks by Parnassus).

128
10000001

The bookmarks window allows you to see all your bookmarks in a list, and across all
open files.

There is also a new kind of bookmark called caret markers. These are a simple
form of bookmark that form a stack. You can drop a caret marker by pressing
Ctrl+Shift+B and go back to it (popping it off the stack) by pressing Escape. The
caret markers allow you to bookmark a particular character position.

Just like Navigator, the configuration is integrated with Delphi, which you access
by clicking on the settings button in the bottom right of the Bookmarks window
or Tools|Options|Third Party|Parnassus|Bookmarks. There are lots of
configuration options, including remapping the shortcut keys.

CodeSite
CodeSite is an extremely easy way to add powerful logging features to your Delphi
application. CodeSite comes in two flavours, the Studio version, which is the full
commercial version and the Express edition which is free with Delphi. The
Express edition can be installed via the GetIt package manager, and it is the
Express edition that we are going to use here.

Once installed into Delphi, you make use of CodeSite by including the
CodeSiteLogging unit in your uses clause. You can then send a log message:

CodeSite.Send('My Message');

129
10000010
This code sends the message to the CodeSite Dispatcher (which should be
running in your system tray), and it forwards it to the CodeSite Live Viewer

The Viewer can be quite slow to process messages compared to how fast you can
generate them in your application. The Viewer does not block your application
which means that if you are logging lots of messages in a short space of time,
they lag behind your application, but eventually will catch up.

It’s also possible to log larger things, such as an entire form:

CodeSite.Send('Form', Form1);

In the image below, I have enabled the Message Detail Panel (at the bottom) and
the Inspector (Right). I have logged the form twice resizing in between. I then
selected both messages which show the differences in the properties in the
Inspector.

130
10000011

Further Learning
To get a real feel of the power of CodeSite you should watch its author Ray
Konopka demonstrate it in this YouTube video11.

GExperts
This is another free collection of tools similar to cnWizards that has far too many
capabilities than there is room for here. However, it provides good help and
documentation. I don’t currently use GExperts, but I’ve used it in the past. You
can obtain GExperts from gexperts.org12.

We are going to look at a small number of tools starting with the Clipboard
History (GExperts|Clipboard History).

Clipboard History

The Clipboard History is a dockable window that monitors the clipboard and
copies anything that appears in it (whether from Delphi or another application).
This allows you to copy to the clipboard multiple times and access your history.
You can then paste anything in your history back into your code. It saves you
having to scroll back and forth looking for that piece you want to replicate, or
pasting the wrong bit when you forget it isn’t the last thing you copied.

11 https://www.youtube.com/watch?v=KQcxH5qbl5M
12 https://blog.dummzeuch.de/experimental-gexperts-version/ (if you want the latest experimental version)

131
10000100
File Favorites
The File Favorites (GExperts|File Favorites or File|Favorites|Configure
Favorites) allows you to configure files or projects you use regularly.

This tool is quite powerful as it allows you to have multiple folders to organise
whatever files you want to access quickly. Above I’ve added a few random
projects to a “projects” subfolder, and below you can see this being accessed
from the File menu.

AutoCorrect
If there is something that you regularly mistype in Delphi, you might find the
AutoCorrect feature helpful (GExperts|Code Proofreader|AutoCorrect). Once
enabled, it can correct mistyped words, or you can use it to create shortcuts.

132
10000101

For instance, when you type “;=” it will automatically correct it to “:=”.

Backup Project
Backup Project (GExperts|Backup Project) is a simple way of zipping up all the
files related to a project.

As you can see, it has found all the files included in my project file, there are also
some additional options which you can find in the configuration (GExperts|
Configuration).

133
10000110
You’ll find a considerable amount of options here. From the IDE tab, I’ve enabled
the enhanced Goto dialog.

This setting replaces the default Goto dialog (Search|Go to line number… or
Alt+G)

With one that has some additional useful locations in the current file.

134
10000111

There are a great number of additional tools and enhancements to the IDE in
GExperts, and I would encourage you to explore what’s available.

Other Non-Delphi Specific Tools


There are several other applications that I like to use to achieve various tasks
which I’m going to mention here briefly. All of the following are free.
 NotePad++. This is a fantastic text editor with many powerful plugins. It
supports syntax highlighting of code and has many powerful plugins. I
find its syntax highlighting helpful when dealing with XML or JSON files,
and its ability to reformat them into a readable form.
 Agent Ransack. I use this for searching as it is multi-threaded and much
faster than the Delphi “Find in Files”.
 GrepWin. I use this for search and replace within files as it supports
regular expressions (see the meta-programming section for an example
of using this).
 Tortoise SVN. A Windows Explorer plugin for Subversion, this is my
preferred way of using Subversion in combination with the Delphi IDE.

Third-Party Libraries
While this is outside the scope of this book, I must at least mention using code
written by a third party is a great way to leverage your time. It can often take
quite a bit of effort to learn to make use of this code or components, but that
effort is usually tiny compared to trying to develop that functionality yourself.
I’ve used code and components from several different vendors which often
represents more code in an application than I have written myself. Why? Because
it is faster.

135
10001000
Metaprogramming
Metaprogramming is where you are writing code or using a tool to write or
modify code. This is an extremely useful ability as it allows you to do things like
contextual search and replace, for instance, to replace a component, while also
adding, removing and updating several properties on that component. This can
save you weeks (or more) of tedious manual work.

Case study - BDE Replacement.


I’ve worked on a software application since 1999. It was based on the BDE
(Borland Database Engine) for which there are probably still a large number of
applications that use it. I had dabbled in replacing the BDE with DBX (DB
Express) for quite some time. However, the conversion was not a simple
replacement with one component with another - it required at least three
components of quite different behaviour. There wasn’t an easy path for this until
Embarcadero acquired the AnyDAC components (renaming them to FireDAC).
The FireDAC TFDQuery and TFDTable are suitable replacements for TQuery and
TTable. They have similar behaviour and properties - however not enough for a
direct search and replace.

I used a tool that’s supplied with Delphi called reFind (covered below), that
allows you to provide a script to replace components, update properties and uses.
I used this and some other tools I wrote to migrate the application and database.
I would run the tools, test the new application, find problems, revert the code and
make modifications so that the migration process worked better (either by
improving the tools or modifying the application), then repeat. This allowed me
to continue developing the application (fixing bugs and adding features) while
converting it to use FireDAC. When I had an application that ran sufficiently well
on FireDAC, I branched the BDE version in SVN, with the FireDAC version being
trunk. I did it this way as there were not sufficient resources to maintain two
versions of the application.

Unfortunately, there are still some users using the old BDE version, so I ended up
doing a bit of double maintenance on critical updates - but they almost all have
been converted to FireDAC now.

136
10001001
Find and Replace
This is probably the most trivial case of meta-programming, however, it can still
be very powerful and save considerable time over manual changes.

In the IDE
The Find in Files tool, unfortunately, doesn't allow you to replace text, only find.
However, you can still use the standard Find and Replace (Ctrl+R or Ctrl+H), but
you are limited to the current file. For example, a simple search and replace
regular expression might be something like the following.

Here we try to find “colou?r” (the “u” is optional) and replace with “hue”. So
“BackgroundColor” would become “BackgroundHue”, and “TextColour” would
become “TextHue” allowing us to replace both “Color” and “Colour” in a single
operation.

You can also use expressions in the “Replace with” field. If you have some code
with // comments within it, try using //{.*} in the text to find, and {\0} in the
replace text. See the GrepWin example below to see how this works, although the
syntax used by Delphi is a bit different.

Turbo GREP
This command-line tool allows you to use regular expressions to search within
files. It’s been in Delphi for a long time, and there is documentation in the help.
At the command prompt (or in PowerShell) if you enter the command:

grep "T(Speed)?Button" *.pas

137
10001010
It searches all files in the current directory with the “pas” extension for either
TButton or TSpeedButton and prints out something like the following (assuming
something is found).

Unit11.pas
Button1: TButton;
Unit12.pas
btnColor: TButton;
sbGo: TSpeedButton;

I don’t use the grep command-line tool, preferring something with a bit more
power (and friendliness) such as...

GrepWin
GrepWin is my favourite tool for doing large scale search and replace operations.
For instance, if you are upgrading a legacy application, it might be the case that
the GUI uses MS Sans Serif. This is a bitmapped font meaning no font smoothing
or scaling - and your application will look like it belongs back in the Windows 95
era. An easy fix for this is to search and replace all the instances of “MS Sans
Serif” with another font, like “Tahoma” (or Segoe UI) within your form files
(*.dfm).

138
10001011
Here you can see that I’ve done just this. I’ve also not bothered to create backup
files as I can revert these changes via source control. It should be noted that
Tahoma is not a drop-in replacement for MS Sans Serif, so it is worth checking
that the forms do indeed look correct before committing changes. I have been
caught out once where there were a bunch of instances where MS Sans Serif was
being used with some unsupported point sizes. Windows was just rounding them
to the closest supported size - but when replaced with Tahoma, the actual point
sizes were used making the form look very strange.

Because GrepWin allows you to use regular expressions, you can do very powerful
things. Suppose you had a source code convention not to use // comments and
only use { }. If you had a large number of // comments, it could result in an
excruciatingly manual process (search for //, then edit the text to use { }).
Instead, use a Regex search and set the Search for text to the regular expression:

//(.*)

This expression roughly translates to: “find every // comment and match all
characters after the // as the first match” (the contents of the brackets). Set the
Replace with text to

{ $1 }

Translation: “Take our first match, and place it between curly braces”.

This allows you to change all the comments in your code all at once, thus
something like

var
x : integer; //important comment
begin
x := 10; //should 10 be a constant?
end;

Would become

var
x : integer; { important comment }
begin
x := 10; { should 10 be a constant? }
end;

139
10001100
This would work across an entire source tree, possibly thousands of (or more)
files. Hopefully, every time you find yourself doing a repetitive process, this
trivial example inspires you to play with regular expressions and see if you can
come up with an expression that does all the work for you. Although I have to
confess that on occasion, I spent more time formulating an expression than it
would have taken to do the work manually.

Delphi AST
The Delphi AST13 (Abstract Syntax Tree) library allows you to parse Delphi source
code and make alterations to it. It’s maintained by Roman Yankovsky14 and is the
basis of Fix Insight and many other utilities that need to understand Delphi
source files. I’ve only made use of this a few times, and the example I’ve chosen
to show here graphs the uses between units (the UsesGrapher sample project). It
parses a Delphi project file (.dpr) and extracts all the uses that have paths, then
parses all of those units and builds up a graph to show each unit with an arrow
showing any other units it’s using. It produces the graph in the Graphviz15
language, which you can then render using the Graphviz application.

If we open and run this project, then use it to open another Delphi project (not
itself as its output is rather dull). The screenshot above is for a MineSweeper
clone that I wrote.

The actual output the application produces is shown below.

13 https://github.com/RomanYankovsky/DelphiAST
14 http://roman.yankovsky.me/
15 https://www.graphviz.org/

140
10001101
digraph G {
MineSweeperFMForm -> MineSweeperEngine
MineSweeperFMForm -> MineSweeperSettings
MineSweeperFMForm -> MinesweeperSoundInterface
MineSweeperFMForm -> MinesweeperAudioManagerSound
MineSweeperFMForm -> MineSweeperFMSettings
MineSweeperFMXSound -> MinesweeperSoundInterface
MinesweeperAudioManagerSound -> AudioManager
MinesweeperAudioManagerSound -> MinesweeperSoundInterface
MineSweeperFMSettings -> MineSweeperSettings
}
Once rendered in Graphviz, it looks like the following:

This uses graph is from my MineSweeper application (FireMonkey version).

The above graph is quite instructive for this small application, and it clearly
shows the unit dependencies within the project. However, for any significant
application, the graph can be large, confusing and difficult to see anything. If you
find Graphviz too limiting try Gephi16.

Writing an application to parse the uses of a project is a very non-trivial exercise,


but if you take advantage of Delphi AST, it becomes much easier.

DFM Parser
I’ve used Robert Love’s17 DFM parser several times (uDFMParser.pas). It allows
you to manipulate a DFM (or compatible) file. In the past I’ve used it to change
components and properties, for example, you might want to convert fonts from
using MS Sans Serif to Tahoma, but also increase the point size by 1 - which is
pretty hard to do with search and replace. I’ve also used it to change components
types, with adding and removing properties, the PAS file can then be updated
with a simple ‘search and replace’ if required.
What the code allows us to do is to convert the contents of a text-based DFM file
into a tree (using the ObjectTextToTree procedure). We can then manipulate

16 https://gephi.org/
17 http://robstechcorner.blogspot.com/

141
10001110
that tree using its various properties and save it back to a DFM file (using
ObjectTreeToText).

The example procedure below is from a project that tidied up after a BDE to
FireDAC migration. The original BDE application used a BDE Alias on all the
TQuery and TTable components, these were converted to TFDQuery and
TFDTable, and the Alias was converted to a ConnectionName property (which is a
string). However instead I wanted to use a centralised TFDConnection, so the
code searches through all the components to find the ones I want to be changed
and update the Connection (a string) to a ConnectionName (an identifier - which
refers to a specific control).

You’ll find this sample as part of the DFMParser\DFMParser.dpr project, note


that this application is exactly what I used to migrate all the forms in a project to
work better with FireDAC. It was also executed as the final stage of a much larger
BDE replacement script (you can use the /DFM command-line parameter to
execute automatically). You won't find it useful “as is” but feel free to customise
it to suit.

You can also see that I also remove unsupported properties, and then recurse into
any children objects that DFMObj owns.

procedure TfrmMigrateDFMs.ProcessDFMObject(DFMObj: TDfmObject);


var
I: Integer;
p : TDfmProperty;
begin
if MatchText(DFMObj.DfmClassName, ['TFDQuery', 'TFDTable']) then
begin
for I := 0 to DFMObj.DfmPropertyCount-1 do
begin
p := DFMObj.DfmProperty[i];
if SameText(p.PropertyName, 'ConnectionName') then
begin
//we want to change all ConnectionNames to a Connection
p.PropertyName := 'Connection';
p.PropertyType := ptIdent;
p.StringValue := 'dmFireDACStandard.Connection';
end;

142
10001111
end;
for I := DFMObj.DfmPropertyCount-1 downto 0 do
begin
//remove properties that don't exist on TFDQuery or TFDTable
p := DFMObj.DfmProperty[i];
if MatchText(p.PropertyName,
['FieldDefs', 'StoreDefs', 'IndexDefs']) then
DFMObj.RemoveDfmProperty(p);
end;
end
else
begin
//recurse for all children
for I := 0 to DFMObj.OwnedObjectCount-1 do
ProcessDFMObject(DFMObj.OwnedObject[i]);
end;
end;

reFind
This is a command-line tool that allows you to make certain changes to Delphi
source files based on a script file and/or command-line options. A script file
allows you to add and remove units from the uses clauses and add, remove and
replace properties on classes.

I found that reFind was pretty slow, so if you are going to be running it a lot, you
should only run it across files that need to be changed (rather than every file in a
project). I did this by running it on every file in the project, then using
SubVersion to tell me which files had changed, then produced a batch file to
migrate only those files.

The reFind documentation18 is pretty good, so I probably don’t need to reproduce


it here - but a simple example might be instructive. Let’s say that we want to
migrate every TButton in an application to TSpeedButton. We need to make a
script file with some rules in it. Firstly we want to convert the classes across and
add the unit that TSpeedButton is in (Vcl.Buttons), so we need a rule:

#migrate TButton -> TSpeedButton, Vcl.Buttons

18 http://docwiki.embarcadero.com/RADStudio/en/
ReFind.exe,_the_Search_and_Replace_Utility_Using_Perl_RegEx_Expressions

143
10010000
This converts across all the controls in the DFM and PAS files, however, we might
run into problems. Firstly TButton has a TabOrder property that TSpeedButton
does not, so we need to remove this:

#remove TabOrder

However, lots of controls have a tab order property, and unfortunately, there is
no way to constrain the remove command to a single class, so a bit of hackery is
required. Instead, we could use the rule:

#migrate TButton:TabOrder -> TButtonTabOrder

This line converts the TabOrder property to the nonexistent TButtonTabOrder


property, which we can subsequently remove. We can save our script in the same
directory as our project file calling it migrate.txt, which will look like the
following

#migrate TButton:TabOrder -> TButtonTabOrder


#remove TButtonTabOrder
#migrate TButton -> TSpeedButton, VCL.Buttons

Notice that I’m changing the TabOrder property first as there won’t be any
TButtons if we do it the other way around. We can upgrade our project on the
command line using:

refind *.dfm *.pas /X:migrate.txt

The more astute of you may have noticed that TButton has several other
properties, such as ButtonKind and ModalResult, which we could also remove in
a similar way. The conversion scripts can be quite long and powerful. There are
some samples, for example, the BDE to FireDAC script which you can find in:

C:\Users\Public\Documents\Embarcadero\Studio\21.0\Samples\Object
Pascal\Database\FireDAC\Tool\reFind\BDE2FDMigration

Or whatever the equivalent directory is for your version of Delphi.

I have one other example. If you use the MessageDlg function, but don’t include
the System.UITypes unit, you will get a compiler hint:

144
10010001
[dcc32 Hint] ButtonForm.pas(31): H2443 Inline function 'MessageDlg'
has not been expanded because unit 'System.UITypes' is not
specified in USES list

If you are updating a legacy application, you might get this message over a great
many units. A quick solution I found was to run the script:

#migrate MessageDlg-> MessageDlg, System.UITypes

Which adds the System.UITypes to every unit that uses the MessageDlg function.

Mida Converter
You can think of Mida Converter19 as a much more powerful version of reFind.
It’s a commercial product which I’ve used to convert a VCL application to
FireMonkey mobile. This is perhaps a bit of an overstatement, Mida Converter
allows you to convert VCL code over to its FMX equivalent. I’ve found this to
work reasonably well; however, a considerable amount of effort is still required to
make each form mobile friendly - I found that the conversion saved me a
significant amount of time over building the application from scratch.

To use Mida Converter, you specify a directory containing the VCL source code
you want to migrate and a destination directory where you want the FireMonkey
conversion to be placed. Many VCL and third party components are supported,
and you can add your own custom conversions.

19 http://midaconverter.com/

145
10010010
cnWizards Property Corrector
The property corrector allows you conditionally alter properties on the current
form, project or project group. Let’s say you want to convert labels to use Segoe
UI font rather than Tahoma, and if the font size is less than 10, increase it to 10.
We would open the property corrector dialog (cnPack|Property Corrector), go
into the options and add two new rules (I’ve removed the default rules that were
present).

We can then click OK to go back to the wizard and click Forms in Current Project
then Search. This searches your entire project and gives you a list of results that
it found.

You can then click Confirm All if you are happy to make all the changes. If there
are a few that you didn’t want to be modified, you can Right-click and select
Undo. Alternatively, you can Right-click and confirm individual changes or
navigate to the control.

146
10010011
GExperts Replace Components
If you have GExperts installed, you can use the Replace Components dialog
(GExperts|Replace Components). This dialog allows you to convert all the
controls of a particular type into something else. Let’s, for instance, convert a
TLabel to a TEdit. First, we need to define some rules in the Settings for Replace
Components (GExperts|Replace Components|Settings).

The first rule we want to add is to convert the Caption property of the TLabel, to
the Text property of the TEdit.

You can add subsequent rules by pressing the green + button. For the next two
properties, we want to Assign constant value, firstly set color to -16777211
(which is clWindow) and height to 25.

147
10010100
We can then right-click on a label, select Replace Component and change our
TLabel with a TEdit.

It automatically converts over the caption property and updates the height and
colour.

This has made for a rather short edit box, so we might also want to do something
about the width.

148
10010101

Your Physical Environment


You are a squishy meat-sack existing in this physical world, but you need to
interact effectively with that non-corporeal world where we create software -
which is a great excuse to purchase several cool and shiny tools, as well as think
of what your body needs.

Hardware
Hardware is the stuff you touch, which can significantly influence your
productivity.

Keyboard
Mechanical keyboards are generally preferred for typing, and it is what’s
allowing me to bang out this text super quickly. Bang is probably not quite the
right word - but they can be a bit noisy, if you are working in an open-plan office
you need to consider co-workers, perhaps opting for a less ‘clicky’ keyboard.
Most mechanical keyboards allow you to replace the keycaps. If you want to be
super cool, you can replace all the keys with blank keycaps - effectively defeating
non-touch typists from using your computer. Nothing spells 313373 like a
completely jet black keyboard. This won’t make you faster if you can’t touch
type, but you’ll look cool! All else being equal, mechanical keyboards feel good
and are fast to type on.

Mouse
I like to use a gaming mouse as they have adjustable sensitivity, are pretty high
precision (yup, justification!), although generally, I’m happy using anything that
has two buttons and a wheel (honestly - we are trying to reduce mouse usage
after all). I also like to have a soft gaming mousepad, which makes the mouse
slide better and more quietly. It also stops it from picking up whatever that goo is
from a hard surface. I don’t think I’d ever want to go back to cleaning a ball
mouse (ahh, the good old days). Now it’s lasers all the way, baby!

Computer
You want something that you don’t have to wait for, preferably something with
excellent single-threaded performance (i.e. lots of GHz / IPC) as lots of
programming tasks can’t be parallelised, and good multicore performance (lots
of cores) for those tasks that can. I like a desktop machine to do most of my
work, and a laptop for when I’m ‘out and about’ or lazing on the couch. A fast

149
10010110
hard drive is also essential. I remember when I first moved from a ‘spinny’
mechanical hard disc drive to an SSD - it was a significant improvement in
compile times due to the large number of small files involved (a bit of a killer for a
mechanical drive).

Screens
I’m a big fan of multiple monitors, and I’ll usually use three where I can. I find a
sizeable primary monitor and two cheaper monitors in landscape mode either
side to be optimal. I’ll do all the development on the primary screen and have
various web pages and documentation on the others. These days you can get
good monitors at a reasonable price. I’ll show my vintage recalling the days when
a 23 inch Sony Trinitron tube was king (with its price and weight proving it!).

Chair
Make sure you have something comfortable that you can sit in for long periods. I
have an old Aeron chair that I quite like - but there is a lot of personal preference
here. I think mesh chairs are better for long term work than foam padded chairs
as they ‘breathe’ better. Chairs come with an ‘hour rating’ for how long sitting in
them is comfortable. The longer the better, although you really shouldn’t do 8
hours sitting at a time, get a chair that makes this bearable when you do.

You can also try using a swiss ball to sit on, for the ultimate challenge try
kneeling on one while coding.

Desk
Generally, I don’t need a huge amount of room to write code, but I like to have
enough width for my expanse of screens, and enough depth for mouse &
keyboard, plus some room for doodling diagrams when I need to do so to
understand things better, or drafting a solution to some problem.

Desks should be the right height for you, your chairs and your screens, and
appropriately sturdy. Consider a standing desk as they are currently considered
healthier, but understand you need to build up your tolerance to them if you
haven't used these before, starting with short stints and building up over weeks.

150
10010111
Other Considerations

Environmental
Having some peace and quiet can certainly help. Music has been proven to be
distracting and reduce productivity. Music without lyrics is generally less
distracting, but sometimes I like to listen to music while I code - particularly if
it’s something I don’t particularly want to write. This might be a problem if you
are in an open-plan office. So a good pair of noise-cancelling headphones can go
a long way to remove any background sounds and allow you to concentrate (while
allowing a proper appreciation for the music of your choice). Consider ‘white
noise’ as your background track for coding - it is very effective at minimising
distracting sounds, and you can download many different Apps to try these. The
shush of constant rain sounds might even be relaxing for you.

Interruptions
These can hurt productivity, particularly if you are ‘in the zone’. It can be quite
hard sometimes to get into the rhythm of solving a complicated problem, and the
last thing you need is someone asking you a bunch of questions that they can
answer for themselves with five minutes of Googling. Arrange with those around
you what signals you all should use for ‘leave me alone, I’m in the zone, only let
me know if the building is burning down’. Refrain from using it all day every day
- people around you likely need your input to increase their productivity too, so
set up fair rules and schedules so everyone benefits. It’s also good from time to
time to get out of your zone for a bathroom break and to refuel the body, and the
subconscious mind often keeps turning over a problem even when you are not
actively ‘on task’, so chatting with a colleague about their problem might be just
what the doctor ordered to make a breakthrough.

A phone placed on do not disturb, or even in another room, can be very helpful.
Set up your voicemail to state when you will be available to take calls again, or
who they can contact if an immediate answer really, and I mean really, is needed.

Turning off pop up notifications on your email or social media will help reduce
distractions, however fleeting they may be. Productivity experts recommend
scheduling time to deal with emails all at once, say every 2 hours, rather than as
each one comes in. This can be hard to do if it is counter to your team culture, but
worth considering.

151
10011000
Multitasking
While multicore CPUs are great at this, your mind can only focus on a single task
at a time. Sure, in the background, your unconscious can be solving something,
but your conscious attention can only be focused on one task at a time. So if you
think you’re multitasking, you’re not, what you are doing is task switching. And
each time you switch between tasks, it takes your brain a certain amount of time
to load all the relevant information, and this process can be quite time expensive.

Quit pretending you can multitask and deal with just one thing at a time. Start by
implementing the suggestions above and see what kind of difference it makes to
your concentration and output.

152
10011001

Sharpening the Saw


This is the seventh habit of highly effective people (according to Stephen Covey)
and is a metaphor for self-improvement or getting better at something.
Sometimes it’s better to stop and sharpen the saw, rather than to continue
grinding away with a dull saw blade. Sometimes even a small insight can make a
complicated task much easier, but where can these insights come from?

Where to go when you are Stuck


Programming is a process of continuous problem solving, most of these
problems you solve without even thinking, others drive you insane as you keep
going round and round in circles as you try to find a solution. Here are some
suggestions for you that will hopefully prevent you from wasting time on a
problem that someone else has already solved (or knows how to solve).

Google is Your Friend


Google is the first place I go when I’m stuck. However, working out what to
search for is quite an art. You want to include enough keywords so that you get
relevant search results, but not too specific that you miss out. For instance, if
there is a part of your application that is not fast enough, and you need some way
of working out what to improve. You might start with “Delphi Faster” as your
first search, however, this might be too general and “Delphi Execution
Performance” might get you more specific results. Often one search leads to
another, in the results you see something about profilers, so you might then
search for “Delphi Profiler”.

Not all search results are created equal. You want to zero in on the ones that solve
your problem and ignore results that have no relevance. I’ll prioritise search
results from the Delphi DocWiki, Stack Overflow, and many other sites that I’ve
found quality information from previously. It is worth noting the date of the
result - older results might not be taking advantage of the latest features of
Delphi.

There are many ways you can do more powerful searches. You can place a hyphen
before a word to exclude it, or quote a word (or phrase) to indicate that it must be
present. There also binary operators (AND which is the default, OR or |), and ways
of searching within specific file types (for example, you knew what you wanted

153
10011010
was contained within a PDF), and plenty more - check out the advanced search
page20.

Asking Questions
Sometimes you can’t find an answer to your question. If you are part of a
development team, then asking a team member for help would be the next step.
This is something you need to be very considerate of, as interruptions can harm
productivity. An email is less intrusive than an instant message which is less
intrusive than face to face. However, being more intrusive will likely get you an
answer quicker. So you are going to need to do a mental calculation regarding
your productivity vs your co-workers. See ‘Interruptions’ above about setting up
some signals with your team as to when is a good time to ask for help.

If you are a member of an online community (I belong to both the New Zealand
and Australian Delphi Users Groups), you can ask there. It’s often helpful to be
able to ask people that you already know and that are in the same or similar time
zones to you.

Stack Overflow
If your searches turn up fruitless, then asking in a forum is your final step. My
favourite place to ask is stackoverflow.com. Stack Overflow is a huge question
and answer site, with millions of members and I would recommend that you
become one of them if you are not already. While you have full access to the site
without being a member if you want to ask a question, you need to join. I’ve
found Stack Overflow to become a pretty harsh place, and you need to read the
rules, or they get pointed out to you rather severely. That aside, there are a large
number of Delphi developers willing to answer questions that belong to the site.

It is worth taking the time to formulate your question so that it is clear and
unambiguous (and is in the form of a question). The people answering are
volunteers, so you don’t want to waste their time, and conversely, if it is not a
good question, they may ignore it (or downvote it). Sometimes you can get an
answer in minutes, other times it might take days - or never be answered.

Recommended Reading
Throughout this book, you have seen me recommend many books, here is a
complete list of books that I recommend you read, it will probably develop over
time https://learndelphi.tv/books

20 https://www.google.com/advanced_search

154
10011011
Social Networks
There are many Delphi groups that you can join on various social networks.
Posting on these is a great way to build your reputation within the Delphi
community. Improving your reputation can lead to additional career
opportunities or access to people and resources that are not available to your
“average” developer.

Facebook
There are several groups that you could potentially join and people you could
follow. A good starting point would be to join the Delphi Developer21 group and
follow Embarcadero Technologies22. There are many other great developers you
can follow. I primarily use Facebook for following people that I actually know and
have met.

LinkedIn
LinkedIn is far more business-focused, and I'm connected to significantly more
Delphi developers on it than Facebook. This is an excellent way of building a
network and gaining lots of industry-specific knowledge. You can join my
network (linkedin.com/in/alisterchristie), and let me know that you’ve read this
book. There are also three English Delphi groups (and others for different
languages)
Delphi and Pascal Developers Group23, Delphi Professionals24, and Embarcadero
Technologies25. I also belong to NZDUG and ADUG groups, which you should join
if you are in the Australia / New Zealand area - there might be other groups
relevant to your area.

Twitter
I’m not a big twitter user but occasionally dip into the firehose. It can be a great
source of up to the second, unfiltered information, and there a large number of
Delphi developers that you can follow, me included on
twitter.com/AlisterChristie.
#CodeFasterInDelphi #CodeBetterInDelphi #LearnDelphiTV

Meetup
Meetup is a way of organising in-person events. You can create or join a group of
other like-minded individuals in your area. The meetup can be as simple as a

21 https://www.facebook.com/groups/137012246341854/
22 https://www.facebook.com/EmbtDelphi/
23 https://www.linkedin.com/groups/1290947/
24 https://www.linkedin.com/groups/101829/
25 https://www.linkedin.com/groups/2551723/

155
10011100
group meeting at a bar or restaurant and just ‘shooting the breeze’, or a full-on
presentation. It is often possible to find a sponsor for the event to provide or
cover the venue and perhaps pizza and beer. In the past, I’ve run Delphi meetups.
You can too, and it’s a great way to meet other Delphi developers.

YouTube
While not a social networking site, it does allow you to subscribe to many
YouTubers so that you can be notified of videos that may have relevance to you.
You can follow me (youtube.com/user/codegearguru) or many others. In
particular, Embarcadero26 and Quark Cube27 are good places to start.

StackOverflow
I’ve already discussed this as a site to get answers to questions. The site is based
on a reputation system, where it’s members can up or down vote questions and
answers. If you provide a good question or answer, it may get upvoted,
increasing your reputation, with the reverse also being true. As your reputation
increases, you get additional privileges and badges for certain achievements.
Because Stack Overflow is so well known, this reputation can be used on your
résumé / CV.

Delphi-PRAXiS
For many years this was (and still is) a great German Delphi forum - for which
Google Translate was my friend. In the last few years, the English version of the
site has taken off: en.delphipraxis.net.

Becoming Known as an Expert


This is certainly not for everyone, and many fantastic developers don’t
participate in the community and quietly work on projects churning out lots of
great code. However, I would encourage you to become engaged in the
community. This might be as simple as commenting on a blog post or YouTube
video, or as extreme as you writing a book or producing a video course.

There are lots of levels in between others might include


 Creating a blog
 Answering a question on Stack Overflow
 Joining a community
 Posting on Facebook or LinkedIn

26 https://www.youtube.com/c/EmbarcaderoTechnologies
27 https://www.youtube.com/user/QuarkCube

156
10011101
 Tweeting
 Reviewing a book on Amazon
 Attending or hosting a local meetup
 Liking or Commenting on a post
 Creating a YouTube tutorial
Make a start, however small, and make a regular contribution.

What’s Improved Productivity Worth


Something that I find amazing at many of the organisations that I’ve worked at
has been the lack of resources put into developing their developers. This is
something that should not only be encouraged but mandated.

As an Employer
If you can raise a developer’s productivity by 10%, what might this be worth?
Let’s run some numbers. Let’s say a developer has a salary of $100k, and costs a
further $50k in expenses, so the total cost of that developer is $150k. Let’s say
that the developer brings in $200k worth of value, therefore $50k profit. If we
increase that developer’s productivity by 10%, how much is that worth? Well,
they now bring in $220k, their salary and fixed costs remain the same, so their
profitability has gone from $50k to $70k - a 40% increase in profit! Suppose by
investing in a developers productivity you could double their productivity to
$400k, at even double their fixed costs ($50k becomes $100k), that’s increasing
the profit from that developer by 400%, which is a truly impressive amount, and
a great return on investment in anyone’s book.

Can you increase a developer's productivity by 10% or more? Well given how
much I see the average company invests in professional development and tools
for their developers I would say this could be achieved with trivial, and low cost,
adjustments. Having a good computer and screens, having administrative access
to their local machine, and allowing them to choose their tools are all low cost
and any one of them could easily achieve this. Even buying a few books might be
enough to have a significant breakthrough, along with the time to read and
implement their learning. Several innovative companies have ‘20% time’ in
which the team can work on their own projects (usually something in the
companies interest) or professional development. In our hypothetical example
here, this would cost the company $20k per annum, or 40% of the current profit.
If the return on that was more productive team members who stay longer and
have more job satisfaction, what could that add to the profit line?

Doubling productivity is challenging, but given these numbers, an organisation


can spend vast amounts of time and money on improving their developers and
157
10011110
still be well ahead, but I think regular investment, week on week on skills and
infrastructural improvement will do the trick.

As an Employee
On the flip side, if you are paid a salary, a company is generally not willing to pay
twice as much for a developer who is twice as good - but is usually willing to pay
more. It is also great to work somewhere where your skills are both valued and
cultivated. If you don’t feel valued, then this might be as simple as discussing
your needs with your manager or as hard as changing jobs.

Another option is to start a side project or business. You don’t necessarily need to
make money directly from the project, for instance, working on an open-source
project can add to your experience and profile, and you might be able to monetise
it somehow (via advertising revenue, support, or a Pro version).

I’ve had several side projects over the years, for instance, the LearnDelphi.tv
website and making commercial videos. They’ve all been worth doing. Even
using paid work or side projects to fund other investments, for example, stock
markets or investing in real estate is worth doing - nothing gives you the ability
to pick and choose your fate as financial freedom. Your day job probably won’t
make you rich, but that killer app might!

Self Employed
If you work for yourself, then any improvement in productivity can go straight
into your pocket. This is particularly true if you are selling a product. This could
just be improving your existing product faster, or that extra productivity could
mean that you have time to develop additional products.

Being able to code once and sell it time and again is an excellent way to leverage
your time and expertise for profit. Whether that is a product you sell, or licence,
or just code you can utilise for different clients (for example, a booking and lead
generation system for a hairdresser can be adapted for other hairdressers, or
even for dentists and so on). Your options are only limited by your ability to think
of them.

Diminishing returns on investment


Getting your first 10% improvement in productivity is usually pretty easy, the
next is slightly harder, and the next harder still. The ‘harder’ may be a financial
cost, time cost, bureaucratic cost, willingness/resistance, or limits of your
imagination. It isn’t always possible to fully optimise something, or worth it to

158
10011111
do so. Generally speaking, pick the low hanging fruit first - that is, do what is
easiest for the most return on investment.

Further Learning
This kind of what this whole section is about, but I’d recommend The Complete
Software Developer's Career Guide by John Sonmez (2017), I listened to the
audiobook version and found it excellent.

159
10100000
Final Words and Conclusion
Congratulations you’ve reached the end of this book. I hope that you’ve enjoyed
reading it and learnt many great ideas for being more productive in Delphi. This
doesn’t need to be the end of our journey together.

Check out my book - Code Better in Delphi to improve the code you produce -
which you can order via www.learndelphi.tv

Additionally, I’ve prepared additional material for you to get even more out of
your journey to being a faster Delphi developer. You can get the ‘super-secret’
bonus material from:

LearnDelphi.tv/faster

Have any coding or productivity tips you think I’ve missed? Have you spotted a
mistake in this book? Continue the conversation by contacting / following me
on...

Email: alister@learndelphi.tv
YouTube: https://www.youtube.com/user/codegearguru
Web: https://LearnDelphi.tv
LinkedIn: https://linkedin.com/in/alisterchristie/
Twitter: https://twitter.com/AlisterChristie
Facebook: https://www.facebook.com/LearnDelphitv/
Instagram: https://www.instagram.com/christiealister/

I look forward to hearing from you as you continue your Delphi journey.

end.

160

You might also like