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.
Friday, April 19, 2013
Thursday, February 14, 2013
Happy 18th Birthday, Delphi!
It's Delphi's 18th birthday, and I am celebrating by coding in Delphi XE3 (the 16th version) and posting this picture of my first copy of Delphi.
Monday, February 4, 2013
Delphi Editor Key Combination Table Updated
In May of 2009 I blogged about a table of Delphi editor keystrokes that I assembled and made available online. That table, which was relatively complete (incomplete might be a more honest appraisal), was more or less current as of Delphi 2009. I had intended to keep this table up-to-date, but like so many casual obligations, intention met reality, and the table has been left untouched, until now.
What motivated the update is that this past December I had the pleasure to cross paths once again with fellow Embacadero MVP and Delphi advocate Brian Long. We were speakers at the annual Sofware Developer Network conference in The Netherlands, were one of Brian's sessions was titled "IDE Productivity Tips." A big part of his presentation covered useful editor keystrokes.
We got talking after his presentation, and I mentioned my negleted key combination table and asked if he'd care to take a look and help bring it up to date. He agreed, but I really don't think either of us could imagine how much work it was going to take. Now, two months later, after many email exchanges, and hours of review and research, we are prepared to share our efforts and publish the updated table of Delphi editor keystrokes and key combinations.
The updated table now includes the many key combinations that have been added in the four releases of Delphi since I published the original table, and boasts a whopping 185 different editor key and key combinations. The new list also includes those editor keystrokes that are active during debugging, which were missing entirely from the original list.
Brian has a keen eye. That, combined with his extensive knowledge of Delphi, significantly improved the scope and consistency of the original table. I am grateful for his effort, and hope that you will appreciate it as well.
Edit: I will make an effort to keep this table updated, and have made several changes since the original 4 February 4 2013 post. The most recent version of this table was updated on 6 February 2013.
You can access the latest version of the updated table using the following link: http://www.JensenDataSystems.com/DelphiEditorKeyTable.pdf
Wednesday, January 23, 2013
Delphi Developer Days 2013 Cities and Dates
I am pleased to announce the cities and dates for this
year's Delphi Developer Days tour. We will be in Chicago May 6th and 7th,
Frankfurt on May 30th and 31st, and Amsterdam on June 3rd and 4th.
Delphi Developer Days are intense, two-day events that blend
the in-depth coverage of formal training with the atmosphere and networking
opportunities of a small conference. Joining with me in offering this year's
tour is renowned Delphi expert Bob Swart (Dr. Bob). Bob is an Embarcadero MVP,
winner of the coveted Spirit of Delphi award, as well as a developer,
consultant, and author. His skills and Delphi knowledge make him uniquely
qualified to replace my previous Delphi Developer Days partner, Marco Cantù,
who became the Delphi Product Manager this past fall.
Delphi Developer Days includes joint sessions, presented by
both Bob and me, as well as simultaneous tracks, where Bob and I break out into
separate rooms to present focused topics. Like past Delphi Developer Days
tours, we will be covering many of Delphi's newest features as well as topics
that apply to older versions of Delphi. This year's offerings include database
development, mobile applications, SOAP (Simple Object Access Protocol) and REST
(REpresentational State Transfer), DataSnap, Windows services, debugging,
migrating to FireMonkey, and the always popular Tips and Tricks session.
Additional sessions cover threads and thread synchronization, browser-based
clients, as well as gestures and touch. For a full list this year's sessions,
visit www.DelphiDeveloperDays.com.
Also making a return is the popular Delphi Developer Days
course book. This extensive manual includes papers written by Bob and me and
covers all of our Delphi Developer Day sessions. This means that while you'll
have to select which talk to attend when Bob and I break out into our separate
sessions, you will still receive the material, including source code, from all
of our talks. Past year course books have run hundreds of pages in length, and
have included more content than most books on software development. We expect
this year's course book to be no different, and Bob and I are in currently in
the process of writing it.
In addition to our sessions, Delphi Developer Days includes
a keynote presentation from an Embarcadero representative, and well as a
different guest speaker in each city. We also include open Q&A sessions
each day where you can ask questions about any Delphi-related topics. We also
have a raffle at the close of the second day where you have an opportunity to
win valuable prizes contributed by our many sponsors.
All of our Delphi Developer Days 2013 locations are easy to
get to. Our Chicago event is close to Chicago's O'Hare airport, and the hotel
offers a free shuttle bus to and from that airport. Our Amsterdam location is
across the street from Amsterdam's Central Station, and our Frankfurt location
is one and a half kilometers (one mile) from Frankfurt's main train station
(Frankfurt am Main Hauptbahnhof), from which you can take a taxi. There is also
a city bus that runs from the train station and stops right in front of the
hotel. Hotel locations, travel details, and more are available at
www.DelphiDeveloperDays.com.
Seating is limited to 42 attendees in each city. These
limits ensure that attendees get an opportunity to interact with me and Bob, as
well as network with each other. However, we sold out a number of cities last
year, and anticipate that this will happen again this year. As a result, you
should register early in order to ensure your spot in the city of your choice.
You can also earn a discount if you register early. There
are additional discounts for previous Delphi Developer Days attendees, as well
as for companies that register three or more employees.
Bob and I, as well as Delphi Developer Days coordinator Loy
Anderson, are very excited about this year's tour. We think we've put together
an exceptional program, and we hope to see you there.
For more information, and to register, please visit
www.DelphiDeveloperDays.com.
Wednesday, November 7, 2012
Delphi Developer Days with Bob Swart and Cary Jensen
November 7, 2012
It is my special pleasure to announce that Bob Swart is joining me in presenting the annual Delphi Developer Days tours. Bob, known internationally as "Dr.Bob," is one of the world's leading Delphi experts and a dedicated supporter of the Delphi community.
Bob is singularly qualified to fill the vacancy created when Marco Cantú accepted the position of Delphi Product Manager. Like me, Bob is a Delphi developer, consultant, trainer, and author. His blog, "Dr. Bob Examines," has served as an invaluable lifeline to countless Delphi developers, and his Delphi courseware is highly regarded.
Bob and I have known each other for years. He has served as my editor for the articles that I write for the Software Development Network Magazine, and I have worked with him on numerous Delphi conferences. Bob has also been a featured guest speaker at Delphi Developer Days for the past several years, so he is intimately familiar with the format.
I am excited about this new opportunity to work together with Bob Swart on Delphi Developer Days. In the coming months, we will announce our 2013 topics, cities, and dates. We look forward to bringing to you the highest quality Delphi events, and invite your to visit www.DelphiDeveloperDays.com to learn more about these intense, two-day Delphi seminars.
It is my special pleasure to announce that Bob Swart is joining me in presenting the annual Delphi Developer Days tours. Bob, known internationally as "Dr.Bob," is one of the world's leading Delphi experts and a dedicated supporter of the Delphi community.
Bob is singularly qualified to fill the vacancy created when Marco Cantú accepted the position of Delphi Product Manager. Like me, Bob is a Delphi developer, consultant, trainer, and author. His blog, "Dr. Bob Examines," has served as an invaluable lifeline to countless Delphi developers, and his Delphi courseware is highly regarded.
Bob and I have known each other for years. He has served as my editor for the articles that I write for the Software Development Network Magazine, and I have worked with him on numerous Delphi conferences. Bob has also been a featured guest speaker at Delphi Developer Days for the past several years, so he is intimately familiar with the format.
I am excited about this new opportunity to work together with Bob Swart on Delphi Developer Days. In the coming months, we will announce our 2013 topics, cities, and dates. We look forward to bringing to you the highest quality Delphi events, and invite your to visit www.DelphiDeveloperDays.com to learn more about these intense, two-day Delphi seminars.
Tuesday, November 6, 2012
Congratulations Marco Cantú, Delphi Product Manager
November 6, 2012
I am not the first, nor will I be the last, but I want to wish Marco Cantú the very best in his new position as Delphi Product Manager at Embarcadero. This is great news for the Delphi community, and I am excited about what the future will bring.
I've known Marco since we first met in London in the mid-1990s at the annual Borland European conference, and have worked closely with him for more than five years organizing and delivering our annual Delphi Developer Days tours. I can attest to his commitment, knowledge, humor, and insight, and know that he will bring his many gifts to his new role.
As far as Delphi Developer Days is concerned, this is not the end, but rather a transition. I have exciting news to share about DDD, but that will have to wait until tommorow. For now, let me join in the chorus to extend a warm and hearty "congratulations" to both Marco and Embarcadero Technologies. May your future successes be ours as well.
I am not the first, nor will I be the last, but I want to wish Marco Cantú the very best in his new position as Delphi Product Manager at Embarcadero. This is great news for the Delphi community, and I am excited about what the future will bring.
I've known Marco since we first met in London in the mid-1990s at the annual Borland European conference, and have worked closely with him for more than five years organizing and delivering our annual Delphi Developer Days tours. I can attest to his commitment, knowledge, humor, and insight, and know that he will bring his many gifts to his new role.
As far as Delphi Developer Days is concerned, this is not the end, but rather a transition. I have exciting news to share about DDD, but that will have to wait until tommorow. For now, let me join in the chorus to extend a warm and hearty "congratulations" to both Marco and Embarcadero Technologies. May your future successes be ours as well.
Wednesday, August 22, 2012
Dragging and Dropping into DBGrids
In this post I demonstrate how to implement advanced drag-and-drop features with a DBGrid as the target using a ClientDataSet and cloned cursors. But first, let me acknowledge the posts of Zarko Gajic on delphi.about.com. I started writing this post five days ago, and he posted a new entry concerning drag-and-drop two days ago. It's entirely a coincidence. Nonetheless, if you are new to drag-and-drop operations, Zarko has posted many excellent articles on the subject, and I recommend that you visit his site to learn more about this and other Delphi-related topics.
Drag-and-drop operations, where the user drags data from one object and drops it into another object, has been a standard feature in Delphi applications for a long time. Is dragging and dropping with a DBGrid as the target more difficult than, say, a ListBox? Well, that depends.
If you are dragging from some source object and simply dropping that value into a DBGrid, without concern for where the dropped data will appear in the DBGrid, then no. From the DBGrid's OnDragDrop event you read the data from the source object and insert that data into the underlying DataSet. If the DataSet is displaying its data using an index, the dropped data will appear in the grid at the position dictated by the index key, otherwise it will appear at the end of the grid.
It's when you actually want to drop the source data into a particular row of the DBGrid that things become more complicated. It is even more complicated if you want to permit the user to be able to re-order the records in a DBGrid by dragging a record from one position in the DBGrid, dropping it into a new position.
Before I demonstrate how to implement sophisticated drag-and-drop operations using a DBGrid, let's first consider what makes this operation complicated. In a word, indexes. In most databases the order of records in a table is dictated by an index, and in most cases, a primary index. For the same reason, the sequence in which the records appear in a DBGrid are either affected by an index, or in the case of a query, an ORDER BY clause.
What this suggests is that you must manipulate the fields involved in the index or ORDER BY clause of your DataSet when implementing drag-and-drop in a DBGrid, which is correct. Following this logic, you might assume that with every drop operation you are going to write to your database. While you can do that, it is something that I personally want to avoid. Instead, I would prefer to implement the drag-and-drop functions on a cached version of the data, writing to the database only after the user is satisfied with the order of the data they are working with.
So, how is this done? With a ClientDataSet, of course.
Here's the basic approach. Associate a DBGrid with a ClientDataSet that makes use of an integer index that dictates the order of records in the grid. In some cases, including the code sample associated with this post, the ClientDataSet might be empty initially. In other cases, the ClientDataSet is first populated from a database.
When the data is loaded from a database, the field associated with the ordering index is most likely not actually a field in the database. Instead, it is an extra field added for ordering purposes. However, if your underlying data has a field that specifies order, you can use that. In my particular implementation it is necessary that this ordering field begin with 1 (the first record) and be sequential. This is just a detail, however, and the code that I will show could be modified to accommodate any kind of ordinal data.
Once you have a ClientDataSet with these features, all you need to do is to enable drag-and-drop and then implement OnDragOver and OnDragDrop event handlers from which you perform some basic operations. These techniques are demonstrated in the DBGridDragDrop project, which you can download from the preceding link. I wrote this project in Delphi XE2, and tested the posted code in Delphi 7. As a result, I assume that it will run in Delphi 7 Professional and later.
This project demonstrates two especially valuable drag-and-drop techniques. The first is dropping to an arbitrary position in the DBGrid. The second is dragging records from one position in the DBGrid to another.
There are two drag sources in this project, a ListBox and a DBGrid, and one target, the DBGrid. For demonstration purposes, the ListBox is loaded from a call to the PopulateListBox method, shown here:
The ClientDataSet in this project begins as an empty DataSet. This ClientDataSet is created in the following method.
Both of these methods are called when the main form is first created, producing the form shown in the following figure.

Figure 1. The DBGridDragDrop project main form
We could set the DragMode property of both the ListBox and DBGrid to dmAutomatic, but I like to avoid that. Instead, I prefer to initiate the drag operation only after the mouse has been dragged some reasonable distance. Otherwise, a drag operation begins with the slightest mouse down drag. As a result, I initiate the drag operation from the OnMouseMove event handler of both drag sources. This event handler is shown here.
We need to add an OnDragOver event handler to the drop target in order to have the mouse cursor change to indicate that dropping is allowed (and to permit the OnDragDrop event handler to trigger). This event handler is shown here, and it is assigned only to the DBGrid.
Finally, the drop operation is performed from the target's OnDragDrop event handler. From here we determine the source of the drag operation (ListBox versus DBGrid) and take the appropriate action. Again, here is the event handler. It is only associated with the DBGrid.
If the ClientDataSet is empty, we simply insert a record, assigning a sequence of 1. If the integer Y property of the TGridCoord instance returned by MouseCoord evaluates to -1, the user has dropped at the end of the DBGrid. This, too, is a simple matter of inserting the record with a sequence of TClientDataSet.RecordCount + 1.
It is when the drop occurs somewhere in the middle of the DBGrid that the approach is more complicated. In short, we need to re-sequence all of the records appearing in the drop location, adding 1 to their sequence field value. After that we can insert the record using the drop position record number as the value for the sequence field. In the OnDragDrop event handler listed previously this task is handled first by a call to ResequenceCDS, after which the TClientDataSet's InsertRecord method is invoked.
It is in the ResequenceCDS method that things get fun. As you can see in the code listing that follows, a ClientDataSet cloned cursor is used to perform the record resequencing. This has the effect of changing the underlying indexed field values with a minimal impact on the user interface.
The following three figures demonstrate this drag-and-drop in action. Figure 2 depicts the DBGrid after three records have been added, while Figure 3 shows a fourth record being dropped into the second position of the DBGrid. Figure 4 shows how the DBGrid appears following the drop operation.

Figure 2. Three records have been dropped onto the DBGrid from the ListBox.

Figure 3. A value from the ListBox is being dropped into the second position of the DBGrid

Figure 4. The drag-and-drop operation has dropped the new value in position 2
As you can see from this code, we begin by repositioning the record that is being moved to the end of the index order, assigning to it RecordCount + 1 (in reality this could be a any value outside the current range of sequence numbers, just so long as we get the record we are moving out of the way. Next, depending on whether the move is upwards or downwards, we resequence each of the records that need to be repositioned before we perform the final movement. Finally, we reposition the record we are moving into its new position.
This operation is demonstrated in the following two figures. In Figure 5 the record at position 4 is being dragged to position 2. Figure 6 shows the DBGrid after the drop operation has been completed.

Figure 5. The record in position 4 is being dragged to position 2.

Figure 6. The dragged record has now been moved to position 2.
Removal is performed by the RemoveFromSequence method, shown here.
If you want to learn more about ClientDataSets, including an entire chapter on cloned cursors, please take a look at my most recent book Delphi in Depth: ClientDataSets.
Copyright © 2012 Cary Jensen. All Rights Reserved.
Drag-and-drop operations, where the user drags data from one object and drops it into another object, has been a standard feature in Delphi applications for a long time. Is dragging and dropping with a DBGrid as the target more difficult than, say, a ListBox? Well, that depends.
If you are dragging from some source object and simply dropping that value into a DBGrid, without concern for where the dropped data will appear in the DBGrid, then no. From the DBGrid's OnDragDrop event you read the data from the source object and insert that data into the underlying DataSet. If the DataSet is displaying its data using an index, the dropped data will appear in the grid at the position dictated by the index key, otherwise it will appear at the end of the grid.
It's when you actually want to drop the source data into a particular row of the DBGrid that things become more complicated. It is even more complicated if you want to permit the user to be able to re-order the records in a DBGrid by dragging a record from one position in the DBGrid, dropping it into a new position.
Before I demonstrate how to implement sophisticated drag-and-drop operations using a DBGrid, let's first consider what makes this operation complicated. In a word, indexes. In most databases the order of records in a table is dictated by an index, and in most cases, a primary index. For the same reason, the sequence in which the records appear in a DBGrid are either affected by an index, or in the case of a query, an ORDER BY clause.
What this suggests is that you must manipulate the fields involved in the index or ORDER BY clause of your DataSet when implementing drag-and-drop in a DBGrid, which is correct. Following this logic, you might assume that with every drop operation you are going to write to your database. While you can do that, it is something that I personally want to avoid. Instead, I would prefer to implement the drag-and-drop functions on a cached version of the data, writing to the database only after the user is satisfied with the order of the data they are working with.
So, how is this done? With a ClientDataSet, of course.
Here's the basic approach. Associate a DBGrid with a ClientDataSet that makes use of an integer index that dictates the order of records in the grid. In some cases, including the code sample associated with this post, the ClientDataSet might be empty initially. In other cases, the ClientDataSet is first populated from a database.
When the data is loaded from a database, the field associated with the ordering index is most likely not actually a field in the database. Instead, it is an extra field added for ordering purposes. However, if your underlying data has a field that specifies order, you can use that. In my particular implementation it is necessary that this ordering field begin with 1 (the first record) and be sequential. This is just a detail, however, and the code that I will show could be modified to accommodate any kind of ordinal data.
Once you have a ClientDataSet with these features, all you need to do is to enable drag-and-drop and then implement OnDragOver and OnDragDrop event handlers from which you perform some basic operations. These techniques are demonstrated in the DBGridDragDrop project, which you can download from the preceding link. I wrote this project in Delphi XE2, and tested the posted code in Delphi 7. As a result, I assume that it will run in Delphi 7 Professional and later.
This project demonstrates two especially valuable drag-and-drop techniques. The first is dropping to an arbitrary position in the DBGrid. The second is dragging records from one position in the DBGrid to another.
There are two drag sources in this project, a ListBox and a DBGrid, and one target, the DBGrid. For demonstration purposes, the ListBox is loaded from a call to the PopulateListBox method, shown here:
procedure TForm1.PopulateListBox;
begin
ListBox.Clear;
ListBox.Items.Add('One');
ListBox.Items.Add('Two');
ListBox.Items.Add('Three');
ListBox.Items.Add('Four');
ListBox.Items.Add('Five');
ListBox.Items.Add('Six');
ListBox.Items.Add('Seven');
ListBox.Items.Add('Eight');
ListBox.Items.Add('Nine');
ListBox.Items.Add('Ten');
end;
The ClientDataSet in this project begins as an empty DataSet. This ClientDataSet is created in the following method.
procedure TForm1.CreateClientDataSet;
begin
ClientDataSet := TClientDataSet.Create(Self);
//This example assumes that the field defining the
//order of records is named Sequence. This field
//can appear in any position in the table structure.
ClientDataSet.FieldDefs.Add('Sequence', ftInteger);
ClientDataSet.FieldDefs.Add('Field', ftString, 30);
//The ClientDataSet can have any number of fields
//The following field is just for demonstration
ClientDataSet.FieldDefs.Add('RandomNumber', ftInteger);
ClientDataSet.CreateDataSet;
ClientDataSet.IndexFieldNames := 'Sequence';
DataSource1.DataSet := ClientDataSet;
end;
Both of these methods are called when the main form is first created, producing the form shown in the following figure.
Figure 1. The DBGridDragDrop project main form
We could set the DragMode property of both the ListBox and DBGrid to dmAutomatic, but I like to avoid that. Instead, I prefer to initiate the drag operation only after the mouse has been dragged some reasonable distance. Otherwise, a drag operation begins with the slightest mouse down drag. As a result, I initiate the drag operation from the OnMouseMove event handler of both drag sources. This event handler is shown here.
procedure TForm1.MouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
const //this constant is typically declared with a higher scope
MouseMovePixels = 15;
begin
if ssLeft in Shift then
TListBox(Sender).BeginDrag(False, MouseMovePixels);
end;
We need to add an OnDragOver event handler to the drop target in order to have the mouse cursor change to indicate that dropping is allowed (and to permit the OnDragDrop event handler to trigger). This event handler is shown here, and it is assigned only to the DBGrid.
procedure TForm1.DBGridDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); begin //Test for acceptable drag origin classes or objects Accept := (Source is TListBox) or (Source is TDBGrid); end;
Finally, the drop operation is performed from the target's OnDragDrop event handler. From here we determine the source of the drag operation (ListBox versus DBGrid) and take the appropriate action. Again, here is the event handler. It is only associated with the DBGrid.
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
begin
//An item is being dropped into the DBGrid
if ClientDataSet.IsEmpty then
begin
//The grid is empty. Add the item in the first position
ClientDataSet.AppendRecord([1,
TListBox(Source).Items[TListBox(Source).ItemIndex],
RandomRange(1, 101)]);
end
else
begin
//Insert the item at the position of the drop
if GridRow = -1 then //the drop is at the end of the DBGrid
GridRow := ClientDataSet.RecordCount + 1
else //the drop needs to be inserted into the DBGrid. Make room
ResequenceCDS(ClientDataSet, GridRow);
//Insert the new item at the drop position
ClientDataSet.InsertRecord(
[GridRow, TListBox(Source).Items[TListBoxSource).ItemIndex],
RandomRange(1, 101)]);
end;
//Remove the dropped item from the source (optional)
TListBox(Source).Items.Delete(TListBox(Source).ItemIndex);
end
else
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
MoveRecord(ClientDataSet, OriginalRow, GridRow);
end;
end;
As you can see, one of the tricks to this technique is getting the row over which the drop operation occurred. This is done using the DBGrid's public MouseCoord method, to which we pass the X and Y coordinates, which we obtain from the event handler's parameters. We then use the Y property of the returned TGridCoord object to discover the record being dropped on. (MouseCoord is public in Delphi 7 and later. If you are using an earlier version where this method is protected, refer to Zarko's THackDBGrid to expose this method to your code.)Dragging and Dropping to a Specific DBGrid Position
What happens next depends on the source of the drag operation. If the drag operation originated from a ListBox, we need to ensure that it is dropped into the proper position within the DBGrid. To do this, we have to check for three possible conditions.If the ClientDataSet is empty, we simply insert a record, assigning a sequence of 1. If the integer Y property of the TGridCoord instance returned by MouseCoord evaluates to -1, the user has dropped at the end of the DBGrid. This, too, is a simple matter of inserting the record with a sequence of TClientDataSet.RecordCount + 1.
It is when the drop occurs somewhere in the middle of the DBGrid that the approach is more complicated. In short, we need to re-sequence all of the records appearing in the drop location, adding 1 to their sequence field value. After that we can insert the record using the drop position record number as the value for the sequence field. In the OnDragDrop event handler listed previously this task is handled first by a call to ResequenceCDS, after which the TClientDataSet's InsertRecord method is invoked.
It is in the ResequenceCDS method that things get fun. As you can see in the code listing that follows, a ClientDataSet cloned cursor is used to perform the record resequencing. This has the effect of changing the underlying indexed field values with a minimal impact on the user interface.
procedure TForm1.ResequenceCDS(cds: TClientDataSet; FromRow: Integer);
var
clone: TClientDataSet;
SequenceFld: TField;
begin
clone := TClientDataSet.Create(nil);
try
clone.CloneCursor(cds, True);
SequenceFld := clone.FieldByName('Sequence');
begin
//Shift all records down to make room in the sequence
//for the record being inserted
clone.Last;
while (SequenceFld.AsInteger >= FromRow) and not clone.bof do
begin
clone.Edit;
SequenceFld.AsInteger := SequenceFld.AsInteger + 1;
clone.Post;
clone.Prior;
end;
end
finally
clone.Free;
end;
end;
The following three figures demonstrate this drag-and-drop in action. Figure 2 depicts the DBGrid after three records have been added, while Figure 3 shows a fourth record being dropped into the second position of the DBGrid. Figure 4 shows how the DBGrid appears following the drop operation.
Figure 2. Three records have been dropped onto the DBGrid from the ListBox.
Figure 3. A value from the ListBox is being dropped into the second position of the DBGrid
Figure 4. The drag-and-drop operation has dropped the new value in position 2
Dragging and Dropping Existing Records in a DBGrid
Dragging an existing record from its current position to a new position within the DBGrid is a bit more involved. This process is handled by the MoveRecord method shown here.procedure TForm1.MoveRecord(cds: TClientDataSet; OldPos, NewPos: Integer);
var
clone: TClientDataSet;
SequenceFld: TField;
begin
clone := TClientDataSet.Create(nil);
try
clone.CloneCursor(cds, True);
SequenceFld := clone.FieldByName('Sequence');
clone.RecNo := OldPos;
clone.Edit;
//Move the record being moved to the end of the sequence
SequenceFld.AsInteger := cds.RecordCount + 1;
clone.Post;
if OldPos < NewPos then
begin
//Shift the records after the original old position up one position
clone.RecNo := OldPos;
while (clone.RecNo < NewPos) do
begin
clone.Edit;
SequenceFld.AsInteger := SequenceFld.AsInteger - 1;
clone.Post;
clone.Next;
end;
end
else
begin
//Shift the record before the original position down one position
clone.IndexFieldNames := 'Sequence';
clone.RecNo := OldPos - 1;
while (clone.RecNo >= NewPos) and (not clone.bof) do
begin
clone.Edit;
SequenceFld.AsInteger := SequenceFld.AsInteger + 1;
clone.Post;
clone.Prior;
end;
end;
//Move the record being moved to its new position
clone.RecNo := cds.RecordCount;
clone.Edit;
SequenceFld.AsInteger := NewPos;
clone.Post;
finally
clone.Free;
end;
cds.RecNo := NewPos;
end;
As you can see from this code, we begin by repositioning the record that is being moved to the end of the index order, assigning to it RecordCount + 1 (in reality this could be a any value outside the current range of sequence numbers, just so long as we get the record we are moving out of the way. Next, depending on whether the move is upwards or downwards, we resequence each of the records that need to be repositioned before we perform the final movement. Finally, we reposition the record we are moving into its new position.
This operation is demonstrated in the following two figures. In Figure 5 the record at position 4 is being dragged to position 2. Figure 6 shows the DBGrid after the drop operation has been completed.
Figure 5. The record in position 4 is being dragged to position 2.
Figure 6. The dragged record has now been moved to position 2.
Deleting Records
We also have to update the sequence when a record is removed. Since the ReadOnly property is set to True on the DBGrid, record removal is something that we do from a custom popup menu. This popup menu has a single item, whose caption is Remove. This menu is surpressed when there are no records in the grid by the OnPopup event handler, shown here.procedure TForm1.Remove1Click(Sender: TObject); var cds: TClientDataSet; begin cds := TClientDataSet(DBGrid.DataSource.DataSet); RemoveFromSequence(cds, cds.RecNo); end;
Removal is performed by the RemoveFromSequence method, shown here.
procedure TForm1.RemoveFromSequence(cds: TClientDataSet; Position: Integer);
var
clone: TClientDataSet;
SeqFld: TField;
begin
clone := TClientDataSet.Create(nil);
try
clone.CloneCursor(cds, True);
SeqFld := clone.FieldByName('Sequence');
clone.RecNo := Position;
// Edit - Added this to next line: and (clone.RecordCount = 1)
if (Position = 1) and (clone.RecordCount = 1) then
begin
//There is just one record. Delete it,
//but do not try to set a new record position
clone.Delete;
end
else
begin
if clone.RecNo = clone.RecordCount then
begin
clone.Delete;
cds.RecNo := cds.RecordCount;
end
else
begin
clone.Delete;
while not clone.eof do
begin
clone.Edit;
SeqFld.AsInteger := SeqFld.AsInteger - 1;
clone.Post;
clone.Next;
end;
cds.RecNo := Position;
end;
end;
finally
clone.Free;
end;
end;
To Clone or Not To Clone
All of the methods that I have shown here make use of cloned cursors, which are very handy for working with a ClientDataSet in the background. However, cloned cursors are not essential for these techniques to work. In fact, the code sample in the download for this post includes two versions of the three workhorse methods (ResequenceCDS, MoveRecord, and RemoveFromSequence). The version that I have shown here that employes cloned cursors, and another version that does not. You might find that one version works better than the other, depending on the circumstances of your data.Summary
Dragging and Dropping is a common feature in Delphi applications, and a great technique for productive user interface design. Dragging and dropping within DBGrids, however, is more involved, especially if you want to drop somewhere other than the end of the DBGrid. In this posting, I have demonstrated how you can use a ClientDataSet and a cloned ClientDataSet cursor to implement sophisticated drag-and-drop operations involving a DBGrid as a target.If you want to learn more about ClientDataSets, including an entire chapter on cloned cursors, please take a look at my most recent book Delphi in Depth: ClientDataSets.
Copyright © 2012 Cary Jensen. All Rights Reserved.
Subscribe to:
Posts (Atom)


