Thursday, June 19, 2014

Ode to Code

Ode to Code:
Reflections on Software Development in Iambic Heptameter
by Cary Jensen

While true, it's said, there's work to do, and now it's time to start
This is my task, so plain and clear, it's science, love, and art

My slate starts clean, a screen pure white, no logic bears my will
Upon this page, through thought and might, it's my intent to fill

I make my plan, each step designed, my spec is well thought out
It matters much, oh yes it does, there can't be any doubt

My fingers fly, the logic pours, a waterfall sublime
I see my goal, I type and click, I lose all sense of time

My words are key, my syntax right, and subtly reserved
I own this world, I'm in control, though humble, numb, and nerved

It's getting close, the tension firms, anticipation's high
I hit F9, compile and run, I feel success is nigh

But wait! It fails! How can this be, catastrophe I feel
I've lost my touch, my senses fail, my mortal soul revealed

Oh, damn the Gods! This logic broke, a bug I cannot find
I check the source. I check each line. I think I'll lose my mind

But what is this, a misplaced test, can this thing truly be
Compile again, and it just works, I'm now in ecstasy

I document the change I made, my comment explains why
I always try to take this step, to prevent a future cry

Relaxed, I am, my goal achieved, a conquest satisfied
It's what I do, a job compelled, a source of joy and pride

You beauty mine, my cherished child, the product of my soul
From white blank slate to final code, a void has morphed to whole

No eyes will see this gold I've spun, no heart with feel its beat
It lies beneath the interface, resplendent and complete

I scratch one item off my list, but I cannot pretend
With this task done, there will be more, my work will never end;

Copyright © 2014 Cary Jensen. All Rights Reserved

Tuesday, May 13, 2014

Simplifying LiveBindings Configuration in Mobile Apps Using Frames

Unless you have been sleeping under a rock, you already know that Delphi can create native executables for both iOS and Android mobile devices. These applications must be created using FireMonkey, since FireMonkey is Delphi's cross-platform component library. When using FireMonkey, most developers make use of LiveBindings and the LiveBindings Designer to enable data awareness in the visual controls.

Unfortunately, the evolving best practices for mobile application design in Delphi has relied on applications based on a single form, where a TabControl containing two or more TabItems defines the various screens (pages) of the application.

This design avoids some of the issues associated with having multiple forms in a mobile application. For example, Android applications do not support the concept of non-modal forms, and although iOS does, the active form always occupies the entire screen.

Another issue related to having more than one form in a modal application is related to mobile device orientation. Specifically, many applications will re-orient themselves when a user rotates their device 90 degrees, switching from a portrait view to a landscape view and back. The problem here is that the OnResize event is received only by the form with focus. No other forms in memory receive this event, and therefore may not be aware that orientation has changed.

If a mobile application needs two or more pages (or views or forms or whatever you want to call them), this can be achieved by including a single form on which a TabControl appears. You then create one TabItem for each page of the application, and provide user interface elements such as menus, tabs, or buttons to navigate between the available TabItems. In fact, this is the model used by all of Delphi's multi-page wizards in the Object Repository, which you can select from when you select File | New | FireMonkey Mobile Application from Delphi's main menu.

While the TabControl/TabItem configuration works, it introduces an unwanted side effect. Specifically, if you use the LiveBindings Designer to visually bind your controls to BindSourceDB components, every control appears on the default layer, and the designer quickly becomes cluttered, making it difficult to create and manage your LiveBindings. And even though the LiveBindings Designer permits you to define layers on which to display select controls, the clutter remains a challenging issue. And, the clutter increases in direct proportion with the overall number of controls on your one form. This can be seen in the following view of the LiveBindings Designer for a somewhat simple two TabItem TabControl.

 
Unfortunately, a two-page mobile application is uncommon. Most mobile applications will have many more pages (TabItems), and the problem of clutter in the LiveBindings Designer can get completely out of hand.

There is another solution, however, and it can be found in FireMonkey frames, which were added to FireMonkey in Delphi XE3 (the VCL introduced frames in Delphi 5, and I have found them to be invaluable in my Windows applications).

Here's the deal. A frame is a design surface, and each design surface in Delphi has its own LiveBindings Designer. As a result, only those controls that appear on the frame automatically appear when you view the frame's LiveBindings Designer.

Here is what you can do. In the simplest case, you create one frame for each TabItem that you will include in your single form application. You then place each frame into its corresponding TabItem, and then align the frame to alClient. If you want to get really clever, you might even place two or more frames into a single TabItem, but that is a design issue.

For each frame, you include in its uses clause only those data modules whose TDataSets you want to bind using LiveBindings. The result is that each LiveBindings Designer includes only a subset of controls that appear in the single form. This can be seen in the following view, which shows a frame that replaces the controls that appear on the first TabItem of the form whose LiveBinding Designer was shown earlier in this post.

 
The problem is not entirely solved. Specifically, the one form of your application on which the TabControl appears will display all of the objects from all of the frames placed on the individual TabItems within the LiveBindings Designer when the form itself is selected in the designer, so the clutter still exists. But you will not really care. As long as you can configure LiveBindings at the Frame level, you will use the LiveBindings Designer only with individual frames, in which case only the components on the selected frame will be visible. Furthermore, the benefits of this frame-by-frame configuration will increase in direct proportion to the number of TabItems on your form.

This is just one of the tips, along with many other fun and useful Delphi techniques, that I will be covering along with Bob Swart (Dr.Bob) at this years Delphi Developer Days 2014 tour. We have completed our first two cities in the United States, and the response was excellent. We now continue on to Frankfurt, Germany on May 26th and 27, Amsterdam, The Netherlands on June 12th and 13th, and London, Great Britain on June 18th and 19th. For more information and to register, please visit http://wwwDelphiDeveloperDays.com.

Copyright (2014) Cary Jensen. All Rights Reserved

Tuesday, February 11, 2014

Delphi Developer Days 2014

It is again my pleasure to announce the dates and cities for our annual Delphi Developer Days tour by me and Delphi expert Bob (Dr.Bob) Swart. We will be visiting two cities in the United States and three in Europe/United Kingdom. We will be in the Washington DC/Baltimore area on May 5th and 6th and Chicago on May 8th and 9th.

We continue in Frankfurt, Germany on May 26th and 27th, continuing to Amsterdam, The Netherlands on June 12th and 13th. We have once again added London to our spring tour, and we will be there June 18th and 19th.

If you are not familiar with Delphi Developer Days, it is an intense, two-day Delphi-focused event, where we cover some of Delphi's latest features, as well as topics that apply to many versions of Delphi. Bob and I present some of the more general topics together in joint sessions, where we share our knowledge and personal perspectives. We also include a number of breakout sessions, where Bob and I go to separate rooms to present different topics.

I am very pleased with this year's agenda. Our joint sessions include an overview of mobile development with Delphi, where we will talk about both iOS and Android development, device setup, app deployment, and Delphi's next generation compiler. In another joint session we review some of the more advanced language features that have been added to Delphi since Delphi 2009.

We also have a joint session on data awareness in cross-platform applications. In this session we discuss LiveBindings, as well as alternative techniques for presenting data to your users. And Delphi Developer Days would not be complete without our Tips, Tricks, and Techniques session. In this joint session Bob and I share a collection of powerful, and sometimes odd features of Delphi that you can use to extend your applications and improve your development experience.

We also have a wide variety of separate sessions. In these sessions we cover FireDAC, Delphi's new data access framework, REST servers and clients, debugging and development support techniques, including unit testing, and an extensive and in-depth survey of object-oriented programming in Delphi. Other separate sessions cover advanced mobile development, a comparison of Delphi's target platforms and how these operating systems differ, as well as a session on when and where to place your data entry validation and business rules in database applications.

For a complete list of sessions, view our agenda  at http://www.delphideveloperdays.com/descriptions.html. On this page you will also find a link to view a more detailed description of each session.

It's a lot of material in two days, and due to the separate sessions, you won't be able to physically attend every session. That's why Bob and I document every session we cover (with the obvious exception of the question and answer sessions that we hold at the end of each day). These limited edition course books, available only to Delphi Developer Days attendees, are extensive and detailed, typically weighing in at 300-400 pages. This year is no different. I have set aside a significant amount of time over the next three months for my writing, and I know Bob is doing the same. Regardless of which sessions you attend, you'll get all of the material we cover, include the many code samples we use in our demonstrations.

I also want to mention that we are particular about where we hold our events. All of our venues are close to airports and/or train stations. For example, both of the hotels in the US have free shuttles from the nearby airports. These hotels also have free parking if you arrive by car. Our Frankfurt hotel is less than 2 kilometers from Frankfurt's main train station, from where you can hire a taxi or even catch a bus that stops outside the hotel. Alternatively, the hotel is a 30 euro taxi ride from Frankfurt Airport, one of the largest airports in Europe. There is also free parking for attendees on the days of the event.

In Amsterdam, we are at the historic Victoria Park Plaza, which is directly across the street from Amsterdam's Central Station, which is a short train ride from Amsterdam's Schiphol Airport, also one of the largest in Europe. Finally, in London we are at the Park Inn London Heathrow. Fly into Heathrow and you can catch a free bus that drops you off a short walk from the hotel. There is also free parking for attendees on the days of the event.

Attendance in each city is limit to 42 developers, which permits sufficient networking opportunities while keeping the experience intimate. As a result, however, we have sold out most cities over the past years, so you should consider registering early to ensure that you reserve a seat in the city of your choice. We are also offering discounts for early registration, and if you've attended Delphi Developer Days since 2009, there are additional discounts. There are also discounts when your company sends three or more of their developers.

Delphi Developer Days History

Delphi Developer Days in a long-time Delphi touring event focusing exclusively on Delphi development. Cary Jensen, who authored and was the featured speaker on the original Delphi World Tours (1995 through 1999), started Delphi Developer Days in the fall of 2001. From 2009 through 2012, Marco Cantù of Wintech Italia, Srl and Cary joined forces to co-offer Delphi Developer Days in Europe and in the United States. When Marco was hired to be the Delphi product manager, noted Delphi expert Bob Swart of Bob Swart Training & Consultancy (eBob42) teamed up with Cary to keep the Delphi Developer Days tradition alive.
For more information, and to register, visit www.DelphiDeveloperDays.com.

Thursday, January 9, 2014

Two Approaches to Sub-classing Components Compared

Delphi is an object oriented programming language. Unless you write nothing but console applications, this fact is obvious. For example, when your application includes either a form or a data module, the class that defines the form or data module is a descendent of an existing class. In the case of a form, it is a TForm descendant, and in the case of a data module it is a TDataModule descendant.

This process of extending an existing class, especially one that is not TObject, is used extensively by the visual component library (VCL), and to a lesser extent in the runtime library (RTL). Importantly, it is a technique that you can use to create your own custom classes, ones that inherit the power of an existing class, and which extend that class to add additional features. These features might include new properties, additional methods, or alternative behaviors for methods inherited from the ancestor class.

Overall, the VCL is a remarkable and rich component library. Nonetheless, it is not uncommon, especially with seasoned developers, to want to extend existing classes of the VCL or RTL to add custom capabilities.

In this post I am going to discuss two different, though not entirely dissimilar, techniques for creating a new class based on an existing VCL component. In most cases, these techniques can also be used to extend any component, whether created by you or your development team or a third party. Towards the end of this post I will compare these two techniques by discussing the particular strengths of each approach.

The Traditional Method

Most developers who sub-class components of the VCL do so by declaring that class as a descendant of the existing component, followed by compiling that class into a runtime package which they install in Delphi. I am going to demonstrate this technique by creating a component that extends the TDBGrid class, adding a handy feature for reading data from it's underlying TDataSet.

This sub-classed grid exposes a GetField method, which returns a TField associated with the current record of the data set being displayed by the grid based on the name of the underlying dataset field. This new class also includes an InitializeDictionary method which must be called at least once after the grid has been associated with a dataset, but before the first call to GetField.

Here is the declaration of this new class, named TEasyReaderDBGrid:

type
  TEasyReaderDBGrid = class(Vcl.DBGrids.TDBGrid)
  private
    { Private declarations }
    FDict: TDictionary< string, TField>;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure InitializeDictionary(DataSet: TDataSet);
    /// 
    /// You must call InitializeDictionary each time you assign the
    /// DataSet property of the grid before you can use GetField
    /// 
    function GetField(const Name: string): TField;
  end;

This class is not very complicated. It uses a generic TDictionary (declared in System.Generics.Collections) to implement the GetField method. This can be seen in the implementation of this class, shown here:
{ TDBGrid }
constructor TEasyReaderDBGrid.Create(AOwner: TComponent);
begin
  inherited;
  FDict := TDictionary< string, TField>.Create;
end;
destructor TEasyReaderDBGrid.Destroy;
begin
  FDict.Free;
  inherited;
end;
function TEasyReaderDBGrid.GetField(const Name: string): TField;
begin
  Result := FDict.Items[Name];
end;
procedure TEasyReaderDBGrid.InitializeDictionary(DataSet: TDataSet);
var
  Field: TField;
begin
  for Field in DataSet.Fields do
    FDict.Add(Field.FieldName, Field);
end;
All we need to do to make this component available on the component palette is to create a design time package, add this component's unit to the package, make a call to RegisterComponents from a Register procedure, and then install this new package.

I've added the forward declaration and implementation of the Register method to the same unit in which the TEasyReaderDBGrid class is declared, adding this new component to the Samples page of the Tool Palette. Finally, here is the source of the design time package:

package EasyReaderDBGrid;

{$R *.res}
{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
{$DEBUGINFO ON}
{$EXTENDEDSYNTAX ON}
{$IMPORTEDDATA ON}
{$IOCHECKS ON}
{$LOCALSYMBOLS ON}
{$LONGSTRINGS ON}
{$OPENSTRINGS ON}
{$OPTIMIZATION OFF}
{$OVERFLOWCHECKS OFF}
{$RANGECHECKS OFF}
{$REFERENCEINFO ON}
{$SAFEDIVIDE OFF}
{$STACKFRAMES ON}
{$TYPEDADDRESS OFF}
{$VARSTRINGCHECKS ON}
{$WRITEABLECONST OFF}
{$MINENUMSIZE 1}
{$IMAGEBASE $400000}
{$DEFINE DEBUG}
{$ENDIF IMPLICITBUILDING}
{$DESCRIPTION 'Runtime package for TEasyReaderDBGrid'}
{$DESIGNONLY}
{$IMPLICITBUILD ON}

requires
  rtl,
  vcl,
  dbrtl,
  vcldb;

contains
  Traditionalu in 'Traditionalu.pas';

end.

Once we compile and install this new design time package, the new component, TEasyReaderDBGrid appears on the Samples page of the Tool Palette, as shown in Figure 1.

Figure 1. A traditionally sub-classed component appears on the Tool Palette

Figure 2 shows a VCL Forms application on which an instance of the TEasyReaderDBGrid class has been placed at design time. In this figure a call to GetField has been made from the OnClick event handler of a button, shown here:

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(EasyReaderDBGrid1.GetField('CustNo').AsString);
end;


Figure 2. The GetField method of the TEasyReaderDBGrid component returns a TField associated the grid's TDataSet.

The Interceptor Method

Unlike the traditional method, which involves adding a component to the component palette, the interceptor method "intercepts" the name of the existing VCL component, mapping it to a different class. Consider the form shown in Figure 3.


Figure 3. A VCL Forms application using a class that intercepts TDBGrid

The form in Figure 3 looks and behaves like that shown in Figure 2. The difference is that the grid that appears in Figure 3 is not a TEasyReaderDBGrid. Instead, the grid is actually an instance of the TDBGrid class. This class, however, is intercepted and extended, giving the TDBGrid class the methods necessary to support the features of the TEasyReaderDBGrid class.
There are two general approaches to class interception. One is to intercept the class within the module where the extended class is used. This approach is shown in the following type declaration:

type
  TDBGrid = class(Vcl.DBGrids.TDBGrid)
  private
    { Private declarations }
    FDict: TDictionary< string, TField>;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure InitializeDictionary(DataSet: TDataSet);
    /// 
    ///   You must call InitializeDictionary each time you assign the
    ///   DataSet property of the grid before you can use GetField
    /// 
    function GetField(const Name: string): TField;
  end;
  TForm1 = class(TForm)
    ClientDataSet1: TClientDataSet;
    DataSource1: TDataSource;
    Button1: TButton;
    DBGrid1: TDBGrid;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  end;

Alternatively, the interceptor class can be declared in its own unit. In that case, all you need to do is ensure that the unit in which the interceptor class is declared appears later in the uses clause than the unit of the class that is being extended. Here is an example of how the interface section of an interceptor unit might look (the implementation is identical to that shown earlier for the TEasyReaderDBGrid class):

unit Interceptoru;

interface

uses System.Classes, Vcl.Grids, Vcl.DBGrids,
     System.Generics.Collections, Data.DB;

type
  TDBGrid = class(Vcl.DBGrids.TDBGrid)
  private
    { Private declarations }
    FDict: TDictionary< string, TField>;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure InitializeDictionary(DataSet: TDataSet);
    /// 
    ///   You must call InitializeDictionary each time you assign the
    ///   DataSet property of the grid before you can use GetField
    /// 
    function GetField(const Name: string): TField;
  end;

When using an interceptor unit, I generally find it necessary to document the placement of the interceptor unit in the uses clause, as shown here:

unit MainformIu;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils,
  System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids,
  Vcl.DBGrids, system.Generics.Collections, Data.DB,
  Vcl.StdCtrls,Datasnap.DBClient,
  Interceptoru;  //This unit must appear later in this
                 //uses clause than the Vcl.DBGrids unit

type
  TForm1 = class(TForm)
    ClientDataSet1: TClientDataSet;
    DataSource1: TDataSource;
    Button1: TButton;
    DBGrid1: TDBGrid;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  end;

The real difference in implementation between the traditional method and the interceptor method is that your code looks as though you are using the class that you sub-classed, as opposed to the sub-class itself. This can be seen in the following event handler, which is similar to the OnClick event handler for the button shown earlier in this article.

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(DBGrid1.GetField('CustNo').AsString);
end;

Comparing These Two Methods

While the end result of these two approaches is identical, there are significant differences. These differences make each of these mechanisms better suited for some uses over the other. I'll begin by considering the advantages of the traditional approach.

Advantage of the Traditional Approach

The primary advantage of the traditional approach is that your sub-classed component can appear in the Tool Palette. Having the component in the Tool Palette provides two benefits. First, any published properties that you add to your sub-classed component will appear in the Object Inspector at design time. Second, once you've placed the component onto your module from the Tool Palette, the unit in which your component is defined will be added to your interface section automatically the next time you save or compile your project.

Both of these benefits come down to convenience. Traditionally sub-classed components are easier to use.

Advantages of the Interceptor Approach

While the interceptor approach has the drawback that it is somewhat more complicated to use, it also introduces benefits that make it a powerful alternative. To begin with, creating a interceptor class takes less time. There is no need to create a design time package and a design time package does not need to be installed.

Not needing a design time package actually makes interceptor classes easier to share with a development team. Using the traditional approach, you need to provide each of the developers who will be working on the project with access to the package, which they will need to install into their copy of Delphi. When using an interceptor class, all you need to do is add the interceptor class unit into a directory on your library search path, and it will just work. Alternatively, you can make the interceptor unit a unit of your project, again making any classes defined in it immediately available to any unit that uses the interceptor unit.

Another advantage of an interceptor class is that it makes it remarkably easy to extend an existing class when you want to add only one or two new features. Furthermore, when you declare your interceptor class directly in the unit from which it will be used, you can customize each instance of the ancestor class for that module. For example, you might want to add an additional method to a TListBox interceptor on a given form. If you have a second form that also needs a modified TListBox, but with a different custom method, no problem. Create a different interceptor class for each form and you are done.

Finally, and what I think is absolutely the best advantage of interceptor classes is that it lets you make customizations to a component's sub-classes. For example, imagine that you want to add one or more custom properties to the individual menu items of a TMainMenu. This can be done very easily by creating an interceptor class for TMenuItem. After that, all of the menu items you add to your main menu will have those properties. Granted, you can only access those properties at runtime, but that is just a detail.

Here is a simple example of a TMenuItem interceptor type declaration that, if added to a unit prior to the TForm declaration, adds a MyInteger runtime property to any menu item appearing on that form.

type
  TMenuItem = class(Vcl.Menus.TMenuItem)
  strict private
    FMyInteger: Integer;
  public
    property MyInteger: Integer read FMyInteger write FMyInteger;
  end;

By comparison, if you use the traditional approach, you would have to actually sub-class TMainMenu and implement an extended version of TMenuItem from within the owner class.

Summary

Sub-classing existing classes of the VCL (or other libraries) is a powerful tool that every Delphi developer should take advantage of. In this article I have looked at the two primary mechanisms for sub-classing existing components. Each of these approaches have their strengths, making each best suited for some applications over the other.

Copyright (c) 2013 Cary Jensen. All Rights Reserved

Wednesday, September 18, 2013

New 2013 Delphi Developer Days Event

I am pleased to announce a special edition of Delphi Developer Days 2013 and Mobile Development Workshop, to be held this December 4th, 5th, and 6th near London's Heathrow Airport. This three-day event includes the classic two-day Delphi Developer Days 2013 sessions by me and Delphi expert Bob (Dr.Bob) Swart, where Bob and I present joint sessions, where we present in-depth topics together, as well as separate sessions, where we break out into separate rooms to present diverse topics.

The optional third day is the Delphi Developer Days Mobile Development Workshop, where Bob and I work with you to develop and deploy mobile applications to your iOS * or Android ** mobile device. This one-of-a-kind workshop includes focused discussions by Bob and me where we explain aspects of mobile development, followed by hand-on sessions where you implement the concepts we describe. During this time we are available to answer questions, provide guidance, and help you build and deploy your sample applications.

You can attend all three days of this special event, just the first two days, or only the Delphi Developer Days Mobile Development Workshop. Discounts are available to previous Delphi Developer Days attendees, and their is a special discount for the hands-on workshop available to attendees of this spring's Delphi Developer Days. There are also discounts for early registration.

Lunch is provided on each day that you attend. All attendees registered for the first two days will receive our Delphi Developer Days 2013 course book, containing more than 400 pages of material covered in our presentations. Similarly, attendees of the Delphi Developer Days Mobile Development Workshop will receive material on iOS, Android, and FireDAC mobile development written by Bob and me. Space is limited, so register now to reserve your seat.

Visit www.DelphiDeveloperDays.com for more information, including registration, agenda, software and hardware requirements, location details, and more.

* iOS devices include iPhone, iPad, and iPad mini devices
** Android devices must be based on ARMv7 compatible CPUs. Android Jelly Bean is preferred, though most Ice Cream Sandwich and Gingerbread devices are supported

Wednesday, August 7, 2013

Dragging and Dropping in DBGrids Revisited

Please note: a bug was found in the original code upload. It was reported in a comment below.

I have uploaded a code example that fixes the problem (see link at the of this post). However, that code can only handle drag/drop operations in a DBGrid so long as the Options property of that DBGrid includes the dgTitles flag.

I am leaving this post, and a link to the updated code, live on this blog, as I know that the feature described here is a valuable one. I will continue to work on addressing the remaining issue, and will update this blog and the code upload once I have accomplished that.

Once again, thank you for your patience.

About a year ago I posted a blog that described in detail how to implement drag and drop operations with a DBGrid. The technique that I described employed a ClientDataSet as a key element in the drag and drop process, and it is one that I have used extensively in the year since.

There was one glich, however. During this past spring's Delphi Developer Days tour with my colleague Bob Swart (a.k.a Dr.Bob) one of the attendees asked what would happen if the DBGrid displayed less than all records in the underlying ClientDataSet. It turns out that this is an issue with my original code example (and it has since been pointed out elegantly in a comment posted to the blog post I linked to above). If the first record displayed in the DBGrid does not correspond to the first record in the underlying ClientDataSet, my original code would incorrectly place a record dropped into the top position of the DBGrid in the top-most position of the ClientDataSet, instead of above the record onto which the drop operation targeted.

Initially I did not worry about this discrepancy too much, selfishly I will admit. In my real world usage, the number of records being dragged or dropped never exceeded the visual display capacity of the DBGrid. But it is a problem, and Michael Riley's posted comment on my earlier blog motivated me once again to look for a solution.

As that last paragraph implied, I did try to find a solution earlier, in response to the question posed by the Delphi Developer Days attendee. However, I could not see a way to determine the record number of the underlying DataSet when the visible rows of the DBGrid were greater than the number of records in the DataSet, and the DBGrid was scrolled down.

Having taken up the challenge again, I turned to every coder's best friend, Stack Overflow. There I posted my question, hoping that someone had discovered an appropriate mechanism. Even though I posted the question on a Saturday, long-time Stack Overflow contributor Uwe Raabe was quick to post an answer. With this information in hand, I was able to quickly adapt my earlier drag and drop example to correctly perform drag and drop into and within a DBGrid under all relevant conditions.

What Uwe proposed was the creation of a class helper for the TDBGrid class. His class helper looked like the following:

TDBGridHelper = class helper for TDBGrid
public
    function RecNoFromVisibleRow(Value: Integer): Integer;
end;

His implementation of the RecNoFromVisibleRow method is shown here:

function TDbGridHelper.RecNoFromVisibleRow(Value: Integer): Integer;
begin
  Result := DataSource.DataSet.RecNo - Row + TopRow + Value;
  if dgTitles in Options then
    Dec(Result);
end;

This worked like a charm, and solve the problem that I specifically asked about in my Stack Overflow post, which concerned the first visible record in the DBGrid. Testing my updated drag and drop example revealed another issue that I had not anticipated. Specifically, if the DBGrid contained more records than the number visible in the DBGrid, and the user dropped a new record past the end of the last visible record, that record would be placed at the end of the underlying ClientDataSet, rather than after the last visible record.

This problem was easy to solve by modifying Uwe's code in the following manner.

function TDBGridHelper.RecNoFromVisibleRow(Value: Integer): Integer;
begin
  if Value = -1 then
  begin
    Result := DataSource.DataSet.RecNo - Row + TopRow + VisibleRowCount
  end
  else
  begin
    Result := DataSource.DataSet.RecNo - Row + TopRow + Value;
    if dgTitles in Options then
      Dec(Result);
  end;
end;

Over the past several days I have tested this code pretty hard, and it appears to work under all conditions. A link to the download for the final code example is located at the end of this blog post.

I will leave it up to you to follow the link I provided at the outset of this post to read the details about how this dragging and dropping in DBGrids works. I will, however, point out the minor changes that the introduction of this class helper has on the original code. In addition, I will discuss the use of class helpers, and offer an alternative solution, in the form of an interceptor class.

My original OnDragDrop event handler on the DBGrid began like the following:

procedure TForm1.DBGridDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  GridRow: Integer;
  OriginalRow: Integer;
begin
  GridRow := DBGrid.MouseCoord(X,Y).Y;
  if GridRow = 0 then
    GridRow := 1;
  if (Source is TListBox) then

It now looks like this, where the initial adjustment of the GridRow variable is replaced by a call to the RecNoFromVisibleRow method:

procedure TForm1.DBGridDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  GridRow: Integer;
  OriginalRow: Integer;
begin
  GridRow := DBGrid.MouseCoord(X,Y).Y;
  GridRow := DBGrid.RecNoFromVisibleRow((*ClientDataSet.RecNo, *)GridRow);
  if (Source is TListBox) then

However, when dragging and dropping within the DBGrid itself, GridRow values of 0 do need to be adjusted to 1, so that happens later in this method method, once a within grid drag/drop is confirmed, as shown here:

  if Source = Sender then
  begin
    //We are dragging within the DBGrid
    if ClientDataSet.IsEmpty then exit;
    OriginalRow := ClientDataSet.RecNo;
    if (OriginalRow = GridRow) or (GridRow = -1) then
      exit
    else
    if GridRow = 0 then GridRow := 1;
    MoveRecord(ClientDataSet, OriginalRow, GridRow);
  end;

I also added an additional line of code to reposition the current record in the underlying ClientDataSet at the end of the segment associated with a drop from the ListBox (dropping from outside of the DBGrid). This line, and the several above it, are shown here:

  //Insert the new item at the drop position
  ClientDataSet.InsertRecord( [GridRow,  TListBox(Source).Items[TListBox(Source).ItemIndex], RandomRange(1, 101)]);
  //Make the dropped record the current record
  ClientDataSet.RecNo := GridRow;

Other than the declaration of the class helper and the implementation of RecNoFromVisibleRow, all of the remaining original code is untouched.

Now, let me address the use of the class helper. First let me say that using a class helper is a perfectly sound solution. It does, however, have one drawback. You can have only one class helper for a given class. If you already have a class helper for TDBGrid (or if Embarcadero introduces one), this new class helper will make that one unavailble.

An alternative solution is to use an interceptor class. An interceptor class is one that has the same class name as the existing class, and extends that existing class, but which has a scope closer to the code that uses it than the original class. When those conditions exist, the interceptor class takes precedence, and its overridden or introduced methods, properties, fields, and such, are available to your code. In addition, since the interceptor class is a descendant of the original, any protected (other than strict protected) members of the original class are visible to it. This is important, since Row and TopRow, which are protected members of the Vcl.DBGrids.TDBGrid class, need to be access from within the RecNoFromVisibleRow method.

In my code sample, the interceptor class is declared prior to the TForm class, which results in any TDBGrid instances appearing on the form being instances of my interceptor class. This class declaration looks like the following (in which I have included a little bit of the TForm declaration as well):

type
  TDBGrid = class(DBGrids.TDBGrid)
  public
    function RecNoFromVisibleRow(Value: Integer): Integer;
  end;
  TForm1 = class(TForm)
    ListBox: TListBox;
    DBGrid: TDBGrid;

Of course, you must implement the RecNoFromVisibleRow method in the interceptor class. In this case, since the interceptor class is named TDBGrid, the only difference between the implementation of the interceptor method and class helper method is the class name (TDBGrid versus TDBGridHelper). As a result, I will not repeat that code here. Otherwise, the remaining code is exactly the same. A helper class adds the RecNoFromVisibleRecord method to the Vcl.DBGrids.TDBGrid class, while the interceptor class introduces this method, and TDBGrid instances on the form are instances of the interceptor, instead of the interceptor's ancestor.

One final note. While I declared the interceptor class in the same unit as my form declaration, I could have just as easily declared it in some other unit. The only requirement for the use of the interceptor class in my form is that the unit in which the interceptor class is declared must appear in the interface section uses clause, and later in that uses clause than the Vcl.DBGrids unit.

Here is the link to the updated code. Please enjoy.

Friday, April 19, 2013

Delphi Developer Days 2013 Course Book

Over the past month and a half, Bob Swart and I have been writing the material for Delphi Developer Days 2013 (http://www.DelphiDeveloperDays.com). In all we produced seven chapters each. Bob and I each wrote two of the joint sessions, and we each wrote our own four individual sessions. We also each wrote our half of the Tips, Tricks, and Techniques session. This is why the course book has 14 chapters while Delphi Developer Days includes 13 unique sessions.

Loy Anderson, who manages Delphi Developer Days, had given us an early April deadline in order to have the course books printed in time for Chicago. It was a lot of work, but we did it, and Loy worked hard to compile our chapters into a unified book as we finished each chapter. She submitted the book for printing on April 7th, and we fully anticipated getting the books in time for our Chicago event, which begins on May 6th.

To our amazement, the books arrived this week, both those intended for Chicago as well as a European delivery of books for our Frankfurt and Amsterdam events. The book is over 400 pages in length, and we are not talking slideshows here. As you can see in the following picture, our chapters are detailed, and include screenshots and code samples.

Of course, this detail is necessary for our individual sessions. During these sessions, Bob and I break out into separate rooms to present our specific topics. If you decide to go to Bob's presentation, you can always catch up on my presentation by reading my chapter on the topic in the book. But we feel that detailed chapters are also important for our joint sessions, giving you something to refer back to long after Delphi Developer Days is over.

I am especially pleased this year with our content. Like I have done in the past with Marco Cantù, Bob and I worked hard to create a solid selection of topics that should be of interest to almost every Delphi developer. Some of our sessions cover the absolute latest information on Delphi, including Delphi XE4, which was announced just over a week ago. These talks include iOS development, Delphi's new NextGen compiler, and FireDAC, the new data access component framework.

There is also plenty of material to engage developers using older versions of Delphi. For example, there are talks on multithreaded development, DataSnap, debugging, browser-based clients, and Windows services. When appropriate, these talks discuss features added in recent versions of Delphi. However, the bulk of the information in these chapters applies to older versions of Delphi (some going back as far as the original Delphi).

In addition to working to find a balance of topics, Bob and I also worked to organize the talks intelligently. For example, talks on data access (including FireDAC) and multithreaded development are presented prior to those on DataSnap (which assumes knowledge about data access and multithreaded programming).

Likewise, we tried to pair our individual presentations in a meaningful way. When one of us is speaking about one of the most recent versions of Delphi, the other is presenting a topic that appeals to developers using older versions. Likewise, we tried to match an Internet-related presentation with one that applies to traditional workstation applications.

Although the book is printed (and in our hands), we all continue preparing for the actual events. Loy has a lot of organizational details to complete, including the printing of name badges and onsite signage, as well as arranging for lunches and our various guest speakers. Bob and I continue to work on our talks, creating our slideshows, and adding to, and improving, the demo projects that we'll use.

We are really looking forward to this year's event, and I am looking forward to this first year presenting with Bob Swart. For those of you who have already registered, we look forward to seeing your there. While Chicago has sold out, we still have space available in both Frankfurt and Amsterdam, but we expect these cities to sell out as well. Furthermore, at the time of this posting we still have a 10% discount for early registration in Europe, which ends April 30, 2013. See http://www.DelphiDeveloperDays.com for details and pricing.

Delphi Developer Days is fun, and we are looking forward to it. We hope you are, too.