<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6222893893652665733</id><updated>2012-01-23T11:32:04.315-08:00</updated><title type='text'>Cary Jensen  "Let's Get Technical"</title><subtitle type='html'>Technical discussions related to software development. Particular attention is paid to Delphi development, including both native Delphi development and .NET development using Delphi Prism. Also expect a healthy dose of database-related content, including Advantage Database Server, SQL, and general database design.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>37</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-3163105637724336853</id><published>2012-01-23T09:25:00.000-08:00</published><updated>2012-01-23T11:32:04.332-08:00</updated><title type='text'>Delphi Developer Days 2012 Tour</title><content type='html'>Marco Cantù and I want to share with you that we are once again taking our Delphi Developer Days tour on the road this spring. This year, our two-day live Delphi event visits six cities with stops in Baltimore/Washington DC and Chicago in the United States, and London, Amsterdam, Frankfurt, and Rome in Europe. We start in London on March 26th and wrap up in Rome on May 18th.&lt;br /&gt;&lt;br /&gt;This is our fourth year that we've toured together, and in case you have not attended a Delphi Developer Days event, I wanted to share with you what we are doing in hope that you will consider attending.&lt;br /&gt;&lt;br /&gt;Marco and I are both long time trainers and authors. Combined we have nearly 40 years of experience training professional developers, and have published close to 40 books. In putting together Delphi Developer Days, we strive to create a perfect balance of formal training, sharing, and networking.&lt;br /&gt;&lt;br /&gt;To begin with, we limit the number of attendees in each city to a maximum of 35 developers. By doing so we retain the intimacy of a small group while generating the necessary synergy for the sharing of ideas and perspective. Personally, it gives us the opportunity to interact with each attendee without getting lost in the crowd.&lt;br /&gt;&lt;br /&gt;There is also a little bit of conference flavor to Delphi Developer Days. In each city we have a keynote address from an Embarcadero representative, and also have a guest speaker who presents on an area of their own specialization. Our guest speakers this year include Stephen Ball (London), Bob Swart (Amsterdam), Ray Konopka (Chicago), Jim McKeeth (Washington DC/Baltimore), Bruno Fierens (Frankfurt), and Daniele Teti (Rome).&lt;br /&gt;&lt;br /&gt;But make no mistake about it, the main goal is covering Delphi in depth. Over the course of the two days, there are more than fifteen technical sessions covering everything from the latest features in Delphi XE2 to classic topics such as Unicode migration and RTTI (runtime type information). There are also talks on Internet-related development, mobile applications, cross platform development, and traditional client/server topics. In some of the talks Marco and I present together, offering our individual insights on the topic, while for other sessions, we break out into separate rooms.&lt;br /&gt;&lt;br /&gt;For the combined sessions, we have chosen topics that should be of interest to all Delphi developers. This year's combined sessions include a comparison of Delphi database development strategies, an overview of options for Internet development, and the always popular Tips, Tricks, and Techniques session. For the break out sessions, we have worked to create attractive options for creating your own agenda. For example, when a Delphi XE2-specific topic is being presented in one room, a classic topic that applies to many versions of Delphi is being covered in the other room.&lt;br /&gt;&lt;br /&gt;Of course, this means that you might have to choose between two equally attractive topics. But we have that covered. Both Marco and I are prolific writers, and we commit to writing a paper for every talk we present. This means that if you have to skip a particular talk you will be able to read the associated paper. Over the past three years, the course book that each attendee to Delphi Developer Days receives has been several hundred pages in length, containing as much material as a book, and this year will be no different.&lt;br /&gt;&lt;br /&gt;Delphi Developer Days takes a lot of planning, but it wouldn't be possible without our many sponsors. Both of last year's platinum sponsors, Embarcadero Technologies and Sybase (an SAP Company) have returned this year, as have all of last year's gold sponsors: /n software, DevArt, Gnostice Technologies, Programmer's Paradise, RemObjects Software, and TMS Software. In addition, this year we welcome&amp;nbsp;two new gold sponsors: Fast Reports and Sisulizer. And of course, we value our more than 20 regular sponsors and Delphi user groups for their strong and continued support.&lt;br /&gt;&lt;br /&gt;We are currently offering a ten percent discount early bird special if you register and make payment the deadline listed online. We also offer additional discounts for previous attendees of the Delphi Developer Days 2009 through 2011 tours. There is also a group discount when 3 or more employees of the same company register together. But don't wait too long to register. Our early registrations are on a record pace. Make sure you sign up now to guarantee your spot at Delphi Developer Days 2012.&lt;br /&gt;&lt;br /&gt;For more information about Delphi Developer Days, including dates, topics, locations, and pricing, please visit &lt;a href="http://www.delphideveloperdays.com/"&gt;http://www.delphideveloperdays.com/&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-3163105637724336853?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/3163105637724336853/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2012/01/delphi-developer-days-2012-tour.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3163105637724336853'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3163105637724336853'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2012/01/delphi-developer-days-2012-tour.html' title='Delphi Developer Days 2012 Tour'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-3114363484428501972</id><published>2012-01-12T13:19:00.000-08:00</published><updated>2012-01-12T13:23:59.388-08:00</updated><title type='text'>What’s Your Favorite LiveBindings Example?</title><content type='html'>LiveBindings, which were introduced in Delphi XE2, provide developers with new options for associating objects. And they are only one of a wealth of new features introduced in this groundbreaking version of Delphi. They are also a source of some confusion. &lt;br /&gt;&lt;br /&gt;One of the problems is that most of the demonstrations of LiveBindings are simple, in part because LiveBindings are so new. Another way to put this is that it's hard to think differently about object binding when we are so familiar with Delphi's existing mechanisms. As a result, most examples that I've seen so far duplicate much of what we already achieve in Delphi.&lt;br /&gt;&lt;br /&gt;But this is bound to change. I believe that once we start to see creative applications of LiveBindings, we, the collective Delphi community, will begin to think about them differently. &lt;br /&gt;&lt;br /&gt;I hope to jump start this process by collecting examples of LiveBindings that represent the way that we'll be using them in the future, and I'll publish these here. Of course, I'll give credit if you contribute so that you can bask in the gratitude of your fellow Delphi developers.&lt;br /&gt;&lt;br /&gt;So, here is my question. Do you have examples of LiveBindings that go beyond the obvious? Alternatively, have you seen an example that breaks the mold? Is so, please share. &lt;br /&gt;&lt;br /&gt;And, in case you haven't given much thought to LiveBindings, here is a short introduction.&lt;br /&gt;&lt;h3&gt;LiveBindings&lt;/h3&gt;LiveBindings is a general term for Delphi's new object/property binding mechanism first introduced in RAD Studio XE2. It is the only binding mechanism available to the new FireMonkey cross-platform component library (FMX), and is also available for traditional visual component library (VCL) components.&lt;br /&gt;&lt;br /&gt;At its core, LiveBindings is a mechanism for creating associations between objects and expressions. Expressions are strings that are evaluated by Delphi's new expression engine, and in the case of LiveBindings, they define what effect the expression will have on an object.&lt;br /&gt;&lt;br /&gt;While expressions are strings, they are evaluated by the expression engine at runtime, which is quite a bit different than your Delphi code, which is compiled by Delphi's compiler at compile time. As a result, expressions are different from other string types you normally encounter in Delphi code. For one thing, expression strings can define literal values using either single quotes or double quotes. In addition, the expression engine recognizes special methods that have been registered with it through Delphi's Open Tools API (OTA), and can employ custom output converters to transform data from one type to another. &lt;br /&gt;&lt;br /&gt;Another concept critical to LiveBindings is &lt;i&gt;scope&lt;/i&gt;. In LiveBindings terminology, scope defines what is visible to the expression engine. Most LiveBindings require a control component (the object to which the expression will be applied), and in most cases a source component as well. In the case of these LiveBindings components, the control and source components are both in scope, making their properties available to the expression engine. Similarly, those custom methods that have been registered with Delphi from a design time package are also in scope, making those methods callable from your expressions.&lt;br /&gt;&lt;br /&gt;It's worth noting that while LiveBindings use expressions, expressions can be used without LiveBindings. Specifically, you can create a scope programmatically, adding to it the objects and methods you want the expression engine to evaluate, and then ask the expression engine to perform the evaluation, returning a value based on the expression string. It's an important point, as far as the expression engine is concerned, but not something that you necessarily need to think about when you are using the LiveBindings components.&lt;br /&gt;&lt;h3&gt;Do We Need LiveBindings?&lt;/h3&gt;I recently spoke about LiveBindings during the "24 Hours of Delphi" broadcast with David Intersimone. One of the listeners asked a question about LiveBindings that I hear pretty often, though he gave a somewhat new twist. "Why do we need LiveBindings?" he asked. "After all, it appears that LiveBindings is just another way of doing what we already do using event handlers. It kind of seems like fishing poles. In older days we had cane fishing poles, and they worked just fine. The new fiberglass and graphite rods are nice, but they don't really do more than the old rods."&lt;br /&gt;&lt;br /&gt;I like the analogy a lot, because it actually highlights why LiveBindings are a positive thing. Let's take the fishing pole example. A recent television show on The History Channel called "101 Gadgets that Have Changed the World," the publishers of the magazine Popular Mechanics list the top 101 devices that have had a dramatic impact on our daily lives. And, guess what, fiberglass fishing poles made the list (at 100), beating out duct tape and being edged out by the stapler.&lt;br /&gt;&lt;br /&gt;In any case, the point is that while cane poles and fiberglass fishing rods perform the same task, they work differently, and fiberglass rods are functionally better on every level.&lt;br /&gt;&lt;br /&gt;I think we are going to be saying the same thing about LiveBindings, once we get our heads around them. Yes, you can do many things with LiveBindings that can be achieved without them, but as we get more familiar with their capabilities, I believe we will discover a whole range of features that are enabled only through LiveBindings.&lt;br /&gt;&lt;br /&gt;So, let me hear from you. Post a link to your example, or an example that you find on the Web, as a comment to this posting.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-3114363484428501972?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/3114363484428501972/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2012/01/whats-your-favorite-livebindings.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3114363484428501972'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3114363484428501972'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2012/01/whats-your-favorite-livebindings.html' title='What’s Your Favorite LiveBindings Example?'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-1727778793424950057</id><published>2012-01-09T18:28:00.000-08:00</published><updated>2012-01-10T05:29:21.009-08:00</updated><title type='text'>For The Record</title><content type='html'>Records are data structures that hold one or more pieces of information. While records have been around since the earliest days of Pascal, they have taken on a much larger role in the most recent versions of Delphi. Today's records possess many features previously only found in objects.&lt;br /&gt;&lt;br /&gt;This&amp;nbsp;post begins with a brief overview of records. It continues with a discussion of features that have been added to records since Delphi 2005. This&amp;nbsp;post concludes with a look at several of the important new record types that have been introduced to Delphi in recent versions.&lt;br /&gt;&lt;h3&gt;Record Basics&lt;/h3&gt;The traditional style of record definition is somewhat similar to an array, in that it can hold more than one data value. But there are a number of features that distinguish records from arrays. &lt;br /&gt;&lt;br /&gt;In an array the various values are identified using an ordinal index. Records, by comparison, make use of named fields. Furthermore, arrays consist of a collection of elements that are all of the same data type (even if all elements of the array are variants). In records, the individual fields can be almost any valid Delphi type, including integers, Booleans, real numbers, object references, arrays, interfaces, and enumerations.&lt;br /&gt;&lt;h4&gt;Declaring a Record&lt;/h4&gt;Consider the following type declarations. Together, these types define a record type, TDog, that includes four fields: A double, an enumeration, a TDateTime, and a TObject reference.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;type&lt;/b&gt;&lt;br /&gt;  TPerson = class(TObject)&lt;br /&gt;&lt;b&gt;private&lt;/b&gt;&lt;br /&gt;  FName: String;&lt;br /&gt;&lt;b&gt;public&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  property&lt;/b&gt; Name: String &lt;b&gt;read&lt;/b&gt; FName &lt;b&gt;write&lt;/b&gt; FName;&lt;br /&gt;&lt;b&gt;  constructor&lt;/b&gt; Create(Name: String);&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;br /&gt;&lt;br /&gt;TBreed = (Akita, Beagle, Chihuahua, Dachshund);&lt;br /&gt;TDog = &lt;b&gt;record&lt;/b&gt;&lt;br /&gt;  Weight: Double;&lt;br /&gt;  Breed: TBreed;&lt;br /&gt;  Born: TDatetime;&lt;br /&gt;  Owner: TPerson;&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Using a Record &lt;/h4&gt;Once you have defined a record type, it is easy to create an instance of that record. Simply declare a variable of that record type and use it. It is not even necessary to allocate memory for the record — Delphi does that for you. For example, the following code demonstrates how to declare and use a record of type TDog.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;procedure&lt;/b&gt; TForm1.Button2Click(Sender: TObject);&lt;br /&gt;&lt;strong&gt;var&lt;br /&gt;&lt;/strong&gt;  Dog: TDog;&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;  Dog.Name := 'Skippy';&lt;br /&gt;  Dog.Breed := Beagle;&lt;br /&gt;  Dog.Born := StrToDateTime('2010-10-15');&lt;br /&gt;  Dog.Owner := TPerson.Create('Trevor');&lt;br /&gt;...&lt;/pre&gt;&lt;br /&gt;Actually, it's not necessary to declare a record type in order to use a record. Instead, a variable can be defined as a record in its declaration. This might look something like the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;procedure&lt;/b&gt; TForm1.Button3Click(Sender: TObject);&lt;br /&gt;&lt;b&gt;var&lt;/b&gt;&lt;br /&gt;  Dog: &lt;strong&gt;record&lt;/strong&gt;&lt;br /&gt;    Name: String;&lt;br /&gt;    Breed: TBreed;&lt;br /&gt;    Born: TDatetime;&lt;br /&gt;    Owner: TPerson;&lt;br /&gt;&lt;b&gt;   end&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;  Dog.Name := 'Skippy';&lt;br /&gt;  // ...&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;More Variations&lt;/h4&gt;There are a couple of additional variations that are somewhat unique to records, and these are associated with enumerations and variant parts. Let's consider enumerations first.&lt;br /&gt;&lt;br /&gt;In the TDog record type, Breed was declared as a field of type TBreed. An alternative would be to declare the Breed field to be an enumeration directly, without the TBreed declaration. Here is another record type declaration that uses this syntax:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;type&lt;/b&gt;&lt;br /&gt;  TAppointment = &lt;b&gt;record&lt;/b&gt;&lt;br /&gt;    Time: TDateTime;&lt;br /&gt;    DayOfWeek: (Mon, Tue, Wed, Thu, Fri, Sat, Sun);&lt;br /&gt;    MeetingWith: TPerson;&lt;br /&gt;    MeedtingBy: TPerson;&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In the TAppointment record type, the DayOfWeek field is an enumeration, which is defined in the record type declaration directly.&lt;br /&gt;&lt;br /&gt;Another interesting feature of traditional records is the optional variant part. A variant part is a conditional portion of the record structure that can hold variable pieces of information. The variant part, if present, always appear at the end of a record declaration.&lt;br /&gt;&lt;br /&gt;For example, consider the following declaration of Delphi's TRect, which appears in the Types unit.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;TRect = &lt;b&gt;record&lt;/b&gt;&lt;br /&gt;  case Integer of&lt;br /&gt;    0: (Left, Top, Right, Bottom: Longint);&lt;br /&gt;    1: (TopLeft, BottomRight: TPoint);&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Using this syntax, you can use one or the other variant record structures (four Longints or two TPoints, though you cannot mix them in an individual instance of a record). For example, the following code declares and assigns data to two TRect instances (which are identical):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;var&lt;/b&gt;&lt;br /&gt;  r1, r2: TRect;&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;  r1.TopLeft := Point(5,5);&lt;br /&gt;  r1.BottomRight := Point(100, 200);&lt;br /&gt;  r2.Top := 5;&lt;br /&gt;  r2.Left := 5;&lt;br /&gt;  r2.Bottom := 100;&lt;br /&gt;  r2.Right := 200;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There is a second syntax, in which the value of one of the record's fields determines which of the alternative structures the record uses. For example, consider the following record type declaration:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;type&lt;/b&gt;&lt;br /&gt;  TAdult = &lt;b&gt;record&lt;/b&gt;&lt;br /&gt;    Name: String;&lt;br /&gt;    DateOfBirth: TDate;&lt;br /&gt;&lt;b&gt;    case&lt;/b&gt; MilitaryService: Boolean &lt;b&gt;of&lt;/b&gt;&lt;br /&gt;      True: (&lt;br /&gt;        WhichService: (Army, Navy, Marines, AirForce);&lt;br /&gt;        StartDate: TDate;&lt;br /&gt;        EndDate: TDate;&lt;br /&gt;        );&lt;br /&gt;      False: (&lt;br /&gt;        Reason: (NoDraft, UnFit, Objection);&lt;br /&gt;        );&lt;br /&gt;&lt;b&gt;    end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In this record, when the Boolean field MilitaryService is assigned the value True the WhichService, StartDate, and EndDate fields are available. If MilitaryService is False, a single enumerated field, Reason, can be used.&lt;br /&gt;&lt;h3&gt;Class-Like Records&lt;/h3&gt;Beginning with Delphi 2005, records began to receive a major overhaul with the introduction of member visibility. Until this time, all members of a record were public. Since Delphi 2005, the same visibility directives that you can use in class declarations can now also be used in record declarations.&lt;br /&gt;&lt;br /&gt;But Delphi 2006 is the version in which the really big changes were introduced, giving records many features previously reserved for classes, and greatly improving their utility in your Delphi applications. These features include methods, properties, constructors, class constants, class variables, class static methods, class properties, nested types, overloaded operators, and record helpers. Each of these features are consider in the following sections.&lt;br /&gt;&lt;h4&gt;Methods and Properties&lt;/h4&gt;A record's methods are functions and procedures associated with the record. Like their class cousins, a record's methods can access all of the other members of the record, including fields, properties, and other methods. Similar to the methods of a class, a record's methods have a reference to the record's instance through a variable named Self. &lt;br /&gt;&lt;br /&gt;Record methods also permit overloading. When overloading a record's method, you follow the same rules that you follow when overloading the methods of a class. Specifically, the compiler must be able to distinguish between each overloaded version based on the signature of the method (including whether the method is a function or procedure).&lt;br /&gt;&lt;br /&gt;Record properties are also similar to their class counterparts. A record's properties can be implemented with read and/or write parts, and can use either direct access (reading and writing from fields of the record) or accessor methods (implementing reading and writing through specified methods of the record. Accessor methods permit a property to perform side effects).&lt;br /&gt;&lt;br /&gt;The following is a simple example of a record with two fields (variables), a method, and three properties.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;type&lt;/b&gt;&lt;br /&gt;  TRectangle = &lt;b&gt;record&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  strict private&lt;/b&gt;&lt;br /&gt;    FWidth: Double;&lt;br /&gt;    FDepth: Double;&lt;br /&gt;&lt;b&gt;    function&lt;/b&gt; GetArea: Double;&lt;br /&gt;&lt;b&gt;  public&lt;/b&gt;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Area: Double &lt;b&gt;read&lt;/b&gt; GetArea;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Width: Double &lt;b&gt;read&lt;/b&gt; FWidth &lt;b&gt;write&lt;/b&gt; FWidth;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Depth: Double &lt;b&gt;read&lt;/b&gt; FWidth &lt;b&gt;write&lt;/b&gt; FDepth;&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The methods declared in a record must be implemented (records do not permit abstract methods). Furthermore, after declaring a method in a record you can use class completion (Ctrl-Shift-C) to generate the necessary implementation stubs for your record's methods. The following is the implementation of the GetArea method of the TRectangle record.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;function&lt;/b&gt; TRectangle.GetArea: Double;&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;  Result := FWidth * FDepth;&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;/pre&gt;&lt;h4&gt;Record Constructors&lt;/h4&gt;All records have an implicit, zero parameter constructor that is called when you attempt to use a record for the first time. If you want, you can add additional constructors. These constructors, however, must have at least one parameter, and must be distinguishable from any other overloaded constructor based on their signatures.&lt;br /&gt;&lt;br /&gt;The following shows the TRectangle record with an overloaded constructor (note that the overload directive was not required, but makes the code clearer). The implementation of the constructor follows in this code segment.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;type&lt;/b&gt;&lt;br /&gt;  TRectangle = &lt;b&gt;record&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  strict private&lt;/b&gt;&lt;br /&gt;    FWidth: Double;&lt;br /&gt;    FDepth: Double;&lt;br /&gt;&lt;b&gt;    function&lt;/b&gt; GetArea: Double;&lt;br /&gt;&lt;b&gt;  public&lt;/b&gt;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Area: Double &lt;b&gt;read&lt;/b&gt; GetArea;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Width: Double &lt;b&gt;read&lt;/b&gt; FWidth &lt;b&gt;write&lt;/b&gt; FWidth;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Depth: Double &lt;b&gt;read&lt;/b&gt; FWidth &lt;b&gt;write&lt;/b&gt; FDepth;&lt;br /&gt;&lt;b&gt;    constructor&lt;/b&gt; Create(Width, Depth: Double); &lt;b&gt;overload&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;&lt;b&gt;constructor&lt;/b&gt; TRectangle.Create(Width, Depth: Double);&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;  FWidth := Width;&lt;br /&gt;  FDepth := Depth;&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;br /&gt;&lt;/b&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;Class Constants and Class Variables&lt;/h4&gt;While each instance of a record has one memory location for each of its fields, class constants and class variables are shared between all instances of a given record type. A class constant is a constant value, and its declaration looks a lot like a regular declaration of a constant. The difference is that the class constant is a characteristic of the specific record type. The class constant can be read using a reference to an instance of the record type, or through the record type itself.&lt;br /&gt;&lt;br /&gt;A class variable is a field that is shared by all instances of a particular record type. Changing the value of a class variable has the effect of changing the one memory location shared by the record type and all instances of it.&lt;br /&gt;&lt;br /&gt;Class constants appear within a &lt;b&gt;const&lt;/b&gt; clause of a record declaration, and class variables appear within a &lt;b&gt;class&lt;/b&gt; &lt;b&gt;var&lt;/b&gt; clause. These clauses conclude at the end of the record declaration, or when a method, type, variable, property, constructor, visibility directive, or variant part is encountered.&lt;br /&gt;&lt;br /&gt;The following record type includes two class constants and two class variables:&lt;br /&gt;&lt;pre&gt;TRectangle = &lt;b&gt;record&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  strict private&lt;/b&gt;&lt;br /&gt;    FWidth: Double;&lt;br /&gt;    FDepth: Double;&lt;br /&gt;&lt;b&gt;    function&lt;/b&gt; GetArea: Double;&lt;br /&gt;&lt;b&gt;    const&lt;/b&gt;&lt;br /&gt;      MaxWidth = 100.0;&lt;br /&gt;      MaxDepth = 200.0;&lt;br /&gt;&lt;b&gt;  public&lt;/b&gt;&lt;br /&gt;&lt;b&gt;    class&lt;/b&gt; &lt;b&gt;var&lt;/b&gt;&lt;br /&gt;      PreferredMinWidth: Double;&lt;br /&gt;      PreferredMinDepth: Double;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Area: Double &lt;b&gt;read&lt;/b&gt; GetArea;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Width: Double &lt;b&gt;read&lt;/b&gt; FWidth &lt;b&gt;write&lt;/b&gt; FWidth;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Depth: Double &lt;b&gt;read&lt;/b&gt; FWidth &lt;b&gt;write&lt;/b&gt; FDepth; &lt;br /&gt;&lt;b&gt;    constructor&lt;/b&gt; Create(Width, Depth: Double); &lt;b&gt;overload&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Class Static Methods and Class Properties&lt;/h4&gt;Unlike classes, which have both class methods and class static methods, records support only class static methods (as far as class methods go). A class static method is one that can be called on an instance of a record or through a reference to the record type itself. Like the class static methods in a class declaration, a record's class static methods have no reference to Self, and therefore cannot access any other members of the class, other than other class static methods or class variables. &lt;br /&gt;&lt;br /&gt;Class properties are properties that can be referenced using an instance of a record type, or the record type itself. While class properties support both direct access and accessor methods (like normal properties), direct access can only employ class variables, and accessor methods must use only class static methods.&lt;br /&gt;&lt;br /&gt;The following is a portion of a record type declaration that includes two class variables, a class static method, and two class properties. The implementation of the class static method follows.&lt;br /&gt;&lt;pre&gt;&lt;b&gt;type&lt;/b&gt;&lt;br /&gt;  TRectangle = record&lt;br /&gt;&lt;b&gt;  strict&lt;/b&gt; &lt;b&gt;private&lt;/b&gt;&lt;br /&gt;    //...&lt;br /&gt;&lt;b&gt;    class&lt;/b&gt; &lt;b&gt;var&lt;/b&gt;&lt;br /&gt;      FPreferredMinWidth: Double;&lt;br /&gt;      FPreferredMinDepth: Double;&lt;br /&gt;    //...&lt;br /&gt;&lt;b&gt;  public&lt;/b&gt;&lt;br /&gt;&lt;b&gt;    class&lt;/b&gt; &lt;b&gt;function&lt;/b&gt; PreferredMinArea: Double; &lt;b&gt;static&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;    class&lt;/b&gt; &lt;b&gt;property&lt;/b&gt; PreferredMinWidth: Double&lt;br /&gt;&lt;b&gt;      read&lt;/b&gt; FPreferredMinWidth &lt;b&gt;write&lt;/b&gt; FPreferredMinWidth;&lt;br /&gt;&lt;b&gt;    class&lt;/b&gt; &lt;b&gt;property&lt;/b&gt; PreferredMinDepth: Double&lt;br /&gt;&lt;b&gt;      read&lt;/b&gt; FPreferredMinDepth &lt;b&gt;write&lt;/b&gt; FPreferredMinDepth;&lt;br /&gt;    //...&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;  class&lt;/b&gt; &lt;b&gt;function&lt;/b&gt; TRectangle.PreferredMinArea: Double;  &lt;br /&gt;&lt;b&gt;  begin&lt;/b&gt;&lt;br /&gt;     Result := FPreferredMinWidth * FPreferredMinDepth;&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Nested Types&lt;/h4&gt;A nested type in a record is identical to a nested type in a class declaration. A nested type is a type declaration internal to a record. The nested type is often a class type that is designed to act as a helper class. Specifically, it is designed to perform tasks related to the record in which it is declared.&lt;br /&gt;&lt;br /&gt;The following is another record class segment, which includes a simple nested type declaration.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;type&lt;/b&gt;&lt;br /&gt;  TRectangle = &lt;b&gt;record&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  strict private&lt;/b&gt;&lt;br /&gt;    FWidth: Double;&lt;br /&gt;    FDepth: Double;&lt;br /&gt;&lt;b&gt;  public&lt;/b&gt;&lt;br /&gt;    //...&lt;br /&gt;&lt;b&gt;    function&lt;/b&gt; GetArea: Double;&lt;br /&gt;&lt;b&gt;    type&lt;/b&gt;&lt;br /&gt;      WorkerClass = &lt;b&gt;class&lt;/b&gt;(TObject)&lt;br /&gt;&lt;b&gt;      private&lt;/b&gt;&lt;br /&gt;        FName: String;&lt;br /&gt;&lt;b&gt;      public&lt;/b&gt;&lt;br /&gt;&lt;b&gt;        function&lt;/b&gt; SayMyName: String;&lt;br /&gt;&lt;b&gt;        property&lt;/b&gt; Name: String &lt;b&gt;read&lt;/b&gt; FName &lt;b&gt;write&lt;/b&gt; FName;&lt;br /&gt;&lt;b&gt;    end&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Area: Double &lt;b&gt;read&lt;/b&gt; GetArea;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Width: Double &lt;b&gt;read&lt;/b&gt; FWidth &lt;b&gt;write&lt;/b&gt; FWidth;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Depth: Double &lt;b&gt;read&lt;/b&gt; FWidth &lt;b&gt;write&lt;/b&gt; FDepth;&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Operator Overloading&lt;/h4&gt;One additional feature added to records that is not shared by classes is operator overloading. In short, you can explicitly define what happens when an operator is applied to a particular record (unary operators) are to two records (binary operators). For example, you can define what happens when two of your records are added together using the addition (+) operator, or what happens if you implicitly cast your record to another type.&lt;br /&gt;&lt;br /&gt;There are almost 30 operators that can be overloaded. These operators are shown in Listing 1.&lt;br /&gt;&lt;table border="1" cellpadding="0" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Method Name&lt;/td&gt;&lt;td valign="top" width="68"&gt;Operator&lt;/td&gt;&lt;td valign="top" width="119"&gt;Use Example&lt;/td&gt;&lt;td valign="top" width="110"&gt;Returns&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Implicit &lt;/td&gt;&lt;td valign="top" width="68"&gt;&lt;/td&gt;&lt;td valign="top" width="119"&gt;&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Explicit &lt;/td&gt;&lt;td valign="top" width="68"&gt;&lt;/td&gt;&lt;td valign="top" width="119"&gt;TRecord(r)&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Negative&lt;/td&gt;&lt;td valign="top" width="68"&gt;-&lt;/td&gt;&lt;td valign="top" width="119"&gt;- r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Positive&lt;/td&gt;&lt;td valign="top" width="68"&gt;+ &lt;/td&gt;&lt;td valign="top" width="119"&gt;+ r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Dec&lt;/td&gt;&lt;td valign="top" width="68"&gt;dec&lt;/td&gt;&lt;td valign="top" width="119"&gt;dec(r)&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;LogicalNot&lt;/td&gt;&lt;td valign="top" width="68"&gt;not&lt;/td&gt;&lt;td valign="top" width="119"&gt;not r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Trunc&lt;/td&gt;&lt;td valign="top" width="68"&gt;trunc&lt;/td&gt;&lt;td valign="top" width="119"&gt;trunc(r)&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Round&lt;/td&gt;&lt;td valign="top" width="68"&gt;round&lt;/td&gt;&lt;td valign="top" width="119"&gt;round(r)&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;In&lt;/td&gt;&lt;td valign="top" width="68"&gt;in&lt;/td&gt;&lt;td valign="top" width="119"&gt;in [a, b, r]&lt;/td&gt;&lt;td valign="top" width="110"&gt;Boolean&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Equal&lt;/td&gt;&lt;td valign="top" width="68"&gt;=&lt;/td&gt;&lt;td valign="top" width="119"&gt;a = r&lt;/td&gt;&lt;td valign="top" width="110"&gt;Boolean&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;NotEqual&lt;/td&gt;&lt;td valign="top" width="68"&gt;&amp;lt;&amp;gt;&lt;/td&gt;&lt;td valign="top" width="119"&gt;a &amp;lt;&amp;gt; r&lt;/td&gt;&lt;td valign="top" width="110"&gt;Boolean&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;GreaterThan&lt;/td&gt;&lt;td valign="top" width="68"&gt;&amp;gt;&lt;/td&gt;&lt;td valign="top" width="119"&gt;a &amp;gt; r&lt;/td&gt;&lt;td valign="top" width="110"&gt;Boolean&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;GreaterThanOrEqual&lt;/td&gt;&lt;td valign="top" width="68"&gt;&amp;gt;= &lt;/td&gt;&lt;td valign="top" width="119"&gt;a &amp;lt;= r&lt;/td&gt;&lt;td valign="top" width="110"&gt;Boolean&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;LessThan&lt;/td&gt;&lt;td valign="top" width="68"&gt;&amp;lt; &lt;/td&gt;&lt;td valign="top" width="119"&gt;a &amp;lt; r&lt;/td&gt;&lt;td valign="top" width="110"&gt;Boolean&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;LessThanOrEqual&lt;/td&gt;&lt;td valign="top" width="68"&gt;&amp;lt;= &lt;/td&gt;&lt;td valign="top" width="119"&gt;a &amp;lt;= r&lt;/td&gt;&lt;td valign="top" width="110"&gt;Boolean&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Add&lt;/td&gt;&lt;td valign="top" width="68"&gt;+ &lt;/td&gt;&lt;td valign="top" width="119"&gt;a + r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Subtract&lt;/td&gt;&lt;td valign="top" width="68"&gt;- &lt;/td&gt;&lt;td valign="top" width="119"&gt;a - r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Multiply&lt;/td&gt;&lt;td valign="top" width="68"&gt;* &lt;/td&gt;&lt;td valign="top" width="119"&gt;a * r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Divide&lt;/td&gt;&lt;td valign="top" width="68"&gt;/ &lt;/td&gt;&lt;td valign="top" width="119"&gt;a / r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;IntDivide&lt;/td&gt;&lt;td valign="top" width="68"&gt;div &lt;/td&gt;&lt;td valign="top" width="119"&gt;a div r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;Modulus&lt;/td&gt;&lt;td valign="top" width="68"&gt;mod &lt;/td&gt;&lt;td valign="top" width="119"&gt;a mod r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;LeftShift&lt;/td&gt;&lt;td valign="top" width="68"&gt;shl &lt;/td&gt;&lt;td valign="top" width="119"&gt;a shl r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;RightShift&lt;/td&gt;&lt;td valign="top" width="68"&gt;shr &lt;/td&gt;&lt;td valign="top" width="119"&gt;a shr r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;LogicalAnd&lt;/td&gt;&lt;td valign="top" width="68"&gt;and &lt;/td&gt;&lt;td valign="top" width="119"&gt;a and r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;LogicalOr&lt;/td&gt;&lt;td valign="top" width="68"&gt;or &lt;/td&gt;&lt;td valign="top" width="119"&gt;a or r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;LogicalXor&lt;/td&gt;&lt;td valign="top" width="68"&gt;xor &lt;/td&gt;&lt;td valign="top" width="119"&gt;a xor r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;BitwiseAnd&lt;/td&gt;&lt;td valign="top" width="68"&gt;and &lt;/td&gt;&lt;td valign="top" width="119"&gt;a and r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;BitwiseOr&lt;/td&gt;&lt;td valign="top" width="68"&gt;or &lt;/td&gt;&lt;td valign="top" width="119"&gt;a or r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="154"&gt;BitwiseXor&lt;/td&gt;&lt;td valign="top" width="68"&gt;xor &lt;/td&gt;&lt;td valign="top" width="119"&gt;a xor r&lt;/td&gt;&lt;td valign="top" width="110"&gt;return type&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Listing 1 Record operators that can be overloaded&lt;br /&gt;&lt;br /&gt;To overload one of these operators, you implement the specified method name using the &lt;b&gt;class&lt;/b&gt; &lt;b&gt;operator&lt;/b&gt; syntax (instead of function) in your record type definition. You must also define the type of value that the operator will return. For those operators that are declared to return a Boolean value, the operator must return a Boolean value. All other operators can return whatever type is appropriate for that operator. In other words, a non-Boolean operator can return a value other than the record type.&lt;br /&gt;&lt;br /&gt;The following code segment includes a portion of the TRectangle class with an overloaded addition operator, the implementation of this operator overload, and an event handle that uses the operator.&lt;br /&gt;&lt;pre&gt;&lt;b&gt;type&lt;/b&gt;&lt;br /&gt;  TRectangle = record&lt;br /&gt;&lt;b&gt;  strict&lt;/b&gt; &lt;b&gt;private&lt;/b&gt;&lt;br /&gt;    FWidth: Double;&lt;br /&gt;    //...&lt;br /&gt;&lt;b&gt;  public&lt;/b&gt;&lt;br /&gt;&lt;b&gt;    property&lt;/b&gt; Width: Double &lt;b&gt;read&lt;/b&gt; FWidth &lt;b&gt;write&lt;/b&gt; FWidth;&lt;br /&gt;    //...&lt;br /&gt;&lt;b&gt;    class&lt;/b&gt; &lt;b&gt;operator&lt;/b&gt; Add(a, b: TRectangle): TRectangle;&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;class&lt;/b&gt; &lt;b&gt;operator&lt;/b&gt; TRectangle.Add(a, b: TRectangle): TRectangle;&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;  Result.Width := a.Width + b.Width;&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;procedure&lt;/b&gt; TForm1.Button7Click(Sender: TObject);&lt;br /&gt;&lt;b&gt;var&lt;/b&gt;&lt;br /&gt;  a, b, c: TRectangle;&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;  a.Width := 5;&lt;br /&gt;  b.Width := 20;&lt;br /&gt;  c := a + b;&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;/pre&gt;&lt;h4&gt;Record Helpers&lt;/h4&gt;Like classes in Delphi 2005, records can also have helpers. Record helpers serve essentially the same purpose as class helpers, though they only apply to records. Specifically, record helpers permit you to attach additional methods, properties, and class variables to an existing record. Record helpers can also replace existing methods of a class, if they use the same name and signature, since methods of a record helper supercede those of the record they are helping.&lt;br /&gt;&lt;br /&gt;While the use of class helpers is largely discouraged, record helpers can play a role that cannot be provided through any other means. Specifically, record helpers provide the only mechanism for extending an existing record type. Classes, on the other hand, support inheritance. As a result, it is often better to descend a new class from an existing class, and introduce new methods and properties in the descendant than to use a class helper.&lt;br /&gt;&lt;br /&gt;Record helpers have the same limitations as class helpers. A given record can be supported by only one record helper. If there is more than one record helper for a given record, the record will employ the record helper that is closer in scope.&lt;br /&gt;&lt;br /&gt;The following code shows the declaration of a record helper for the TRectangle record, along with the implementation of the single method declared within the record helper. Notice in the GetMaxArea implementation that Self refers to the record instance.&lt;br /&gt;&lt;pre&gt;&lt;strong&gt;type&lt;/strong&gt;&lt;br /&gt;  TRectHelper = &lt;strong&gt;record&lt;/strong&gt; &lt;strong&gt;helper&lt;/strong&gt; for TRectangle&lt;br /&gt;&lt;strong&gt;function&lt;/strong&gt; GetMaxArea: Double;&lt;br /&gt;&lt;strong&gt;end&lt;/strong&gt;;&lt;br /&gt;&lt;strong&gt;function&lt;/strong&gt; TRectHelper.GetMaxArea: Double;&lt;br /&gt;&lt;strong&gt;begin&lt;/strong&gt;&lt;br /&gt;Result := Self.MaxWidth * Self.MaxDepth;&lt;br /&gt;&lt;strong&gt;end&lt;/strong&gt;;&lt;br /&gt;&lt;strong&gt;procedure&lt;/strong&gt; TForm1.Button8Click(Sender: TObject);&lt;br /&gt;&lt;strong&gt;var&lt;/strong&gt;&lt;br /&gt;  a: TRectangle;&lt;br /&gt;&lt;strong&gt;begin&lt;/strong&gt;&lt;br /&gt;  ShowMessage(FloatToStr(a.GetMaxArea));&lt;br /&gt;&lt;strong&gt;end&lt;/strong&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;Records Versus Classes&lt;/h3&gt;At this point you might be inclined to think that the enhanced record syntax makes records nearly identical to classes, making it difficult to choose between them. The truth is that there are very significant differences between records and classes, and these differences can help you decide which to use in a particular situation.&lt;br /&gt;&lt;br /&gt;First of all, records are allocated on the stack, as long as they are not declared globally or managed using New and Dispose. Class instances, by comparison, are allocated on the heap.&lt;br /&gt;&lt;br /&gt;This has a significant impact on memory management. Specifically, while classes that do not have an owner must be explicitly freed, memory allocated for record instances is automatically freed when the record goes out of scope merely by its data being removed from the stack. Of course, if your record creates class instances during its lifecycle, that memory must be managed by you or your record, or a memory leak may exist. This is made a bit more difficult since records do not support destructors.&lt;br /&gt;Another significant difference between classes and records is that you do not need to call a record's constructor, while a class instance must be explicitly created. You can declare a variable of a record type and then immediately use it. In this case, Delphi will call the zero parameter constructor for you. &lt;br /&gt;&lt;br /&gt;You are still free to call a record's constructor if you want. In fact, if you've declared one or more additional constructors, it is probably because you want to create your record in a particular fashion, and you can do so by calling one of those overloaded constructors.&lt;br /&gt;Another major difference between records and classes is that records do not support inheritance. A record never descends from an existing record. Similarly, records cannot implement interfaces (though they may have fields or properties of interface type, but that is different). The bottom line here is that records do not support polymorphism. A corollary of this is that record methods are necessarily static. Records do not support virtual, dynamic, or abstract methods.&lt;br /&gt;&lt;br /&gt;As you learned earlier, records can also include a variant part. A variant part permits a record to hold data that is specific to its nature, and may vary from record instance to record instance. All instances of a class have the same set of members, regardless of their data.&lt;br /&gt;Finally, records are value types, as opposed to reference types. Consider the following code:&lt;br /&gt;&lt;pre&gt;&lt;strong&gt;var&lt;br /&gt;&lt;/strong&gt;  a, b: TRectangle;&lt;br /&gt;&lt;strong&gt;begin&lt;/strong&gt;&lt;br /&gt;  a.Width := 5;&lt;br /&gt;  a.Depth := 5;&lt;br /&gt;  b := a;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The TRectangle named b in this code segment is a completely new record that contains a copy of the data held within record a. By comparison, if TRectangle was a class type, following the assignment of variable a to variable b, variable b would point to the same memory address as a.&lt;br /&gt;&lt;br /&gt;Finally, records support operator overloading, while classes do not. Operator overloading provides you with a rich tool for managing exactly what happens when records are used in expressions, and the results can get very interesting.&lt;br /&gt;&lt;h3&gt;Examples of Useful Records in Delphi&lt;/h3&gt;Record types are playing an increasingly important role in the latest versions of Delphi, and this is due in large part to the differences between record types and class types described in the preceding section. Let's consider a few of the more significant record types.&lt;br /&gt;&lt;br /&gt;Delphi 2009 saw the introduction of the System.TMonitor type. TMonitor is a record that can be used to acquire and manage a lock on any TObject. It provides a convenient and powerful synchronization mechanism for multithreaded applications.&lt;br /&gt;&lt;br /&gt;A major new layer of runtime type information (RTTI) was introduced in Delphi 2010. The principle type for working with the new RTTI, TRTTIContext, is a record type.&lt;br /&gt;&lt;br /&gt;Additional records types introduced in Delphi 2010, and further enhanced in Delphi XE, include TFile, TPath, and TDirectory. These types, which are declared in the IOUtils unit, are record types.&lt;br /&gt;&lt;h3&gt;Summary&lt;/h3&gt;It is no longer safe to assume that the entity you are calling a method on, or are reading a property from, is an object. It might be a type of record. Indeed, records types posses a unique blend of features that make them better suited for a variety of situations in which you would have previously used a class type.&lt;br /&gt;&lt;br /&gt;Authors Note: I originally published this&amp;nbsp;article in the SDN Magazine (issue 110), published by the Software Development Network on&amp;nbsp;August 26, 2011. For information on the Software Development Network, visit &lt;a href="http://www.sdn.nl/"&gt;http://www.sdn.nl/&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-1727778793424950057?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/1727778793424950057/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2012/01/for-record.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/1727778793424950057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/1727778793424950057'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2012/01/for-record.html' title='For The Record'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-8492344273602299543</id><published>2011-10-31T17:07:00.000-07:00</published><updated>2011-10-31T17:07:44.628-07:00</updated><title type='text'>Cloning ClientDataSet Cursors</title><content type='html'>Last winter I got around to writing a book about Delphi's ClientDataSet component. This book was published at the end of March, and is now available worldwide (there's a link at the end of this article).&lt;br /&gt;&lt;br /&gt;This post covers an interesting advanced feature of ClientDataSets called cloned cursors. Chapter 11 of my book provides an in-depth discussion of cloning ClientDataSet cursors, but I have refrained from repeating any material from that chapter. Instead, this is a fresh article that I hope adds additional value above and beyond the book's discussion. In addition, this article includes a look of several valuable new features introduced in some of the latest versions of Delphi, including anonymous methods and anonymous threads.&lt;br /&gt;&lt;h3&gt;Overview of Cloned ClientDataSet Cursors&lt;/h3&gt;Cloning can mean very different things, when it comes to software. In some cases, such as in Delphi's TJSONObject (JavaScript Object Notation Object) and .NET's DataSet, a clone is a complete and separate copy of the original instance.&lt;br /&gt;&lt;br /&gt;Cloned ClientDataSet cursors are different. A cloned ClientDataSet cursor is a new reference to an existing ClientDataSet's internal storage. &lt;br /&gt;&lt;br /&gt;That internal storage has two parts: Data and Delta. Data is an OLEVariant that holds a ClientDataSet's data, in its current state, which includes any changes that have been made to that data since the data was loaded into the ClientDataSet. Delta is also an OLEVariant, and it contains the &lt;i&gt;change cache&lt;/i&gt;. The change cache is a description of just the changes that have been made to the data since it was loaded (so long as the change cache is being maintained, which is what happens when a ClientDataSet's LogChanges property is set to True, the default value).&lt;br /&gt;&lt;br /&gt;That both Data and Delta are OLEVariants is important, in that OLEVariants are reference counted data structures. Importantly, when the last reference to an OLEVariant goes out of scope, it is removed from memory automatically, and this behavior plays an important role in ClientDataSet cloned cursors.&lt;br /&gt;&lt;br /&gt;Cloning a cursor involves two ClientDataSets, at a minimum, but can include many more than that. One of these ClientDataSets is the &lt;i&gt;source&lt;/i&gt; ClientDataSet, and it must be active. When a ClientDataSet is active, it has a reference to a Data and Delta. Even then, Data and/or Delta may be empty, but they exist and can hold data. For example, if they are both empty to begin with, adding a record to the source ClientDataSet will add one record to Data, as well as one to Delta. Data reflects the current state of the ClientDataSet's content with one record, and Delta reflects that one record was added.&lt;br /&gt;&lt;br /&gt;The second ClientDataSet in a cloning operation is a ClientDataSet that will receive the cloned cursor, and I will call this ClientDataSet the &lt;i&gt;target&lt;/i&gt;. When a source ClientDataSet's cursor is cloned, the target ClientDataSet's Data and Delta properties refer to the Data and Delta properties of the cloned (source) ClientDataSet.&lt;br /&gt;&lt;br /&gt;From that point on, and changes to Data (including index creation) and Delta are reflected in both the clone (target) and the original (source). That does not mean, however, that these ClientDataSets will be exactly the same. Although both will refer to the same Data and Delta, there are many other properties that are independent. Specifically, each ClientDataSet referring to a shared Data and Delta will maintain their own values in the AppServer, Filter, Filtered, IndexName (or IndexFieldNames), MasterFields, MasterSource, OnFilterRecord, ProviderName, ReadOnly, RecNo (current record), and RemoteServer properties.&lt;br /&gt;&lt;br /&gt;You clone a ClientDataSet's cursor by calling CloneCursor from the target ClientDataSet. This method has the following signature:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;procedure&lt;/b&gt; CloneCursor(Source :TCustomClientDataSet; &lt;br /&gt;  Reset: Boolean; KeepSettings: Boolean = False);&lt;/pre&gt;&lt;br /&gt;You pass at least two parameters to CloneCursor. The first argument is a reference to the source ClientDataSet whose cursor you want to clone.&lt;br /&gt;&lt;br /&gt;You use the second and third parameters to indicate whether the clone should adopt view-specific properties of the ClientDataSet it is cloning. If Reset is False and KeepSettings is True, the receiving ClientDataSet will adopt the AppServer, Filter, Filtered, IndexName (or IndexFieldNames), MasterFields, MasterSource, OnFilterRecord, ProviderName, ReadOnly, and RemoteServer properties of the source ClientDataSet. If you pass True in the second parameter (in which case you should pass False, the default, in the third parameter), the values of these properties in the clone will be reset to their default values.&lt;br /&gt;&lt;br /&gt;Once a ClientDataSet has cloned another ClientDataSet, the two ClientDataSets will continue to refer to a common Data and Delta, until one of them is closed or clones a different ClientDataSet. Importantly, their properties will remain independent, regardless of the values of the second and third parameters of CloneCursor. From that point on, each can use a different filter, range, current record, and so forth, and therefore may reflect radically different views of the common data store.&lt;br /&gt;&lt;br /&gt;There is one exception to the above statement. If you clone a ClientDataSet that is filtered (Filter is True and Filtered is a Boolean expression limiting which records are displayed), and you pass a value of False (no reset) in the second parameter of CloneCursor, the receiving ClientDataSet will also employ the filter and it cannot be removed. Under this situation, you can apply an additional filter to the clone, but you can never get back to a completely unfiltered view. This appears to be a bug. As a result, I recommend that you always pass True in the second parameter and manually apply properties from the source ClientDataSet to the target.&lt;br /&gt;&lt;br /&gt;Once a source ClientDataSet is cloned, the target and the source are equal owners of the Data and Delta. Any one of them can post new changes to Data, and can undo changes in Delta.&lt;br /&gt;&lt;br /&gt;When one of these ClientDataSets performs one of these actions, the result is immediately apparent in both. Furthermore, closing one of these ClientDataSets has no effect on Data and Delta, unless it is the last ClientDataSet that refers to Data and Delta, in which case Data and Delta will be released.&lt;br /&gt;&lt;br /&gt;As implied earlier in this post, cloning is not necessarily about two ClientDataSets. It is perfectly possible to clone a source ClientDataSet, and then use the target as the source of yet another clone, and so on. Once again, all ClientDataSets that point to the same Data and Delta are equal. In other words, a source ClientDataSet has no special privileges with respect to the Data and Delta.&lt;br /&gt;&lt;h4&gt;A Simple Example&lt;/h4&gt;The sample project SimpleClone demonstrates some basic elements of a cloned cursor. The main form of this application is shown in Figure 1.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-XqVUXk1oq_4/Tq8ycspT6iI/AAAAAAAAAH8/STX-RdR5-OA/s1600/figure1.tif" imageanchor="1"&gt;&lt;img border="0" height="198" src="http://3.bp.blogspot.com/-XqVUXk1oq_4/Tq8ycspT6iI/AAAAAAAAAH8/STX-RdR5-OA/s320/figure1.tif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;Figure 1. The main form of the SimpleClone project&lt;br /&gt;&lt;br /&gt;This project contains a ClientDataSet, named ClientDataSet1, which is opened when the project runs and displayed in the upper DBGrid. When you click the button labeled Clone ClientDataSet1, ClientDataSet2 is cloned from ClientDataSet1, and displayed in the lower DBGrid.&lt;br /&gt;&lt;br /&gt;The following is the code that appears on the OnClick event handler of the button labeled Clone ClientDataSet1.&lt;br /&gt;&lt;pre&gt;&lt;b&gt;procedure&lt;/b&gt; TForm1.CloneButtonClick(Sender: TObject);&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  if&lt;/b&gt; ClientDataSet1.Active &lt;b&gt;then&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  begin&lt;/b&gt;&lt;br /&gt;    ClientDataSet2.CloneCursor(ClientDataSet1, False, True); &lt;br /&gt;    CloneButton.Enabled := False;&lt;br /&gt;    CloseButton.Enabled := True;&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  else&lt;/b&gt;&lt;br /&gt;    raise Exception.Create('ClientDataSet1 not active. Cannot clone');&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;Once cloning is complete, the views of these ClientDataSets are independent, in that they can have different sort orders and different current records, as shown in the Figure 2.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-DvFGvlIS7yI/Tq8y4pjoN4I/AAAAAAAAAII/DBkt42MYudc/s1600/figure2.tif" imageanchor="1"&gt;&lt;img border="0" height="198" src="http://4.bp.blogspot.com/-DvFGvlIS7yI/Tq8y4pjoN4I/AAAAAAAAAII/DBkt42MYudc/s320/figure2.tif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Figure 2. Two ClientDataSets share Data and Delta&lt;br /&gt;&lt;br /&gt;If you now close ClientDataSet1, by clicking the button labeled Close ClientDataSet1, the ClientDataSet2 will remain active, and can continue working with the data by editing it and saving it to a file, or pointing to a properly configured DataSetProvider, after which it could call ApplyUpdates to apply its changes to an underlying database.&lt;br /&gt;&lt;h4&gt;A More Complex Example&lt;/h4&gt;One technique that I've found particularly useful is to use cloned cursors to permit concurrent access to Data and Delta from two or more threads. So long as Data and Delta are treated as readonly, each thread can read Data (and Delta), perform its task, and then terminate.&lt;br /&gt;&lt;br /&gt;Here is an example. You might create a Windows service that exposes an Internet Direct (Indy) TIdTCPServer component. This component can listen for requests from TIdTCPClient components. Each client connection is accepted on a different thread, and each client might request one or more records from a common ClientDataSet. Importantly, each client request is distinct from every other client request.&lt;br /&gt;&lt;br /&gt;The thread on the Windows service that receives a client request can clone a cursor onto a globally available ClientDataSet, retrieve the data associated with that request and return it to the client, and then free the clone before terminating. So long as no code needs to make changes to Data or Delta, the ClientDataSet is thread safe.&lt;br /&gt;&lt;br /&gt;If changes did need to be made to the shared data, the addition of a synchronization object, such as a TMultiReadExclusiveWriteSynchronizer could be used to create a thread-safe architecture for permitting updates.&lt;br /&gt;&lt;br /&gt;I considered creating a somewhat simple example of a Windows service that returned data from a shared ClientDataSet to client applications using Indy socket components. However, doing so would raise a great many issues that I cannot address adequately in this post. &lt;br /&gt;&lt;br /&gt;Instead, I decided to demonstrate the essential methods that can be used to retrieve specific subsets of data from a shared ClientDataSet in a multi-threaded environment. Fortunately, this demonstration also makes use of a couple of handy features that have been introduced into Delphi in the last several releases: Anonymous methods (Delphi 2009) and anonymous threads (Delphi XE).&lt;br /&gt;&lt;br /&gt;Here's the setup. I created an application that needs to create physical files that contain only a subset of data from a shared ClientDataSet. These physical files are written by worker threads that retrieve a subset of data from the shared ClientDataSet, write that data to a file, and then terminate. These worker threads perform their task concurrently.&lt;br /&gt;&lt;br /&gt;The main form of this application is shown Figure 3. Here you can see a button that will initiate the file creation process. Also shown is a listbox that contains the customer numbers for those customer's whose orders records will be written to individual files, one file per customer.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-gx3_jo8unec/Tq8zH5sTrhI/AAAAAAAAAIU/wvTzngm8n8g/s1600/figure3.tif" imageanchor="1"&gt;&lt;img border="0" height="113" src="http://4.bp.blogspot.com/-gx3_jo8unec/Tq8zH5sTrhI/AAAAAAAAAIU/wvTzngm8n8g/s320/figure3.tif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Figure 3. The main form of the CDSCloneToFile project&lt;br /&gt;&lt;br /&gt;The code that extracts the data for a particular customer from the shared ClientDataSet is shown in the ReturnRecordsFromCDS method. ReturnRecordsFromCDS is called by a worker thread which passes to this method the customer number whose orders are contained in the shared ClientDataSet named ClientDataSet1.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;function&lt;/b&gt; TDataModule1.ReturnRecordsFromCDS(CustNo: Integer): TClientDataSet;&lt;br /&gt;&lt;b&gt;var&lt;/b&gt;&lt;br /&gt;  CDS: TClientDataSet;&lt;br /&gt;  DSP: TDataSetProvider;&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;  //Create the Result and a temporary ClientDataSet&lt;br /&gt;  Result := TClientDataSet.Create(nil);&lt;br /&gt;  CDS := TClientDataSet.Create(nil);&lt;br /&gt;&lt;b&gt;  try&lt;/b&gt;&lt;br /&gt;    //Create the DataSetProvider and hook it up&lt;br /&gt;    DSP := TDataSetProvider.Create(nil);&lt;br /&gt;    DSP.DataSet := CDS;&lt;br /&gt;    Result.SetProvider(DSP);&lt;br /&gt;&lt;b&gt;    try&lt;/b&gt;&lt;br /&gt;      CDS.CloneCursor(ClientDataSet1, True);&lt;br /&gt;      CDS.IndexName := 'CustNoIdx';&lt;br /&gt;      CDS.SetRange([custno], [custno]);&lt;br /&gt;      //The following populates the Result CDS&lt;br /&gt;      //with just the orders for a particular customer&lt;br /&gt;      Result.Open;&lt;br /&gt;&lt;b&gt;    finally&lt;/b&gt;&lt;br /&gt;      DSP.Free;&lt;br /&gt;&lt;b&gt;    end&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;  finally&lt;/b&gt;&lt;br /&gt;    CDS.Free;&lt;br /&gt;&lt;b&gt;  end&lt;/b&gt;;&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;ReturnRecordsFromCDS works by using a DataSetProvider and a ClientDataSet combination to retrieve only a range of records from a cloned cursor. These records, once returned to the thread that called ReturnRecordsFromCDS, are then written to a file using the returned ClientDataSet's SaveToFile method.&lt;br /&gt;&lt;br /&gt;But this method also points out some interesting elements of a ClientDataSet on which a range is set. Namely, if you set a range on a ClientDataSet and then call SaveToFile, all of the records of the ClientDataSet are saved, not just those in the range.&lt;br /&gt;&lt;br /&gt;To get around this, ReturnRecordsFromCDS creates a new ClientDataSet (Result), and associates it with a DataSetProvider (DSP). This combination is used to populate Result with a set of records from a DataSet (the DataSet to which the DataSetProvider's DataSet property is associated). This DataSet is a ClientDataSet (CDS) that is cloned from ClientDataSet1.&lt;br /&gt;&lt;br /&gt;Once cloned, an index is set on CDS (this index is a feature of Data obtained as a result of the cloning process) and then a range is set. After that, calling Open on Result draws only those records from CDS in the range. Result is then returned to the calling method.&lt;br /&gt;&lt;br /&gt;The fun stuff, if you will, can be found in the method that calls ReturnRecordsFromCDS. This method, named GenerateXMLFileForCustomer, is passed the customer number whose orders data needs to be written to a file. It performs its work using an anonymous thread. GenerateXMLFileForCustomer is shown in the following code listing.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;procedure&lt;/b&gt; TForm1.GenerateXMLFileForCustomer(CustNo: Integer);&lt;br /&gt;&lt;b&gt;var&lt;/b&gt;&lt;br /&gt;  AnonThread: TThread;&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;  //Create the anonymous thread to run an anonymous method&lt;br /&gt;  AnonThread := TThread.&lt;br /&gt;     CreateAnonymousThread(&lt;br /&gt;       &lt;b&gt;procedure&lt;/b&gt;&lt;br /&gt;       &lt;b&gt;var&lt;/b&gt;&lt;br /&gt;            CDS: TClientDataSet;&lt;br /&gt;            FileToWrite: String;&lt;br /&gt;       &lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;         CDS := DataModule1.ReturnRecordsFromCDS(CustNo);&lt;br /&gt;         &lt;b&gt;try&lt;/b&gt;&lt;br /&gt;           FileToWrite := FAppDir + '\' + IntToStr(CustNo) + '.xml';&lt;br /&gt;           &lt;b&gt;if&lt;/b&gt; FileExists(FileToWrite)&lt;b&gt;then&lt;/b&gt;&lt;br /&gt;                DeleteFile(FileToWrite);&lt;br /&gt;                CDS.SaveToFile(FileToWrite);&lt;br /&gt;           &lt;b&gt;finally&lt;/b&gt;&lt;br /&gt;             //Free the ReturnRecordsFromCDS Result&lt;br /&gt;             CDS.Free;&lt;br /&gt;           &lt;b&gt;end&lt;/b&gt;;&lt;br /&gt;       &lt;b&gt;end&lt;/b&gt;);&lt;br /&gt;  //Start the anonymous thread&lt;br /&gt;  AnonThread.Start;&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;An anonymous thread is created by passing a reference to a procedure to the TThread class method named CreateAnonymousThread. CreateAnonymousThread returns a TAnonymousThread (a TThread descendant), which you then execute by calling its Start method. Once the anonymous thread has completed the execution of the procedure, it terminates and frees itself.&lt;br /&gt;&lt;br /&gt;In the case of GenerateXMLFileForCustomer, the procedure passed to CreateAnonymousThread is an anonymous method. An anonymous method is an unnamed method whose implementation appears inline in your code. &lt;br /&gt;&lt;br /&gt;Importantly, an anonymous method closes over the local variables (including formal parameters) of the method in which it appears, maintaining the context of these values even after the method in which the anonymous method is defined has exited. This is called a &lt;i&gt;closure&lt;/i&gt;, and it permits each execution of this anonymous method to maintain the value passed in the CustNo formal parameter.&lt;br /&gt;&lt;br /&gt;Each call to GenerateXMLFileForCustomer creates a different worker thread that runs the anonymous method. Each anonymous method calls the ReturnRecordsFromCDS method, which returns a new ClientDataSet that holds the orders for the given customer. After ensuring that the target file does not already exists (it will be deleted if it does), the anonymous methods writes the contents of this ClientDataSet to the appropriately named file, after which it frees the ClientDataSet created by the call to ReturnRecordsFromCDS.&lt;br /&gt;&lt;br /&gt;GenerateXMLFileForCustomer is called from the OnClick event handler of the button labeled Create Files for Customers. The following is the code that appears on that event handler:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;procedure&lt;/b&gt; TForm1.CreateFilesButtonClick(Sender: TObject);&lt;br /&gt;&lt;b&gt;var&lt;/b&gt;&lt;br /&gt;  i: Integer;&lt;br /&gt;&lt;b&gt;begin&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  for&lt;/b&gt; i := 0 to ListBox1.Count - 1 &lt;b&gt;do&lt;/b&gt;&lt;br /&gt;    GenerateXMLFileForCustomer(StrToInt(ListBox1.Items[i]));&lt;br /&gt;&lt;b&gt;end&lt;/b&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Turning back our attention to the Windows service/client example I mentioned earlier, GenerateXMLFileForCustomer would be implemented on the OnExecute event handler of the TIdTCPServer component. Since that method executes in a different thread for each client connection, the use of the anonymous method would be unnecessary. All the OnExecute event handler would need to do is to read the customer number passed by the client, execute the ReturnRecordsFromCDS method passing to it this customer number, and then return the XMLData property of the returned ClientDataSet to the client application, after which the returned ClientDataSet would be freed. &lt;br /&gt;&lt;br /&gt;Alternatively, the returned ClientDataSet could be returned to the requesting client using a stream. Whether text or binary data is returned to the client, the service might also encrypt and/or compress the data before returning it, in which case the client must know to decrypt and/or decompress the data it receives.&lt;br /&gt;&lt;h3&gt;Summary&lt;/h3&gt;When you clone a ClientDataSet cursor, you permit an additional ClientDataSet to refer to the Data and Delta properties of an existing ClientDataSet. While all ClientDataSets that share the same cursor share the same Data and Delta, they do not share any other properties, permitting each ClientDataSet to maintain an independent view of the data.&lt;br /&gt;&lt;br /&gt;For more information about Delphi in Depth: ClientDataSets, as well as for links for purchasing the book, please visit &lt;a href="http://www.jensendatasystems.com/cdsbook"&gt;http://www.JensenDataSystems.com/cdsbook&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Copyright © 2011 Cary Jensen. All Rights Reserved&lt;br /&gt;&lt;br /&gt;Author’s Note: This article originally appeared in the SDN Magazine # 109, published on May 5th, 2011.&amp;nbsp; For information about the Software Development Network, please visit &lt;a href="http://www.sdn.nl/"&gt;http://www.sdn.nl/&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-8492344273602299543?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/8492344273602299543/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2011/10/cloning-clientdataset-cursors.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/8492344273602299543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/8492344273602299543'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2011/10/cloning-clientdataset-cursors.html' title='Cloning ClientDataSet Cursors'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-XqVUXk1oq_4/Tq8ycspT6iI/AAAAAAAAAH8/STX-RdR5-OA/s72-c/figure1.tif' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-2692786307318317425</id><published>2011-09-26T06:13:00.000-07:00</published><updated>2011-09-27T04:08:28.399-07:00</updated><title type='text'>Creating a TThread in Delphi that can Self-Terminate on Timeout</title><content type='html'>&lt;p&gt;Author's Note. When I originally wrote this post, I described a technique by which a thread forcibly terminated itself by calling the TerminateThread function of the Windows API. Part of my goal was to share a technique that I haven't seen described before, which also made me somewhat suspicious that the technique might be wrong minded.&lt;/p&gt;&lt;p&gt;Though the code seemed to work alright, I invited comments from readers if they knew otherwise. The first comments to this post stated very clearly that this technique is not an acceptable one. In short, unless there is a very good reason (and there are few of those), you should not call the TerminateThread function.&lt;/p&gt;&lt;p&gt;Still, the original concept, that of a self-terminating thread, is an attractive one. However, instead of forcibly terminating a thread, the code presented in this updated post simply attempts to terminate the thread through its normal mechanism, by calling the thread's Terminate method. While this might not terminate the thread quickly (or ever if it is truly deadlocked), there is much less of a downside compared to the use of TerminateThread.&lt;/p&gt;&lt;p&gt;I considered removing this post. On the other hand, I think the comments are very good, well considered, and educational. In addition, I have modified the code presented here to remove the potentially harmful call to TerminateThread, and replaced it with a less forceful, yet still useful alternative. The following is the modified posting.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;What can you do when a thread becomes unresponsive? Better yet, how can you create an instance of Delphi's TThread class that terminates itself if it becomes unreasonably slow?&lt;/p&gt;&lt;p&gt;This is the question that I had to address recently. In short, I was creating a Delphi application that used an Internet Direct (Indy) TIdTcpClient to make calls to a RESTful Web service. In doing this, I considered the sage advise of my friend and fellow &lt;a href="http://www.delphideveloperdays.com" target="_blank"&gt;Delphi Developer Days&lt;/a&gt; presenter &lt;a href="http://www.marcocantu.com" target="_blank"&gt;Marco Cantù&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Marco is fond of pointing out that you should make remote calls to Web services or other Internet resources from a thread, permitting your user interface to remain responsive even if the service to which you are communicating becomes slow or worse.&lt;/p&gt;&lt;p&gt;There is a problem, however. Terminating a thread by killing it outright is serious business, and can easily lead to memory leaks and other similarly nasty side effects. For example, resources that your thread has allocated may end up abandoned upon the termination of the thread. And, as pointed out in some of the comments to the original post, can go so far as to undermine your entire process.&lt;/p&gt;&lt;p&gt;Since my original issue was associated with communicating with a RESTful Web service using a connection object (an Indy socket client in this case), I reflected on the technique I've used in JavaScript to make asynchronous calls to the same service. In that code I created a timer that was canceled once the XMLHttpRequest object (XHR) callback triggered. If the timer expired before the asynchronous request returned, the timer canceled the request. On the other hand, if the callback was executed, the timer was canceled.&lt;/p&gt;&lt;p&gt;So, here for your consideration is a simple TThread descendant that calls its own Terminate method (from the primary thread of execution) if it does not terminate on its own after a specified timeout. I follow this unit with some observations and final comments.&lt;/p&gt;&lt;pre&gt;unit TimeoutThreadu;&lt;br /&gt;&lt;br /&gt;// No guarantees or warranties are expressed or implied concerning&lt;br /&gt;// the applicability of techniques or code included in this example.&lt;br /&gt;// If you wish to use techniques or code included in this example,&lt;br /&gt;// it is your responsibility to test and certify any code or&lt;br /&gt;// techniques design adopted as a result of this project.&lt;br /&gt;&lt;br /&gt;interface&lt;br /&gt;&lt;br /&gt;uses&lt;br /&gt;  Classes, Windows, ExtCtrls, SysUtils, SyncObjs;&lt;br /&gt;&lt;br /&gt;type&lt;br /&gt;  TTimeoutThread = class(TThread)&lt;br /&gt;  strict private&lt;br /&gt;    { Private declarations }&lt;br /&gt;    FTimer: TTimer;&lt;br /&gt;    FTimeout: Boolean;&lt;br /&gt;    procedure OnTimer(Sender: TObject);&lt;br /&gt;  protected&lt;br /&gt;    procedure Execute; override;&lt;br /&gt;  public&lt;br /&gt;    constructor Create(CreateSuspended: Boolean;&lt;br /&gt;      Timeout: Integer); overload;&lt;br /&gt;    destructor Destroy; override;&lt;br /&gt;  end;&lt;br /&gt;&lt;br /&gt;implementation&lt;br /&gt;&lt;br /&gt;uses mainformu;&lt;br /&gt;{ TTimeoutThread }&lt;br /&gt;&lt;br /&gt;constructor TTimeoutThread.Create(&lt;br /&gt;  CreateSuspended: Boolean; Timeout: Integer);&lt;br /&gt;begin&lt;br /&gt;  FTimer := TTimer.Create(nil);&lt;br /&gt;  FTimer.Interval := timeout;&lt;br /&gt;  FTimer.OnTimer := OnTimer;&lt;br /&gt;  Self.FreeOnTerminate := True;&lt;br /&gt;  FTimeout := True;&lt;br /&gt;  Self.Create(CreateSuspended);&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;destructor TTimeoutThread.Destroy;&lt;br /&gt;begin&lt;br /&gt;  //Note that a destructor is called even if&lt;br /&gt;  //an exception is raised in the constructor.&lt;br /&gt;  //That is why FTimeout is set to True after&lt;br /&gt;  //the thread's resources have been created.&lt;br /&gt;  if FTimeout then&lt;br /&gt;  begin&lt;br /&gt;    FTimer.Enabled := False;&lt;br /&gt;    FTimer.Free;&lt;br /&gt; end;&lt;br /&gt;  inherited;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;procedure TTimeoutThread.Execute;&lt;br /&gt;begin&lt;br /&gt;  if FTimeout then&lt;br /&gt;    FTimer.Enabled := True;&lt;br /&gt;  while True do&lt;br /&gt;  begin&lt;br /&gt;    //Your thread's code goes here&lt;br /&gt;    //Simulate work&lt;br /&gt;    sleep(2000);&lt;br /&gt;    if Self.Terminated then exit;&lt;br /&gt;  end;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;procedure TTimeoutThread.OnTimer(Sender: TObject);&lt;br /&gt;begin&lt;br /&gt;  //This code executes in the primary&lt;br /&gt;  //thread of execution. As a result, it will&lt;br /&gt;  //execute even if the thread is currently blocked.&lt;br /&gt;  //Note, however, if the thread is blocked, it will&lt;br /&gt;  //not actually terminate until it is no longer blocked.&lt;br /&gt;  //Furthermore, calling a thread's Terminate method does&lt;br /&gt;  //not actually terminate the thread. It only set the thread's&lt;br /&gt;  //Terminated property to True. If your thread is designed&lt;br /&gt;  //to run continuously until it detects its termination,&lt;br /&gt;  //it is the responsibility of the code in the Execute method&lt;br /&gt;  //to test the Terminated property, and to exit gracefully&lt;br /&gt;  //once it finds that Terminated has been set to True.&lt;br /&gt;  FTimer.Enabled := False;&lt;br /&gt;  Self.Terminate;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;end.&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This thread can be created as a self-terminating thread using code as simple as the following:&lt;/p&gt;&lt;pre&gt;with TTimeoutThread.Create(True, 10000) do&lt;br /&gt;  Start; //call Resume with Delphi 2007 or earlier&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;To begin with, this thread can be run without or without a timeout. If you call the inherited constructor, it does not timeout.&lt;/p&gt;&lt;p&gt;When you call the overloaded constructor, you pass the timeout, in milliseconds, in the second parameter. In that case, the first statement in the Execute method (the one method of the thread that runs in a worker thread) initiates the Timer.&lt;/p&gt;&lt;p&gt;If the thread terminates normally, and it was created with a timeout, it terminates the timer. And, if the timer expires at about the same time as the thread is terminating, there is no harm. Calling Terminate on a terminated thread is not a problem, and setting a timer's Enabled property to False when it is already set to False is likewise not a problem.&lt;/p&gt;&lt;p&gt;But permit me to mention a limitation. This thread is freed on termination. If you need a thread to stick around after it has terminated, this one is not for you. On the other hand, you can create an OnTerminate event handler, and persist any information that the thread has collected from there.&lt;/p&gt;&lt;p&gt;Once again, this post has been modified. The original code included a call to the Windows API function TerminateThread from within a synchronized block of code in the OnTimer event handler. I asked for input regarding this approach, and the response was universal: it was the wrong thing to do. Please enjoy the thoughtful comments submitted by readers.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-2692786307318317425?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/2692786307318317425/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2011/09/creating-tthread-in-delphi-that-can.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/2692786307318317425'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/2692786307318317425'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2011/09/creating-tthread-in-delphi-that-can.html' title='Creating a TThread in Delphi that can Self-Terminate on Timeout'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-6163332222649923272</id><published>2011-08-29T04:23:00.000-07:00</published><updated>2011-08-29T04:23:42.991-07:00</updated><title type='text'>Creating Custom Compiler Errors in Delphi</title><content type='html'>&lt;div class="MsoNormal" style="margin: 0in 0in 0pt;"&gt;Unlike my normal posts, this one is short, and it describes an interesting Delphi solution that I want to share with you.&lt;/div&gt;&lt;div class="MsoNormal" style="margin: 0in 0in 0pt;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal" style="margin: 0in 0in 0pt;"&gt;Here is the issue that I was faced with, along with the solution. I work with a number of different clients on a wide range of technologies (though it seems as though most of my work these days is Internet-related). Furthermore, I use many different versions of Delphi. Though it is not truly relevant to the technique that I want to share today, I run each version of Delphi in a separate virtual machine. If you are curious about why I do this, please feel free to read &lt;a href="http://caryjensen.blogspot.com/2010/09/developing-in-virtual-world-revisted.html" target="_blank"&gt;Developing in a Virtual World&lt;/a&gt; and &lt;a href="http://caryjensen.blogspot.com/2009/12/creating-more-manageable-development.html" target="_blank"&gt;Creating a More Manageable Development Environment&lt;/a&gt;&lt;/div&gt;&lt;div class="MsoNormal" style="margin: 0in 0in 0pt;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="MsoNormal" style="margin: 0in 0in 0pt;"&gt;Getting to the point, here is the issue I faced. I have a project that I originally wrote using Delphi 2007, and in which I used Internet Direct (Indy) components that employ SSL (secure socket layer). I have since migrated that project to a more recent version of Delphi, and in the process&amp;nbsp;deployed newer versions of the ssleay32.dll and libeay32.dll DLLs. These newer DLLs are incompatible with the version of Indy I have installed in Delphi 2007.&lt;/div&gt;&lt;br /&gt;This might not sound like there is a problem, but there is. Specifically, now that I have moved to a later version of the SSL libraries, I&amp;nbsp;should never&amp;nbsp;compile and deploy that application using Delphi 2007. Remember, as a consultant I use many different versions of Delphi, and for the particular client for whom I built this application, we are using no less than three different versions of Delphi. Since I don't work on these projects everyday, it would be easy for me to check out this application from version control and try to update it using the wrong version of Delphi.&lt;br /&gt;&lt;br /&gt;What I needed was some mechanism to prevent me from attempting to compile and deploy this particular application in Delphi 2007 (or earlier). What if, I thought, I could instruct the compiler to generate a fatal error upon attempting to compile the project in Delphi 2007, but not from a later version. Oh, sure, I could kludge a solution by including some reference to a symbol that was introduced in Delphi 2009, but what I really wanted was to be able to generate a meaningful compiler error, such as "This project must be compiled in Delphi 2009 or later" instead of a relatively meaningless&lt;br /&gt;&lt;pre&gt;[DCC Error] Unit1.pas(28): E2003 Undeclared identifier: 'TBalloonHint'&lt;/pre&gt;&lt;br /&gt;Fortunately, there is an elegant solution, but I was unaware of it at the time. So, in my quest to find the correct solution, I turned to the Gods of Software, which is to say that I posted by inquiry on &lt;a href="http://www.stackoverflow.com/" target="_blank"&gt;StackOverflow&lt;/a&gt;. My original &lt;a href="http://stackoverflow.com/questions/6162457/can-i-generate-a-custom-compiler-error-if-so-how" target="_blank"&gt;post&lt;/a&gt; was answered by several people (Delphi folks that you'll likely know), and I had my answer.&lt;br /&gt;&lt;br /&gt;So, here is the compiler directive that I added to my project, which I adapted from the answers provided on StackOverflow:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;{$IF CompilerVersion &amp;lt;= 19.0} // Delphi 2007 = 19.0&lt;br /&gt;  {$MESSAGE Error 'This project must be compiled in Delphi 2009 or later'}&lt;br /&gt;{$IFEND}&lt;br /&gt;&lt;/pre&gt;And do you know what? This past week, when I was onsite with the client for whom I built the application, I encountered the following compiler error. &lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-3rZUs6B7myE/TltwghGOMRI/AAAAAAAAAHk/dITB1d9oyio/s1600/error.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" qaa="true" src="http://2.bp.blogspot.com/-3rZUs6B7myE/TltwghGOMRI/AAAAAAAAAHk/dITB1d9oyio/s1600/error.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;span style="font-family: &amp;quot;Times New Roman&amp;quot;; font-size: 12pt; mso-ansi-language: EN-US; mso-bidi-language: AR-SA; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: EN-US;"&gt;&lt;br /&gt;&lt;span style="font-family: Verdana, sans-serif;"&gt;Thank goodness my application is now smarter than me.&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-6163332222649923272?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/6163332222649923272/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2011/08/creating-custom-compiler-errors-in.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6163332222649923272'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6163332222649923272'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2011/08/creating-custom-compiler-errors-in.html' title='Creating Custom Compiler Errors in Delphi'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-3rZUs6B7myE/TltwghGOMRI/AAAAAAAAAHk/dITB1d9oyio/s72-c/error.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-5181087607067573479</id><published>2011-06-30T06:25:00.000-07:00</published><updated>2011-07-02T13:16:16.652-07:00</updated><title type='text'>Backward Compatibility using Defalt Parameters</title><content type='html'>When a function, procedure, or method requires at least one parameter, the final (right-most) parameters in the parameter list can be declared to have a default value. When the parameters declared to have a default value are omitted during the invocation of the subroutine, the default values are assigned to the corresponding formal parameters.&lt;br /&gt;&lt;br /&gt;While this feature is interesting by itself, there is another use for this feature that permits you to extend an existing method without breaking older versions.&lt;br /&gt;&lt;br /&gt;The best way to explain this trick is with an example. Imaging that you have a method you call to display an error message on your page. This method takes a single string parameter, and assigns that value to one or more Label controls (or some other display element).&lt;br /&gt;&lt;br /&gt;Here is an example of what the implementation of that method might look like:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;procedure TForm1.SetMessageText(Value: String);&lt;br /&gt;begin&lt;br /&gt;  MessageLabel1.Caption := Value;&lt;br /&gt;  MessageLabel2.Caption := Value;&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let's assume that these Label controls use a red foreground color, so that when a non-empty string is assigned the message stands out, and looks like an error message. Assigning an empty string to the method sets the Labels's caption property to an empty string, effectively turning off the message.&lt;br /&gt;&lt;br /&gt;So, whenever you need to display an error message, you simply call this method, using something similar to the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SetMessageText('You must enter a starting date before continuing');&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now image that at some later time you realize that you want to be able to display non-error messages using these same Labels. These non-error messages should use a black font for the Labels, and not a red one. &lt;br /&gt;&lt;br /&gt;Instead of create a new method, you can simply extend the existing method easily by adding a new parameter. Importantly, in order to avoid having to go back and modify your existing calls to SetMessageText, this second parameter will use a default value to direct the method to use a red font when this parameter is absent. &lt;br /&gt;&lt;br /&gt;Here is how the updated method implementation might look:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;procedure TForm1.SetMessageText(Value: String; IsError: Boolean = True);&lt;br /&gt;begin&lt;br /&gt;  if IsError then&lt;br /&gt;  begin&lt;br /&gt;    MessageLabel1.Font.Color := clRed;&lt;br /&gt;    MessageLabel2.Font.Color := clRed;&lt;br /&gt;  end&lt;br /&gt;  else&lt;br /&gt;  begin&lt;br /&gt;    MessageLabel1.Font.Color := clBlack; &lt;br /&gt;    MessageLabel2.Font.Color := clBlack;&lt;br /&gt;  end;&lt;br /&gt;  MessageLabel1.Caption := Value;&lt;br /&gt;  MessageLabel2.Caption&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;Now, when you use a call something like the following, your message will be display in a black font, indicating that this is an informational message:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SetMessageText('The time is ' + DateTimeToStr(Now), False);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Any call that omits the second parameter, or calls that include True in the second parameter, will display the message in a red font.&lt;br /&gt;&lt;br /&gt;There are several restrictions concerning the use of default parameters. These are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The specified default values must be represented by a constant expression&lt;/li&gt;&lt;li&gt;Parameters that are object types, variants, records, or static arrays cannot have default values&lt;/li&gt;&lt;li&gt;Parameters that are class, class reference, dynamic arrays, and procedural types can only have nil as their default value&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-5181087607067573479?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/5181087607067573479/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2011/06/backward-compatibility-using-defalt.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/5181087607067573479'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/5181087607067573479'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2011/06/backward-compatibility-using-defalt.html' title='Backward Compatibility using Defalt Parameters'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-7480742870812084951</id><published>2011-05-05T11:48:00.000-07:00</published><updated>2011-05-05T11:48:47.927-07:00</updated><title type='text'>Advantage AdsQuery and RecNo</title><content type='html'>At the time of this writing, Marco Cantù and I have completed the US part of our &lt;a href="http://www.delphideveloperdays.com/" target="_blank"&gt;2011 Delphi Developer Days&lt;/a&gt; tour, and are beginning the European part at the end of next week (yes, there are a few seats available in each city. If you are interested in attending, you must act soon. Otherwise, you’ll have to wait until next year). One of the benefits of Delphi Developer Days is that we limit registration to just over 30 people in each city, which ensures that we have a sufficient number of attendees to stimulate discussion and networking, but few enough that every attendee has the opportunity to talk with Marco and me on a one-on-one basis.&lt;br /&gt;&lt;br /&gt;At our Baltimore stop in April, one of the attendees approached me with a problem that he encountered during his attempt to convert his commercial application from using the Borland Database Engine (BDE) to the Advantage Database Server (ADS). (This is the most logical and easiest migration for BDE developers, in my opinion). &lt;br /&gt;&lt;br /&gt;In his tests, the AdsQuery (the ADS equivalent to the BDE Query) did not treat the RecNo property the same as other DataSets. In all other DataSets, he noted, RecNo reports the position of the current record within the current DataSet with respect to the order of the records in the DataSet. For example, in a ClientDataSet, a Query, or a SQLDataSet, RecNo will be equal to 1 when you are on the first record of the DataSet, which is the default when you first open the DataSet, regardless of which index you’ve select (ClientDataSet) or SQL ORDER BY clause (Query or SQLDataSet) you are using.&lt;br /&gt;&lt;br /&gt;“There must be a way around this,” he conceded, “since the Advantage Data Architect can report the correct record position for records in queries that include in an ORDER BY clause.” &lt;br /&gt;&lt;br /&gt;I put together a simple test between sessions, and confirmed his observation that RecNo, at least using the default settings of the AdsQuery, did in fact return the position of that record within the underlying table, and not the position of the record in the current record order. As a result, when an AdsQuery that employs an ORDER BY clause is first opened, RecNo for the current record (the first record in the view) might 55, or 1,002,837, or whatever position that first record occupies in the underlying table, regardless of the order of that record’s position in the result set.&lt;br /&gt;&lt;br /&gt;RecNo has an additional use, as well. RecNo can be used to navigate a DataSet (at least in DataSets that support navigation). Specifically, if you set RecNo to 1, the DataSet navigates to record 1 in the current sort order. However, with the AdsQuery in my test, setting RecNo to 1 navigated to the first record in the underlying table, which is not necessary the first record in the indexed or sorted order of the result set.&lt;br /&gt;&lt;br /&gt;I had not seen this problem before, and I didn’t solve the problem on the spot. However, I did say that I would try to understand the issue better, and report my results to the Advantage team if I could not find an acceptable solution.&lt;br /&gt;&lt;br /&gt;I’ve been very busy, but I finally found time to put together a demonstration of the issue. It confirmed that in the default configuration, the AdsQuery.RecNo property is associated with a record’s true position in the underlying table. However, after some poking around, I found two properties that change this behavior. Specifically, when you set an AdsQuery’s Sequenced property to True, and its SequenceLevel property to slExact, the AdsQuery treats RecNo the same as all other DataSets I tested.&lt;br /&gt;&lt;br /&gt;I imagine that there is a performance explanation for the default values of the AdsQuery properties. Specifically, so long as you don’t need to use the RecNo property to determine the view-specific position of a record in a result set, and do not need to use it to perform view-specific navigation, the default property values result in faster query performance. When the standard DataSet.RecNo behavior is required, however, the extra overhead is worth the benefit. So, if you are using Advantage, and need the AdsQuery.RecNo property to behave the DataSet way, all you need to do it tweak its properties a bit.&lt;br /&gt;&lt;br /&gt;I am sharing this story for two reasons. First, I hope that this post helps the next Advantage user who runs into this problem. Second, it demonstrates added value that Marco and I bring to Delphi Developer Days. I can’t promise that we can solve every problem for you, but we are there to listen, and we do our best to offer suggestions for solutions.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-7480742870812084951?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/7480742870812084951/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2011/05/advantage-adsquery-and-recno.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7480742870812084951'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7480742870812084951'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2011/05/advantage-adsquery-and-recno.html' title='Advantage AdsQuery and RecNo'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-6025927178431387751</id><published>2011-04-25T17:53:00.000-07:00</published><updated>2011-07-14T06:21:37.864-07:00</updated><title type='text'>Live Templates Revisited</title><content type='html'>While code templates have been in Delphi since Delphi 4, they seem simple compared to the new live templates feature first introduced in Delphi 2006. Live templates provide self-describing, intelligent code insertion, and interactive navigation to the variable parts of the template. If you are not using them now, it's time to take another look. &lt;br /&gt;&lt;br /&gt;Consider the inserted template shown in the following figure. This template was created by executing the forb template, which inserts a for loop with a begin..end block.&lt;br /&gt;&lt;br /&gt;&lt;div align="left"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;a href="http://1.bp.blogspot.com/-foU0gHSda_w/TbYOhPimK5I/AAAAAAAAAFU/KwGWmQOqUKo/s1600/figure1.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="94" i8="true" src="http://1.bp.blogspot.com/-foU0gHSda_w/TbYOhPimK5I/AAAAAAAAAFU/KwGWmQOqUKo/s320/figure1.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;Notice that initially the I (the loop iteration variable) is highlighted. This permits you to immediately edit the variable name, if you like. For example, many Delphi developers use a lowercase variable named i as their loop iterator (even though Delphi itself is case insensitive).&lt;br /&gt;&lt;br /&gt;You will also notice that the I, as well as the 0 and List.Count appear as bordered text. These bordered text areas represent the sync points of the live template. After changing I to i, press Tab or Shift-Tab to navigate between the sync points of the template. As you navigate, you will notice that the hints change to describe the role of the currently selected point.&lt;br /&gt;&lt;br /&gt;The following figure shows the same live template shown in the preceding figure after you have navigated to the third variable part.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-V7gXLY0Rvjk/TbYOjQHIo0I/AAAAAAAAAFY/0R8NhY3vGyo/s1600/figure2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="94" i8="true" src="http://1.bp.blogspot.com/-V7gXLY0Rvjk/TbYOjQHIo0I/AAAAAAAAAFY/0R8NhY3vGyo/s320/figure2.jpg" width="320" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Many live templates, such as the forb template, also support intelligent code insertion, or &lt;i&gt;active scripting&lt;/i&gt;. With active scripting, code executes when you insert or interact with the template, conditionally performing one or more tasks for you. In the case of the forb template, if the function in which you insert this template does not already contain a declaration for the loop iterator variable, one will be inserted for you once you press the Tab key after completing all sync points. The following code segment depicts how a previously empty method might look after the execution of the active script associated with the forb live template.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;a href="http://3.bp.blogspot.com/-tuPArThmks4/TbYOksGKwXI/AAAAAAAAAFc/98ZhS4VhoBI/s1600/figure3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="122" i8="true" src="http://3.bp.blogspot.com/-tuPArThmks4/TbYOksGKwXI/AAAAAAAAAFc/98ZhS4VhoBI/s320/figure3.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h3&gt;Invoking Live Templates&lt;/h3&gt;Delphi provides you with a large number of standard live templates for each of its language personalities (Delphi, C++, XML, and so forth). These templates can be invoked either on demand or automatically, depending on how the live template is defined as well as how the Delphi environment is configured.&lt;br /&gt;&lt;br /&gt;Regardless of a live template's definition, you can invoke a live template on demand by positioning your cursor within the editor where you want to place the live template and press Ctrl-J. Delphi responds by displaying the Insert Template menu, shown in the following figure. Select the template you want to insert from this menu and press Enter.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-TfgNYvI2TL4/TbYOl7QDkAI/AAAAAAAAAFg/FsUHsl8Gsoc/s1600/figure4.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="120" i8="true" src="http://2.bp.blogspot.com/-TfgNYvI2TL4/TbYOl7QDkAI/AAAAAAAAAFg/FsUHsl8Gsoc/s320/figure4.jpg" width="320" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;Similarly, you can begin typing the first few characters of the template shortcut name before pressing Ctrl-J. If only one template's shortcut name matches the characters you've typed, that template is selected and inserted, without the Insert Template menu being displayed. If two or more template shortcut names match what you've typed, the Insert Template menu appears, though only the matching template names are shown, in which case you then select which of the displayed templates you want and press Enter.&lt;br /&gt;&lt;br /&gt;Automatic invocation is available when a live template and Delphi are both configured for it. For those live templates that include the invoke="auto" attribute in the template element of the template definition, auto completion is available when the Code templates completion and Auto complete checkboxes are checked on the Code Insight table of the Options dialog box. You display this dialog box by select Tools &lt;br /&gt;Options from Delphi's main menu.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-Uog4Z7NFYzw/TbYOnPA71KI/AAAAAAAAAFk/YrjeQUn7FYI/s1600/figure5.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="218" i8="true" src="http://3.bp.blogspot.com/-Uog4Z7NFYzw/TbYOnPA71KI/AAAAAAAAAFk/YrjeQUn7FYI/s320/figure5.jpg" width="320" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;With automatic live template invocation, live templates are inserted when you type the name of a template shortcut and press Tab, Spacebar, or Ctrl-J. For example, if you type forb and press Tab with code templates completion enabled, the forb live template is instantly inserted into your code at the position of your cursor.&lt;br /&gt;Live templates also show up in the code completion menu, which you can invoke by pressing Ctrl-Spacebar. Templates always appear at the end of this list.&lt;br /&gt;&lt;br /&gt;You can also invoke live templates from the Templates pane, shown in the following figure. To display the Templates pane, select View | Templates from Delphi's main menu.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-tXWRzWHvvMA/TbYOsTcqEjI/AAAAAAAAAFo/o9kwmcxBV1A/s1600/figure6.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" i8="true" src="http://3.bp.blogspot.com/-tXWRzWHvvMA/TbYOsTcqEjI/AAAAAAAAAFo/o9kwmcxBV1A/s1600/figure6.jpg" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;Each of the standard templates that ship with Delphi are defined by an XML file. You can edit this XML if you want to change the template's content or behavior. To edit one of the standard templates, select the template in the Template pane and then click Edit from the Template pane's toolbar. The following figure shows the case live template in the code editor.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;a href="http://4.bp.blogspot.com/-_OxzxYTmiVo/TbYOtyChNyI/AAAAAAAAAFs/-cGdtfWZ3g0/s1600/figure7.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="269" i8="true" src="http://4.bp.blogspot.com/-_OxzxYTmiVo/TbYOtyChNyI/AAAAAAAAAFs/-cGdtfWZ3g0/s320/figure7.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The case live template is a good example to learn from, since it includes point, hint, text, description, author, and script elements. Furthermore, this is a surround template, which means that a code segment that you select before executing this template will result in the selected block of code being enclosed in the try block.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The Parts of a Live Template&lt;/h3&gt;The root element of a live template is the codetemplate element. The codetemplate element two attributes, xmlns and version, which define an XML namespace and a version, respectively. The codetemplate element has a single child element, named template.&lt;br /&gt;&lt;br /&gt;The template element can contain four attributes. The name attribute is required, and must define a unique name for the template for the specified language.&lt;br /&gt;&lt;br /&gt;The remaining attributes are optional. Use surround="true" to define a surround template, one that can surround a block of code. Use invoke= to define the type of invocation for the template. Valid values for invoke are "auto," "manual," and "none" (the default).&lt;br /&gt;&lt;br /&gt;Set invoke to auto or manual to insert the template by typing its name and pressing a key. For auto templates you can press Space or Tab, and for manual templates you can press Tab. With any invocation type, you can type the name of the template and insert it using Ctrl-J, or you can press Ctrl-Spacebar and select the template from the displayed list.&lt;br /&gt;&lt;br /&gt;The final attribute, inplace="true", defines an expansion template. It is unclear what an expansion template is, since it does not appear to be used.&lt;br /&gt;&lt;br /&gt;The point, description, and author elements are child elements of template, and they are optional. When one or more point elements are used, they define custom sync points in your template code. Each sync point can include a text element, defining the default value, and a hint element, which sets a hint for the point.&lt;br /&gt;&lt;br /&gt;A point, as well as the code element, can also define a script attribute, which references an active script. Active scripts are described later in this post.&lt;br /&gt;&lt;br /&gt;The description element provides the description of the template that appears in the popup menus and the Templates dialog box. The author element identifies the template's author.&lt;br /&gt;&lt;br /&gt;The final element, the code element, describes the code that the template inserts. This element has several attributes, as well as a CDATA section. For a Delphi template, set the language attribute to Delphi. Also, define the delimiter that identifies the custom and predefined sync points. Since the $ character, which is the default delimiter, has special meaning in Delphi, most developers define the | character (the vertical bar) as the delimiter.&lt;br /&gt;&lt;br /&gt;The code itself appears in the CDATA section, between the [ and the ]]&amp;gt; characters. This code can include literal characters, hard returns, and sync points, both custom and predefined. In the code segment shown in the&amp;nbsp;case live template, the |expression| and |cases| strings represent the position of the corresponding sync points in the live template. You can see these points defined earlier in the template definition.&lt;br /&gt;&lt;br /&gt;In addition to your custom sync points, there are three predefined points. These are |surround|, |*|,&amp;nbsp;and |end|. If your code template is a surround template, you include the |surround| point to identify where the selected block will appear, relative to the rest of the template. When |*| appears in your template, it specifies that what following on that line should be indented by the block indent amount defined on you Options dialog box. You can include as many |*| predefined points in your live template as you need.&lt;br /&gt;&lt;br /&gt;Finally, the |end| predefined point identifies where your cursor will appear after the live template has been inserted. Like the |surround| point, there can be only one |end| point in your live template.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Custom Live Templates&lt;/h3&gt;While the standard live templates that ship with Delphi are extremely useful, you will be delighted to discover that you can create your own, custom live templates.&lt;br /&gt;&lt;br /&gt;To create a custom live template, click New from the Templates pane's toolbar. Alternatively, you can select the Template Wizard from the Object Repository. To do this, select File | New | Other from Delphi's main menu. Then, select the Template Wizard from the Other Files node of the Object Repository and select Ok.&lt;br /&gt;&lt;br /&gt;The new template appears as an XML document in the code editor, as shown in the following figure. As you inspect this figure, you will notice that the new template is displaying a hint. This is because the new custom live template is created by a live template. Not only does this reveal the incredible power of live templates, but the hints that are provide greatly simplify the process of creating your own new, live template.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-m2ysNPS3ZHc/TbYOu-rRSwI/AAAAAAAAAFw/pEA1QX3OkGY/s1600/figure8.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="112" i8="true" src="http://1.bp.blogspot.com/-m2ysNPS3ZHc/TbYOu-rRSwI/AAAAAAAAAFw/pEA1QX3OkGY/s320/figure8.jpg" width="320" /&gt;&lt;/a&gt; &lt;br /&gt;&amp;nbsp;To complete your custom live template, fill in the various variable parts of the template, and add your own elements, as needed, to define the features you want in your template.&lt;br /&gt;&lt;br /&gt;In addition to the hints that the Live Template template provides you with, you should also examine the standard live templates that ship with Delphi for insight and tips about writing your own, first-rate custom live templates.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Surround Templates &lt;/h3&gt;Surround templates are a special subset of live templates that permit you to quickly enclose, or surround, a selected block of code. Examples of surround templates include forb, try, procedure, and comment live templates.&lt;br /&gt;&lt;br /&gt;For example, imagine that you want to enclose the following code selection in a try ... except block in a Delphi project.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-BhbouTquPTM/TbYOwF-MO6I/AAAAAAAAAF0/ILkA8aLy7xQ/s1600/figure9.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="88" i8="true" src="http://4.bp.blogspot.com/-BhbouTquPTM/TbYOwF-MO6I/AAAAAAAAAF0/ILkA8aLy7xQ/s320/figure9.jpg" width="320" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;With the desired code selected, select the tryf live template from the Templates pane, and then click Execute. Alternatively, select the block of code to surround, right-click in the editor, select Surround from the displayed context menu, and then select tryf.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;a href="http://3.bp.blogspot.com/-p2HIlxNhhXA/TbYOxdrGUaI/AAAAAAAAAF4/yZLzOqXzg58/s1600/figure10.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="95" i8="true" src="http://3.bp.blogspot.com/-p2HIlxNhhXA/TbYOxdrGUaI/AAAAAAAAAF4/yZLzOqXzg58/s320/figure10.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Notice also that the code block that you selected has been indented as well, making the resulting code more readable. You are now ready to insert a call to the Free method of the TStringList in the finally block, ensuring its destruction once you are through with it.&lt;br /&gt;&lt;br /&gt;If you create your own custom surround templates, those will automatically appear in the Surround context menu. Specifically, the Surround context menu includes any custom live templates that you've created for the current language personality where the surround attribute is set to true in the &lt;template&gt;element.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Active Scripts&lt;/h3&gt;Active scripts are compiled Delphi classes that can react to events triggered during your interaction with a live template. Many of Delphi's templates use active scripts, including the case live template shown earlier , which invokes the PopulateCase active script. PopulateCase is one of about six active scripts that are shipped with Delphi. Sadly, their source code is not available.&lt;br /&gt;&lt;br /&gt;While it is a more involved process, similar to that of creating a keyboard macro, you can also write your own active scripts to be called from your live templates. You do this by creating a design time package. Within this design time package you create a class that descends from TInterfacedObject and implements IOTACodeTemplateScriptEngine. The unit in which this class is implemented should use the ToolsAPI and CodeTemplateAPI units.&lt;br /&gt;&lt;br /&gt;From within this new class, implement the GetIDString, GetLanguage, and Execute methods. Use GetIDString to return a unique string, one that will not be used by any other active script. As a convention, this string typically uses dot notation, and includes your company's name or acronym followed a dot and the active script name. No spaces can appear in this string.&lt;br /&gt;&lt;br /&gt;You use GetLanguage to return the name of the language that will be used to identify your scripting class. This string is used in a script element, which instructs the editor to use your scripting class.&lt;br /&gt;&lt;br /&gt;Finally, you implement the Execute method. This method is passed five parameters, some of which may be nil for a particular active script. The first parameter is an object that implements the IOTACodeTemplate interface. This object provides you with access to the text associated with the script element that is calling your script.&lt;br /&gt;&lt;br /&gt;The second parameter is an object that implements an IOTACodeTemplatePoint. This object represents the current sync point triggering an onenter or onexit script. For onvalidate scripts, this object is nil.&lt;br /&gt;&lt;br /&gt;The third parameter is an object that implements IOTASyncEditPoints. Use this object to examine all sync points in the live template. Again, this object is nil for onvalidate scripts, since the template does not actually exist yet.&lt;br /&gt;&lt;br /&gt;The fourth parameter is an object that implements IOTACodeTemplateScript. This object provides you access to the CDATA section of the code element.&lt;br /&gt;&lt;br /&gt;The final parameter is a Boolean parameter. You use this from an onvalidate script to signal that the template has been processed successfully.&lt;br /&gt;&lt;br /&gt;From within your active script, your execute method examines sync points and the code template to determine what actions to take. Furthermore, since the ToolsAPI unit is in your uses clause, you can use the BorlandIDEServices object to gain access to the Editor ToolsAPI, which permits you to read, add, delete, and change text within the editor.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Summary&lt;/h3&gt;Live templates are one of those lesser used treasures in Delphi. While less approachable than their code template cousins from early versions of Delphi, they are much more powerful, and it is worth your while to take another look at them.&lt;br /&gt;&lt;br /&gt;&lt;!--StartFragment --&gt;A&amp;nbsp;Russian translation of this post can be found in Aleksey Timohin's Delphi blog:&amp;nbsp;&lt;a href="http://www.tdelphiblog.com/2011/07/live-templates-revisited.html"&gt;http://www.tdelphiblog.com/2011/07/live-templates-revisited.html&lt;/a&gt;. Aleksey is the author of&amp;nbsp;&lt;a href="http://www.lazyproject.info/"&gt;Lazy Delphi Builder&lt;/a&gt;&amp;nbsp;-&amp;nbsp;free build tool for Delphi with automatic dependency resolving. &lt;br /&gt;&lt;br /&gt;&lt;!--StartFragment --&gt;&amp;nbsp;Русский перевод этой записи доступен в Delphi блоге Алексея Тимохина по адресу:&amp;nbsp;&lt;a href="http://www.tdelphiblog.com/2011/07/live-templates-revisited.html"&gt;http://www.tdelphiblog.com/2011/07/live-templates-revisited.html&lt;/a&gt;. Алексей также является автором бесплатного инструмента для сборки Delphi-программ: &lt;a href="http://www.lazyproject.info/"&gt;Lazy Delphi Builder&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-6025927178431387751?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/6025927178431387751/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2011/04/live-templates-revisited.html#comment-form' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6025927178431387751'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6025927178431387751'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2011/04/live-templates-revisited.html' title='Live Templates Revisited'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-foU0gHSda_w/TbYOhPimK5I/AAAAAAAAAFU/KwGWmQOqUKo/s72-c/figure1.jpg' height='72' width='72'/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-6041972582002722935</id><published>2011-03-29T07:43:00.000-07:00</published><updated>2011-03-29T08:18:24.691-07:00</updated><title type='text'>Delphi in Depth: ClientDataSets</title><content type='html'>I'm pleased to announce that my latest book, Delphi in Depth: ClientDataSets, is now available for &lt;a href="http://www.jensendatasystems.com/cdsbook"&gt;purchase&lt;/a&gt;. The following is the introduction from this book. At the end of this post you will find a link to the book site. That site includes links for ordering the book from several different outlets, and also includes a special offer for US residents.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.jensendatasystems.com/cdsbook"&gt;&lt;img alt="Delphi in Depth: ClientDataSets" border="0" height="250" src="http://www.jensendatasystems.com/image/did_cds_cover_194x240.jpg" width="194" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Introduction&amp;nbsp;from Delphi in Depth: ClientDataSets&lt;/h3&gt;&lt;br /&gt;Before there was the ClientDataSet, Delphi's Borland Database Engine supported cached updates. When cached updates were enabled, changes to your database tables and editable queries were not written to the underlying database on a record-by-record basis. Instead, updates were delayed, permitting a group of changes to be applied to the underlying database in an all-or-none fashion (or even discarded altogether).&lt;br /&gt;&lt;br /&gt;I wrote extensively about cached updates in the early days of Delphi, but over time cached updates lost their appeal. There were two reasons for this. First, the Borland Database Engine became dated, and it's SQL counterpart, the Borland SQL Links for Windows, was deprecated. The second is that cached updates became hopelessly broke, and this happened sometime around the release of Delphi 6.&lt;br /&gt;&lt;br /&gt;Fortunately, there was a ready replacement — ClientDataSets. ClientDataSets supported all of the basic features of cached updates and much more. In addition to providing a mechanism for grouping updates, ClientDataSets also introduced in-memory management of data and indexes, the ability to persist data and restore it to its previous state, an intuitive interface for managing the cache, and a convenient mechanism for transferring a ClientDataSet's state between processes.&lt;br /&gt;&lt;br /&gt;Originally ClientDataSets were only in the high-end versions of Delphi. Specifically, the Client/Server edition. Furthermore, the license to use a ClientDataSet in a distributed application was separate from Delphi, and was originally very expensive.&lt;br /&gt;&lt;br /&gt;With the release of Kylix and subsequently Delphi 6 the ClientDataSet became available in the Professional version of Delphi, and the MIDAS (Multitier Distributed Application Services) license become affordable, and later was included with the product. (The technology that was MIDAS is now called DataSnap.)&lt;br /&gt;&lt;br /&gt;With ClientDataSets now within reach of all Delphi developers, I became an enthusiastic advocate for their use. Not only did I speak extensively about the virtues of ClientDataSets at developer conferences, I also published an extended series of articles on the Borland Developer Network detailing the use and capabilities of ClientDataSets.&lt;br /&gt;&lt;br /&gt;At a recent Delphi conference at which I was presenting a talk on advanced ClientDataSet features, I had a revealing discussion with an attendee. His questions about an issue he was having with ClientDataSets demonstrated a fundamental misunderstanding about how they worked.&lt;br /&gt;&lt;br /&gt;When I explained what was happening, he expressed frustration that this information was not more readily available. I directed him to my previous articles and commented, in passing, that maybe I should write a book about ClientDataSets. "I'll be the first one in line to buy it if you write that book" was his reply.&lt;br /&gt;&lt;br /&gt;This idea has been with me for some time, and this past winter I finally pulled myself together and started on this book in earnest.&lt;br /&gt;&lt;br /&gt;Initially I thought that I could write this book by starting with some of my previous writings, including both the published series as well as a two-day Delphi course that I wrote and train called &lt;i&gt;Mastering ClientDataSets&lt;/i&gt;. The trick, I reasoned, would be in the organization, and I had a lot of ideas about how I would go about this.&lt;br /&gt;&lt;br /&gt;Projects like these tend to take on a life of their own, and once I was happy with the book's outline, I realized that I was committed to writing new material, a lot of new material. In the end, I estimate that at least 80 percent of this book is completely original, and that material that I salvaged from my previous writings often received extensive revision.&lt;br /&gt;&lt;br /&gt;I am really happy about how this book turned out. I hope that you are too.&lt;br /&gt;&lt;h3&gt;About this Book&lt;/h3&gt;This book takes an in-depth look at ClientDataSets. It begins with an overview of what a ClientDataSet is, and describes some of the ways that they can be used in your Delphi applications. It continues with a close examination of one of the more common uses for a ClientDataSet, reading and writing data from an underlying database through its interaction with a DataSetProvider.&lt;br /&gt;&lt;br /&gt;Next you learn how to define the structure of a ClientDataSet, a process that can be undertaken both at design time as well as at runtime. The relative roles of FieldDefs and Fields are discussed, and the roles of persistent versus dynamic Fields are considered. How virtual Fields differ from data Fields is also explained.&lt;br /&gt;&lt;br /&gt;Chapter 5 introduces indexes, including the difference between persistent and temporary indexes. The relationship between a ClientDataSet's indexes and those of an underlying database is also discussed.&lt;br /&gt;&lt;br /&gt;In chapter 6 we take a long look at the change cache, the mechanism responsible for caching updates. Here you learn how to enable and disable the cache, how to detect changes to it, and how to modify the contents of the cache.&lt;br /&gt;&lt;br /&gt;Chapters 7 through 9 introduce topics that will be familiar to many Delphi database developers, including how to edit, navigate, search, and filter data. These discussions, however, demonstrate the flexibility that ClientDataSets bring to these features, providing you with capabilities not available in other DataSets.&lt;br /&gt;&lt;br /&gt;Chapters 10, 11, and 12 introduce some of the more interesting features found only in ClientDataSets. These features include aggregates, cloned cursors, and nested datasets.&lt;br /&gt;&lt;br /&gt;The final three chapters of this book take a close look at DataSnap. These chapters specifically look at DataSnap applications where ClientDataSets in a client application interact with DataSetProviders running in a DataSnap server. Here you will learn to create DataSnap servers and clients using both the classic COM-based DataSnap as well as the new IP-based DataSnap first introduced in Delphi 2009. &lt;br /&gt;&lt;h3&gt;For More Information&lt;/h3&gt;For more information about the book, including links for purchasing this book, please go to &lt;a href="http://www.jensendatasystems.com/cdsbook"&gt;http://www.JensenDataSystems.com/cdsbook&lt;/a&gt;. This site also includes a special discount offer for US residents.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-6041972582002722935?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/6041972582002722935/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2011/03/delphi-in-depth-clientdatasets.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6041972582002722935'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6041972582002722935'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2011/03/delphi-in-depth-clientdatasets.html' title='Delphi in Depth: ClientDataSets'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-7924945939004438880</id><published>2011-02-28T16:48:00.001-08:00</published><updated>2011-03-24T09:05:58.219-07:00</updated><title type='text'>Advantage Replication, Triggers, and Notifications</title><content type='html'>&lt;p&gt;Most of my posts here are associated with Delphi, but I am also a big fan of the Advantage Database Server, an ISAM (indexed sequential access method) remote database server marketed by Sybase, an SAP company. As I write this I am on a plane returning from an extended onsite visit to one of my clients. During this visit we implemented a number of interesting features in their database application that made extensive use of three of Advantage’s more sophisticated features – replication, triggers, and notifications.&lt;/p&gt;&lt;p&gt;This is not the first time that I’ve used these Advantage features. At the same time, I haven’t written a whole lot about the real world application of these capabilities, and this seems like a good time to share, particularly because I feel a genuine excitement about how nicely these facilities came together to form a meaningful and maintainable solution to a complex problem. &lt;/p&gt;&lt;p&gt;I’ll begin by describing the basic problem that we were faced with, after which I’ll describe how Advantage replication, triggers, and notifications provided a convenient and minimally invasive solution. And for the record, the development environments employed are a mixture of Delphi (native and .NET), C#, SQL, JavaScript/jQuery, Web services, console applications, and Windows services, which is pretty typical of an enterprise-level application. &lt;/p&gt;&lt;p&gt;This particular client operates a number of business interests that interact with each other, and they develop a lot of custom software to support the business processes that distinguish them from their competitors. To say that they are technologically savvy is an understatement. &lt;/p&gt;&lt;p&gt;On my recent visit they determined that they needed to be able to extend communication between two separate database applications that were built independently for very different purposes. Specifically, they needed to make data managed by one application available to another, more or less in real time. That data, in turn, needed to be used to send text messages to specific users of the second database about specific changes occurring in the data on the first database. &lt;/p&gt;&lt;p&gt;These database applications were developed by different teams, and are running on completely different servers. The first application is a native client/server application, and the second is a browser-based application. In addition, the first application is employing Advantage 9.1, the previous version of Advantage, while the second is using the latest version, which is Advantage 10.1. To make things more difficult, this new capability needs to be implemented with no modification to the source application on the first database, if at all possible. &lt;/p&gt;&lt;p&gt;The solution to sharing the data without modifying the source application was to use Advantage's replication feature. Replication, which requires a special replication license on each replicating database, permitted us to transfer changes made to several of the database tables of interest between the two databases. When a table is being replicated, records that are inserted into that table on the source database are also inserted into a corresponding table on the destination database. Similarly, a record that is inserted or update on the source database causes that same change to be performed on the corresponding record in the destination database. &lt;/p&gt;&lt;p&gt;Our solution involved replication of two tables, which I will call events and notifications. The events table represents upcoming events and the current status of that event (schedule, in-process, or complete). This table is a key table of the first application, and represents potential events of interest to users of the second application. The events table is replicated using one-way replication, in that the second database will never make changes to the upcoming events. In other words, even if a user on the second database were to make a change to the events table (which we do not permit), that change would not be sent back to the first database.&lt;/p&gt;&lt;p&gt;The notifications table identifies those events that are of interest to users of the second application, and this table is replicated bi-directionally. Bi-directional replication permits users of the second database to create and remove events about which they want to be notified, as well as to permit the first database to update the notifications table once a notification has been performed. (How this is achieve is discussed later in this post). Through bi-directional notification, both databases can edit the notifications table and see changes made by the other database.&lt;/p&gt;&lt;p&gt;We also leveraged a feature of replication introduced in Advantage 9; the ability to replicate some, but not all of the fields of a source table. The events table contains a lot of fields that are not of interest to users who need notification. Therefore we replicated only the selected fields of interest from the first database to the second. As a result, the inter-database communication used only the bandwidth necessary to transfer the data we needed. &lt;/p&gt;&lt;p&gt;One of the nice characteristics of Advantage replication is that the source database queues replications if a connection to the destination database is lost. So, for example, if we need to reboot the server on which the destination database is running, the source database will hold those changes that need to be replicated until the destination database comes back online. As a result, data is never lost, though sometimes it will be delayed a little, but that is better than losing the data altogether. &lt;/p&gt;&lt;p&gt;Since we were trying to implement these features with no changes to the source application, our solution to managing the notifications employed two additional Advantage features: Triggers and notifications.&lt;/p&gt;&lt;p&gt;Advantage triggers are typically SQL statements that are executed by Advantage in response to record-level events on a table, such as record insertions, deletions, and updates. In this case, we placed a trigger on the events table on the source database. Each time the events table is changed, this trigger determines if the event is associated with an active notification in the notifications table. If so, it calls a stored procedure that inserts a records into a special table used for scheduling messages. It also then sets the timestamp field in the notifications table for that notification, making that particular notification inactive (no more notifications will be sent about that particular event).&lt;/p&gt;&lt;p&gt;And here is where Advantage notifications made their presence felt. Actually, we had been meaning to implement a generic email/texting solution in this database for some time, and we used this opportunity to do just that. Here is how we did it. The messages table, which is located in the source database, includes fields for a subject line, a message body, an attachment, as well as additional fields that could hold one or more email addresses to which the email or text message should be sent. (A future update will include a child table where zero, one, or more attachments can be defined, but we'll save that one for later.) &lt;/p&gt;&lt;p&gt;The messages table also includes a timestamp field that is populated when the emails and text messages are sent, and this field is used to distinguish between messages sent and those still pending. This table also includes an after insert trigger that generates a notification when a record is inserted. &lt;/p&gt;&lt;p&gt;Due to the way that the messages table and its trigger are designed, any application (console, standalone, Web-based, or mobile) can send an message simply by inserting a record into the messages table. We simplified things a bit further by creating an easy to use stored procedure that performs the insertion into messages, along with some additional validation and error handling. &lt;/p&gt;&lt;p&gt;Advantage notifications are created by calling a system stored procedure named sp_SignalEvent. Any application that wants to be notified about a particular notification calls the sp_CreateEvent system stored procedure, after which it can call the sp_WaitForEvent or sp_WaitForAnyEvent system stored procedure to be blocked until a notification of interest is received. As you might imagine, a call to sp_WaitForEvent or sp_WaitForAnyEvent should typically be called from a worker thread, and that's just what we did. &lt;/p&gt;&lt;p&gt;We built a separate Windows service using Delphi to generate the emails and text messages. Each time the Windows service starts, it spawns a thread that begins by calling sp_CreateEvent for two different Advantage notifications. One of these is the notification generated by the after insert trigger on the messages table. The second is a notification generated by a stored procedure that the Windows service itself executes when it receives an instruction to shut down. This notification is used to wake up the worker thread when the Windows service is being terminated. &lt;/p&gt;&lt;p&gt;Next, the worker thread queries the messages table, looking for records where the sent timestamp field is null. If one or more unsent messages are found, the thread generates the necessary emails and text messages for each message, using Delphi's Internet Direct (Indy) TIdSMTP component, after which it sets the messages timestamp field to the time the message was sent. &lt;/p&gt;&lt;p&gt;After sending any pending emails, the Windows service calls sp_WaitForAnyEvent, which blocks the thread until it is receives one of the two notifications of interested. &lt;/p&gt;&lt;p&gt;When the thread in the Windows service receives one of the notifications, it first checks to see if it has been terminated (the Windows service is shutting down). If not, it processes any unsent messages from the messages table, after which it calls sp_WaitForAnyEvent again. This process repeats as long as the Windows service continues to run. &lt;/p&gt;&lt;p&gt;Importantly, all emails are sent, even if the Windows service needs to be shutdown now and then, similar to how replication restarts if a connection to the destination database is temporarily lost. &lt;/p&gt;&lt;p&gt;This set of solutions provided us with the seamless complement of features that we needed, with minimal complexity and modest modifications to the databases. It also permitted us to implement a messaging system that can be used to send emails or text messages by any applications that can talk to the database. Yes, these features could have been implemented in a number of different ways, but this set of solutions provided us with a minimally invasive modification to our databases while providing a reliable and maintainable answer to our needs. &lt;/p&gt;&lt;p&gt;In closing, I've got one more bit of good news. As many of you know, Loy Anderson and I have written three books (to date) about the Advantage Database Server. Our latest book, Advantage Database Server: A Developer's Guide, 2nd Edition, is available on Amazon, CreateSpace (Amazon's publishing company), and Fastspring. This book includes chapters on replication, triggers, stored procedures, SQL, and notifications. See &lt;a href="http://www.jensendatasystems.com/ADSBook10"&gt;http://www.jensendatasystems.com/ADSBook10&lt;/a&gt; for details. More importantly, it recently became available on Amazon's European and Japanese sites. &lt;/p&gt;&lt;p&gt;Amazon is weird this way, and I hope they change (since we are just finishing our next book on &lt;a href="http://www.jensendatasystems.com/cdsbook"&gt;Delphi ClientDataSets&lt;/a&gt;, which we will be publishing on Amazon this spring). Our Advantage book has been available on Amazon in the US since late August, but only became available in Europe, the UK, and Japan in the past month. Heck, we're happy that the book is now being sold at the various Amazons, but are still a little annoyed at the delay between its initial publication and its wider availability.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-7924945939004438880?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/7924945939004438880/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2011/02/advantage-replication-triggers-and.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7924945939004438880'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7924945939004438880'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2011/02/advantage-replication-triggers-and.html' title='Advantage Replication, Triggers, and Notifications'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-8749751155170416183</id><published>2011-01-10T05:22:00.001-08:00</published><updated>2011-01-10T05:31:27.373-08:00</updated><title type='text'>Delphi Developer Days 2011</title><content type='html'>&lt;p&gt;With his &lt;a href="http://blog.marcocantu.com/blog/delphi_developer_days_2011_announced.html" target="_blank"&gt;blog&lt;/a&gt; on December 16, Marco Cantù officially announced our 2011 Delphi Developer Days tour, &lt;a href="http://www.delphideveloperdays.com/" target="_blank"&gt;Delphi Developer Days 2011 with Marco Cantù and Cary Jensen&lt;/a&gt;. Over the past few days we have posted our full agenda and session descriptions online, and this seems like an ideal time to talk about Delphi Developer Days in some detail. &lt;/p&gt;&lt;p&gt;Delphi Developer Days is an intense, two-day Delphi event that is like a cross between a small, intimate conference and a rigorous training seminar, and this year we will be visiting four cities in North America and Europe in April and May. Each event consists of 18 different presentations, some of which are presented jointly by Marco and me, and others are breakout sessions, where we present different sessions simultaneously in separate rooms.&lt;/p&gt;&lt;p&gt;For the joint presentations, we have chosen topics that we feel are of interest to all Delphi developers, such as the future of Delphi and preparing for 64-bit computing with Delphi. We also include several open question and answer sessions, where all Delphi topics are considered. &lt;/p&gt;&lt;p&gt;With respect to the breakout sessions, a lot of thought went into which sessions we should offer, and which sessions to present against one and other. For example, we included a collection of topics that cover earlier versions of Delphi, as well as sessions that explore some of Delphi's latest features. In addition, when one session is covering Internet/multi-tier/network topics, the other is not. A breakout covering some of Delphi’s most current features is balanced with a talk that applies to many versions of Delphi. It was a balancing act, but we think we did pretty well.&lt;/p&gt;&lt;p&gt;Furthermore, we have included a wide range of topics, including DataSnap, VCL component development, recent Delphi language enhancements, REST and jQuery, and debugging, just to name of few. And since we know that a number of our attendees from previous Delphi Developer Days seminars will be attending, we have included a lot of new topics. In fact, this years topics only include one session that is a repeat from last year. This talk is Marco's talk on VCL (visual component library) component development. I presented this topic last year, so even if you were in my session, you might be interested in Marco's perspective. &lt;/p&gt;&lt;p&gt;We are very happy with the agenda, and actually hope that you have difficulty choosing which of the breakout sessions to attend. But you will still not miss a thing, since the course book that you will receive when you attend Delphi Developer Days includes detailed coverage of every breakout session (and joint sessions as well). And I am not talking about just any course book. In addition to being Delphi trainers, developers, and conference speakers, Marco and I are both book authors. &lt;/p&gt;&lt;p&gt;Marco is the best-selling author of more than a dozen Delphi books, beginning with Mastering Delphi (Sybex, 1995), and continuing with his popular Delphi Handbook series. He is currently working on his next publication, Delphi XE Handbook.&lt;/p&gt;&lt;p&gt;I, too, am an author, having published more than 20 books. My next book, covering Delphi's ClientDataSet, will be available this spring. But the point is, we like to write as well as code, and when it comes to Delphi Developer Days, we take the time to write down our presentations so that you have something to take home with you after the event (letting you concentrate on our presentations rather than taking notes). In fact, the sessions in our course book contain more information than we can cover in our live presentations, but that’s all the better for you.&lt;/p&gt;&lt;p&gt;For the past two years, the Delphi Developer Days course book has been about 500 pages in length, making it longer than most Delphi books. We don't know the final page count yet, but this year's course book will be similar. And, no, you cannot buy the course book separately (sorry). Only attendees of Delphi Developer Days 2011 receive it. &lt;/p&gt;&lt;p&gt;Seating availability for Delphi Developer Days is limited, and this decision was intentional. Specifically, each city is limited to no more than about 34 attendees (we'll add cities, if necessary, but we won't increase the attendance). By doing this, we ensure two things. First, by keeping the total attendance reasonable we ensure that everyone gets an opportunity to speak with us, as well as each other. However, we don't want it too small. This size seems a perfect number for encouraging interaction, sharing different viewpoints, and supporting stimulating interaction. &lt;/p&gt;&lt;p&gt;Another aspect of Delphi Developer Days that is the result of much thought is the locations we selected. In short, we specifically choose hotels located near large airports and/or train stations. Our Washington DC/Baltimore location is close to the BWI (Baltimore/Washington International) airport, and there is a free hotel shuttle from the airport. (The hotel is about three miles away.) From New York, Philadelphia, Washington DC, and other cities, you can take the Amtrak train to BWI (there is a free shuttle between the Amtrak station and BWI). &lt;/p&gt;&lt;p&gt;In Houston, the hotel is right next to Houston Intercontinental Airport (IAH), one of the largest airport hubs in the US, and this hotel also has a free airport shuttle. Our Frankfurt location is a short 2 kilometer city bus trip or taxi from the Frankfurt Hauptbahnhof (the Frankfurt main train station), which itself is a short and inexpensive train ride from Frankfurt Airport. Or, it’s a 20-minute taxi ride from Frankfurt Airport (FRA).&lt;/p&gt;&lt;p&gt;In Amsterdam, our final city, it's even better. Delphi Developer Days 2011 Amsterdam is in the historic Park Plaza Victoria Hotel Amsterdam, which is across the street from Amsterdam Central Station (there are trains between Amsterdam Central Station and Schiphol Airport (AMS) four times an hour, the trip takes less than 20 minutes, and the cost is less than 4 Euros)&lt;/p&gt;&lt;p&gt;Our attendees come from all over the world, and we’ve tried to make this as simple as possible. So, no matter where you are coming from, you will have an easy time getting to Delphi Developer Days events. &lt;/p&gt;&lt;p&gt;Attendees to Delphi Developer Days receive the Delphi Developer Days course book, the source code shown in our sessions, and lunch on both days. You also get a chance to win products donated by our many sponsors. Last year there were more than 20 official sponsors of Delphi Developer Days, including our platinum sponsors, Embarcadero Technologies and Sybase (an SAP company). We expect this year to be similar, and attendees have a good chance of winning something.&lt;/p&gt;&lt;p&gt;I want to share with you another aspect about Delphi Developer Days, but it is more of an intangible. Marco and I are good friends, and really enjoy being able to offer an event like Delphi Developer Days to you. We share an enthusiasm for Delphi, professional software development, and learning. As previous attendees to Delphi Developer Days can attest, it’s fun. Sure, we cover a lot of information, but that’s all part of the fun. &lt;/p&gt;&lt;p&gt;We are currently offering an early-bird discount for Delphi Developer Days 2011. Attendees who register and make payment by March 5 for the US cities, and those who register and make payment by April 5 for the European cities, will receive a 10% discount. We also offer an additional discount for 3 or more attendees from the same company (same physical address), as well as an &lt;a href="http://www.delphideveloperdays.com/notifyme.html" target="_blank"&gt;additional 10% discount&lt;/a&gt; if you previously attended a Delphi Developer Days event in 2009 or 2010. &lt;/p&gt;&lt;p&gt;We are excited about the Delphi Developer Days 2011 tour, and hope you are, too. It’s going to be the “Delphi Event of the Year.” Here are more details:&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.delphideveloperdays.com/WashDC-BWI-area.html" target="_blank"&gt;April 11-12, 2011&lt;/a&gt;: Washington DC/Baltimore, Maryland (Holiday Inn Express Hotel &amp;amp; Suites, BWI Airport West)&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.delphideveloperdays.com/Houston.html" target="_blank"&gt;April 14-15, 2011&lt;/a&gt;: Houston, Texas (Clarion Inn at Bush Intercontinental Airport)&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.delphideveloperdays.com/Frankfurt.html" target="_blank"&gt;May 12-13, 2011&lt;/a&gt;: Frankfurt (main), Germany (Holiday Inn Express Frankfurt-Messe)&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.delphideveloperdays.com/Amsterdam.html" target="_blank"&gt;May 16-17, 2011&lt;/a&gt;: Amsterdam, The Netherlands (Park Plaza Victoria Hotel Amsterdam)&lt;/p&gt;&lt;p&gt;Visit &lt;a href="http://www.delphideveloperdays.com/" target="_blank"&gt;http://www.delphideveloperdays.com/&lt;/a&gt; for more information.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-8749751155170416183?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/8749751155170416183/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2011/01/delphi-developer-days-2011.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/8749751155170416183'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/8749751155170416183'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2011/01/delphi-developer-days-2011.html' title='Delphi Developer Days 2011'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-8882852868857225375</id><published>2010-11-29T06:24:00.001-08:00</published><updated>2010-11-29T07:14:41.111-08:00</updated><title type='text'>Hey! Debugger. Leave My Exception Alone!</title><content type='html'>&lt;em&gt;(with appologies to Roger Waters)&lt;/em&gt; &lt;p&gt;In a recent post (&lt;a href="http://caryjensen.blogspot.com/2010/08/breakpoints-with-side-effects.html"&gt;Breakpoints with Side Effects&lt;/a&gt;) I wrote about breakpoints with side effects. One of the side effects that I mentioned was the ability to ignore all subsequent exceptions, as well as restore the normal response of the debugger to encountered exceptions (pause the execution of your code).&lt;/p&gt;&lt;p&gt;In response, a reader posted a comment asking how to instruct the debugger to ignore exceptions, particularly those that cannot be anticipated. He wrote "when debugging unit-tests that have to trigger exceptions ... I'm not interested in the expected exceptions, I'm interested in the unexpected ones! Having the debugger break on them all, can be quite inconvenient."&lt;/p&gt;&lt;p&gt;Fortunately, Delphi and its debugger provide you with a number of mechanisms for ignoring exceptions. These techniques can be divided into two general categories. The first is to disable breaking on exceptions, even though the debugger is active. The second is to run your code without activating the debugger (but with the option to active it at runtime). Let's begin with the first of these two approaches. &lt;/p&gt;&lt;h3&gt;Disabling Exception Breaking&lt;/h3&gt;&lt;p&gt;When you are running your application from the Delphi IDE, and the debugger is active, there are a number of ways to instruct the debugger to ignore exceptions. And by "ignore," I mean to instruct the debugger to not pause execution of your application when an exception is encountered. &lt;/p&gt;&lt;p&gt;Under normal circumstances, when you are running an application with the debugger active, an encountered exception causes the debugger to pause the execution of your code and display a dialog box describing the exception that occurred. This happens whether or not the exception is caught by an exception block.&lt;/p&gt;&lt;p&gt;For example, consider this overly simplistic code segment. &lt;/p&gt;&lt;pre&gt;var&lt;br /&gt;  i, j, k, l, m: Integer;&lt;br /&gt;begin&lt;br /&gt;  i := 23;&lt;br /&gt;  j := 0;&lt;br /&gt;  l := 2;&lt;br /&gt;  m := 4;&lt;br /&gt;try&lt;br /&gt;  k := i div j * (l div m);&lt;br /&gt;except&lt;br /&gt;  k := 0;&lt;br /&gt;end;&lt;br /&gt;ShowMessage(IntToStr(k));&lt;/pre&gt;&lt;p&gt;In this code segment, an EDivByZero exception is raised within the try block. But even though that exception is handled by an except clause, if you are running this code from the IDE with the debugger active, the debugger will pause your code, and display a message like the one shown in the following figure.&lt;/p&gt;&lt;a href="http://4.bp.blogspot.com/_oiz-KoL9VE4/TPPBmC2yRRI/AAAAAAAAAEg/MaPMx1hdLKM/s1600/fig1.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 87px;" src="http://4.bp.blogspot.com/_oiz-KoL9VE4/TPPBmC2yRRI/AAAAAAAAAEg/MaPMx1hdLKM/s320/fig1.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5544988425569060114" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;p&gt;Figure 1. An exception has caused the debugger to pause execution &lt;/p&gt;&lt;p&gt;At this point, you have the option of selecting Break, which places you in debug mode, or selecting Continue, which resumes the execution of your code. The point here is that even though the exception is already handled by your code, the debugger has taken upon itself to get involved and pause the execution of your program.&lt;/p&gt;&lt;p&gt;The problem is even worse if the exception is not raised within the try block of a try-except. In those cases, selecting Continue causes the program to resume, at which point it immediately displays the exception in the default exception handler dialog box, the one intended to display the exception to the end user. &lt;/p&gt;&lt;p&gt;In situations where you do not want the debugger to pause the execution of your code in response to an exception, you have several options. As described in my previous article, you can set a non-breaking breakpoint at some point in your code prior to the execution of the code segment that raises the exception, and use the Ignore subsequent exceptions Advanced breakpoint option, shown in Figure 2, to instruct the debugger to ignore the exception.&lt;/p&gt;&lt;a href="http://2.bp.blogspot.com/_oiz-KoL9VE4/TPPCJXF_aDI/AAAAAAAAAEo/TX5AEJLQEfs/s1600/fig2.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 223px; height: 320px;" src="http://2.bp.blogspot.com/_oiz-KoL9VE4/TPPCJXF_aDI/AAAAAAAAAEo/TX5AEJLQEfs/s320/fig2.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5544989032296966194" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;p&gt;Figure 2. The Breakpoint properties dialog box in Advanced mode&lt;/p&gt;&lt;p&gt;If you want to restored the normal operation of the debugger, you would then place a breakpoint downstream from the exception you want it to ignore and place another non-breaking breakpoint, and set its Advanced breakpoint property to Handle subsequent exceptions.&lt;/p&gt;&lt;p&gt;This approach, while effective, is time consuming to put into place. It requires you to anticipate those locations in your code where you want to ignore exceptions, and place corresponding pairs of non-breaking breakpoints on either side of these code segments to ignore and then to once again handle exceptions. &lt;/p&gt;&lt;h3&gt;Ignoring Specific Exceptions&lt;/h3&gt;&lt;p&gt;If you are only concerned about specific exceptions, there is a much easier alternative, which first became available in Delphi 2005. From the Language Exceptions node of the Options dialog box, shown in Figure 3, you can instruct the debugger to globally ignore specific exception hierarchies.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_oiz-KoL9VE4/TPPCJgLj3EI/AAAAAAAAAEw/cb88maJP2W8/s1600/fig3.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 218px;" src="http://3.bp.blogspot.com/_oiz-KoL9VE4/TPPCJgLj3EI/AAAAAAAAAEw/cb88maJP2W8/s320/fig3.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5544989034736245826" /&gt;&lt;/a&gt;&lt;br /&gt;Figure 3. The Language Exceptions node of the Options dialog box &lt;/p&gt;&lt;p&gt;To do this, click the Add button to display the Add Language Exception dialog box, shown in figure 4. and add the name of the exception class that you want to ignore.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_oiz-KoL9VE4/TPPCJ1gXUAI/AAAAAAAAAE4/OWkpxvwQISs/s1600/fig4.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 112px;" src="http://4.bp.blogspot.com/_oiz-KoL9VE4/TPPCJ1gXUAI/AAAAAAAAAE4/OWkpxvwQISs/s320/fig4.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5544989040460648450" /&gt;&lt;/a&gt;&lt;br /&gt;Figure 4. The Add Language Exception dialog box &lt;/p&gt;&lt;p&gt;Once added, this exception appears in the Exception Types to Ignore list of the Language Exceptions node of the Options dialog box. For instance, if you use the Add Language Exception dialog box to add an exception named EDivByZero, the Exceptions types to Ignore list will include EDivbyZero, as shown in Figure 5.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_oiz-KoL9VE4/TPPCKBcHOVI/AAAAAAAAAFA/pZLvKpzc6DM/s1600/fig5.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 218px;" src="http://1.bp.blogspot.com/_oiz-KoL9VE4/TPPCKBcHOVI/AAAAAAAAAFA/pZLvKpzc6DM/s320/fig5.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5544989043664042322" /&gt;&lt;/a&gt;&lt;br /&gt;Figure 5. A newly added exception hierarchy will be ignored by the debugger &lt;/p&gt;&lt;p&gt;If you now run the previous code segment, the debugger will not pause your code upon encountering the division by zero exception. Likewise, it will also ignore any exceptions that descend from EDivByZero, meaning that this mechanism makes it easy to disable entire hierarchies of exceptions.&lt;/p&gt;&lt;p&gt;If, at a later time, you want to re-enable the debugger's normal behavior with respect to this exception (as well as those that descend from it), display the Language Exceptions page node of the Options dialog box, and remove the checkmark from the checkbox next to EDivByZero. Alternatively, you can remove this exception class from the Exceptions to Ignore list altogether by selecting that exception in the list and clicking the Remove button.&lt;/p&gt;&lt;p&gt;If you want to instruct the debugger to ignore every exception that it encounters, the process is even easier. Simply uncheck the Notify on language exceptions checkbox that appears at the bottom of the Language Exceptions node of the Options dialog box. When this checkbox is unchecked, the debugger ignores all language exceptions. (This checkbox can be seen in Figures 3 and 5).&lt;/p&gt;&lt;p&gt;To be honest, it would be unusual to add an exception, such as EDivByZero, to the Exceptions to Ignore list. Instead, you are more likely to add custom exceptions, those that you define for your own application, to this list. Specifically, you are more likely to add exceptions that you explicitly raise within your application by calling the raise keyword.&lt;/p&gt;&lt;p&gt;In general, it is considered a poor programming practice to raise an instance of one of Delphi's exception classes in your code. Instead, you should define your own exceptions classes, and raise one of them.&lt;/p&gt;&lt;p&gt;For example, you may define two classes of exceptions that you potentially raise at runtime from the code within your application. One of these may be raised due to simple user errors, errors that the user needs to be informed about. The other may be serious failures, ones that you cannot handle normally.&lt;/p&gt;&lt;p&gt;Here is an example of such a declaration: &lt;/p&gt;&lt;pre&gt;type&lt;br /&gt;  ECustomException class(Exception)&lt;br /&gt;  end;&lt;br /&gt;  EWarningException = class(ECustomException)&lt;br /&gt;  end;&lt;br /&gt;  ECriticalException = class(ECustomException)&lt;br /&gt;  end;&lt;/pre&gt;&lt;p&gt;Assuming that an EWarningException is raised by your code in response to a user's invalid input, this exception is one that you probably do not want the debugger to notify you of (while you are debugging the application). As a result, you probably do not want the debugger to display a dialog box like the one shown in Figure 1.&lt;/p&gt;&lt;p&gt;The simple solution for this problem is to instruct the debugger to ignore EWarningException instances, which is so say that you instruct the debugger to ignore your non-critical exceptions. That's the real power of the Exceptions to Ignore list.&lt;/p&gt;&lt;h3&gt;Running without Debugging&lt;/h3&gt;&lt;p&gt;When the debugger is active, unless you take steps to disable exceptions, raised exceptions cause the debugger to pause the execution of your code. One approach to preventing the debugger from pausing the execution of your application upon encountering an exception is to run your code without the debugger being active.&lt;/p&gt;&lt;p&gt;There are two ways to do this. One is to first compile or build your application, and then to run it outside of the IDE. In other words, after compiling or building your project, follow this by executing the resulting EXE creating during compilation.&lt;/p&gt;&lt;p&gt;Since Delphi 2005, there has been an easier way. You can select Run Run Without Debugging, or simply press Ctrl-Shift-F9. In Delphi XE it's even easier, since there is a Run Without Debugging button on the Debug toolbar.&lt;/p&gt;&lt;p&gt;When you run without debugging, Delphi will first compile your executable, if necessary. It will then run your application, but without the services of the debugger. This means that all breakpoints are ignored (whether they are breaking or non-breaking), and exceptions do not load the debugger.&lt;/p&gt;&lt;p&gt;One advantage of running without debugging is that the editor features, such as code navigation, code insight, and all other features of the editor, are active when you run without debugging. This means that you can test your running application, and at the same time make changes to and inspect the project from which the application was compiled.&lt;/p&gt;&lt;p&gt;But what if you encounter a problem while running without debugging, and you want to enable the debugger in order to examine what's going on? Here again, Delphi offers a powerful option. You can attach the debugger to the running process (this option also was introduced in Delphi 2005). Once you have attached to the running process, the debugger will be active without your having to stop and restart your application.&lt;/p&gt;&lt;p&gt;To attach the debugger to an application that is running without debugging, select Run Attach to Process. Delphi responds by displaying the Attach to Process dialog box shown in Figure 6.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_oiz-KoL9VE4/TPO5X3MvsYI/AAAAAAAAAEY/dGzbhRr4xno/s1600/fig6.jpg"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 266px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5544979385828749698" border="0" alt="" src="http://4.bp.blogspot.com/_oiz-KoL9VE4/TPO5X3MvsYI/AAAAAAAAAEY/dGzbhRr4xno/s320/fig6.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;Figure 6. The Attach to Process dialog box &lt;/p&gt;&lt;p&gt;If you want to immediately pause the application with the debugger active, ensure that the Pause after attach checkbox is checked before you click the Attach button. Personally, I usually leave this option unchecked, since I probably do not want the debugger to pause the application until it encounters a breakpoint or an exception that it is not instructed to ignore. Nonetheless, when you are ready to attach, click the Attach button.&lt;/p&gt;&lt;p&gt;Once attached, it is as if you had run the application with the debugger active. At this point, editor features such as code insight and class navigation are once again unavailable. However, once you are done using the debugger, you have the option to select Run Detach from Program, to once again return to a state where your application is running without the services of the debugger.&lt;/p&gt;&lt;p&gt;In closing, I want to mention one additional capability that the Attach to Process option provides you. If you are writing a DLL (dynamic link library) in the IDE, but this DLL is loaded by a process for which you do not have source code, you can use Attach to Process to debug your DLL.&lt;/p&gt;&lt;p&gt;Here's how you do it. When you are ready to debug your DLL, set your breakpoints and then compile the DLL program. Then, run the external program (the EXE) that will load your DLL.&lt;/p&gt;&lt;p&gt;Next, select Run Attach to Process. Use the Attach to Process dialog box to select the program that will load your DLL. Once you are attached, do whatever you need to do to cause that program to load your DLL. As soon as the attached process loads your DLL, and one of your breakpoints is encountered (or an exception is raised), Delphi's debugger will pause the execution inside your DLL source, permitting you to inspect variables, trace into, step over, or whatever else you need the debugger for. When you are done with the debugger, click the Run button or Press F9 to resume normal execution of the attached program.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;While you need the services of Delphi's debugger, you do not necessarily need it all of the time. By learning how to instruct the debugger to ignore specific exceptions, as well as disable and enable particular breakpoints, you gain more control, and more precision, over your debugging tasks.&lt;/p&gt;&lt;p&gt;&lt;cite&gt;This posting is based on an article that I originally published in the Software Development Network Magazine (issue #107). This is the official magazine published by the Software Development Network (&lt;a href="http://www.sdn.nl/"&gt;http://www.sdn.nl/&lt;/a&gt;).&lt;/cite&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-8882852868857225375?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/8882852868857225375/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/11/hey-debugger-leave-my-exception-alone.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/8882852868857225375'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/8882852868857225375'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/11/hey-debugger-leave-my-exception-alone.html' title='Hey! Debugger. Leave My Exception Alone!'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_oiz-KoL9VE4/TPPBmC2yRRI/AAAAAAAAAEg/MaPMx1hdLKM/s72-c/fig1.jpg' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-3050799782821387691</id><published>2010-10-31T09:27:00.000-07:00</published><updated>2010-10-31T09:51:08.921-07:00</updated><title type='text'>First Look at Advantage Database Server 10</title><content type='html'>&lt;p&gt;With the release of Advantage 10, Sybase continues the tradition of consistent improvements to this high-performance, low maintenance database server. In addition to a rich array of additional features and feature enhancements, this release also includes a large number of internal optimizations that will improve the performance of most Advantage client applications simply by upgrading the server to Advantage 10. These improvements add significant value to the already impressive collection of features that make Advantage Database Server a perfect match for small to medium size database applications.&lt;/p&gt;&lt;h1&gt;Overview &lt;/h1&gt;&lt;p&gt;The Advantage Database Server (ADS) is a high-performance, low maintenance database server by Sybase. ADS supports an impressive set of features often found in high-end databases. These features, in combination with its ease of installation and nearly maintenance free operation, make it a favorite database for vertical market applications.&lt;/p&gt;&lt;p&gt;With each new release of Advantage, the Sybase team has consistently created added value by introducing new and enhanced features, as well as improving the already impressive performance of the server. This tradition continues with this release of Advantage 10.&lt;/p&gt;&lt;p&gt;This post is designed to provide you with a brief overview of the Advantage Database Server, followed by a look at the new and enhanced features found in Advantage 10. If you are already familiar with Advantage, you may want to go directly to the section New Features and Enhancements in Advantage 10.&lt;/p&gt;&lt;h1&gt;Overview of Advantage Database Server&lt;/h1&gt;&lt;p&gt;The Advantage Database Server provides a unique set of features that make it an ideal database for small to medium size applications. The major features that make Advantage special are described in the following sections.&lt;/p&gt;&lt;h2&gt;High Performance&lt;/h2&gt;&lt;p&gt;To put it simply, Advantage is fast. Much of its speed comes from its architecture, which is based on ISAM (indexed sequential access method) technology. ISAM makes extensive use of indexes to provide high-speed table searches, filters, and table joins.&lt;br /&gt;&lt;p&gt;Unlike other ISAM technologies, such as dBase and Clipper, Advantage Database Server is a transaction processing, remote database server. As a result, it provides application developers with a reliable, distributed solution for managing data using client/server technology.&lt;/p&gt;&lt;h2&gt;Low Maintenance&lt;/h2&gt;&lt;p&gt;The Advantage Database Server installs in minutes, and rarely needs attention after that. Indeed, unlike high-end database servers, most Advantage installations do not have a database administrator. This makes Advantage an ideal server for vertical market applications where the server may be installed in many facilities that do not have their own IT department.&lt;/p&gt;&lt;h2&gt;Navigational and Set Based Orientation&lt;/h2&gt;&lt;p&gt;While Advantage is based on the navigational ISAM architecture, it also supports industry standard SQL (structured query language), with most of the SQL operations optimized for lighting fast execution. As a result, Advantage is one of the rare remote database servers to support both the navigational model of data access as well as set-based SQL, giving you a wealth of options for presenting and managing your data.&lt;br /&gt;&lt;h2&gt;Advanced Feature Set&lt;/h2&gt;&lt;p&gt;The Advantage Database Server sports an impressive collection of features often only found in high-end database servers. These include security provided by users and groups, table encryption, and support for encrypted client/server communication. Additional high-end features supported by Advantage include stored procedures, SQL PSMs (persistent stored modules), views, user defined functions, table- and field-level constraints, referential integrity, online backup, triggers, notifications, and replication.&lt;/p&gt;&lt;h2&gt;Scalable&lt;/h2&gt;&lt;p&gt;Advantage comes in two basic flavors: the Advantage Database Server (ADS) and the Advantage Local Server (ALS). ALS is a free, file-server based technology whose API (application programming interface) is identical to ADS. ALS permits developers to deploy their Advantage applications royalty free to clients who do not need the stability and power of a separate database server.&lt;/p&gt;&lt;p&gt;Importantly, as the needs of those applications deployed with ALS grow over time, those applications can be almost effortlessly scaled to client/server technology, in many cases simply by deploying ADS. So long as the client applications are designed correctly, those applications will begin using ADS the next time they execute.&lt;/p&gt;&lt;h1&gt;New Features and Enhancements in Advantage 10&lt;/h1&gt;&lt;p&gt;Rather than reciting a laundry list of updates, I have organized the enhancements into the following sections: Major performance improvements, enhanced notifications, additions to Advantage SQL, nested transactions, Unicode support, additional 64-bit clients, added design-time support for Delphi, and side-by-side installation. For a detailed listing of all of the updates found in Advantage 10, see the white paper at the following URL:&lt;/p&gt;&lt;pre&gt;&lt;a href="http://www.sybase.com/files/White_Papers/Advantage_WhatsNewADS10_WP.pdf"&gt;http://www.sybase.com/files/White_Papers/Advantage_WhatsNewADS10_WP.pdf&lt;/a&gt;&lt;br /&gt;&lt;/pre&gt;&lt;h2&gt;Major Performance Improvements&lt;/h2&gt;&lt;p&gt;The Advantage Database Server has always been recognized for its superior performance, being able to handle very large amounts of data with blinding speed. That makes it all the more remarkable that one of the most enticing aspects of upgrading to Advantage 10 involves performance. Specifically, the performance of database operations in client applications will improve simply by upgrading the server to Advantage 10. In some cases, these performance gains will be significant.&lt;/p&gt;&lt;p&gt;Many of the internal systems that contribute to Advantage's already impressive performance were evaluated by Advantage's R&amp;amp;D engineers. Where possible, improved algorithms were introduced, caching was implemented or enhanced, and resources were pooled. These changes resulted in more efficient indexes, improved transaction handling, and more intelligent management of resources such threads, record locks, and file writes.&lt;/p&gt;&lt;p&gt;The effects of these improvements range from nice to stunning. During Advantage 10's Beta cycle, one of the Beta testers reported the results of his performance tests on some of his larger queries involving, in some cases, millions of records. He found that some Advantage 10 queries executed 40 percent faster than the same queries in Advantage 9. In other cases, the Advantage 10 queries were exponentially faster (one query that ran in 2.7 seconds in Advantage 9 took about 1 millisecond in Advantage 10). The R&amp;amp;D team has found similar improvements during testing.&lt;/p&gt;&lt;p&gt;But SQL queries are not the only area of Advantage to benefit from these internal improvements. Operations that benefit from Advantage's support for navigational operations have also improved. In fact, the Help files for Advantage 10 list no less than 20 specific improvements or optimizations introduced in Advantage 10. And these updates affect everything from cascading referential integrity updates to record insertion, from memo file header updates to table creation, from low-level index operations to worker thread management. Simply put, the performance enhancements introduced in Advantage 10 alone make a solid business case for upgrading from an earlier version of Advantage.&lt;/p&gt;&lt;h2&gt;Enhanced Notifications&lt;/h2&gt;&lt;p&gt;Notifications are a feature originally introduced in Advantage 9, and they provide you with a mechanism by which Advantage can notify interested client applications that some change has occurred on the server. For example, a client application can subscribe to a notification in order to be informed when the contents of a specific table have changed. The client application can then use this information to update the end user's view of that data.&lt;/p&gt;&lt;p&gt;A small change to notifications in Advantage 10 has resulted in a very significant improvement in their utility: Advantage 10 notifications now support a data packet. This data packet, in the form of a memo field, permits you to include any data you like along with the notification. This data may include the record ID of the record that was affected in the table of interest, the connection id of the user who made the change, the type of change, or any other data you like.&lt;/p&gt;&lt;p&gt;This data permits you to implement advanced features in your notification-subscribing clients. For example, you can now distinguish between changes made by your client application's user and those made by other users. This information can be used to automatically update a user's view of data when someone else has made changes, ignoring those changes posted by that user.&lt;/p&gt;&lt;h2&gt;Additions to Advantage SQL&lt;/h2&gt;&lt;p&gt;There are many updates and additions to Advantage's support for the structured query language (SQL). Of these, my favorite update is the new ability to use a stored procedure in the FROM clause of a SELECT query.&lt;/p&gt;&lt;p&gt;If you have a stored procedure that returns a result set, you can treat that result set like a table in a SQL SELECT statement, permitting you to select specific fields or expressions from the result set, link the stored procedure result to other tables (or other stored procedure result sets), and define WHERE clause conditions to select just those records in which you are interested. You can even use the predefined Advantage system stored procedures in the FROM clause.&lt;/p&gt;&lt;p&gt;Another enhancement is the ability to use Boolean expressions in your SQL statements. For example, if you have a table named CUSTOMER in which a Boolean (logical) field named Active appears, the following query will select all records where the Active field contains True. &lt;pre&gt;SELECT * FROM CUSTOMER WHERE Active;&lt;/pre&gt;&lt;p&gt;In previous versions of Advantage, you would have to form your query like the following:&lt;/p&gt;&lt;pre&gt;SELECT * FROM CUSTOMER WHERE Active = True;&lt;/pre&gt;&lt;p&gt;Also, TOP queries now support a START AT clause, which permits you to select a specific number of records beginning from some position in the result set other than the top. For example, the following query will return records 11 through 15 from the CUSTOMER table, ordered by last name.&lt;/p&gt;&lt;pre&gt;SELECT TOP 5 START AT 11 FROM CUSTOMER ORDER BY LastName;&lt;/pre&gt;&lt;p&gt;A collection of bitwise SQL operators have also been introduced. These include AND, OR, and XOR, as well as &gt;&gt; (right-shift) and &lt;&lt; (left-shift).&lt;/p&gt;&lt;p&gt;There is also a new SQL scalar function: ISOWEEK, which returns the ISO 8601 week number for a given date (it is also a new expression engine function). And, some of the SQL scalar functions that were previously not expression engine function are now. These include DAY, DAYOFYEAR, DAYNAME, and MONTHNAME, to name a few. These are in addition to CHAR2HEX and HEX2CHAR, which are newly added expression engine functions. Support in the expression engine means indexes can now be created using these functions, which in turn allows the Advantage query engine to fully optimize restrictions that use these scalars.&lt;/p&gt;&lt;p&gt;Finally, there are a number of new system stored procedures and system variables. The following are just a few of the new system stored procedures available in Advantage 10: sp_SetRequestPriority, sp_GetForeignKeyColumns, and sp_IgnoreTableTransactions.&lt;/p&gt;&lt;p&gt;As far as system variables go, among the new variables are ::conn.OperationCount (number of operations performed on this connection), ::stmt.TrigEventType (the event type of the executing trigger), ::stmt.TrigType (the type of trigger executing), and ::conn.TransactionCount (the current nesting depth of nested transactions).&lt;/p&gt;&lt;h2&gt;Nested Transactions&lt;/h2&gt;&lt;p&gt;Speaking of nested transactions, Advantage 10 now supports them. In previous versions of Advantage, code executing in an active transaction could not attempt to start a transaction without raising an exception. This is no longer the case. As a result, if you write a stored procedure whose operations should be performed in a transaction, you can safely call BEGIN TRANSACTION, even if that stored procedure is called by code where a transaction is already active.&lt;/p&gt;&lt;h2&gt;New Table Features&lt;/h2&gt;&lt;p&gt;Several interesting new table-specific features have been introduced in Advantage 10. Several of these are related to transactions and table caching. Let's consider table caching first.&lt;/p&gt;&lt;p&gt;To begin with, so long as memory resources permit, temporary tables are now kept entirely in cache. As a result, operations that rely on temporary tables are usually very fast.&lt;/p&gt;&lt;p&gt;There is also a new table property called Table Caching. Most tables are created with Table Caching set to None. These tables are not cached, and any changes to these tables are written to the underlying file immediately.&lt;/p&gt;&lt;p&gt;When Table Caching is set to either Read or Write, the corresponding table is kept in cache while it is open, making its data highly available. These settings are normally used for data that is largely static, and which can be reconstructed if the table becomes corrupt. Specifically, tables held in cache are not written to disk except when the table is closed. As a result, changes to their data will be lost if Advantage unexpectedly shuts down without being able to persist those tables' contents (for instance, if there is a sudden failure of your server's power supply). However, this functionality can be very useful for static data (zip codes, part numbers, and so forth).&lt;/p&gt;&lt;p&gt;The transaction free tables feature is also a table property, called Trans Free Table. When set to True, the associated table does not participate in transactions.&lt;/p&gt;&lt;p&gt;There are two implications of a table not participating in an active transaction. First, changes made to a Trans Free Table during a transaction are not rolled back even if the transaction itself is rolled back. Second, changes to data in a Trans Free Table are not isolated during a transaction, being immediately visible to all other client applications, even though the transaction has not yet been committed.&lt;/p&gt;&lt;p&gt;Just like when a table's Table Caching property is set to Read or Write, Trans Free Table is set to True only for special tables in most applications. For example, you may use a table to log a user's actions in an application. In those cases, you may want to log that a user tried to perform some task, even though the action may fail and the user's changes may be rolled back.&lt;/p&gt;&lt;p&gt;Similarly, you may have a table used for generating unique key field values. This table may have a single record and single field that holds an integer value. A client needing a key would lock this table, read the key, increment the integer, and then release the lock.&lt;/p&gt;&lt;p&gt;With such a table, the incremented key needs to be visible to all client applications, even if individual clients increment the key from within a transaction. If such a table were not a Trans Free Table, other clients would not be able to access the incremented key until the transaction was committed, rendering the table useless for its intended purpose.&lt;/p&gt;&lt;h2&gt;Unicode support&lt;/h2&gt;&lt;p&gt;Although Unicode support is arguably a table feature, its significance warrants separate consideration. In short, Advantage 10 introduces three new field types. These types, nchar, nvarchar, and nmemo, are UTF-16 Unicode field types. The nchar type is a fixed length Unicode string field and nvarchar is a variable length Unicode string field. The data for these two field types are stored entirely in the table file.&lt;/p&gt;&lt;p&gt;The nmemo field, by comparison, is a variable length Unicode field that is stored in the memo file. Together, these three fields provide you with a number of options for storing Unicode symbols and characters in Advantage tables.&lt;/p&gt;&lt;h2&gt;More 64-bit Clients&lt;/h2&gt;&lt;p&gt;Advantage 9 introduced 64-bit versions of both the Windows and Linux Advantage servers, as well as 64-bit clients for the Advantage Client Engine (ACE) and the Advantage .NET Data Provider. A number of additional 64-bit clients have been added in Advantage 10, including 64-bit versions of the OLE DB Provider, the ODBC driver, as well as the Linux PHP driver. The Advantage Data Provider for .NET has also been enhanced to use the appropriate 64-bit or 32-bit drivers, depending on the OS on which your managed code is executing.&lt;/p&gt;&lt;p&gt;In addition, 64-bit versions of the Advantage Local Server (ALS) and Advantage backup utility have been introduced in Advantage 10.&lt;/p&gt;&lt;h2&gt;Added Design-Time Support in Delphi&lt;/h2&gt;&lt;p&gt;The SQL Utility, a comprehensive SQL editor and debugger, is now exposed as a property editor directly within the Delphi IDE (integrated development environment). To use the SQL Utility within Delphi, select the ellipsis button on the SQL property in Delphi's Object Inspector when an AdsQuery component is selected.&lt;/p&gt;&lt;p&gt;Using the SQL Utility, you can check the syntax of your SQL, execute it, and even set breakpoints and debug your SQL scripts. Once you are satisfied with your SQL, click the Save button on the SQL Utility toolbar (or press Ctrl-S) to save your SQL to the SQL property of the AdsQuery.&lt;/p&gt;&lt;p&gt;The Advantage Delphi Components also include a new component, TAdsEvent. This component, which you can use to subscribe to and handle notifications, allows you to easily configure and manage the handling of asynchronous events.&lt;/p&gt;&lt;h2&gt;Side-By-Side Installations&lt;/h2&gt;&lt;p&gt;With Advantage 10, it is now possible to run two or more instances of the Advantage server on the same physical server, even different versions of Advantage. For example, it is now possible to run Advantage 9 and Advantage 10 on the same server. This feature is particularly useful for vertical market developers whose applications need to support more than one version of the Advantage server.&lt;/p&gt;&lt;h1&gt;Conclusion&lt;/h1&gt;&lt;p&gt;With the release of Advantage 10, Sybase has once again confirmed its commitment to this unique and valuable database server. In addition to a number of useful additions and enhancements, Advantage 10 also includes a wide range of performance improvements that will improve the performance of most client applications merely by installing this updated server.&lt;/p&gt;&lt;p&gt;Most developers, however, will also want to update their client applications to benefit from the many enhancements found in Advantage 10. From support for Unicode to greatly improved notifications, from updated SQL syntax to enhanced table features, Advantage 10 has something for everybody.&lt;/p&gt;&lt;h2&gt;Want to Learn More&lt;/h2&gt;&lt;p&gt;Loy Anderson and I have released the latest edition of our Advantage book, Advantage Database Server: A Developer's Guide, 2nd Edition with extensive coverage of ADS 10. For more information and to order this book, please visit:&lt;pre&gt;&lt;a href="http://www.jensendatasystems.com/ADSBook10/"&gt;http://www.jensendatasystems.com/ADSBook10/&lt;/a&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_oiz-KoL9VE4/TM2dh52yNUI/AAAAAAAAADo/tfcOgP5TS7Y/s1600/ThumbnailADSBook10.jpg"&gt;&lt;img style="WIDTH: 184px; HEIGHT: 240px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5534252722900055362" border="0" alt="" src="http://1.bp.blogspot.com/_oiz-KoL9VE4/TM2dh52yNUI/AAAAAAAAADo/tfcOgP5TS7Y/s400/ThumbnailADSBook10.jpg" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-3050799782821387691?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/3050799782821387691/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/10/first-look-at-advantage-database-server.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3050799782821387691'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3050799782821387691'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/10/first-look-at-advantage-database-server.html' title='First Look at Advantage Database Server 10'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_oiz-KoL9VE4/TM2dh52yNUI/AAAAAAAAADo/tfcOgP5TS7Y/s72-c/ThumbnailADSBook10.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-6416332333145345531</id><published>2010-09-30T02:25:00.000-07:00</published><updated>2010-10-03T04:57:59.343-07:00</updated><title type='text'>Developing in a Virtual World</title><content type='html'>Back in December of last year I described how I set up my most recent laptop with VMWare, installing my various development environments in virtual machines instead of on my host operating system (&lt;a href="http://caryjensen.blogspot.com/2009/12/creating-more-manageable-development.html"&gt;Creating a More Manageable Development Environment&lt;/a&gt;). It's been about ten months since I originally setup that machine, and this seems like a fine time to share how the experience has been going.&lt;br /&gt;&lt;br /&gt;Overall, I can say that I am very happy with how this is working. I have not had to reinstall my host operating system during the past ten months, and I think that this is the first time that I can think of when I've gone this long. I still have the early, clean image of this machine that I created shortly after performing my base install, but the thought of restoring my machine to that state simply has not crossed my mind.&lt;br /&gt;&lt;br /&gt;In the past, after four or six months, my operating system had slowed down to the point where I felt it necessary to restore from an early image of my system. But not now. I am pleasantly surprised that my host operating system is humming along fine, thank you. And I even have several applications installed on the host that many developers blame for some of their ills, such as iTunes.&lt;br /&gt;&lt;br /&gt;As for the virtual machines, they are doing fine as well. I currently have quite a collection of virtual machines that I use, some more than others. As I described in the previous post, I create a separate virtual machine for each development environment. For example, I have one for RAD Studio 2010, one for RAD Studio 2007, and another for Visual Studio 2008. I recently installed virtual machines for Visual Studio 2010 and Delphi XE. There are also some separate VMs for 32-bit operating systems, though most of them are 64-bit. There are others, but you get the picture.&lt;br /&gt;&lt;br /&gt;My reasoning for this approach is multi-fold. First, by keeping each virtual machine pure (a single development environment) I prevent any possible cross contamination that might compromise one or more of the environments. It's these unintended side effects that I blame for the increasing instability of my previous OS installations. Second, I can test my applications under different conditions, such as 32-bit and 64-bit versions of Windows 7, or under Windows XP.&lt;br /&gt;&lt;br /&gt;The third benefit is that I can keep backups of these individual VMs. In fact, I keep several backups of each. There is the clean backup that contains my original installation. I can always fall back to this if something goes terribly wrong, such as a Windows update that hoses one of the VMs. I also keep a current backup of each VM, and carry that around with me on a portable USB drive when I travel.&lt;br /&gt;&lt;br /&gt;If something happens to my VM, or even to my laptop, I have a solution. For example, if my laptop simply quits on me, I could load my virtual machine onto any other machine on which VMWare is loaded, and I am ready to go.&lt;br /&gt;&lt;br /&gt;Finally, and this is one that I have not actually had to confirm yet, but my thinking is that I can use these VMs on my next laptop. In other words, I will never have to install Delphi XE on Windows 7 ever again. On my next laptop, I will simply copy over the VM, and I should be good to go. Sure, I may have to install it on the next version of Windows (whatever that will be called), but that should only happen once, and then I will be set with Delphi XE and that version of the operating system (this assumes that I will be supporting clients who are using Delphi XE on that new operating system. If not, then I'll only have to install the more current versions of Delphi on that OS).&lt;br /&gt;&lt;H1&gt;Lessons Learned&lt;/H1&gt;&lt;br /&gt;I wanted to do more than simply report that this approach seems to be working well. I also wanted to share some of the tricks and insights that I've gained during this experiment. Note, however, that I am using VMWare (VMWare 7.1.2 to be precise). While other virtual machines may support features similar to those I describe here, I am referring specifically to how they are working for me with VMWare.&lt;br /&gt;&lt;H1&gt;Memory&lt;/H1&gt;&lt;br /&gt;Let's begin with memory and the virtual machines. When I initially set up each virtual machine, I configured each to use 3 gigabytes of RAM. My thinking was that I wanted the virtual machine performance to be similar to running my development environment in the host operating system.&lt;br /&gt;&lt;br /&gt;My laptop has 8 GB of RAM (a minimum, if you ask me, for taking this particular route), which means that using VMs that use 3GBs limits you to running one at a time (sure, you could run two at a time, but I'd be careful about that. If your VMs require so much memory that you leave little left for the host operating system, bad thing can and will happen).&lt;br /&gt;&lt;br /&gt;Over the past several days I have cranked several of the VMs down to 2 GB of RAM. Honestly, I really haven’t noticed a difference. The big change, however, is that I can now run two VMs simultaneously, and still have plenty of breathing room for my host OS.&lt;br /&gt;&lt;H1&gt;License to Thrill&lt;/H1&gt;&lt;br /&gt;One thing that I did not go into in any depth in my first post was the licensing issues that arise when you use multiple VMs. In short, you need to have licenses for the software that you use in your virtual machines. This includes those for the operating systems themselves, as well as for the other software that you install on those virtual machines.&lt;br /&gt;&lt;br /&gt;Let's consider the operating system first. Having the one copy of Windows that shipped with your computer is not enough. That's probably only one license, and it is being used as your host OS. Depending on the operating system that you are using for your virtual machines, you may need one additional license for each VM.&lt;br /&gt;&lt;br /&gt;For example, if you want to install Windows in a virtual machine, you must have a separate license for each virtual machine. No, you cannot rationalize that you only run one at a time. Read the Windows end user license agreement (EULA). It specifically covers virtual machines, and in almost every case, you will need a separate license for each one.&lt;br /&gt;&lt;br /&gt;Fortunately, for us developers, there is a rock solid solution from Microsoft, and it is called Microsoft Developers Network, or MSDN. Your MSDN subscription includes the rights to use certain Microsoft products under certain conditions. And even the lowest level of MSDN subscription, MSDN Operating Systems, includes licenses for the current version of Windows. These licenses are specifically designed for testing and development. And, guess what, if you are using your VMs for building and testing software, you are using those licenses for testing and development.&lt;br /&gt;&lt;br /&gt;(Disclaimer: If you have specific questions about the various MSDN subscriptions, do not take my word for it. Go to the Microsoft site and read what they say about the various MSDN subscriptions. It is possible that my subscription is different from your subscription.)&lt;br /&gt;&lt;br /&gt;At least with the MSDN subscription that I have, I get up to an initial 10 licenses of Windows 7 Ultimate for use in testing and development environments. However, if I use those licenses up, I can request another 10, and another 10 after that, though I cannot imagine needing that many VMs.&lt;br /&gt;&lt;br /&gt;My rather low-level subscription only gives me access to the most current operating system. However, some of the more deluxe subscriptions of MSDN give you access to current and past operating systems, which, depending on the type of testing you need to do, might be perfect. Likewise, if your testing and development involves other products, like SharePoint, Azure, Microsoft Office, SQL Server, and other Microsoft products, there are MSDN subscriptions that includes testing and development licenses for those products as well.&lt;br /&gt;&lt;br /&gt;As far as AntiVirus is concerned, I initially purchased a 3-license pack of Norton Antivirus. That worked fine, until I wanted to install my fourth VM. At that point I got frustrated and began looking for an alternative, as I didn't want to shell out another $100 for 3 more licenses.&lt;br /&gt;&lt;br /&gt;Fortunately, there is a free alternative from Microsoft called Microsoft Security Essentials. It works on Windows 7, Windows Vista, and XP, and I am now a big fan. It installs quickly, has a pretty small footprint, and doesn't pester me as much as Norton did. In fact, I am so happy with it that I removed Norton AntiVirus from my host OS (even though I still had more than half a year left on my license), and installed Windows Security Essentials there.&lt;br /&gt;&lt;H1&gt;Quickie Installations&lt;/H1&gt;&lt;br /&gt;One feature of VMWare that I really didn't start using until recently was the ability to install software into a VM from an ISO image (an archive format used for optical disks). You can install either an entire operating system, or any other software, using an ISO image, which saves a lot of time.&lt;br /&gt;&lt;br /&gt;For example, I recently created a new VM using Windows 7 Ultimate x86. I downloaded the ISO image from the MSDN Web site, and saved it to the hard disk on my host OS. I then created a new virtual machine in VMWare. When prompted, I indicated that I was going to install from an ISO image. Before even starting, VMWare prompted me for my product key, which I entered. It then proceeded to install the operating system, and was done in no time.&lt;br /&gt;&lt;br /&gt;Next, I wanted to install Delphi XE under this VM. I had an ISO image of that installer, and instructed VMWare to treat that image on my host hard drive as the CD drive in the VM. Again, the installation was fast and easy.&lt;br /&gt;&lt;H1&gt;Snapshots and Linked Clones&lt;/H1&gt;&lt;br /&gt;Another one of the benefits of VMs is that you can easily perform "what if" tests without much risk of doing harm. Two of the tools provided in VMWare for this purpose are snapshots and linked clones.&lt;br /&gt;&lt;br /&gt;Imagine that someone has recommended that you try a new profiling tool, but you do not have much experience with it. Wouldn't it be nice to install it without having to worry about what side effects it might cause, or what trash it may leave behind if you end up uninstalling it?&lt;br /&gt;&lt;br /&gt;The most foolproof technique, in my book, is to create a linked clone. A linked clone is a new VM based on an existing one. The big deal here is that a linked clone is very small, since it is relying on a snapshot of an existing VM. Once you have created the linked clone, which takes less than a minute, you can install the suspect piece of software in it and test away.&lt;br /&gt;&lt;br /&gt;Once you are satisfied that the software you are testing is going to work for you, you can re-install it into your regular VM and delete the clone. If you decide that the software is not for you, simply delete the linked clone. In any case, what you installed into the linked clone cannot affect the VM you cloned it from.&lt;br /&gt;&lt;br /&gt;Snapshots are designed to give you a similar capability. A snapshot is a placeholder into the state of a virtual machine. In theory, you should be able to create a snapshot, and then install the software you want to test. If things go badly, you simply revert the virtual machine to the snapshot, and no more pain.&lt;br /&gt;&lt;br /&gt;This all sounds well and fine, and I'm sure it works, but I don't have a whole lot of experience with snapshots, so I am still a little gun shy.&lt;br /&gt;&lt;H1&gt;Shared Folders&lt;/H1&gt;&lt;br /&gt;The final feature that I've started using extensively is shared folders. A shared folder is a location (drive, folder) on your host OS that can be seen by one or more virtual machines.&lt;br /&gt;&lt;br /&gt;This is the place where I put files and programs that I am likely to use from any given virtual machine, such as printer drivers, 7-zip, and other utilities. I then use the Shared Folders option from the Hardware tab of the VMWare Virtual Machine Settings dialog to configure folder sharing.&lt;br /&gt;&lt;br /&gt;For each VM, I select the Always enabled radio button. I then use the Browse button to select the shared folder. I also enable the Map as a network drive in the guest operating system checkbox. When done, the shared folder appears as a network share in each of the VMs.&lt;br /&gt;&lt;br /&gt;Even without this feature, it is easy to copy and paste between VMs. However, having files that you typically use always available from within each VM is even better.&lt;br /&gt;&lt;H1&gt;Still, There Are Drawbacks&lt;/H1&gt;&lt;br /&gt;Don't get me wrong, I have no regrets about going down this road. So far, so good. But there are some issues that I still grapple with.&lt;br /&gt;&lt;br /&gt;For one, there are those infernal updates. Holy smokes! Every time Microsoft comes out with a Windows update I've got all these different VMs nagging me to update them. And, once or twice, the update has messed things up a bit.&lt;br /&gt;&lt;br /&gt;I've addressed this issue by altering my behavior. I don't update Windows every time it asks. In fact, I normally ignore Windows updates until I have some time to really deal with them. Next, before I do the updates, I copy the virtual machines that I am using in my daily work to one of my backup drives. Then, I systematically open each one of these VMs and perform the update.&lt;br /&gt;&lt;br /&gt;For those VMs that I use only on occasion, I wait until I need them. For example, if I am asked to teach a class on Delphi 7, I will backup and then update that VM in preparation for the class.&lt;br /&gt;&lt;br /&gt;As a result, some VMs don't get updated that often. However, if an update causes a problem, I've always got that pre-update copy that I can rely upon.&lt;br /&gt;&lt;H1&gt;Conclusion&lt;/H1&gt;&lt;br /&gt;I'm running into a lot of developers who are approaching their development environment this way, so I'm not so cocky as to think that I made this thing up. However, I am happy to share my experiences in this realm, and hope that if you are still developing in your base OS that you might have gained some insight into whether this approach might better serve you or not&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-6416332333145345531?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/6416332333145345531/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/09/developing-in-virtual-world-revisted.html#comment-form' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6416332333145345531'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6416332333145345531'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/09/developing-in-virtual-world-revisted.html' title='Developing in a Virtual World'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-7797896377587614838</id><published>2010-08-30T16:32:00.001-07:00</published><updated>2010-09-30T12:07:44.178-07:00</updated><title type='text'>Breakpoints with Side Effects</title><content type='html'>&lt;p&gt;A breakpoint with a side effect is a breakpoint that causes a task to be performed upon encountering the enabled breakpoint within your application. Of course, such a breakpoint only performs its side effect when your code is running with Delphi's debugger attached to the process. Nonetheless, this type of breakpoint can be extremely useful, especially when you are in the testing phase of your application development.&lt;/p&gt;&lt;p&gt;This article shows you how to create breakpoints that cause side effects. It begins with a general overview of breakpoints, and continues with a discussion of non-breaking breakpoints. Finally, how to create breakpoints to produce side effects is explained in detail.&lt;/p&gt;&lt;br /&gt;&lt;h1&gt;Breakpoint Overview&lt;/h1&gt;&lt;p&gt;When most Delphi developers think of breakpoints, they typically think of the debugger feature that interupts the execution of your code upon executing a specific line of your code. In reality, this definition is far from complete.&lt;/p&gt;&lt;p&gt;First of all, the only type of breakpoint that is associated with a line of your source code is a source breakpoint. There are three other types of breakpoints: address breakpoints, data breakpoints, and module breakpoints.&lt;/p&gt;&lt;h3&gt;Address Breakpoints&lt;/h3&gt;&lt;p&gt;Address breakpoints permit you to define a breakpoint that will trigger when an instruction at a particular memory address is executed. You can only add an address breakpoint when the debugger is loaded. Then you can select Run -&gt; Add Breakpoint -&gt; Address Breakpoint. Enter the address in the Address field of the Add Address Breakpoint dialog box.&lt;/p&gt;&lt;p&gt;You can also add an address breakpiont from the disassembly pane of the CPU window. With your project stopped in the debugger, display the CPU window by selecting View -&gt; Debug Windows -&gt; CPU. To add an address breakpoint, either click in the left gutter of the disassembly pane in the CPU window on the line associated with the instruction address, press Ctrl-B with your cursor on the instruction line, or right-click the instruction line and select Toggle breakpoint. An address breakpoint is shown in the CPU window in Figure 1.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_oiz-KoL9VE4/THw_2bi_yVI/AAAAAAAAAC4/px8nLH-vwvc/s1600/figure1.jpg"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 245px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5511350248334674258" border="0" alt="" src="http://3.bp.blogspot.com/_oiz-KoL9VE4/THw_2bi_yVI/AAAAAAAAAC4/px8nLH-vwvc/s320/figure1.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;em&gt;Figure 1 An address breakpoint in the CPU window &lt;/em&gt;&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;Data Breakpoints&lt;/h3&gt;&lt;p&gt;Data breakpoints are those that trigger when a particular memory address is written to. The memory address can be represented either as a hexadecimal number or as the variable used to refer to that memory address. Unlike other breakpoint types, data breakpoints last only for the current Delphi session.&lt;/p&gt;&lt;p&gt;To add a data breakpoint, invoke the debugger (either through an exception or by hitting a breaking breakpoint). Then select Run -&gt; Add Breakpoint -&gt; Data Breakpoint. Delphi responds by displaying the Add Data Breakpoint dialog box, as shown in Figure 2.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_oiz-KoL9VE4/THxA4vgLMwI/AAAAAAAAADA/pJlNSDdvuls/s1600/figure2.jpg"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 219px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5511351387562914562" border="0" alt="" src="http://4.bp.blogspot.com/_oiz-KoL9VE4/THxA4vgLMwI/AAAAAAAAADA/pJlNSDdvuls/s320/figure2.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;em&gt;Figure 2 Adding a data breakpoint &lt;/em&gt;&lt;/p&gt;&lt;p&gt;At Address, enter the memory address of the data. Use Length to define the length of the data beginning at that address. If you are running Delphi 2007 or later, you can also enter the name of a variable in the Address field.&lt;/p&gt;&lt;p&gt;In earlier versions of Delphi, to add a data breakpoint based on a variable name, add the variable into the Watches window. (You display the Watches window by selecting View -&gt; Debug Windows -&gt; Watches from Delphi's main menu.) Then, right-click on the watch entry and select the option Break on change. For data breakpoints added using this technique, you can modify the breakpoint properties by right-clicking on the breakpoint in the Breakpoint List dialog box and selecting Properties. (You can display the Breakpoint List dialog box by pressing Ctrl-Alt-B.)&lt;/p&gt;&lt;h3&gt;Module Breakpoints&lt;/h3&gt;&lt;p&gt;Module load breakpoints are those that trigger when a specified module is being loaded into your application. For example, if you want to ensure that a particular DLL is not being used by your application, you can set an enabled breaking module breakpoint for it, ensuring that the debugger will load if for some reason that DLL gets loaded by your application.&lt;/p&gt;&lt;p&gt;To add a module breakpoint, select Run -&gt; Add Breakpoint -&gt; Module Load Breakpoint. Delphi responds by displaying the dialog box shown in Figure 3.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_oiz-KoL9VE4/THxBZ22uMEI/AAAAAAAAADI/qZrb7mYjgs0/s1600/figure3.jpg"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 114px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5511351956472213570" border="0" alt="" src="http://4.bp.blogspot.com/_oiz-KoL9VE4/THxBZ22uMEI/AAAAAAAAADI/qZrb7mYjgs0/s320/figure3.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;em&gt;Figure 3 The Add Module Breakpiont dialog box&lt;br /&gt;&lt;/em&gt;&lt;br /&gt;Enter the name of the module in the Module Name field. Click OK when you are done. This is the only way to set a module load breakpoint on a module that is not yet loaded (or even one that will never be loaded).&lt;/p&gt;&lt;p&gt;You can also create a module load breakpoint from the Modules window, but this is only useful if the project is in the debugger and the module has already been loaded. With the debugger loaded, open the Modules window by selecting View -&gt; Debug Windows -&gt; Modules. Then in the Module pane, right-click the module you want to break on, and select Break On Load. Modules that will trigger a module load breakpoint appear in the Module pane with a red dot to their left. To remove a module load breakpoint, right-click the specific module and select Break On Load once again.&lt;/p&gt;&lt;h3&gt;Non-Breaking Breakpoints&lt;/h3&gt;&lt;p&gt;Another aspect of the breakpoint definition provided at the top of this article that is not completely correct is the part where the breakpoint interupts the execution of your code. In fact, a breakpoint only interupts the execution of your code if it is a breaking breakpoint. Non-breaking breakpoints, like breaking breakpoints, trigger when they are active and are encountered by the debugger. Unlike breaking breakpoints, however, the do not stop code execution.&lt;/p&gt;&lt;p&gt;To define a non-breaking breakpoint, display the Breakpoint Propeties dialog box for the breakpoint. Click the button labeled Advanced &gt;&gt; to display the Advanced breakpoint properties, as shown in Figure 4, and then disable the Break checkbox. (Module breakpoints are the only breakpoints that do not support a non-breaking mode.)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_oiz-KoL9VE4/THxCDfKj9RI/AAAAAAAAADQ/KIv0hmInxvI/s1600/figure4.jpg"&gt;&lt;img style="WIDTH: 222px; HEIGHT: 320px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5511352671667483922" border="0" alt="" src="http://3.bp.blogspot.com/_oiz-KoL9VE4/THxCDfKj9RI/AAAAAAAAADQ/KIv0hmInxvI/s320/figure4.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;em&gt;Figure 4 The Advanced view of the Breakpoint Properties dialog box &lt;/em&gt;&lt;/p&gt;&lt;p&gt;At first you might wonder "What is the value of a breakpoint if it does not stop your code execution?". Let me tell you, the value can be very big indeed. Non-breaking breakpoints permit you to perform a variety of actions without interrupting the execution of your application.&lt;/p&gt;&lt;p&gt;Consider that a non-breaking breakpoint can be used to enable or disable one or more other breakpoints. For example, you can set a non-breaking breakpoint to execute on the second pass of a section of initialization code that you assume will only execute once. If that code is executed a second time, for whatever reason, you can enable a group of additional breakpoints that will load the debugger, permitting you to examine your application's variables in order to determine why the code is executing a second time.&lt;/p&gt;&lt;p&gt;For me, however, the most valuable capability of a non-breaking breakpoint is to perform a side effect. &lt;h1&gt;Breakpoints with Side Effects&lt;/h1&gt;&lt;p&gt;There are five types of side effects that a breakpoint can perform, whether or not it is a breaking breakpoint. It can enable or disable the loading of the debugger in response to an exception, it can write a message to the event log, it can evaluate an expression, it can enable or disable breakpoints associated with a particular group, and in the more recent versions of Delphi, it can write some or all of the call stack to the event log.&lt;/p&gt;&lt;p&gt;Of these, the side effect I consider the most valuable is the ability to evaluate an expression, so I will consider that side effect last. But first, let's consider the other side effects.&lt;/p&gt;&lt;h3&gt;Controlling How The Debugger Handles Exceptions&lt;/h3&gt;&lt;p&gt;If you check the Ignore subsequent exceptions checkbox, the integrated debugger will stop loading when an exception is raised, at least until another breakpoint that enables subsequent exceptions is encountered. To define a breakpoint that enables exceptions, place a second breakpoint (breaking or non-breaking), and check the Enabled subsequent exceptions checkbox.&lt;/p&gt;&lt;p&gt;Disabling and enabling exceptions using non-breaking breakpoints permits you to conveniently run a section of code from the IDE that raises exceptions, without interruption from the debugger. This technique is particularly useful for sections of code that raise exceptions for the purpose of displaying errors or warnings to the user, but which do not constitute bugs or other similar errors in your code.&lt;/p&gt;&lt;p&gt;For example, when client-side validation code (code that tests the validity of user input) raises an exception, it is really not an error, code-wise. Instead, it is to inform the user that validation has failed, suggest to the user how to correct the problem, and to abort any remaining validations or operations that would proceed had the user's input been valid. While testing your validation code it serves you no purpose to be interrupt by the integrated debugger.&lt;/p&gt;&lt;h3&gt;Writing Messages to the Event Log&lt;/h3&gt;&lt;p&gt;Use the Log message field to enter a string that will be written to the event log when the non-breaking breakpoint triggers. This option is a nice alternative to using the Windows API call OutputDebugString, which requires adding additional code to your project. After running your application in the IDE, or while the integrated debugger is loaded, you can view messages written to the event log using the Log message field by selecting View -&gt; Debug Windows -&gt; Event Log. Messages written to the event log using breakpoints are identified by the label "Breakpoint Message."&lt;/p&gt;&lt;h3&gt;Enabling/Disabling Breakpoint Groups&lt;/h3&gt;&lt;p&gt;A breakpoint can be used to enable and disable groups of breakpoints. A breakpoint group consists of one or more breakpoints to which you have assigned the same group name, using the Group field on the Breakpoint Properties dialog box (see Figure 4).&lt;/p&gt;&lt;p&gt;When a breakpoint group is disabled, none of the breakpoints in the group will trigger when encountered by code running in the IDE. Enabling a group restores the enabled property of any disabled breakpoints in the group.&lt;/p&gt;&lt;h3&gt;Evaluating Expressions&lt;/h3&gt;&lt;p&gt;The Eval expression field serves two purposes. First, it can be used to evaluate any expression. This expression can include any object, method, variable, constant, resource, function, or type that is within scope of the breakpoint, and can also include operators appropriate for those symbols. After having entered a value in Eval expression, you have the option to enabled the Log result checkbox. When Log result is checked, the value of the expression is written to the event log. As with the Log Message field, Eval expression results that are logged permit you to avoid writing expression to the event log using OutputDebugString.&lt;/p&gt;&lt;p&gt;So, finally, here is the main point of this article. My favorite use of Eval expression is to execute a function. Specifically, a function that has side-effects. For example, imagine that upon startup your application tests for the existence of a CDS file that is used by a ClientDataSet. If the file is absent, you call CreateDataSet to create the data structure that the ClientDataSet will use, after which that structure is written to a CDS file.&lt;/p&gt;&lt;p&gt;During testing, you may always want to begin your application without an existing CDS file, so that you can test whether or not the CDS file is being created properly. One way of doing this is the write a function that deletes the CDS file if it exists. You can then call this function with a breakpoint by adding the function name to the Eval expression field. &lt;/p&gt;&lt;p&gt;So long as this Eval expression is associated with a breakpoint that is encountered prior to the first time your application tests for the presence of the CDS file, the Eval expression function will always ensure that no file exists when the test occurs. Since breakpoints only trigger when the application is run from the IDE, a deployed application, or one executed without the debugger being enabled, will not destroy an existing CDS file.&lt;/p&gt;&lt;p&gt;In order to execute a function using the Eval expression field, that function must be included in your compiled code by the linker. But this is a bit more complicated than it sounds. Specifically, if the only call to your function that you create to produce your side effect is made by the breakpoint, Delphi's smart linker will not recognize that the function is used, and will therefore omit it from the compiled application. While this feature ensures that your applications only contain code that they use, thereby keeping your exe size to a minimum, it will have the effect of removing your side effect.&lt;/p&gt;&lt;p&gt;Fortunately, there is a work-around for this. Specifically, you can trick the smart linking into thinking that your side effect function &lt;i&gt;might&lt;/i&gt; be called, in which case it will be obligated to include the function in your compiled code. You do this by creating a method that looks like an event hander, and include a call to your side effect function within that method.&lt;/p&gt;&lt;p&gt;Here is how you can do this. Create a published method. While in reality this method can have any signature, you might as well make it a TNotifyEvent, which is a procedure that takes a TObject parameter named Sender. Then, in the implementation of this method, include your call to your side effect function. The following code shows what the pseudo event handler, and the side effect function, might look like:&lt;/p&gt;&lt;pre&gt;&lt;br /&gt;procedure TForm1.FakeEvent(Sender: TObject);&lt;br /&gt;begin&lt;br /&gt;  CleanupCDS;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;function TForm1.CleanupCDS: String;&lt;br /&gt;begin&lt;br /&gt;  if FileExists(ExtractFilePath(Application.ExeName) + 'data.cds') then&lt;br /&gt;    DeleteFile(ExtractFilePath(Application.ExeName) + 'data.cds');&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;All you have left to do is to add the call to CleanupCDS to the Eval Expression property of the breakpoint, as shown in Figure 5. Assuming that the Break property of the breakpoint has been disabled, anytime that this breakpoint is encountered in your code while executing with the debugger active, the CDS file named DATA.CDS, located in the same directory as your application, will be silently deleted.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_oiz-KoL9VE4/THxDTmdUkLI/AAAAAAAAADY/JsDvJ0685qY/s1600/figure5.jpg"&gt;&lt;img style="WIDTH: 222px; HEIGHT: 320px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5511354048014749874" border="0" alt="" src="http://1.bp.blogspot.com/_oiz-KoL9VE4/THxDTmdUkLI/AAAAAAAAADY/JsDvJ0685qY/s320/figure5.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;em&gt;Figure 5 A non-breaking breakpoint with side effects &lt;/em&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-7797896377587614838?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/7797896377587614838/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/08/breakpoints-with-side-effects.html#comment-form' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7797896377587614838'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7797896377587614838'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/08/breakpoints-with-side-effects.html' title='Breakpoints with Side Effects'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_oiz-KoL9VE4/THw_2bi_yVI/AAAAAAAAAC4/px8nLH-vwvc/s72-c/figure1.jpg' height='72' width='72'/><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-1689680094324753438</id><published>2010-06-08T08:24:00.001-07:00</published><updated>2010-06-08T08:48:48.710-07:00</updated><title type='text'>Creating Editor Key Bindings in Delphi</title><content type='html'>&lt;p&gt;There is a powerful but little known feature of the code editor in Delphi and that permits you to add your own custom keystrokes. This feature is referred to as custom key bindings and it is part of the Delphi open tools API (OTA). The open tools API provides you with a collection of classes and interfaces that you can use to write your own extensions to the IDE.&lt;/p&gt;&lt;p&gt;This article provides you with an overview of this interesting feature, and demonstrates a simple key binding class that you can use as a starting point for creating your own custom key bindings. This key binding makes a duplicate, or copy, of the current line in the code editor. If a block of text is selected, this key binding will duplicate that block. This is a feature that is found in other code editors, and now, through key bindings, you can have it in Delphi.&lt;/p&gt;&lt;h3&gt;Overview of Key Bindings&lt;/h3&gt;&lt;p&gt;A key binding is a unit that installed into a design-time package. Writing an editor key binding involves creating class type declarations and implementing interfaces. In fact, creating and installing an editor key binding involves a number of explicit steps. These are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Descend a new class from TNotifierObject. This class must be declared to implement the IOTAKeyboardBinding interface. This class is your key binding. &lt;/li&gt;&lt;li&gt;In addition to the four methods of that IOTAKeyboardBinding interface that you must implement in your key binding class, add one additional method for each new feature that you want to add to the editor. This method is passed an object that implements the IOTAKeyContext interface. You use this object within your implementation to read information about, and control, the behavior of the editor.&lt;/li&gt;&lt;li&gt;Declare and implement a standalone Register procedure. Within this procedure invoke the AddKeyboardBinding method of the BorlandIDEServices object, passing an instance of the class you declared in step 1 as the only argument.&lt;/li&gt;&lt;li&gt;Add the unit that includes this Register procedure to a designtime only package.&lt;/li&gt;&lt;li&gt;Add the designide.dcp package to this design time package's Requires clause. This package is located in lib folder under where you installed Delphi.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Each of these steps is discussed in the following sections. As mentioned earlier, these steps will define a new key binding that adds a single key combination. Once implemented and installed, this key combination will permit you to duplicate the current line in the editor by pressing Ctrl-Shift-D.&lt;/p&gt;&lt;p&gt;(The source code for this editor key binding project can be downloaded from Embarcadero Code Central at &lt;/p&gt;&lt;p&gt;&lt;a href="http://cc.embarcadero.com/item/27635.)"&gt;http://cc.embarcadero.com/item/27635.)&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Declaring the Key Binding Class&lt;/h3&gt;&lt;p&gt;The class that defines your key binding must descend from TNotifierObject and implement the IOTAKeyboardBinding interface. If you are familiar with interfaces, you will recall that when a class is declared to implement an interface, it must declare and implement all of the methods of that interface. Consider for a moment the following declaration of the IOTAKeyboardBinding interface. This declaration appears in the ToolsAPI unit:&lt;/p&gt;&lt;pre&gt;IOTAKeyboardBinding = interface(IOTANotifier)&lt;br /&gt; ['{F8CAF8D7-D263-11D2-ABD8-00C04FB16FB3}']&lt;br /&gt; function GetBindingType: TBindingType;&lt;br /&gt; function GetDisplayName: String;&lt;br /&gt; function GetName: String;&lt;br /&gt; procedure BindKeyboard(const BindingServices:&lt;br /&gt;   IOTAKeyBindingServices);&lt;br /&gt; property BindingType: TBindingType read GetBindingType;&lt;br /&gt; property DisplayName: String read GetDisplayName;&lt;br /&gt; property Name: String read GetName;&lt;br /&gt;end;&lt;/pre&gt;&lt;p&gt;As you can see, this interface declares four methods and three properties. Your key binding class must implement the methods. Note, however, that it does not need to implement the properties. (This is a regular source of confusion when it comes to interfaces, but the fact is that the properties belong to the interface, and are not required by the implementing object. Sure, you can implement the properties in the object. But you do not have to, and the properties were not implemented in this example.)&lt;/p&gt;&lt;p&gt;In addition to the methods of the IOTAKeyboardbinding interface, your key binding class must include one additional method for each custom feature that you want to add to the editor. In order to be compatible with the AddKeyBinding method used to bind these additional methods, these additional methods must be TKeyBindingProc type methods. The following is the declaration of the TKeyBindingProc method pointer type, as it appears in the ToolsAPI unit:&lt;/p&gt;&lt;pre&gt;TKeyBindingProc = procedure (const Context: IOTAKeyContext;&lt;br /&gt;  KeyCode: TShortcut; var BindingResult: TKeyBindingResult)&lt;br /&gt;    of object;&lt;/pre&gt;&lt;p&gt;All this type really means is that each of the additional methods that you write, each one of which adds a different keystroke to the editor, must take three parameters: an IATOKeyContext, a TShortcut, and a TKeyBindingResult.&lt;/p&gt;&lt;p&gt;The following is the key binding class declared in the DupLine.pas unit. This class, named TDupLineBinding, includes only one new key binding. The following is the class type declaration of this class:&lt;/p&gt;&lt;pre&gt;type&lt;br /&gt;  TDupLineBinding = class(TNotifierObject, IOTAKeyboardBinding)&lt;br /&gt;  private&lt;br /&gt;  public&lt;br /&gt;    procedure DupLine(const Context: IOTAKeyContext;&lt;br /&gt;      KeyCode: TShortCut;&lt;br /&gt;      var BindingResult: TKeyBindingResult);&lt;br /&gt;   { IOTAKeyboardBinding }&lt;br /&gt;   function GetBindingType: TBindingType;&lt;br /&gt;   function GetDisplayName: String;&lt;br /&gt;    function GetName: String;&lt;br /&gt;    procedure BindKeyboard(const BindingServices:&lt;br /&gt;      IOTAKeyBindingServices);&lt;br /&gt;  end;&lt;/pre&gt;&lt;h3&gt;Implementing the IOTAKeyboardBindings Interface&lt;/h3&gt;&lt;p&gt;Once you have declared your key binding class, you must implement the four methods of the IOTAKeyboardBinding interface, as well as each of your additional TKeyBindingProc methods. Fortunately, implementing the IOTAKeyboardBinding interface is easy.&lt;/p&gt;&lt;p&gt;You implement GetBindingType by returning the type of key binding that you are creating. There are only two types of key bindings: partial and complete. A complete key binding defines all of the keystrokes of the editor, and you identify your key binding as a complete key binding by returning the value btComplete. A complete key binding is actually a full key mapping. &lt;/p&gt;&lt;p&gt;A partial key binding is what you use to add one or more keystrokes to the key mapping that you are using. The TDupLineBinding class is a partial key binding. The following is the implementation of GetBindingType in the TDupLineBinding class:&lt;/p&gt;&lt;pre&gt;function TDupLineBinding.GetBindingType: TBindingType;&lt;br /&gt;begin&lt;br /&gt;  Result := btPartial;&lt;br /&gt;end;&lt;/pre&gt;&lt;p&gt;You implement GetDisplayName and GetName to provide the editor with text descriptions of your key binding. GetDisplayName should return an informative name that Delphi will display in the Enhancement modules list of the Key Mappings page of the Editor Options node of the Options dialog box. &lt;/p&gt;&lt;p&gt;GetName, on the other hand, is a unique string that the editor uses internally to identify your key binding. Because this string must be unique for all key bindings that a user might install, by convention this name should be your company name or initials followed by the name of your key binding.&lt;/p&gt;&lt;p&gt;The following listing contains the implementation of the GetDisplayName and GetName methods for the TDupLineBinding class:&lt;/p&gt;&lt;pre&gt;function TDupLineBinding.GetDisplayName: String;&lt;br /&gt;begin&lt;br /&gt;  Result := 'Duplicate Line Binding';&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;function TDupLineBinding.GetName: String;&lt;br /&gt;begin&lt;br /&gt;  Result := 'jdsi.dupline';&lt;br /&gt;end;&lt;/pre&gt;&lt;p&gt;You implement the BindKeyboard method to provide for the actual binding of your TKeyBindingProc methods. BindKeyboard is passed an object that implements the IOTAKeyBindingServices interface, and you use this reference to invoke the AddKeyBinding method. &lt;/p&gt;&lt;p&gt;AddKeyBinding requires at least three parameters. The first parameter is an array of TShortcut references. A TShortcut is a word type that represents either a single keystroke, or a keystroke plus a combination of one or more of the following: CTRL, ALT, or SHIFT, as well as left, right, middle, and double mouse button clicks. Because this parameter can include an array, it is possible to bind your TKeyBindingProc to two or more keystrokes or key combinations.&lt;/p&gt;&lt;p&gt;The Menus unit in Delphi contains a function named Shortcut that you can use to easily create your TShortcut references. This function has the following syntax:&lt;/p&gt;&lt;pre&gt;function Shortcut(Key: Word; Shift: TShiftState): TShortcut;&lt;/pre&gt;&lt;p&gt;In this function, the first character is the ANSI value of the keyboard character, and the second is a set of zero, one, or more TShiftState. The following is the declaration of TShiftState, as it appears in the Classes unit:&lt;/p&gt;&lt;pre&gt;TShiftState = set of (ssShift, ssAlt, ssCtrl,&lt;br /&gt;  ssLeft, ssRight, ssMiddle, ssDouble);&lt;/pre&gt;&lt;p&gt;The second parameter of BindKeyboard is a reference to your TKeyBindingProc method that implements the behavior you want to associate with the keystroke or key combination, and the third parameter is a pointer to a context. In the BindKeyboard implementation in the TDupLineBinding class, the method DupLine is passed as the second parameter and nil is passed in this third parameter. The following is the implementation of the BindKeyboard method that appears in the DupLine.pas unit:&lt;/p&gt;&lt;pre&gt;procedure TDupLineBinding.BindKeyboard(&lt;br /&gt;  const BindingServices: IOTAKeyBindingServices);&lt;br /&gt;begin&lt;br /&gt;  BindingServices.AddKeyBinding(&lt;br /&gt;    [ShortCut(Ord('D'), [ssCtrl])], DupLine, nil);&lt;br /&gt;end;&lt;/pre&gt;&lt;p&gt;As you can see, this BindKeyboard implementation will associate the code implemented in the DupLine method with the CTRL-Shift-D keystroke combination.&lt;/p&gt;&lt;h3&gt;Implementing TKeyBindingProc Methods&lt;/h3&gt;&lt;p&gt;Implementing the methods of IOTAKeyboardBindings is pretty straightforward. Implementing your TKeyBindingProc method, however, is not. &lt;/p&gt;&lt;p&gt;As you can see from the TKeyBindingProc method pointer type declaration shown earlier in this section, a TKeyBindingProc is passed three parameters. The first, and most important, is an object that implements the IOTAKeyContext interface. This object is your direct link to the editor, and you use its properties to control cursor position, block operations, and views. The second parameter is the TShortCut that was used to invoke your method. This is useful if you passed more than one TShortCut in the first parameter of the AddKeyBinding invocation, especially if you want the behavior to be different for different keystrokes or key combinations.&lt;/p&gt;&lt;p&gt;The final parameter of your TKeyBindingProc method is a TKeyBindingResult value passed by reference. You use this parameter to signal to the editor what it should do after your method exits. The following is the TKeyBindingResult declaration as it appears in the ToolsAPI unit:&lt;/p&gt;&lt;pre&gt;TKeyBindingResult = (krUnhandled, krHandled, krNextProc);&lt;/pre&gt;&lt;p&gt;You set the BindingResult formal parameter of your TKeyBindingProc method to krHandled if your method has successfully executed its behavior. Setting BindingResult to krHandled also has the effect of preventing any other key bindings from processing the key, as well as preventing menu items assigned to the key combination from processing it.&lt;/p&gt;&lt;p&gt;You set BindingResult to krUnhandled if you do not process the keystroke or key combination. If you set BindingResult to krUnhandled, the editor will permit any other key bindings assigned to the keystroke or key combination to process it, as well as any menu items associated with the key combination.&lt;/p&gt;&lt;p&gt;Set BindingResult to krNextProc if you have handled the key, but want to permit any other key bindings associated with the keystroke or key combination to trigger as well. Similar to setting BindingResult to krHandled, setting BindingResult to krNextProc will have the effect of preventing menu shortcuts from receiving the keystroke or key combination.&lt;/p&gt;&lt;p&gt;As mentioned earlier, the real trick in implementing your TKeyBindingProc method is associated with the object that implements the IOTAKeyContext interface that you receive in the Context formal parameter. Unfortunately, Embarcadero has published almost no documentation about how to do this. One of the few bits of information are the somewhat intermittent comments located in the ToolsAPI unit.&lt;/p&gt;&lt;p&gt;A full discussion of the properties of IOTAKeyContent is well beyond the scope of this article. That having been said, the following is the implementation of the TKeyBindingProc from the TDupLineBinding class:&lt;/p&gt;&lt;pre&gt;procedure TDupLineBinding.Dupline(const Context: IOTAKeyContext;&lt;br /&gt;  KeyCode: TShortcut; var BindingResult: TKeyBindingResult);&lt;br /&gt;var&lt;br /&gt;  EditPosition: IOTAEditPosition;&lt;br /&gt;  EditBlock: IOTAEditBlock;&lt;br /&gt;  CurrentRow: Integer;&lt;br /&gt;  CurrentRowEnd: Integer;&lt;br /&gt;  BlockSize: Integer;&lt;br /&gt;  IsAutoIndent: Boolean;&lt;br /&gt;  CodeLine: String;&lt;br /&gt;begin&lt;br /&gt;  EditPosition := Context.EditBuffer.EditPosition;&lt;br /&gt;  EditBlock := Context.EditBuffer.EditBlock;&lt;br /&gt;  //Save the current edit block and edit position&lt;br /&gt;  EditBlock.Save;&lt;br /&gt;  EditPosition.Save;&lt;br /&gt;  try&lt;br /&gt;    // Store original cursor row&lt;br /&gt;    CurrentRow := EditPosition.Row;&lt;br /&gt;    // Length of the selected block (0 means no block)&lt;br /&gt;    BlockSize := EditBlock.Size;&lt;br /&gt;    // Store AutoIndent property&lt;br /&gt;    IsAutoIndent := Context.EditBuffer.BufferOptions.AutoIndent;&lt;br /&gt;    // Turn off AutoIndent, if necessary&lt;br /&gt;    if IsAutoIndent then&lt;br /&gt;      Context.EditBuffer.BufferOptions.AutoIndent := False;&lt;br /&gt;    // If no block is selected, or the selected block is a single line,&lt;br /&gt;   // then duplicate just the current line&lt;br /&gt;    if (BlockSize = 0) or (EditBlock.StartingRow = EditPosition.Row) or&lt;br /&gt;      ((BlockSize &amp;lt;&amp;gt; 0) and ((EditBlock.StartingRow + 1) =(EditPosition.Row)) and&lt;br /&gt;      (EditBlock.EndingColumn = 1)) then&lt;br /&gt;    begin&lt;br /&gt;      //Only a single line to duplicate&lt;br /&gt;      //Move to end of current line&lt;br /&gt;      EditPosition.MoveEOL;&lt;br /&gt;      //Get the column position&lt;br /&gt;      CurrentRowEnd := EditPosition.Column;&lt;br /&gt;      //Move to beginning of current line&lt;br /&gt;      EditPosition.MoveBOL;&lt;br /&gt;      //Get the text of the current line, less the EOL marker&lt;br /&gt;      CodeLine := EditPosition.Read(CurrentRowEnd - 1);&lt;br /&gt;      //Add a line&lt;br /&gt;      EditPosition.InsertText(#13);&lt;br /&gt;      //Move to column 1&lt;br /&gt;      EditPosition.Move(CurrentRow, 1);&lt;br /&gt;      //Insert the copied line&lt;br /&gt;      EditPosition.InsertText(CodeLine);&lt;br /&gt;    end&lt;br /&gt;    else&lt;br /&gt;    begin&lt;br /&gt;      // More than one line selected. Get block text&lt;br /&gt;      CodeLine := Editblock.Text;&lt;br /&gt;      // Move to the end of the block&lt;br /&gt;      EditPosition.Move(EditBlock.EndingRow, EditBlock.EndingColumn);&lt;br /&gt;      //Insert block text&lt;br /&gt;      EditPosition.InsertText(CodeLine);&lt;br /&gt;    end;&lt;br /&gt;    // Restore AutoIndent, if necessary&lt;br /&gt;    if IsAutoIndent then&lt;br /&gt;      Context.EditBuffer.BufferOptions.AutoIndent := True;&lt;br /&gt;    BindingResult := krHandled;&lt;br /&gt;  finally&lt;br /&gt;    //Move cursor to original position&lt;br /&gt;    EditPosition.Restore;&lt;br /&gt;    //Restore the original block (if one existed)&lt;br /&gt;    EditBlock.Restore;&lt;br /&gt;  end;&lt;br /&gt;end;&lt;/pre&gt;&lt;p&gt;As you can see from this code, the IOTAKeyContext implementing object passed in the first parameter is your handle to access a variety of objects that you can use to to implement your keybinding behavior. And without a doubt, it is the EditBuffer property that is most useful. &lt;/p&gt;&lt;p&gt;This property refers to an object that implements the IOTAEditBuffer interface. You use this object to obtain a reference to additional interface implementing objects, including IOTABufferOptions, IOTAEditBlock, IOTAEditPosition, and IOTAEditView implementing objects. These objects are available using the BufferOptions, EditBlock, EditPosition, and TopView properties of the EditBuffer property of the Context formal parameter. &lt;/p&gt;&lt;p&gt;You use the IOTABufferOptions object to read information about the status of the editor, including the various settings that can be configured on the General page of the Editor Properties dialog box. &lt;/p&gt;&lt;p&gt;The IOTAEditBlock object permits you to control blocks of code in the editor. Operations that you can perform on blocks includes copying, saving to file, growing or shrinking the block, deleting, and so on.&lt;/p&gt;&lt;p&gt;You use the TOTAEditPosition object to manage the insertion point, or cursor. Operations that you can perform with this object include determining the position of the insertion point, moving it, inserting single characters, pasting a copied block, and so forth.&lt;/p&gt;&lt;p&gt;Finally, you use the TOTAEditView object to get information about, and to a certain extent, control, the various editor windows. For example, you can use this object to determine how many units are open, scroll individual windows, make a given window active, and get, set, and goto bookmarks.&lt;/p&gt;&lt;p&gt;Turning our attention back to the DupLine method, this code begins by getting references to the IOTAEditPosition and IOTAEditBlock. While this step was not an essential step, it simplifies the code in this method, reducing the need for repeated references to Context.EditBuffer.EditPosition and Context.EditBuffer.EditBlock. Next, the current state of both the edit position and edit buffer are saved.&lt;/p&gt;&lt;p&gt;The code now saves the current row of the cursor, the size of the selected block (it will be 0 if no block is selected), and the AutoIndent setting of the code editor.&lt;/p&gt;&lt;p&gt;In the next step, the AutoIndent setting is turned off, if necesary. The code now determines whether a single line of code or a block of code needs to be duplicated. If a single block is being duplicated, the length of the current line is measured, the text is copied, and then the copied text is inserted into a new line. If a block is being copied, the selected text is copied, the cursor is positioned at the end of the selected block, and the copied text is inserted.&lt;/p&gt;&lt;p&gt;Finally, the AutoIndent setting is restored (if necessary), the BindResult formal parameter is set to krHandled, and both the edit position and edit block is restored. Restoring the edit position moves the cursor to its original position, and restoring the edit block re-selects the selected text (if a block was selected).&lt;/p&gt;&lt;h3&gt;Declaring and Implementing the Register Procedure&lt;/h3&gt;&lt;p&gt;In order for your key binding to be installed successfully into the editor, you must register it from an installed designtime package using a Register procedure. The Register procedure, whose name is case sensitive, must be forward declared in the interface section of the unit that will be installed into the designtime package. Furthermore, you must add an invocation of the IOTAKeyBindingServices.AddKeyboardBinding method to the implementation of this procedure, passing an instance of your key binding class as the sole parameter. You invoke this method by dynamically binding the BorlandIDEServices reference to the IOTAKeyboardServices interface, and pass as the actual parameter an invocation of your key binding object’s constructor. &lt;/p&gt;&lt;p&gt;The following is how the Register procedure implementation appears in the DupLine.pas unit:&lt;/p&gt;&lt;pre&gt;procedure Register;&lt;br /&gt;begin&lt;br /&gt;  (BorlandIDEServices as IOTAKeyboardServices).&lt;br /&gt;    AddKeyboardBinding(TDupLineBinding.Create);&lt;br /&gt;end;&lt;/pre&gt;&lt;h3&gt;Installing the KeyBinding&lt;/h3&gt;The source code download includes a project for a design-time package, as well as the Dupline.pas unit. Use the following steps to install this keybinding in Delphi.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Open the KeyBind project in Delphi.&lt;/li&gt;&lt;li&gt;Using the project manager, right-click the Keybind project and select Install. The new keybinding should compile and install. (If it does not compile, ensure that the designide package is in the project requires clause, and that the project is a designtime only package.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The keybinding is now active. You should now be able to press Ctrl-Shift-D in a unit to create a duplicate of the current line or selected text.&lt;/p&gt;&lt;p&gt;I hope that this has inspired you to try to create your own key bindings. Note, however, if you create a key binding that uses a keystroke that is already in use by Delphi's editor, and you set BindResult to handled, you will have effectively overwritten the existing keystroke. In fact, Ctrl-Shift-D is current in use by Delphi to display the Declare Field refactoring dialog box. However, you can still access that feature by selected Refactor Declare Field from Delphi's main menu.&lt;/p&gt;&lt;p&gt;Or, you might consider changing the BindKeyboard implementation in this package to map DupLine to something else, such as Ctrl-D. Ctrl-D is mapped to the Source Formatting feature in Delphi 2010. This feature, which reformats your code, is something that you might prefer to have to intentionally select by right-clicking in the code editor and selecting Format Source.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-1689680094324753438?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/1689680094324753438/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/06/creating-editor-key-bindings-in-delphi.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/1689680094324753438'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/1689680094324753438'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/06/creating-editor-key-bindings-in-delphi.html' title='Creating Editor Key Bindings in Delphi'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-7185655801431484050</id><published>2010-06-01T00:37:00.001-07:00</published><updated>2010-06-05T14:04:15.681-07:00</updated><title type='text'>Would You Like Chips * With That Q&amp;A?</title><content type='html'>(* British for French Fries)&lt;br /&gt;&lt;br /&gt;I don't go to McDonald's often. But last Wednesday, I did go to McDonald's, though it was for the technology, not for the food.&lt;br /&gt;&lt;br /&gt;With the exception of last Wednesday, I'm not exactly sure when I last visited a McDonald's. I think it might have been almost four years ago, in Prague, and that was to get a cup of coffee (though I have a vague memory of my wife, Loy, having some kind of ice cream dish). So, I stand by my original assertion, that I do not go to McDonald's that often.&lt;br /&gt;&lt;br /&gt;Many of my European friends find this hard to believe. Certainly, as an American, and one who is on the road a lot (most would say too much), certainly I must succumb to the siren song of the fast and easy food available at these locations. Actually, it's largely because I travel so much that I avoid fast food restaurants in general, if not in principle.&lt;br /&gt;&lt;br /&gt;While I am not a skinny person, it would be disingenuous to characterized me as being overweight. And I work at that. In fact, when I am on the road, I rarely eat dinner at restaurants of any kind. Instead, I find something at a local supermarket and fix it up in my hotel room. Since I normally have a room with a microwave oven and refrigerator, at least in US hotels, there is a lot I can do. Making soup, building sandwiches, re-heating prepared meals, or even preparing raw vegetables. It's amazing, really, what you can do.&lt;br /&gt;&lt;br /&gt;Even when I am not on the road, I rarely eat out. The fact is, I love to cook. To me, eating at a restaurant when I could be cooking at home would be my loss. I would much rather spent my money on ingredients, and maybe a nice bottle of wine, than to give it to someone else to prepare my food.&lt;br /&gt;&lt;br /&gt;In fact, I am shocked each time I do eat out. My goodness, it's expensive (not that this is an issue). But when I think of what I could have done with that 30 bucks (or 50 bucks) I feel a loss. That same money could have bought some beautiful New Zealand green mussels, or Dungeness crab, or Maine lobster (I love seafood), or a great steak, or a little of each!&lt;br /&gt;&lt;br /&gt;But my visit to McDonald's had nothing to do with food. It was for the the WIFI.&lt;br /&gt;&lt;br /&gt;Last Wednesday I was in London, England, as part of the Delphi Developer Days 2010 tour that I was delivering with my friend and colleague, Marco Cantù. We had just finished the first day of our presentations, and I was preparing to go online for a live broadcast of my question and answer (Q&amp;amp;A) session for a web-based presentation I was doing for Embarcadero, called DataRage 2.&lt;br /&gt;&lt;br /&gt;There was a problem, however. Well, two problems, really. The first problem was that the wireless Internet connection at our hotel had gone down. It had been up for most of the day, but now, just as I was supposed to go online, it was down.&lt;br /&gt;&lt;br /&gt;I knew that this might be a problem. At this hotel the Internet goes down at least once a day. In most cases, we simply talk to the front desk and they reboot the router. There, problem solved.&lt;br /&gt;&lt;br /&gt;But this is where the second problem comes in. And his name is Trevor. You see, Trevor works a shift at the front desk, and apparently he is uncomfortable with technology. Basically, if the Internet goes down when Trevor is on duty, you might as well wait until his replacement comes in, and they will have it back up in minutes.&lt;br /&gt;&lt;br /&gt;Asking Trevor to reboot the router is like asking your plumber for health tips. It's a pointless exercise.&lt;br /&gt;&lt;br /&gt;When I reported to Trevor that the hotel had lost its Internet connection, a hint of panic crosses his face. Next he stutters that there is nothing that he can do. When I comment that the other managers simply reboot the router, he starts flipping the power switches on and off on two power strips connected to who knows what, over and over, until he reports that, "There, it's gone. I'm sorry, it won't turn on again."&lt;br /&gt;&lt;br /&gt;It's gone? Trevor, what have you done? I offer to come behind the counter and take a look. After all, I am a computer guy. You know, one of those people who work with these things all the time.&lt;br /&gt;&lt;br /&gt;But Trevor would have none of this. His stuttering is getting worse. He's not supposed to touch it, he informs me. No one is supposed to touch it. It's not the hotel's equipment. No, I cannot come behind the counter. He's becoming more agitated. "We'll just have to wait for someone to come in and fix it," he informs me.&lt;br /&gt;&lt;br /&gt;While this was going on, my presentation was already being broadcast. I submitted a 30- minute recording some weeks before, and this is played prior to my live question and answer period. However, it is now 5:22 pm (London time), and there are about 10 minutes left before I am supposed to speak.&lt;br /&gt;&lt;br /&gt;I grab my backpack, which contains my computer, and start to head out the door. I ask Marco, who has a mobile phone, to send a message to DataRage coordinator Christine Ellis at Embarcadero and let her know that I am going to try to find an Internet café from which I can talk.&lt;br /&gt;&lt;br /&gt;As I head towards the high street, where I had previously seen several Internet cafés, it occurs to me that I left my USB headphones, the ones with over-the-ear earphones and a noise canceling microphone, back in the hotel. But it's too late to go back. I need to get to an Internet connection as soon as possible.&lt;br /&gt;&lt;br /&gt;A couple of minutes later I turn the corner onto the high street, and immediately find an Internet café. Actually, it was more of an all-in-one shop, international phone calls, prepaid phone cards, and several Internet-connected computers lined up along one wall. Success!&lt;br /&gt;&lt;br /&gt;"Do you have wireless?" I ask. "No" was the simple reply. Drat! I need wireless.&lt;br /&gt;&lt;br /&gt;My laptop is already set up with Microsoft Live Meeting, which takes several minutes to install on an existing machine. And, it's not clear that these machines even have microphones, or that I would be permitted to install Live Meeting. I needed another option.&lt;br /&gt;&lt;br /&gt;"Is there an Internet café nearby that provides wireless access?" I ask. "Well," the clerk informs me, "if all you need is wireless access, go to the McDonald's across the street. It's free there."&lt;br /&gt;&lt;br /&gt;I quickly thank him and leave. The McDonald's across the street was not actually across the street, but I could see it from here. And, I needed to go down to a crosswalk, as this was a very busy street.&lt;br /&gt;&lt;br /&gt;As I entered the McDonald's, I did feel a tinge of guilt. I am not a regular customer, and I don't intend to buy anything this time, either. So, I should be as inconspicuous as possible. But as I sat down at a single table in a far corner, away from most of the rest of the patrons, I realize that this is one noisy place.&lt;br /&gt;&lt;br /&gt;To begin with, the place is heaving (this is a particularly British phrase). It's now just after 5:30pm, and my Q&amp;amp;A should be starting. But so is dinner service, and the place is filled with families with young children, each one speaking at the top of their voices. On top of this was the music, a pulsing electronic pop that blared from speakers in the ceiling. The music may have been louder than the children. But the cacophony of it all was too much. There is no way that I could talk from here.&lt;br /&gt;&lt;br /&gt;I began to put my computer back into my bag when I realize that it was going to be McDonald's or nowhere. The nearest Internet café that I could recall was at least two blocks away. I was going to have to make do. If nothing else, maybe I could type my answers to any questions asked.&lt;br /&gt;&lt;br /&gt;So, I restart my computer, and connect to the McDonald's Internet (this took several minutes, as I had to register with their provider first). However, I was soon connected and Live Meeting was loading. During this setup time I once again realized that I didn't have my USB headphones. How am I going to hear the questions?&lt;br /&gt;&lt;br /&gt;I scrounge around in my bag and find an extra pair of cheap earphones that I got on my flight to London. Actually, these were ear buds, and poor ones at that. But, they'd have to do.&lt;br /&gt;&lt;br /&gt;Just about the time I have the ear buds plugged in, and Live Meeting is finally coming on line, I hear the rich voice of Embarcadero Developer Relations Evangelist David Intersimone (affectionately known as "David I") answering a question about my presentation. Suddenly, he stops and says "It sounds like Cary has come online from an Internet café. Cary, are you there?"&lt;br /&gt;&lt;br /&gt;Oh, I'm here all right. Leaning over my laptop, speaking directly into the built-in microphone of my laptop's lid, with my fingers in my ears, trying to push the ear buds in further so that I could hear David while trying to block as much of the external racket as possible. "Yes, David, I'm here. Coming to you from a McDonald's."&lt;br /&gt;&lt;br /&gt;"I can hear that. Sounds like you have a big audience there." But the good news was that I could hear the questions, and remarkably, they could hear my answers. And that's the way it was for the next 20 minutes. Me leaning over my laptop with the forefinger of each hand stuck in each ear.&lt;br /&gt;&lt;br /&gt;I must have been quite a sight. But certainly not enough to discourage the family of five, an exhausted mother and her four children, all yelling at each other at the top of their lungs, from sitting down next to me half way through my session. Gee, I would have thought that a lunatic talking to his computer with his fingers in his ears would be something that a nurturing mother would want to avoid. But what do I know? This is London, after all, and they have their fare share of lunatics, and most of them are harmless.&lt;br /&gt;&lt;br /&gt;Meanwhile, back at the conference hotel, Marco and Loy are back online and listening in (and laughing pretty hard, I'm later told). Apparently someone has rescued Trevor.&lt;br /&gt;&lt;br /&gt;Considering the circumstances, it all went quite well. And at the conclusion of the Q&amp;amp;A, David I noted that we had accomplished a first. Other presenters had handled their Q&amp;amp;As live from the Embarcadero studios, some from their own offices, others from home, but never from a McDonald's. And, despite the constant chatter of children in the background (Marco and Loy said it sounded like I was presenting from a daycare center), we managed to answer all of the questions asked.&lt;br /&gt;&lt;br /&gt;So, I'll have to admit that my first visit to McDonald's in years was an overall satisfying experience. Maybe I'll be back another day, when there is less pressing business. After all, I hear they pour a world-class cup of coffee these days.&lt;br /&gt;&lt;br /&gt;Copyright (c) 2010 Cary Jensen. All Rights Reserved&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-7185655801431484050?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/7185655801431484050/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/06/would-you-like-chips-with-that-q.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7185655801431484050'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7185655801431484050'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/06/would-you-like-chips-with-that-q.html' title='Would You Like Chips * With That Q&amp;A?'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-7138584327191222302</id><published>2010-05-06T09:40:00.000-07:00</published><updated>2010-05-06T14:04:55.912-07:00</updated><title type='text'>Delphi Non-Core Feature Survey: You Can Help</title><content type='html'>Delphi developers, I want to hear from you. Are you using Delphi's frameworks for unit testing, audits, metrics, and design patterns? If no, why? If yes, to what extent do you use these features? &lt;a href="http://www.surveymonkey.com/s/CC9QVBP" target="_blank"&gt;Click here to take the survey&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Here is a little background. Since the release of Delphi 2005, there have been a number of interesting support features introduced in Delphi. For example, Delphi 2005 added support for easily creating unit tests. And audits, metrics, and design pattern support was added in Delphi 2006.&lt;br /&gt;&lt;br /&gt;Initially Delphi's support for unit testing was available in all versions (Professional, Enterprise, and Architect). By comparison, the support for audits, metrics, and design patterns required Together, and this product was shipped only with the high-end versions of Delphi. And, on top of that, the Together product stayed with Borland when CodeGear was spun off. So what did that mean for these features as Delphi evolved?&lt;br /&gt;&lt;br /&gt;I have to admit that I have not used the audits, metrics, and design pattern features of Delphi, though I have noticed the associated menu items in Delphi's menus. So, when I got a request from a client to include discussion of audits and metrics in an upcoming Delphi class that I am going to deliver, it was time to do some research.&lt;br /&gt;&lt;br /&gt;What I found was interesting and puzzling. There is not a whole lot of information out there about these features. And, I did discover that not only are these features available in the absence of Together, but are now even included (with limited support) in the Professional sku of Delphi 2010.&lt;br /&gt;&lt;br /&gt;I have now committed to writing training material on these topics, and this will also lead to my adapting this material for this blog as well for some magazine articles that I am writing. And this is where your help comes.&lt;br /&gt;&lt;br /&gt;I want to hear from you. If you are a Delphi developer, I want to know which of these features you use, and to what extent. If you don't use these features as they ship in the product, do you use third-party tools that provide similar support?&lt;br /&gt;&lt;br /&gt;I have created a short, 10 question survey that should take only a couple of minutes to complete. Please help me by filling out this survey. In addition, please ask your Delphi colleagues to help out as well. &lt;a href="http://www.surveymonkey.com/s/CC9QVBP" target="_blank"&gt;Click here to take the survey&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Copyright (c) 2010 Cary Jensen. All Rights Reserved.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-7138584327191222302?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/7138584327191222302/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/05/delphi-non-core-feature-survey-you-can.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7138584327191222302'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/7138584327191222302'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/05/delphi-non-core-feature-survey-you-can.html' title='Delphi Non-Core Feature Survey: You Can Help'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-4284484730892978824</id><published>2010-05-05T08:25:00.000-07:00</published><updated>2010-05-05T08:49:13.266-07:00</updated><title type='text'>In-Memory DataSets: ClientDataSets and .NET DataTables Part 6: Applying Updates to a Database</title><content type='html'>&lt;p&gt;In the preceding &lt;a href="http://caryjensen.blogspot.com/2010/04/in-memory-datasets-clientdatasets-and.html"&gt;article&lt;/a&gt; in this series I discussed various techniques that you can use to manage the change cache. In this installment I will conclude that discussion by looking at how you can apply the changes held in the change cache to the underlying database from which the data was originally loaded.&lt;/p&gt;Call the ApplyUpdates method of a ClientDataSet to save any changes made to the in-memory data to the underlying database. Specifically, if you edit data obtained through a dataset provider, and then close or free a client dataset without calling ApplyUpdates, any changes stored in the change log are lost. &lt;p&gt;&lt;/p&gt;&lt;p&gt;When ApplyUpdates is called, the contents are sent back to the dataset provider for resolution in the context of a transaction (so long as you pass a non-negative integer as the sole parameter of the ApplyUpdates method. When you pass –1, no transaction is initiated). The dataset provider, in turn, generates the necessary calls to apply the updates to the underlying dataset, applying the changes to the underlying dataset one record at a time.&lt;/p&gt;&lt;p&gt;By default this process is handled by a SQLResolver instance, which is created by the dataset provider. The SQL resolver determines the database that needs to be updated, and then creates the necessary SQL statement to apply the changes based on the contents of the change log. Specifically, one SQL statement will be generated for each change that needs to be applied.&lt;/p&gt;&lt;p&gt;Alternatively, the dataset provider can be configured to use the dataset from which it originally read the data to apply the changes. This approach is only possible when the dataset from which the records were read permits data changes.&lt;/p&gt;&lt;p&gt;For example, if the dataset provider gets its data from a TTable, the dataset provider can edit the TTable directly, inserting, deleting, or posting the changes using the TDataSet interface. Again, these changes are applied one at a time. It should be noted that when the dataset provider resolves the changes through the dataset, the dataset's event handlers, such as BeforeDelete and BeforePost can be used to perform data validation.&lt;/p&gt;&lt;p&gt;By comparison, if the dataset provider gets its data from a dataset that does not permit data editing, the dataset provider cannot resolve the data to the dataset. For example, if the dataset provider gets its data from a SQLDataSet, a dataset that retrieves its data using a unidirectional cursor and which does not permit editing, the dataset provider cannot resolve the changes directly to the dataset.&lt;/p&gt;&lt;p&gt;In these cases, there are two options. Either the default SQLResolver described earlier can be used, or you can write a BeforeUpdateRecord event handler. From within this event handler your code is given a reference to the changes that must be applied, and your code can take any action necessarily to explicitly apply these changes. This approach is the most flexible, although the most difficult to implement.&lt;/p&gt;&lt;p&gt;What kind of SQL (or direct edit) is generated is controlled by the UpdateMode of the DataSetProvider. If set to upWhereAll, a record is updated only if that exact record currently exists in the database. If set to upWhereChanged, the record is updated if a record with the same key fields and same values in the modified fields are found (this is a merge). When set to upWhereKeyOnly, an update is made if a record with the same key is found (last to post wins).&lt;/p&gt;&lt;p&gt;Updating .NET DataSets is similar to updating ClientDataSets. In that case, however, the DbDataAdapter class is the one typically used to apply the updates. DbDataAdapters, such as DataStoreDataAdapter, have four DbCommand properties. The SelectCommand property contains the SQL statement that returns the result set that is inserted into the DataTable when you call the Fill method. The other DbCommand properties, DeleteCommand, InsertCommand, and UpdateCommand, are designed to hold parameterized queries that will get their parameter values from the change log at runtime when you call the DbDataAdapter.Update method.&lt;/p&gt;&lt;p&gt;Some developers write the SQL statements that define the DeleteCommand, InsertCommand, and UpdateCommand objects manually. Doing so give them control over the queries, permitting the queries to be optimized for the underlying database. Other developers use a CommandBuilder to generate these queries. CommandBuilders are easy to use, but do not always generate optimized queries.&lt;/p&gt;&lt;p&gt;To use a CommandBuilder, you call its constructor, passing to it the DbDataAdapter whose SelectCommand command has already been defined. Based on this query, the CommandBuilder generates the DeleteCommand, InsertCommand, and UpdateCommand queries.&lt;/p&gt;&lt;p&gt;The following code shows the configuration of a CommandBuilder:&lt;pre&gt;Connection1 := DataStoreConnection.Create(&lt;br /&gt;  'host=LocalHost;user=sysdba; ' +&lt;br /&gt;  'password=masterkey;database="C:\Users\Public\Documents\' +&lt;br /&gt;  'Delphi Prism\Demos\database\databases\BlackfishSQL\employee"';&lt;br /&gt;Connection1.Open();&lt;br /&gt;//Sql statements are executed by IDbCommand objects&lt;br /&gt;Command1 := DataStoreCommand.Create('SELECT * FROM customers',&lt;br /&gt;                                    Connection1);&lt;br /&gt;//DbDataAdapters are used to populate DataTables and resolve data&lt;br /&gt;DataAdapter1 := SqlDataAdapter.Create(Command1);&lt;br /&gt;//CommandBuilders create DbCommand objects for a DataAdapter's&lt;br /&gt;//DeleteCommand, InsertCommand, and UpdateCommand properties based&lt;br /&gt;//on the DbDataAdapter.SelectCommand IDbCommand property&lt;br /&gt;CommandBuilder1 := DataStoreCommandBuilder.Create(DataAdapter1);&lt;br /&gt;DataSet1 := DataSet.Create;&lt;br /&gt;DataAdapter1.Fill(DataSet1);&lt;/pre&gt;&lt;p&gt;The following code shows how the changes made to the DataTable are applied.&lt;/p&gt;&lt;pre&gt;var&lt;br /&gt;  DataTable1: DataTable;&lt;br /&gt;begin&lt;br /&gt;  DataTable1 := DataSet1.Tables[0].GetChanges;&lt;br /&gt;  if DataTable1 &lt;&gt; nil then&lt;br /&gt;    DataAdapter1.Update(DataSet1.Tables[0]);&lt;/pre&gt;&lt;h1&gt;Applying Updates and Persisted Data&lt;/h1&gt;&lt;p&gt;Probably one of the most important characteristics of an in-memory dataset's ability to apply its updates to the underlying database is its concurrent ability to persist its data and state to a file, stream, or database. Together, these features permit an in-memory dataset to apply its updates at some future time, regardless of whether it has been persisted or not (and independent of its duration of persistence).&lt;/p&gt;&lt;p&gt;In order for a dataset to be able to apply its updates to a database subsequent to its persistence, the dataset's change log must be intact. Without this information, you lack the data required to determine which changes have been made to the dataset since it was originally populated.&lt;/p&gt;&lt;p&gt;If you are relying on a DataSetProvider (for ClientDataSets) or an DbDataAdapter implementation (for .NET) to apply the updates to the database, the object must be in a state compatible with applying the dataset's updates. For example, a DataSetProvider that you use to apply a previously persisted ClientDataSet's updates to a database must point to a TDataSet whose structure is consistent with the one that was used to originally load the ClientDataSet (unless you are using the DataSetAdapter's BeforeUpdateRecord event handler to programmatically apply the update, in which case it's all up to your code).&lt;/p&gt;&lt;p&gt;In the case of a .NET DataTable, the DbDataAdapter that is used to apply its updates must hold DbCommand instances in its DeleteCommand, InsertCommand, and UpdateCommand properties that contain parameterized queries sufficient to the task of applying those updates. This can be achieved by defining these queries manually, or by having an adequate DbDataAdapter.SelectCommand instance that can be used by a CommandBuilder to construct the necessary queries (of course, you must then use a CommandBuilder to create those delete, insert, and update queries objects). Otherwise, you once again must take matters into your own hands and generate all calls to update the underlying database by programmatically examining the change log and generating the necessary queries.&lt;/p&gt;&lt;h1&gt;What If Updates Cannot Be Applied?&lt;/h1&gt;&lt;p&gt;There is another issue that is worth mentioning. Specifically, even when you can construct the necessary queries to apply updates contained in a persisted in-memory dataset, those updates may not be possible. Specifically, it is conceivable that between the time that the data was originally loaded into the in-memory dataset and the point in time at which you want to write it back to the underlying database, the corresponding records in the database have been changed.&lt;/p&gt;&lt;p&gt;While many developers worry about this possibility, it is normally less of a concern than it might at first appear. For example, if you delete a record from an in-memory dataset, and then attempt to apply that deletion (at a later time), but find that the record was already deleted by someone else, who cares? It's gone. Mission accomplished (by someone, at least).&lt;/p&gt;&lt;p&gt;Similarly, if you attempt to apply a record insertion, only to find a record with that key already inserted, then the insertion is not necessary. But what, you might ask, if another user inserted a record with the same key as you (which causes your insertion to fail), but the other user's inserted record is different than the one you intended?&lt;/p&gt;&lt;p&gt;This is really an architectural issue, isn't it? Many of today's developers avoid this issue altogether by assigning arbitrary (and more or less meaningless) primary keys to each and every record. For example, many developers use GUIDs (globally unique identifiers ¾ 128-bit values that are guaranteed to be unique), to each record. In these cases, duplicate records are impossible, no matter who inserts the record.&lt;/p&gt;&lt;p&gt;Updates are a bit more problematic, but nonetheless generally workable. In most cases it is once again an architectural issue. There are, in fact, three options. In the first case, an attempt to update a record that you are holding in memory fails if someone else has changed any part of that record (in the underlying database) since the time you originally loaded it into memory. In this situation, your update query should include a test for all original field values in the WHERE clause of the update query.&lt;/p&gt;&lt;p&gt;In reality, failure to update a record that has been subsequently updated by another user, when those updates must be entirely exclusive, is rarely a tragedy.&lt;/p&gt;&lt;p&gt;The second case is to permit two or more users to update a record, so long as neither user changes the key field values, or a common field. This is a fine compromise in many situations. For example, if, since the time that you read your record into memory and the time that you are attempting to apply your update, another user has changed that record, but not one of the fields that your user changed, who cares? Simply include the record's key fields, and your changed fields, in the WHERE clause of the update query, ignoring those non-key fields you did not change. (If you use an arbitrary key field, such as a GUID, it is unreasonable, and should be prohibited, that anyone should ever change the key field value).&lt;/p&gt;&lt;p&gt;The third option, and one that is appropriate for some database applications, is simply that the last user to attempt to write to a record be permitted to do so, overwriting any previous user's applied updates. For example, imagine that five different sensors record the current temperature in a given area. If all sensors are equally correct, the most current update attempt is the most accurate. Who cares about a temperature that was written ten minutes ago? A similar analogy can be made with respect to stock prices. A more current stock price is more accurate. Past updates are old news.&lt;/p&gt;&lt;p&gt;Copyright (c) 2010 Cary Jensen. All Rights Reserved.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-4284484730892978824?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/4284484730892978824/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/05/in-memory-datasets-clientdatasets-and.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/4284484730892978824'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/4284484730892978824'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/05/in-memory-datasets-clientdatasets-and.html' title='In-Memory DataSets: ClientDataSets and .NET DataTables Part 6: Applying Updates to a Database'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-4687777531606716841</id><published>2010-04-25T08:46:00.001-07:00</published><updated>2010-04-26T07:39:30.187-07:00</updated><title type='text'>In-Memory DataSets: ClientDataSets and .NET DataTables Part 5: Managing the Change Cache</title><content type='html'>&lt;p&gt;The change cache is the source of much of an in-memory dataset's power. It is through the change cache that you can permit a user to review their changes before committing them, as well as permit you to programmatically work with changes that have been made to the dataset.&lt;/p&gt;&lt;p&gt;&lt;definition&gt;Note: In the &lt;a href="http://caryjensen.blogspot.com/2009/09/in-memory-datasets-clientdataset-and.html"&gt;first article&lt;/a&gt; in this series, I called the change cache the &lt;em&gt;change log&lt;/em&gt;. A reader responded that what I described was not really a log, since it doesn’t keep track of individual changes, only information about the state of individual records. The term change log originates from the TClientDataSet interface, which includes the LogChanges property and the MergeChangeLog method. I agree that the term change cache more accurately reflects this feature of in-memory datasets, and will use that term from now on.&lt;/p&gt;&lt;p&gt;The following are operations that can be performed on the change cache for any in-memory dataset:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Detecting changes &lt;/li&gt;&lt;li&gt;Cancel all changes &lt;/li&gt;&lt;li&gt;Filter on changes &lt;/li&gt;&lt;li&gt;Detect field-level changes &lt;/li&gt;&lt;li&gt;Cancel a single change &lt;/li&gt;&lt;li&gt;Erase the change cache &lt;/li&gt;&lt;li&gt;Commit changes in the change cache to the underlying database &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The first six of these operations are described in the following sections. Committing changes to an underlying database is a much larger topic, and I will cover it in the next installment of this series.&lt;/p&gt;&lt;h1&gt;&lt;a name="_Toc224639369"&gt;&lt;/a&gt;&lt;a name="_Toc225302152"&gt;Detecting Changes&lt;/a&gt;&lt;br /&gt;&lt;/h1&gt;&lt;p&gt;You use the ClientDataSet's ChangeCount property to determine if there have been any changes to the data in the ClientDataSet since if was originally loaded. There are no changes if ChangeCount returns 0 (zero). If there have been one or more changes, ChangeCount returns the total number of records that have been inserted, deleted, or modified (including changes to nested datasets).&lt;/p&gt;&lt;p&gt;Note that ChangeCount only counts changed records. For example, a record that is inserted and then modified counts as only one change.&lt;/p&gt;&lt;p&gt;The following code segment demonstrates the use of ChangeCount.&lt;/p&gt;&lt;pre&gt;if ClientDataSet1.State in [dsEdit, dsInsert] then&lt;br /&gt;  ClientDataSet1.Post;&lt;br /&gt;  if ClientDataSet1.ChangeCount &amp;gt; 0 then&lt;br /&gt;    ClientDataSet1.ApplyUpdates(0);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;&lt;definition&gt;Note that there is a bug in ClientDataSets in the original release with Delphi 6 and 7 with respect to ChangeCount. If you save a ClientDataSet to a file or stream, and then later restore the ClientDataSet, ChangeCount will be zero immediately after you reload the ClientDataSet, even if there are changes in the change cache.&lt;/p&gt;&lt;p&gt;In .NET, you call the DataTable's GetChanges method. If GetChanges returns a nil reference, there are no changes. If there are changes, the DataTable returned by GetChanges contains the inserted, deleted, and modified records.&lt;/p&gt;&lt;pre&gt;if DataTable1.GetChanges &amp;lt;&amp;gt; nil then&lt;br /&gt;begin&lt;br /&gt;  //Work with the changes here&lt;br /&gt;  end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h1&gt;&lt;a name="_Toc224639370"&gt;&lt;/a&gt;&lt;a name="_Toc225302153"&gt;Canceling All Changes&lt;/a&gt;&lt;br /&gt;&lt;/h1&gt;&lt;p&gt;When you cancel all changes, the in-memory dataset reverts to the values it contained when it was originally loaded. You cancel all changes in a ClientDataSet by calling the CancelUpdates method. With a .NET DataTable, you call RejectChanges.&lt;/p&gt;&lt;p&gt;Canceling all changes is an irreversible action.&lt;/p&gt;&lt;h1&gt;&lt;a name="_Toc224639371"&gt;&lt;/a&gt;&lt;a name="_Toc225302154"&gt;Filtering On Changes&lt;/a&gt;&lt;br /&gt;&lt;/h1&gt;&lt;p&gt;Both ClientDataSets and .NET DataTables permit you to filter the dataset to display only inserted, deleted, and modified records. With a ClientDataSet, you invoke this operation using the StatusFilter property, which can be set to include the following four flags: usModified, usInserted, usDeleted, and usUnModified.&lt;/p&gt;&lt;p&gt;The following code segment demonstrates the use of StatusFilter:&lt;/p&gt;&lt;pre&gt;if ClientDataSet1.State in [dsEdit, dsInsert] then&lt;br /&gt;  ClientDataSet1.Post;&lt;br /&gt;  if ClientDataSet1.ChangeCount &amp;gt; 0 then&lt;br /&gt;  begin&lt;br /&gt;    ClientDataSet1.StatusFilter := [usModified, usInserted, usDeleted];&lt;br /&gt;    //Do something with the changed records&lt;br /&gt;  end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Just as you must use a DataView to obtain a filtered view of a DataTable, you must use a DataView to filter on Changed records. In this case, you use the RowStateFilter and set it to one of the following eight values of the DataViewRowState enumeration: OriginalRows, CurrentRows, Added, Deleted, ModifiedOriginal, ModifiedCurrent, UnChanged, and None.&lt;/p&gt;&lt;p&gt;The following code demonstrates how to obtain a DataView that contains only the records deleted from a DataTable:&lt;/p&gt;&lt;pre&gt;DataView1 := DataView.Create(DataTable1);&lt;br /&gt;  DataView1.RowStateFilter := DataViewRowState.Deleted;&lt;br /&gt;  if DataView1.Count &amp;gt; 0 then&lt;br /&gt;  begin&lt;br /&gt;    // Do something with the deleted records&lt;br /&gt;  end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h1&gt;&lt;a name="_Toc224639372"&gt;&lt;/a&gt;&lt;a name="_Toc225302155"&gt;Detecting Field-Level Changes&lt;/a&gt;&lt;br /&gt;&lt;/h1&gt;&lt;p&gt;With ClientDataSets, you detect field-level changes by examining the OldValue and NewValue variant properties of your ClientDataSet's TFields. The following code demonstrates how to do this:&lt;/p&gt;&lt;pre&gt;if ClientDataSet1.UpdateStatus = usModified then&lt;br /&gt;begin&lt;br /&gt;  if ClientDataSet1.Fields[0].OldValue &amp;lt;&amp;gt; ClientDataSet1.Fields[0].NewValue then&lt;br /&gt;    begin&lt;br /&gt;      //The first field in the current record has been changed&lt;br /&gt;    end;&lt;br /&gt;end;&lt;/pre&gt;&lt;p&gt;With .NET DataTables, you must obtain two DataViews for the table, setting the RowStateFilter on one of them to ModifiedOriginal, and the RowStateFilter of the second to ModifiedCurrent. You can them compare the fields of the two DataViews to determine what has changed. This is demonstrated in the following code sample:&lt;/p&gt;&lt;pre&gt;DataView1 := DataView.Create(DataTable1);&lt;br /&gt;DataView2 := DataView.Create(DataTable2);&lt;br /&gt;DataView1.RowStateFilter := DataViewRowState.ModifiedOriginal;&lt;br /&gt;DataView2.RowStateFilter := DataViewRowState.ModifiedCurrent;&lt;br /&gt;if DataView1.Item[0].Row[0].ToString &amp;lt;&amp;gt; DataView0.Item[0].Row[0].ToString then&lt;br /&gt;  begin&lt;br /&gt;    //Field 1 of record 1 has been changed.&lt;br /&gt;    //Assumes a string-compatible field&lt;br /&gt;  end;&lt;/pre&gt;&lt;h1&gt;&lt;a name="_Toc224639373"&gt;&lt;/a&gt;&lt;a name="_Toc225302156"&gt;Canceling a Single Change&lt;/a&gt;&lt;br /&gt;&lt;/h1&gt;&lt;p&gt;ClientDataSets provides you with two mechanisms for canceling a change to a record. You call the UndoLastChange method to revert the record that was last inserted, deleted, or modified to its original state. By comparison, if the ClientDataSet is pointing to a record that was inserted, deleted, or modified, you can call RevertRecord.&lt;/p&gt;&lt;p&gt;The following code demonstrates the use of RevertRecord:&lt;/p&gt;&lt;pre&gt;if ClientDataSet1.UpdateStatus in [usInserted, usDeleted, usModified] then&lt;br /&gt;  ClientDataSet1.RevertRecord;&lt;/pre&gt;&lt;p&gt;For a .NET DataRow, you call the RejectChanges method to restore an inserted, deleted, or modified DataRow to its original state. The following code demonstrates how to restore all deleted records to a DataTable:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;var&lt;br /&gt;  DataView2: DataView;&lt;br /&gt;  i: Integer;&lt;br /&gt;begin&lt;br /&gt;  DataView2 := DataView.Create(DataSet1.Tables[0]);&lt;br /&gt;  DataView2.RowStateFilter := DataViewRowState.Deleted;&lt;br /&gt;  for i := (DataView2.Count -1) downto 0 do&lt;br /&gt;    DataView2.item[i].Row.RejectChanges;&lt;/pre&gt;&lt;br /&gt;&lt;h1&gt;&lt;a name="_Toc224639374"&gt;&lt;/a&gt;Erasing the Change Cache&lt;/h1&gt;&lt;p&gt;Erasing the change cache leaves any changes to the dataset intact, but deletes the record of those changes. Most developers do not want to erase the change cache since it makes it impossible to undo changes and also erases the information necessary to write the changes to the underlying database. About the only time that erasing the change cache makes sense is when you want to make the changes permanent before storing the dataset in a file or stream, and never have to resolve those changes back to some underlying database.&lt;/p&gt;&lt;p&gt;With ClientDataSets, you erase the change cache by calling MergeChangeLog. (Note that you can simply turn off the change cache with a ClientDataSet by setting the ClientDataSet's LogChanges property to a Boolean False before making any changes.)&lt;/p&gt;&lt;p&gt;With .NET DataTables, call AcceptChanges to erase the change cache.&lt;/p&gt;&lt;p&gt;Copyright © 2010 Cary Jensen. All Rights Reserved.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-4687777531606716841?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/4687777531606716841/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/04/in-memory-datasets-clientdatasets-and.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/4687777531606716841'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/4687777531606716841'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/04/in-memory-datasets-clientdatasets-and.html' title='In-Memory DataSets: ClientDataSets and .NET DataTables Part 5: Managing the Change Cache'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-6921551695819909068</id><published>2010-04-15T14:06:00.001-07:00</published><updated>2010-05-05T07:51:14.485-07:00</updated><title type='text'>Delphi Developer Days 2010</title><content type='html'>&lt;p&gt;It’s less than a month away from the first city in the &lt;a href="http://www.delphideveloperdays.com/"&gt;2010 Delphi Developer Days&lt;/a&gt; tour, and I thought this might be a good time to share some history and insight into this event. To begin with, Delphi Developer Days is an intense, two-day Delphi event featuring &lt;a href="http://www.jensendatasystems.com/aboutcj.html"&gt;me&lt;/a&gt; and &lt;a href="http://www.marcocantu.com/"&gt;Marco Cantù&lt;/a&gt;. Our first city on this tour is Baltimore/Washington DC (May 11-12), continuing on to Chicago (May 14-15), Los Angeles (May 17-18), London, England (May 26-27); and finishing up in Frankfurt, Germany (May 30 – June 1).&lt;/p&gt;&lt;p&gt;Marco and I have known each other for a long time, are good friends, and have been looking for an opportunity to work together. In May of 2008 we talked about the lack of a live Delphi event in the US, and thought that this provided us an opportunity to offer something unique. &lt;/p&gt;&lt;p&gt;We weren’t interested in trying to run a conference similar to the past annual Borland Conferences or DelphiLive!. Instead, we were looking to offer something similar to a traditional training, but with the interaction and networking that a somewhat larger event can provide. As a result, we limit attendance to around 30 attendees in each city. This gives us a chance to interact with each attendee, while maintaining a critical mass for networking. (In addition to our consultant and development services, we’re both seasoned Delphi trainers.)&lt;/p&gt;&lt;p&gt;But there were a number of challenges that we knew we’d have to address in order for the event to be successful. To begin with, Delphi is a mature product. While there are always some newcomers, most Delphi developers have been using it for some time. Furthermore, not everyone is using the latest versions of Delphi. In fact, by some estimates, nearly half of all active Delphi developers are using Delphi 7 or earlier.&lt;/p&gt;&lt;p&gt;Another challenge is that there are a lot of different types of Delphi developers. Many are engaged in traditional client/server database development, while others are building server-side applications that operate over the Internet. How do you offer something that will appeal to such a diverse group of developers?&lt;/p&gt;&lt;p&gt;Honestly, I think the solution we came up with is the right one. To begin with, we embrace the richness of the Delphi community. We include timely topics about the latest versions of Delphi. Even if you are working with an older version, these topics are relevant. Specifically, even if you are not ready to upgrade now, you want to be informed about what the latest versions can do for you and your development efforts. In the long run, unless your applications are all at the end of their lifecycle, you will upgrade sooner or later.&lt;/p&gt;&lt;p&gt;At the same time, we realize that there is always room for fundamentals, topics that apply to many versions of Delphi (and some of these go all the way back to Delphi 1!). As far as types of applications, we understand that we need to have a healthy dose of both Windows client development as well as distributed computing. &lt;/p&gt;&lt;p&gt;Finally, we wanted to deliver this type of content in a way that would be fun for us as well as our attendees, and do so in a way that gives the attendees something of value, something that lasts well beyond the two days we spend together.&lt;/p&gt;&lt;p&gt;All of these considerations went into the format of Delphi Developer Days. There are a total of 12 presentations over the course of two days, four of which Marco and I do together, and eight which we do individually (four sessions where we each present simultaneously in separate rooms). In choosing the topics, we tried to include something for everybody, from “What’s New in Delphi and Delphi Prism,” to “Leveraging ClientDataSets.” From “Internet Delphi Application Technologies Compared,” to “Delphi Development for Windows 7.” We even include a foundation presentation on “Designing Interfaces and Objects.” (For a complete schedule, visit &lt;a title="http://www.delphideveloperdays.com/descriptions.html" href="http://www.delphideveloperdays.com/descriptions.html"&gt;http://www.delphideveloperdays.com/descriptions.html&lt;/a&gt;.)&lt;/p&gt;&lt;p&gt;When Marco and I are presenting separately, attendees can attend either talk. But what if you are interested in both topics? While we tried to keep the simultaneous individual session topics from overlapping, we acknowledge that this is an issue. Fortunately, both Marco and I are authors, and have published close to 40 books between us. To put this another way, we know how to write, and like doing it.&lt;/p&gt;&lt;p&gt;Each attendee to Delphi Developer Days receives a course book written by us that includes a paper for each talk. So, if you choose to attend one individual session, you can read the paper for the other session that is going on at the same time. Last year our course book was 500 pages in length (you get our slideshows and code, too, but these papers are detailed papers, covering more information than we have time for in our live sessions). We are currently writing the course book for this year’s event, and I’m guessing that it will be similar in length.&lt;/p&gt;&lt;p&gt;This is a course book that you can’t get anywhere else. While there is occasional overlap with other writings that we’ve done (such as our blogs, books, magazine articles, or other courseware), we do not publish or otherwise make this material available outside of Delphi Developer Days. Sorry, please don’t ask to buy the course book; it’s for attendees only.&lt;/p&gt;&lt;p&gt;As I mentioned above, this is not the first time Marco and I have done this. Delphi Developer Days 2009 was a wonderful success. Marco and I had a blast, and the feedback we received encouraged us to do it again this year, with two additional cities added to the tour.&lt;/p&gt;&lt;p&gt;So how does this year’s tour compare to last year’s? In addition to adding two cities (Los Angeles and Frankfurt, Germany, cities added in response to requests from the Delphi community), we added new topics and brought back some of the popular ones. And in order to make this year’s event relevant for previous attendees, we made sure that when a previous topics was covered during our breakout sessions, it was paired with a new topic, giving previous attendees a choice of something new. And, judging from the number of last year’s attendees who have registered for this year, we succeeded.&lt;/p&gt;&lt;p&gt;I would be remiss if I didn’t also mention our sponsors. Embarcadero Technologies and Sybase are once again our platinum sponsors, and each city will include a short presentation by representatives of one or both of these companies. In addition, we have more than 20 additional sponsors from almost every segment of the Delphi community. Many of these sponsors offer tools that work with Delphi, and many of them have provided copies of their products as door prizes for attendees. These sponsors are among the most active companies in the Delphi community, and we encourage you to visit their sites and learn more about their products. You will find a list of our current sponsors at &lt;a title="http://www.delphideveloperdays.com/sponsors.html" href="http://www.delphideveloperdays.com/sponsors.html"&gt;http://www.delphideveloperdays.com/sponsors.html&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;But there is more. On the first night of each US city, either Anders Ohlsson or David I (David Intersimone) will be present to provide a two hour “Embarcadero Delphi Evening,” complete with loads of information about upcoming plans. (Anders will be in Baltimore/Washington DC and Chicago, David I in Los Angeles.) Even if you can’t attend Delphi Developer Days, you can come to these free events. Visit &lt;a title="http://edn.embarcadero.com/article/40546" href="http://edn.embarcadero.com/article/40546"&gt;http://edn.embarcadero.com/article/40546&lt;/a&gt; to register (Delphi Developer Days attendees do not need to register. Your registration for Delphi Developer Days takes care of that).&lt;/p&gt;&lt;p&gt;I want to finish up by saying that Marco and I are huge Delphi fans, and we are extremely positive about the future of Delphi and the Delphi Community as a whole. We hope to see you there. It’s going to be a blast.&lt;/p&gt;&lt;p&gt;Learn more at &lt;a href="http://www.delphideveloperdays.com/"&gt;http://www.delphideveloperdays.com/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Copyright © 2010 Cary Jensen. All Rights Reserved.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-6921551695819909068?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/6921551695819909068/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/04/delphi-developer-days-2010.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6921551695819909068'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6921551695819909068'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/04/delphi-developer-days-2010.html' title='Delphi Developer Days 2010'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-5071793307252689441</id><published>2010-03-20T16:14:00.001-07:00</published><updated>2010-03-20T16:47:37.654-07:00</updated><title type='text'>In-Memory DataSets: ClientDataSets and .NET DataTables Part 4: Navigating, Sorting, and Filtering</title><content type='html'>&lt;p&gt;&lt;a name="_Toc224639365"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In the &lt;a href="http://caryjensen.blogspot.com/2009/12/in-memory-datasets-clientdataset-and.html"&gt;preceding article&lt;/a&gt; in this series I discussed how to populate in-memory datasets. In this continuation, I will look at some of the ways that you can programmatically work with that data, including navigating, sorting, and filtering the data in memory. &lt;/p&gt;&lt;p&gt;Active ClientDataSets are essentially a cursor pointer that refers to the current record in the dataset. To navigate which record your ClientDataSet refers to, you use methods of the ClientDataSet to change the current record. These methods include First, Next, Prior, Last, and MoveBy, to name a few.&lt;/p&gt;&lt;p&gt;The following line of code demonstrates how to navigate to each record of (scan) a ClientDataSet:&lt;/p&gt;&lt;pre&gt;&lt;p&gt;ClientDataSet1.Open;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;while not ClientDataSet1.EOF do&lt;br /&gt;&lt;/p&gt;&lt;p&gt;begin&lt;/p&gt;&lt;p&gt;  //Do something here with the current record&lt;/p&gt;&lt;p&gt;  ClientDataSet1.Next;&lt;/p&gt;&lt;p&gt;end;&lt;br /&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;DataTables in the .NET framework do not support the concept of a current record. Instead, you use the Rows property to indicate which DataRow of the DataTable you want to work with.&lt;/p&gt;&lt;p&gt;The following code segment demonstrates how to programmatically navigate the DataRows of a DataTable.&lt;/p&gt;&lt;pre&gt;var&lt;br /&gt;i: Integer;&lt;br /&gt;DataRow1: DataRow;&lt;br /&gt;begin&lt;br /&gt;  for i := 0 to DataTable1.Rows.Count – 1 do&lt;br /&gt;  begin&lt;br /&gt;    DataRow1 := DataTable1.Rows[i];&lt;br /&gt;    //Do something with the DataRow&lt;br /&gt;  end;&lt;/pre&gt;&lt;p&gt;While the DataTable lacks the concept of a current record, Windows Forms applications have a concept that is very similar to current record. Anytime one or more properties are bound to a multivalue binding context (such as a DataTable) in a Windows Forms application, a CurrencyManager is created that can be used to determine to which DataRow the form's controls are synchronized. If you bind data using a BindingSource instance, CurrencyManager property points to the underlying CurrencyManager. Similarly, there are classes, such as BindingSource, that provide features similar to a CurrencyManager.&lt;/p&gt;&lt;p&gt;Note that in .NET Web Forms applications, most multi-value controls automatically iterate through the records of a DataTable or DbDataReader implementing instance when you call the DataBind method. As a result, Web forms do not have currency managers. &lt;/p&gt;&lt;h2&gt;&lt;a name="_Toc224639366"&gt;&lt;/a&gt;&lt;a name="_Toc225302149"&gt;Sorting In-Memory Data&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;ClientDataSets are sorted using ClientDataSet indexes. There are two types of indexes in ClientDataSets, temporary indexes and persistent indexes.&lt;/p&gt;&lt;p&gt;You create a temporary index using the IndexFieldNames property of the ClientDataSet. When you assign a field name (or a comma-separated list of field names) to the IndexFieldNames property, an index is create for that field (or fields) at that moment the property is assigned, and the records of the ClientDataSet are then sorted based on that ascending index. This is demonstrated in the following code segment:&lt;/p&gt;&lt;pre&gt;ClientDataSet1.IndexFieldNames := 'State;City';&lt;/pre&gt;&lt;p&gt;Persistent indexes, on the other hand, are defined using the IndexDefs property of the ClientDataSet. After defining one or more IndexDef instances, you then set the IndexName property of the ClientDataSet to the name of one of those IndexDef instances.&lt;/p&gt;&lt;p&gt;Indexes created using IndexDefs are more flexible than temporary indexes. While temporary indexes can only be ascending, case-sensitive indexes, your IndexDef definitions can define ascending or descending indexes, as well as case insensitive indexes.&lt;/p&gt;&lt;p&gt;The following line of code demonstrates the creation and use of a persistent index using an IndexDef definition:&lt;/p&gt;&lt;pre&gt;ClientDataSet1.IndexDefs.Add('IDIndex', 'State;City',[ixCaseInsensitive]);&lt;br /&gt;ClientDataSet1.IndexName := 'IDIndex';&lt;/pre&gt;&lt;p&gt;DataTables cannot be sorted directly. If you want to sort the records in a DataTable, you use a DataView. You sort a DataView using its Sort property, which can hold either the name of a DataColumn to sort the DataView DataRows, or a comma-separated list of DataColumn names.&lt;/p&gt;&lt;p&gt;The following code demonstrates creating a DataView to display a sorted list of a DataTable's DataRows.&lt;/p&gt;&lt;pre&gt;var DataView1: DataView;&lt;br /&gt;DataView1.Create(DataTable1);&lt;br /&gt;DataView1.Sort := 'Customer ID';&lt;/pre&gt;&lt;p&gt;Rather than creating a new DataView to display sorted DataRows, you can use the DefaultView of a DataTable to refer to the DataView that each DataTable automatically supports. This is shown in the following code segment:&lt;/p&gt;&lt;pre&gt;DataTable1.DefaultView.Sort := 'Customer ID';&lt;/pre&gt;&lt;p&gt;The alternative is to create a new DataView instance for each sort order you want to use. The following example creates two DataViews for a single DataTable, each with its own sort order:&lt;/p&gt;&lt;pre&gt;var&lt;br /&gt;DataView1, DataView2: DataView;&lt;br /&gt;begin&lt;br /&gt;  DataView1 := DataView.Create(DataTable1);&lt;br /&gt;  DataView1.Sort := 'Customer ID';&lt;br /&gt;  DataView2 := DataView.Create(DataTable1);&lt;br /&gt;DataView2.Sort := 'Country;City';&lt;/pre&gt;&lt;h2&gt;&lt;a name="_Toc224639367"&gt;&lt;/a&gt;&lt;a name="_Toc225302150"&gt;Filtering In-Memory Data&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;When you filter a ClientDataSet, it behaves as though it contains only those records that meet the filter criteria. For example, if you filter a ClientDataSet for all records where the City field contains the name 'London', that ClientDataSet will act as though it only contains records where the City field value is London.&lt;/p&gt;&lt;p&gt;Note: There is one exception to the behavior of filtered ClientDataSets. Specifically, if you read the XMLData property of a ClientDataSet or call SaveToFile or SaveToStream, the results are as if the ClientDataSet is not filtered. In other words, all records are involved, not just the filtered records. To save only the filtered records, you must point to the filtered ClientDataSet with the DataSet property of a DataSetProvider that is referenced by another ClientDataSet. After opening that other ClientDataSet, it will contain only the filtered records (a copy), which you can then save to a file or stream. These records, however, do not include a change log. &lt;/p&gt;&lt;p&gt;There are two ways to filter a ClientDataSet. One is to use the Filter and Filtered properties. Filter takes a Boolean expression. Records that are available in the filtered view are those for whom the expression evaluates to True. The following code segment demonstrates how to filter a ClientDataSet using the Filter and Filtered properties.&lt;/p&gt;&lt;pre&gt;ClientDataSet1.Filter := '[City] = ' + QuotedStr('London');&lt;br /&gt;ClientDataSet1.Filtered := True;&lt;/pre&gt;&lt;p&gt;The second way to filter a ClientDataSet is to use the SetRange method. Unlike Filter and Filtered, SetRange requires that you first set an index. Since an index is being used, SetRange is a much faster filter.&lt;/p&gt;&lt;p&gt;The following code demonstrates the use of SetRange.&lt;/p&gt;&lt;pre&gt;ClientDataSet1.IndexFieldNames := 'City';&lt;br /&gt;ClientDataSet1.SetRange(['London'],['London']);&lt;/pre&gt;&lt;p&gt;You cancel filtering with Filter and Filtered by either setting Filtered to False or setting Filter to an empty string. You cancel filtering using SetRange by calling CancelRange. (For an interesting use of ClientDataSet where Filtered is False but a non-empty value appears in the Filter property, see the FindFirst, FindLast, FindNext, and FindPrior methods).&lt;/p&gt;&lt;p&gt;As is the case with sorting, you do not actually filter a DataTable. Instead, you use a DataView, which supports filtering through its RowFilter property. Filters are more complicated for Delphi developers, since single quotes are used to delimit string literals in the Filter expression. For example, consider the following filter:&lt;/p&gt;&lt;pre&gt;DataView1.RowFilter := '[City] = ''London''';&lt;/pre&gt;&lt;p&gt;With Delphi Prism, you can use the double quote character, which makes creating filter strings much easier, as shown in the following code.&lt;/p&gt;&lt;pre&gt;DataView1.RowFilter := "[City] = 'London'";&lt;/pre&gt;&lt;p&gt;You remove a previously set filter on a DataView by setting its RowFilter property to an empty string.&lt;/p&gt;&lt;p&gt;While it’s true that filtering a DataTable requires the use of a DataView, it is not necessary to actually create a new DataView when you want to set a filter. Each DataTable has a default DataView, which controls how the records in the DataTable appear. This DataView is accessed through the DefaultView property of the DataTable. As a result, assuming that the preceding code sample used a DataView created for a DataTable named DataTable1, the following code segment produces the same result:&lt;/p&gt;&lt;pre&gt;DataTable1.DefaultView.RowFilter := "[City] = 'London'";&lt;/pre&gt;&lt;p&gt;Copyright © 2009-2010 Cary Jensen. All Rights Reserved&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-5071793307252689441?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/5071793307252689441/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/03/in-memory-datasets-clientdatasets-and.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/5071793307252689441'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/5071793307252689441'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/03/in-memory-datasets-clientdatasets-and.html' title='In-Memory DataSets: ClientDataSets and .NET DataTables Part 4: Navigating, Sorting, and Filtering'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-6072539096070540095</id><published>2010-01-26T15:45:00.001-08:00</published><updated>2010-01-28T06:05:00.118-08:00</updated><title type='text'>Shifting TFields in TDataSets Bound to TDBGrids: A Potential Source of Bugs in Your Code</title><content type='html'>&lt;p&gt;I've been working with Delphi since the beginning, with particular emphasis on database development. As a result, it's not often that I encounter a fundamental behavior of data-related components that catches me off guard. Well, it happened last month. And what I observed may be the source of an infrequent yet potentially disastrous bug in a large number of Delphi database applications.&lt;/p&gt;&lt;p&gt;Here is what I observed: The TFields in an open TDataSet changed order at runtime. Specifically, a TField that was originally in the zeroeth position (DataSet.Fields[0]) at the time that I created the TDataset, was in different position in the Fields array a short time later. I discovered this behavior when an exception was raised as a result of my attempting to programmatically read the integer value of the TIntegerField that I created in the first (zeroeth) position in my TDataSet. Between the time I created the TDataSet, and the execution of my code, the integer field had moved.&lt;/p&gt;&lt;p&gt;What happened wasn't magic. The TFields didn't change position by themselves, nor did they change based on anything I did in my code. What caused the TFields to physically change position in the TDataSet was that the user had changed the order of the TColumns in a TDBGrid to which a TClientDataSet was attached (through a TDataSource component, of course). The user's ability to change the position of the TColumns in a TDBGrid, by the way, is the default behavior of a TDBGrid.&lt;/p&gt;&lt;p&gt;Besides being interesting (I kind of assumed that once a DataSet was opened, the position of the TFields in the Fields array was pretty much set), this behavior is the potential source of intermittent exceptions, the type that are particularly difficult to track down. It turns out that this behavior, which I've never seen described before, has been around since Delphi 1. (Actually, I observed this effect in Delphi 7, Delphi 2007, and Delphi 2010. However, I understand that the underlying source of this behavior has been around since Delphi 1, though I have not specifically confirmed this.)&lt;/p&gt;&lt;p&gt;I created a very simple Delphi application that demonstrates this effect. It consists of a single form with one TDBGrid, a TDataSource, a TClientDataSet, and a TButton. The TClientDataSet is bound to the TDBGrid through the TDataSource. The OnCreate event handler of this form looks like the following:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;procedure TForm1.FormCreate(Sender: TObject);&lt;br /&gt;begin&lt;br /&gt;  with ClientDataSet1.FieldDefs do&lt;br /&gt;  begin&lt;br /&gt;    Clear;&lt;br /&gt;    Add('StartOfWeek', ftDate);&lt;br /&gt;    Add('Label', ftString, 30);&lt;br /&gt;    Add('Count', ftInteger);&lt;br /&gt;    Add('Active', ftBoolean);&lt;br /&gt;  end;&lt;br /&gt;  ClientDataSet1.CreateDataSet;&lt;br /&gt;end;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Button1, which is labeled Show ClientDataSet Structure, contains the following OnClick event handler.&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;procedure TForm1.Button1Click(Sender: TObject);&lt;br /&gt;var&lt;br /&gt;  sl: TStringList;&lt;br /&gt;  i: Integer;&lt;br /&gt;begin&lt;br /&gt;  sl := TStringList.Create;&lt;br /&gt;  try&lt;br /&gt;    sl.Add('The Structure of ' + ClientDataSet1.Name);&lt;br /&gt;    sl.Add('- - - - - - - - - - - - - - - - - ');&lt;br /&gt;    for i := 0 to ClientDataSet1.FieldCount - 1 do&lt;br /&gt;    sl.Add(ClientDataSet1.Fields[i].FieldName);&lt;br /&gt;    ShowMessage(sl.Text);&lt;br /&gt;  finally&lt;br /&gt;    sl.Free;&lt;br /&gt;  end;&lt;br /&gt;end;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;To demonstrate the moving field effect, run this application and click the button labeled Show ClientDataSet Structure. You should see something like that shown in Figure 1.&lt;/p&gt;&lt;a href="http://4.bp.blogspot.com/_oiz-KoL9VE4/S19_X3CHb_I/AAAAAAAAACE/ZGNXs9TsVPg/s1600-h/figure1.jpg"&gt;&lt;img style="TEXT-ALIGN: center; MARGIN: 0px auto 10px; WIDTH: 320px; DISPLAY: block; HEIGHT: 196px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5431199723518652402" border="0" alt="" src="http://4.bp.blogspot.com/_oiz-KoL9VE4/S19_X3CHb_I/AAAAAAAAACE/ZGNXs9TsVPg/s320/figure1.jpg" /&gt; &lt;p align="center"&gt;&lt;/a&gt;Figure 1&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Next, drag the Columns of the DBGrid to re-arrange the display order of the fields. Click the Show ClientDataSet Structure button once again. This time you will see something similar to that shown in Figure 2.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p align="center"&gt;&lt;a href="http://2.bp.blogspot.com/_oiz-KoL9VE4/S19_oq4SCUI/AAAAAAAAACM/s6WnsWVgq44/s1600-h/figure2.jpg"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 196px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5431200012313954626" border="0" alt="" src="http://2.bp.blogspot.com/_oiz-KoL9VE4/S19_oq4SCUI/AAAAAAAAACM/s6WnsWVgq44/s320/figure2.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;Figure 2&lt;/p&gt;&lt;p&gt;What is remarkable about this example is that position of the TFields in the TClientDataSet's Fields property changed, such that the field that was in the ClientDataSet.Field[0] position at one point is not necessarily there moments later. And, unfortunately, this is not distinctly a TClientDataSet issue. I performed the same test with BDE-based TTables and ADO-based TADOTables and got the same effect.&lt;/p&gt;&lt;p&gt;It turns out that this behavior has three contributing factors. These are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A TDBGrid connects to a DataSet through a DataSource &lt;/li&gt;&lt;li&gt;The TDBGrid permits the user to move columns at runtime &lt;/li&gt;&lt;li&gt;The TColumns of the TDBGrid are dynamic, meaning that they are created by the TDBGrid at runtime &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If you programmatically refer to the TFields of a DataSet connected to a TDBGrid in which the preceding three conditions exist using a literal indexer, your application can raise an exception, or produce inaccurate results, if the user moves one or more of the TColumns in that TDBGrid. In the following section I will consider several solutions to this problem, as well as share with you the reason for it.&lt;/p&gt;&lt;h3&gt;There Are Several Solutions&lt;/h3&gt;&lt;p&gt;There are a number of tactics that you can use to eliminating this potential bug from your applications. The first is to define the TColumns of your TDbGrid using persistent TColumns. &lt;/p&gt;&lt;p&gt;Creating persistent TColumns can be done either at design time or runtime. To do it at design time, add the TColumns using the Columns Editor. Display the Columns Editor by right-clicking the TDBGrid and selecting Columns Editor or by clicking the ellipsis in the Columns property of the TDBGrid in Object Inspector. If your DataSet is Active, you can click the Add All Fields button in the Columns Editor toolbar. Otherwise, add one or more TColumns and set their FieldName property in the Property Editor.&lt;/p&gt;&lt;p&gt;To create persistent TColumns at runtime, use the Add or Create methods of the TDBGrid's Columns property. You can then set specific properties of the added or created TColumns.&lt;/p&gt;&lt;p&gt;The second solution, though one that has some negative consequences, it to prevent a user from moving the TColumns of a TDBGrid. This can be done by removing the dgResizeColumn flag from the Options property of the TDBGrid. While this approach is effective, it eliminates a potentially valuable user interface option. Furthermore, removing this flag not only restricts column reordering, it prevents column resizing. (To learn how to limit column reordering without removing the column resizing option, see Zarko Gajic's article &lt;a href="http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm"&gt;How to allow column resize by disable movement (in TDBGrid)&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;A third solution is to avoid referring to a TDataSet's individual TFields based on a literal indexer to the Fields array property (since this is the essence of the problem). In other words, if you need to refer to the Count TField in the preceding code sample, don't use ClientDataSet1.Fields[2]. So long as you know the name of the TField, you can use something like ClientDataSet1.FieldByName('Count').&lt;/p&gt;&lt;p&gt;There is one rather big drawback to the use of FieldByName, however. Specifically, this method identifies the field by iterating through the Fields property of the TDataSet, looking for a match based on the field name. Since it does this every time you call FieldByName, you should avoid this method in situations where the TField needs to be referenced many times, such as in a loop that navigates a large TDataSet.&lt;/p&gt;&lt;p&gt;If you do need to refer to the field repeatedly (and a large number of times), consider using something like the following code snippet:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;var&lt;br /&gt;CountField: TIntegerField;&lt;br /&gt;Sum: Integer;&lt;br /&gt;begin&lt;br /&gt; Sum := 0;&lt;br /&gt;  CountField := TIntegerField(ClientDataSet1.FieldByName('Count'));&lt;br /&gt;  ClientDataSet1.DisableControls; //assuming we're attached to a DBGrid&lt;br /&gt;  try&lt;br /&gt;    ClientDataSet1.First;&lt;br /&gt;    while not ClientDataSet1.EOF do&lt;br /&gt;    begin&lt;br /&gt;      Sum := Sum + CountField.AsInteger;&lt;br /&gt;      ClientDataSet1.Next;&lt;br /&gt;    end;&lt;br /&gt;  finally&lt;br /&gt;    ClientDataSet1.EnableControls;&lt;br /&gt;  end;&lt;br /&gt;end;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;The fourth solution is to use the FieldByNumber method of the TDataSet's Fields property. If you already have code that uses an indexer for the Fields array, and it works reliably, so long as the user does not move the TColumns of the bound TDBGrid, there is another solution. Change your code to use the FieldByNumber.&lt;/p&gt;&lt;p&gt;There are two interesting aspects to the use of FieldByNumber. First, you must qualify its reference with the Fields property of your DataSet. Second, unlike the Fields array, which is zero-based, FieldByNumber takes a one-based parameter to indicate the position of the Field you want to reference.&lt;/p&gt;&lt;p&gt;The following is an updated version of the Button1 event handler shown earlier that uses the FieldByNumber method.&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;procedure TForm1.Button1Click(Sender: TObject);&lt;br /&gt;var&lt;br /&gt;  sl: TStringList;&lt;br /&gt;  i: Integer;&lt;br /&gt;begin&lt;br /&gt;  sl := TStringList.Create;&lt;br /&gt;  try&lt;br /&gt;    sl.Add('The Structure of ' + ClientDataSet1.Name +&lt;br /&gt;       ' using FieldByNumber');&lt;br /&gt;    sl.Add('- - - - - - - - - - - - - - - - - ');&lt;br /&gt;    for i := 0 to ClientDataSet1.FieldCount - 1 do&lt;br /&gt;    sl.Add(ClientDataSet1.Fields.FieldByNumber(i + 1).FieldName);&lt;br /&gt;    ShowMessage(sl.Text);&lt;br /&gt;  finally&lt;br /&gt;    sl.Free;&lt;br /&gt;  end;&lt;br /&gt;end;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;For the sample project, this code produces the following output, regardless of the orientation of the TColumns in the associated TDBGrid. This can be seen in Figure 3.&lt;/p&gt;&lt;p align="center"&gt;&lt;a href="http://1.bp.blogspot.com/_oiz-KoL9VE4/S19_yWYWqTI/AAAAAAAAACU/kvw_afAEMYY/s1600-h/figure3.jpg"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 196px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5431200178610022706" border="0" alt="" src="http://1.bp.blogspot.com/_oiz-KoL9VE4/S19_yWYWqTI/AAAAAAAAACU/kvw_afAEMYY/s320/figure3.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align="center"&gt;Figure 3&lt;/p&gt;&lt;p&gt;There is a fifth solution, but this is only available when your TDataSet is a TClientDataSet, like the one in my sample project. In those situations, you can create a clone of the original TClientDataSet, and it will have the original structure. As a result, whichever TField originally appeared in the zeroeth position will still be in that position, regardless of what a user has done to a TDBGrid that displays the TClientDataSet's data.&lt;/p&gt;&lt;p&gt;Note that I am not suggesting that you should reference TFields in a TDataSet using integer literals. Personally, the use of a TField variable that gets initialized through a one-time call to FieldByName is more readable, and is immune to changes in the physical order of a table's structure (though not immune to changes in the names of your fields!).&lt;/p&gt;&lt;h3&gt;Wrap Up&lt;/h3&gt;&lt;p&gt;There are a couple of final points I want to make. First, the actual structure of the underlying data is not affected. Specifically, if, after changing the order of the TColumns in a TDBGrid, you call the SaveToFile method of a TClientDataSet bound to that TDBGrid, the saved structure is the original (true internal) structure. Similarly, if you assign the Data property of one TClientDataSet to another, the target TClientDataSet also shows the true structure (which is similar to the effect observed when a source TClientDataSet is cloned).&lt;/p&gt;&lt;p&gt;Similarly, changes to the column orders of TDBGrids bound to other tested TDataSets, including TTable and ADOTable, do not affect the structure of the underlying tables. For example, a TTable that displays data from the customer.db sample Paradox table that ships with Delphi does not actually change that table's structure on disk (nor would you expect it to).&lt;/p&gt;&lt;p&gt;The second point is that this is not a bug in either the TDataSet or TDBGrid classes (or TColumn or TField, for that matter). This is how these classes were designed to work. And although this behavior can introduce bugs in your applications, this is because we were not aware of this behavior until now. And, you now know about this behavior, as well as how to prevent it from causing exceptions in your Delphi applications.&lt;/p&gt;&lt;p&gt;The final point comes to us from StackOverflow user Sertac Akyuz, who responded to a question about this behavior that I posted to that Web site. I had inspected the source for both the TDataSet as well as TDbGrid classes, and could not locate where this behavior was originating from. Sertac wrote that the behavior is actually found in the TColumns and TFields classes. Specifically, changing the column position of a dynamic TColumn results in a call to set the corresponding TField's Index property, which affects the position of the TField in its TDataSet's Fields property.&lt;/p&gt;&lt;p&gt;Now that you know that this potential problem exists, under what conditions it can surface, as well as the source of the effect, you should now take a look at your applications to see if you have TDbGrids with dynamic TColumns that the user can move at runtime. If you also refer to the underlying TFields associated with these TColumns using literal indexers to the TDataSet Fields property, you can eliminate potential bugs that will result from your indexer referring to the wrong field at runtime by using one of the solutions I outlined earlier in this article.&lt;/p&gt;&lt;p&gt;Copyright (C) 2010 Cary Jensen. All Rights Reserved&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-6072539096070540095?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/6072539096070540095/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/01/shifting-tfields-in-tdatasets-bound-to.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6072539096070540095'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6072539096070540095'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/01/shifting-tfields-in-tdatasets-bound-to.html' title='Shifting TFields in TDataSets Bound to TDBGrids: A Potential Source of Bugs in Your Code'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_oiz-KoL9VE4/S19_X3CHb_I/AAAAAAAAACE/ZGNXs9TsVPg/s72-c/figure1.jpg' height='72' width='72'/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-780575345935548554</id><published>2010-01-09T16:31:00.001-08:00</published><updated>2010-01-09T16:33:13.117-08:00</updated><title type='text'>Migrating Existing Delphi Applications to Unicode-enabled Delphi</title><content type='html'>&lt;p&gt;Over the life of an application it is often necessary or desirable to migrate the application to a newer version of the development environment. By doing so, the application can take advantage of a more modern interface, improvements in performance or memory management, as well as new or improved features.&lt;/p&gt;&lt;p&gt;It comes as no surprise, therefore, that there are many Delphi applications that have been converted, upgraded, and migrated through two or more versions of Delphi. &lt;/p&gt;&lt;p&gt;There is one migration, however, that stands out from the rest. That is the migration of a native code application written in Delphi 2007 or earlier to the latest version. What makes this one so different is that a number of the fundamental data types, types that have been around since the Delphi 1 and Delphi 2 days, have changed. I am talking about String, Char, and PChar, and I am referring to their support for Unicode.&lt;/p&gt;&lt;p&gt;In late 2009, Michael Rozlog, Senior Directory of Delphi Solutions, contacted me to see if I was interested in writing a white paper about migrating Delphi applications to Unicode-enabled Delphi. Ironically, I was in the midst of writing training material about what Unicode support meant for application development. And, I was keenly aware that a Unicode migration white paper, if it was to be effective, needed to rely on much more than just my personal experience. This really was a job for the greater RAD Studio community.&lt;/p&gt;&lt;p&gt;Here was my thinking. Delphi 2009 had been out for almost a year and a half, and Delphi 2010 had shipped three months earlier. As a result, many developers had been through the process of migrating existing applications to the Unicode enabled versions. And, if there was some way of collecting their stories, a clear picture of the migration process would emerge.&lt;/p&gt;&lt;p&gt;The call for stories, code samples, and advice on Unicode migration went out using blogs, tweets, the Embarcadero Developer Network, and some gentle personal prodding of various Delphi experts. And during 6 weeks in October of November I was fortunate enough receive a lot of input. In all, more than 20 contributors provided material, sometimes with a single, valuable code sample, and in other cases, with extensive narratives describing the preparation, process, and techniques used to migrate their applications.&lt;/p&gt;&lt;p&gt;And what a group of contributors this was. A number of them are the innovators behind some of the most popular third-party tools available to Delphi Developers. I also received input from well-known authors, trainers, bloggers, and authorities in the Delphi community. Everyone had something valuable to say, and it all contributed nicely to the paper.&lt;/p&gt;&lt;p&gt;This material is now available in the white paper &lt;a href="http://www.embarcadero.com/images/dm/technical-papers/delphi-unicode-migration.pdf"&gt;Delphi Unicode Migration for Mere Mortals: Stories and Advice from the Front Lines&lt;/a&gt;. This paper begins with a brief overview of some of the technical aspects of Delphi's Unicode support. It then addresses specific areas of application development that may be affected by the changes to Delphi's default string types.&lt;/p&gt;&lt;p&gt;Throughout the paper you will find direct quotes from the contributors, and in most cases you will also find code samples that reflect the kind of changes that you may have to make to your code as you upgrade existing applications (as well as possible changes to some of the core techniques you are accustom to using). I tried hard to give credit where credit was due, as I strongly feel that a paper of this scope and breadth would be nearly impossible were it not for the generous contributions of the contributors.&lt;/p&gt;&lt;p&gt;To everyone who contributed material, and especially to those brave individuals who agreed to review the paper for technical accuracy, thank you. &lt;/p&gt;&lt;p&gt;If you are preparing to migrate an existing application to Delphi 2010 (or Delphi 2009), or are in the midst of your own migration, you will hopefully find a lot of valuable information in this white paper. I do have one request, however. When you are ready to read the white paper, please download it right before reading. I hope to update this paper sometime in the future with additional material, if it makes sense to do so. In fact, the version of this paper that out there at the time of this writing (January 9th, 2010) includes two corrections that were reported by readers. If you downloaded the PDF prior to January 8th, you have the older version. Get the newer version.&lt;/p&gt;&lt;p&gt;One final note. If you also have stories, advise, or code samples that are not reflected in the current version of the white paper, and would like to have your material considered for a future revision, please do not hesitate to send it to me. I cannot promise that new contributions will actually appear in the paper (there was some nice material that didn't fit into this version), but I would like to consider it. &lt;/p&gt;&lt;p&gt;Send your contributions to cjensen@jensendatasystems.com. Use the subject line "Unicode Migration." I promise to acknowledge every contribution, so if you don't hear from me within a week of sending it, it fell through the cracks. Please resend.&lt;/p&gt;&lt;p&gt;Again, the white paper can be downloaded from &lt;a href="http://www.embarcadero.com/images/dm/technical-papers/delphi-unicode-migration.pdf"&gt;Delphi Unicode Migration for Mere Mortals: Stories and Advice from the Front Lines&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Copyright © 2010 Cary Jensen. All Rights Reserved.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-780575345935548554?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/780575345935548554/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2010/01/migrating-existing-delphi-applications.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/780575345935548554'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/780575345935548554'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2010/01/migrating-existing-delphi-applications.html' title='Migrating Existing Delphi Applications to Unicode-enabled Delphi'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-6908794620578534114</id><published>2009-12-17T18:23:00.001-08:00</published><updated>2010-01-05T09:03:09.205-08:00</updated><title type='text'>Creating a More Manageable Development Environment</title><content type='html'>&lt;p&gt;I recently bought a new laptop, something that I do every year or so. And the recent release of Windows 7 gave me a good excuse to do so. But this new purchase also gave me another opportunity to do something that I’ve been thinking about for a while, the opportunity to set up my machine in a fashion that will improve my ability to maintain my development environment.&lt;/p&gt;&lt;p&gt;Here’s the problem. Over time, in some cases in as little as three or four months, my computer performs noticeably slower than when it was first configured. Eventually it gets so bad that I need to reinstall my operating system and all of my software and tools. For me, this can take days.&lt;/p&gt;&lt;p&gt;Part of my problem is that I need quite a few different versions of a number of different programs. For example, I currently support a number of applications written in Delphi 7, Delphi 2007 (both Win32 and Delphi for .NET), Delphi 2009 and Delphi 2010, Visual Studio 2008 (including Delphi Prism). (I am also asked to teach classes in other versions of Delphi, such as Delphi 2006, or even Delphi 5.)&lt;/p&gt;&lt;p&gt;In addition, I use a variety of database servers, including Advantage Database Server and SQL Server, and these must be installed as well. And then there are the various tool sets, set as third-party component sets for the various applications I support, source code control, utilities, and the list goes on and on.&lt;/p&gt;&lt;p&gt;Actually, it’s much worse than it sounds. Let’s take Delphi 2007, for instance. Once you install Delphi 2007 and it’s help files, and register it as a legitimate copy, you must also download and install the various patches. I think that the total time, from start to finish, to get Delphi 2007 installed is something on the order of 5 hours. &lt;/p&gt;&lt;p&gt;And don’t get me started on Visual Studio 2008. In addition to the core development environment, there were several service packs, as well as updates such as Silverlight 3, and so forth. These needed to be downloaded and installed. VS 2008 must have taken a total of 8 hours.&lt;/p&gt;&lt;p&gt;Believe me, I’ve gotten pretty good at configuring my system, but this is a waste of time. Why can’t I just install it all once and be done with it.&lt;/p&gt;&lt;p&gt;I’ve tried to solve this problem in the past. One of my approaches was to carefully install everything I needed, and then make a good, clean image using something like Acronis True Image or Norton Ghost. And while this works, somewhat, it isn’t perfect. After restoring from an image, there is still a lot to do to get a system back into shape. &lt;/p&gt;&lt;p&gt;The fact is, that “clean” image you create early on isn’t perfect. It doesn’t have all the nifty tools you’ve picked up in the intervening months since you created the image. Nor does it have the latest third-party components that you have added to some of your more recent development projects.&lt;/p&gt;&lt;p&gt;Incremental images are not the answer, either. Once you start using your system, things get progressively worse, performance-wise. As a result, while that pristine, original image that you created, once restored, will perform very nicely, those incremental images you’ve been making along the way each contain some of the “gunk” that has been eating away at your performance. Restore from one of those and you’re starting with a non-optimal setup (and you will still need a day or so to get things really back to where you need them).&lt;/p&gt;&lt;h2&gt;A Possible Solution&lt;/h2&gt;&lt;p&gt;Over the past year I have been playing with an idea that is not so original. Most of us use products like VMWare Workstation or Virtual PC to create virtual installations of guest operating systems. Most often we use these guest OSes to install beta software, so that it can run in a clean environment, without having to worry about it doing something ugly to our primary OS (host operating system). Once testing is done, we simply delete the guest operating system and go about our way.&lt;/p&gt;&lt;p&gt;Well, sometime last year I bought a copy of Windows XP Pro, and installed it as a guest OS under VMWare. I then installed Delphi 2007, as well the various support tools that I use. I used this guest to compare how Delphi (and then other tools) worked under XP versus Vista (which I was using as my host OS). &lt;/p&gt;&lt;p&gt;This was all very educational, until at one point something weird happened to my Delphi 2007 installation under my host (personally, I blame the Internet Explorer 8 installation, but I’ll leave that speculation for another article). At the time I was on the road, working for 10 days at a client’s site. And, I didn’t have my RAD Studio 2007 installation disks with me. &lt;/p&gt;&lt;p&gt;Fortunately, I was able to load the guest OS, and continue developing without missing a beat. I simply retrieved a clean copy of the source code from the version control system, and off I went.&lt;/p&gt;&lt;p&gt;A colleague of mine and I talked about what had happened at length, and came to the same conclusion. Maybe I should isolate my development environment from the host operating system as a matter of practice. Furthermore, maybe I should isolate each of my different development environments from each other. Maybe, I thought, this would decrease (I wanted to say eliminate, but that would be blatant hubris) the “gunk,” and reduce the opportunity for incremental decay of performance.&lt;/p&gt;&lt;h2&gt;The Virtual Road is Paved With Good Intentions&lt;/h2&gt;&lt;p&gt;Here is what I did. I bought a fast computer with loads of memory. This machine rus an Intel Core 2 Quad Q9000 CPU running at 2 GHz. (Importantly, this chip supports hardware virtualization.) The machine also has 8 GB of DDR 3 RAM. Plenty of memory, plenty of cores, I was good to go.&lt;/p&gt;&lt;p&gt;From here, I installed onto my host operating system, which was Windows 7 64-bit Home Premium, just that stuff that an ordinary computer user needs, and nothing else. I’m talking about basic stuff, like a word processor, an email client, a browser, antivirus, backup/restore software, a Twitter client, iTunes, and most importantly, virtualization software, which in this case, was VMWare Workstation 7.0. Once all of this was installed, and any available updates applied, I made a nice clean image of this base.&lt;/p&gt;&lt;p&gt;Next, I went to work on the guest operating systems. And I was very systematic about this. First, I created a guest OS with the absolute basics: Windows 7 64-bit Ultimate and antivirus. After installing all updates, I made a full cloned of this guest OS. Onto the clone I installed my very basic stuff that I need in all of my development environments, including version control software, various utilities (like SysInternals Suite), as well as Advantage Database Server and SQL Server (hey, I’m a database guy. No matter what I’m doing, I’m going to need a database). Let’s refer to this virtual machine as “Database Base.”&lt;/p&gt;&lt;p&gt;Now I’m ready for the big time. I cloned Database Base, and installed onto this clone a copy of Delphi 2010. I created a second clone of Database Base and installed Visual Studio 2008 (that took an entire day). In all, I’ve got about seven of these development environments so far.&lt;/p&gt;&lt;p&gt;I also have a nice, 500 GB, portable, external eSATA drive (this laptop supports eSATA, which is about three times faster than USB 2.0). I backed up the image of my host OS, as well as all of the nice new guest OSes, to this drive.&lt;/p&gt;&lt;p&gt;Despite the limitations, which I’ll share with you in a moment, this is a pretty nice setup. First of all, since I don’t actually develop in the host, and have only limited software installed on it, I gain two benefits. First, the host is small, and really, if I had to install it from scratch, it would take hours, not days. And, since I have a nice, clean image of the host, I can actually restore it in a matter of minutes, if necessary.&lt;/p&gt;&lt;p&gt;The second benefit is that there is little going on in the host. Sure, I end up installing necessary evils such as Goto Meeting, Adobe Air, and other stuff like that, but it’s limited, since I am not developing in the host. And, so far (it’s been a couple of months now), the host is showing little signs of slowing down.&lt;/p&gt;&lt;p&gt;And it’s working out fairly well with the virtual machines, so far. Performance is nice I’m allocating 3 GB of RAM to each virtual machine, and this give them some room to stretch, while permitting me to run two of them simultaneously without choking the host OS (though, frankly, things do get slow when the host has only 2 GBs to play on, so I try not to run two guests at a time).&lt;/p&gt;&lt;p&gt;And here’s the great thing. Since I’ve got the individual VMs backed up on the eSATA drive, I can restore them as well. In fact, if my idea works as planned, I should be able to migrate these VMs to my next machine, meaning that I may not have to install RAD Studio 2007 ever again. (I wish it were true, but I know in my heart that it is a lie. I know I’ll have to install RAD Studio 2007 again, but just not as many times as I would had I not taken this particular route.)&lt;/p&gt;&lt;p&gt;There are two additional, and compelling, advantages that I now have. First, if something awful happens to one of my development environments, I’ve got a quick solution. For example, if, when testing a new routine that removes an old key from the Windows registry I accidentally delete every ProgID, no worries. I simply copy the backed up guest VM file from my eSATA drive (which, of course, I have backed up to another storage drive as well), and I’m cooking with gas (this means that I’m back to work quickly).&lt;/p&gt;&lt;p&gt;The second advantage is this. When I get to the point of shipping a product (or hit a major milestone, or whatever), I can make a backup of the VM that I used, and I’ll have that exact environment forever. If I ever need to return to the precise installation of service packs, updates, component sets, and the like, that was used to create that special build, I’ve got it, right there, in that virtual machine that I’ve saved (and backed up, of course).&lt;/p&gt;&lt;h2&gt;But It’s Not Perfect&lt;/h2&gt;&lt;p&gt;I wish I could say that I’m completely satisfied with this solution, but I cannot. There are problems, and some of them are not trivial. They are not horrific, either. In other words, while there are benefits to this approach, I’ve realized some serious limitations, even though I’m only a few months into this experiment.&lt;/p&gt;&lt;p&gt;The first issue is, honestly, a pretty minor one: It takes a little bit longer to get up and running, as far as development goes. In short, I have to wait for two OSes to load (the host and a guest) before I can get to work. Fortunately, these OSes, being Windows 7, do load quickly, so its only a minor inconvenience.&lt;/p&gt;&lt;p&gt;The second issue is more complicated. I run square into a major issue involving software updates. You know, those annoying message you get from our fine friends at Microsoft that inform you that updates are being installed (actually, I don’t get those, because I refuse to let Microsoft decide when to install updates. I get to choose when.) &lt;/p&gt;&lt;p&gt;Well, each of the individual VMs have this problem, which means that right now I don’t install one update, I install eight (host plus seven guests). And, Java and Adobe, and every other bloke on the block who wants to make sure that their bugs don’t destroy my system(s), want to install updates as well. You get the picture, and it isn’t pretty.&lt;/p&gt;&lt;p&gt;Ok, if you only work in one development environment, say Visual Studio, this will not be an issue. Most of us, though, must support a variety of environments. So if you take the road I did, creating a separate guest OS for each, you're going to have to face this issue (which may be to ignore updates altogether, with the exception of major service packs).&lt;/p&gt;&lt;p&gt;There is another, related issue. What if I find a great new tool (for example, the best merge tool that you’ve ever seen, one that flawlessly and perfectly merges two different versions of the same source file). Or, what if I realize, after creating all of my swell VMs, that I failed to install one of my more useful utilities. Well, at present, I have to install these things separately in each VM. A time consuming task if taken all at once, or an annoyance if done piecemeal each time I discover the missing utility in a particular VM.&lt;/p&gt;&lt;p&gt;I was hoping that a feature of VMWare Workstation, called linked clones, was the answer (it’s not). A linked clone is where you create a clone that is based on an existing VM. When I first started looking into linked clones, I thought my problems were solved. But after reading more closely, the VMWare Workstation help makes it clear that a linked clone is associated with a particular snapshot of an existing VM, and that subsequent changes to the cloned VM do not appear in the clone.&lt;/p&gt;&lt;p&gt;I was really hoping that I could create a linked clone to a base VM, like Database Base, and then perform maintenance only to Database Base. For example, if a Windows update is released, I was hoping I could update only Database Base, and all the linked clones created from it would automatically have the updates. That’s not how it works. Even with linked clones, the individual clones need Windows updates (Oh, the humanity!).&lt;/p&gt;&lt;p&gt;While there is a slight performance decrement when using linked clones (not really an issue; we're not trying to run games on these systems), there is a benefit. Specifically, the individual linked clones, while requiring that you keep around a copy of the original VM that you cloned, take up much less disk space than full clones. For example, one full clone I have takes up 19 GB, while a comparable linked clone consumes 6.5 GB of disk space. This makes the linked clone much more convenient to backup and restore.&lt;/p&gt;&lt;h2&gt;What’s The Solution?&lt;/h2&gt;&lt;p&gt;As I said, this approach has some serious benefits, but it doesn’t solve all of the problems, either. Is there a perfect solution? I don't know.&lt;/p&gt;&lt;p&gt;Some developers I've talked to have used a system like this, and report that they are very pleased. In those cases, however, they had only one guest OS to maintain. And, either they only needed one development tool, or they installed all of their development environments on the single guest OS. While that might work, these guest OSes tend to get very large, and increase the likelihood that one tool (Visual Studio, for example) may introduce issues with another (say Eclipse).&lt;/p&gt;&lt;p&gt;Other developers I know accept the predictable cycle of re-installation of the OS and all tools. Of these, one or the more compelling approaches involved not only installing all tools and development environments, but also installing all installation CDs and DVDs to a directory or partition on the system. In addition, each time they install a service pack, they first download it to a folder along side the installation disk images.&lt;/p&gt;&lt;p&gt;So long as you a good image of your base operating system, a backup of your installation images, and keep a handy record of your serial numbers, registration keys, and the like, re-installation goes much faster. It still takes time (days?), but it takes much less time than when using disks.&lt;/p&gt;&lt;p&gt;So, while I'm pretty satisfied with my current setup, I'm still looking for something better. I’d like to hear what you think, and what you’ve done to address these issues. &lt;/p&gt;&lt;p&gt;Copyright © 2009 Cary Jensen. All Rights Reserved&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-6908794620578534114?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/6908794620578534114/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/12/creating-more-manageable-development.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6908794620578534114'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6908794620578534114'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/12/creating-more-manageable-development.html' title='Creating a More Manageable Development Environment'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-526282125191099976</id><published>2009-12-14T17:14:00.001-08:00</published><updated>2009-12-14T17:18:25.063-08:00</updated><title type='text'>In-Memory DataSets: ClientDataSet and .NET DataTable Compared: Part 3: Populating In-Memory DataSets</title><content type='html'>&lt;p&gt;In the preceding article in this series I showed you how to create in-memory datasets at runtime (as well  as how to define a ClientDataSet’s metadata at design time). In this third installment in this series I will demonstrate how to populate your in-memory dataset.&lt;/p&gt;&lt;p&gt;In short, data can be inserted into an in-memory dataset in one of four ways. These are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;You can add data programmatically &lt;/li&gt;&lt;li&gt;You can load previously saved data &lt;/li&gt;&lt;li&gt;You can retrieve data from a database &lt;/li&gt;&lt;li&gt;You can permit an end user to enter data manually &lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Each of these techniques is described in the following sections.&lt;/p&gt;&lt;h2&gt;Writing Data Programmatically&lt;/h2&gt;&lt;p&gt;After creating a ClientDataSet, you use its TField instances to add data programmatically. Alternatively, you can use the ClientDataSet's InsertRecord or AppendRecord methods to add data directly. The following code sample demonstrates adding a record using TField instances:&lt;/p&gt;&lt;pre&gt;ClientDataSet1.Insert;&lt;br /&gt;ClientDataSet1.FieldByName('Account ID').AsInteger := 1001;&lt;br /&gt;//This next line assumes that the second column is a string field&lt;br /&gt;ClientDataSet1.Field[1].AsString :=&lt;br /&gt;  'Frank Borland';&lt;br /&gt;ClientDataSet1.Post;&lt;br /&gt;//Adding a record using the InsertRecord method:&lt;br /&gt;ClientDataSet1.InsertRecord([1002, 'Phillipe Kahn']);&lt;/pre&gt;&lt;p&gt;With .NET DataTables, you add a DataRow instance, which you then populate with data. This is demonstrated in the following code segment.&lt;/p&gt;&lt;pre&gt;  //Add two records to the DataTable&lt;br /&gt;  var DataRow1: DataRow := DataTable1.NewRow();&lt;br /&gt;  //Referencing columns using column name&lt;br /&gt;  DataRow1['Account '] := 1001;&lt;br /&gt;  DataRow1['Customer Name'] := 'Frank Borland';&lt;br /&gt;  DataTable1.Rows.Add(DataRow1);&lt;br /&gt;&lt;br /&gt;  DataRow1 := DataTable1.NewRow();&lt;br /&gt;  //Referencing columns using ordinal position&lt;br /&gt;  DataRow1[0] := 1002;&lt;br /&gt;  DataRow1[1] := 'Bill Gates';&lt;br /&gt;  DataTable1.Rows.Add(DataRow1);&lt;/pre&gt;&lt;h2&gt;Loading Data From Files or Streams&lt;/h2&gt;&lt;p&gt;There are a variety of ways to load a ClientDataSet from a file or stream. One of the most common is to invoke the ClientDataSet's LoadFromFile or LoadFromStream methods. This is shown in the following code sample:&lt;/p&gt;&lt;pre&gt;ClientDataSet1.LoadFromFile('c:\mydata.xml');&lt;/pre&gt;&lt;p&gt;You can also use the XMLData property, which is a read/write property that represents the ClientDataSet's data as a string of XML. The following line of code shows how you can load a ClientDataSet from a memo field of a table:&lt;/p&gt;&lt;pre&gt;ClientDataSet1.XMLData :=&lt;br /&gt;  ClientDataSet2.FieldByName('Hold Data').AsString;&lt;/pre&gt;&lt;p&gt;Loading a DataTable from a file or stream can either be done directly (ADO.NET 2.0 or later) or can be done using a DataSet that contains the DataTable (ADO.NET 1.1). The following line of code demonstrates this technique:&lt;/p&gt;&lt;pre&gt;DataTable1.ReadXML('c:\netdata.xml', XmlReadMode.ReadSchema);&lt;/pre&gt;&lt;p&gt;It is important to note that both ClientDataSets and DataTables that are loaded from a file or stream will obtain their metadata and data store during the loading process. In other words, it is not necessary to define the structure of a ClientDataSet or DataTable prior to loading it from a file or stream.&lt;/p&gt;&lt;h2&gt;The Other Side: Saving Data to Files or Streams&lt;/h2&gt;&lt;p&gt;Of course, for you to be able to correctly load your data into a ClientDataSet or DataTable, the data that you are loading must be in the correct form. Although it may be possible to transform an incompatible XML file into the correct format (using XSLT or Delphi's XML Mapper Utility), in most cases the file or stream that you are loading is one that was previously created by saving a dataset to a file or stream.&lt;/p&gt;&lt;p&gt;With ClientDataSets, you save your data to a file or stream using the SaveToFile or SaveToStream methods. The following is an example of how this may look:&lt;/p&gt;&lt;pre&gt;ClientDataSet1.SaveToFile('c:\mydata.xml', dfXML);&lt;/pre&gt;&lt;p&gt;Similarly, saving a ClientDataSet's data to a memo field in a database looks something like this:&lt;/p&gt;&lt;pre&gt;ClientDataSet2.FieldByName('Hold Data').AsString :=&lt;br /&gt;  ClientDataSet1.XMLData;&lt;/pre&gt;&lt;p&gt;If you'd rather store your data in a more compressed format (the native CDS format takes 20 to 60 percent less space than the XML format), you can do something like this:&lt;/p&gt;&lt;pre&gt;var&lt;br /&gt;  ms: TMemoryStream;&lt;br /&gt;begin&lt;br /&gt;  ms := TMemoryStream.Create;&lt;br /&gt;  try&lt;br /&gt;    ClientDataSet1.SaveToStream(ms);&lt;br /&gt;    TBlobField(ClientDataSet2.&lt;br /&gt;      FieldByName('rawdata')).LoadFromStream(ms);&lt;br /&gt;  finally&lt;br /&gt;    ms.Free;&lt;br /&gt;  end;&lt;/pre&gt;&lt;p&gt;You save the contents of a .NET DataTable using the WriteXml method. While there are a number of overloaded versions of this method (16 at last count), one of the more interesting parameters is the XML write mode.&lt;/p&gt;&lt;p&gt;There are two write mode enumeration values that are typically used by developers writing out the contents of a DataTable. These are XmlWriteMode.WriteSchema and XmlWriteMode.DiffGram.&lt;/p&gt;&lt;p&gt;When you call WriteXML with the WriteSchema enumeration, DataTable metadata is written to the XML file in the form of a schema definition. This information is required in order for a DataTable loading the saved XML to accurately reconstruct the metadata of the DataTable.&lt;/p&gt;&lt;p&gt;By comparison, if you use XmlWriteMode.IgnoreSchema, the DataTable will try to reconstruct the metadata based on the data it encounters while loading, which is rarely 100 percent correct. For example, if you are loading a saved DataTable from XML, and there are string fields, the DataTable will estimate the size of the string fields based on the longest string found in the XML file, which may be substantially shorter than the original DataTable's metadata permits.&lt;/p&gt;&lt;p&gt;Like WriteSchema, XmlWriteMode.DiffGram writes schema information into the XML file. DiffGram writes the change cache information as well, which makes this enumeration essential if you want to persist the DataTable's state. Recall that the change cache is crucial if you want to be able to write the changes back to an underlying database.&lt;/p&gt;&lt;p&gt;The following example shows a DataTable and its change cache being written to an XML file.&lt;/p&gt;&lt;pre&gt;DataTable1.WriteXml('c:\savedat.xml', XmlWriteMode.DiffGram);&lt;/pre&gt;&lt;p&gt;If you need to save a DataTable as text, which is what you need to do in order to persist the DataSet to a memo field of a database, you must write your XML to a descendant of TextWriter (an abstract class). The following example shows a DataSet, and its change log, being persisted to a memo field in a database using a StringWriter (obviously a TextWriter descendant).&lt;/p&gt;&lt;pre&gt;  var StringWriter1: StringWriter;&lt;br /&gt;  StringWriter1 := StringWriter.Create;&lt;br /&gt;  DataTable1.WriteXml(StringWriter1, XmlWriteMode.DiffGram);&lt;br /&gt;  DataTable2.Rows[0]['Current Data'] :=&lt;br /&gt;    StringWriter1.ToString;&lt;/pre&gt;&lt;h2&gt;Retrieving Data From a Database&lt;/h2&gt;&lt;p&gt;Loading data into a ClientDataSet from a database requires the use of a DataSetProvider. The DataSetProvider, in turn, points to a TDataSet descendant. When you make the ClientDataSet active, the DataSetProvider will open the TDataSet, navigate the result set, loading the data in the TDataSet to an OleVariant. Once the DataSetProvider completes this navigation, it (usually) closes the TDataSet (based on a number of factors, which I will not go into now), and populates the ClientDataSet's Data property with the OleVariant.&lt;/p&gt;&lt;p&gt;This process is demonstrated in the following code segment:&lt;/p&gt;&lt;pre&gt;DataSetProvider1.DataSet := SqlDataSet1;  //using dbExpress&lt;br /&gt;ClientDataSet1.ProviderName := DataSetProvider1.Name;&lt;br /&gt;ClientDataSet1.Open;&lt;/pre&gt;&lt;p&gt;Note that when you use this technique, both the ClientDataSet and the DataSetProvider must be owned by the same owner. If that is not the case, or if the DataSetProvider does not have a name, you can use the following technique:&lt;/p&gt;&lt;pre&gt;DataSetProvider1.DataSet := Query1;&lt;br /&gt;ClientDataSet1.Data := DataSetProvider1.Data;&lt;/pre&gt;&lt;p&gt;In .NET, you acquire data into a DataTable from a database using the Fill method of an instance of a DbDataAdapter. Before you can call Fill, the DbDataAdapter class must have a SQL SELECT statement associated with an DbCommand class assigned to its SelectCommand property. The following code segment demonstrates loading a DataTable using a DataStoreDataAdapter:&lt;/p&gt;&lt;pre&gt;Connection1 := DataStoreConnection.Create;&lt;br /&gt;Connection1.ConnectionString := 'host=LocalHost;user=sysdba; ' +&lt;br /&gt;  'password=masterkey;database="C:\Users\Public\Documents\' +&lt;br /&gt;  'Delphi Prism\Demos\database\databases\BlackfishSQL\employee"';&lt;br /&gt;Connection1.Open();&lt;br /&gt;Command1 := Connection1.CreateCommand;&lt;br /&gt;Command1.CommandText := 'select * from customers';&lt;br /&gt;DataAdapter1 := DataStoreDataAdapter.Create;&lt;br /&gt;DataAdapter1.SelectCommand := Command1;&lt;br /&gt;DataTable1 := DataTable.Create();&lt;br /&gt;DataAdapter1.Fill(DataTable1);&lt;/pre&gt;&lt;p&gt;As is the case when you load data into an in-memory dataset from a file or a stream, you do not need to define the metadata or data store in advance of loading a dataset from a database. The metadata will be obtained from the result set you load into the dataset.&lt;/p&gt;&lt;h2&gt;&lt;a name="_Toc224639364"&gt;&lt;/a&gt;&lt;a name="_Toc225302147"&gt;Direct Input From the User&lt;/a&gt;&lt;br /&gt;&lt;/h2&gt;&lt;p&gt;Both ClientDataSets and DataTables can be associated with GUI (graphical user interface) controls and displayed to a user. The user can then use those GUI controls to view and edit the contents of the dataset.&lt;/p&gt;&lt;p&gt;If the in-memory dataset is created by loading the data from a file, stream, or database, it is not necessary to define the metadata of the dataset prior to presenting it to the user. If the dataset is not loaded by one of these techniques, it is necessary to define the metadata (otherwise the dataset will have no fields/columns, and therefore no data store).&lt;/p&gt;&lt;p&gt;In Win32 applications, you display data from datasets using data aware controls (primarily found on the Data Controls page of the Component Palette). At a minimum, you must point a DataSource to the dataset, and then assign the DataSource property of the data aware control to that DataSource. For those data aware controls that bind to a specific field in the dataset, you must also assign the DataField property.&lt;/p&gt;&lt;p&gt;For .NET controls (any visual control that descends from System.Windows.Form.Control), you bind the control using a BindingSource, its DataBindings property, or, if available, use its DataSource field (and its DataMember field if necessary). (Binding System.Web.UI controls is somewhat similar, though differences do exist. This issue, however, is beyond the scope of this article). &lt;/p&gt;&lt;p&gt;In the next segment in this series I will described how to programmatically navigate ClientDataSets and .NET DataTables.&lt;/p&gt;&lt;p&gt;Copyright © 2009 Cary Jensen. All Rights Reserved&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-526282125191099976?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/526282125191099976/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/12/in-memory-datasets-clientdataset-and.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/526282125191099976'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/526282125191099976'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/12/in-memory-datasets-clientdataset-and.html' title='In-Memory DataSets: ClientDataSet and .NET DataTable Compared: Part 3: Populating In-Memory DataSets'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-432806734047800295</id><published>2009-11-30T13:08:00.001-08:00</published><updated>2009-11-30T13:10:16.136-08:00</updated><title type='text'>In-Memory DataSets: ClientDataSet and .NET DataTable Compared: Part 2 Creating In-Memory DataSets</title><content type='html'>&lt;p&gt;Creating an in-memory dataset involves defining its metadata and constructing its data store. Regardless of which in-memory dataset you are creating, this can be achieved in one of two ways. You can both define the metadata and construct the data store programmatically, or you can load the dataset from a query result set. When you load the dataset from a query result set, the contents of the query define the dataset's metadata and the data store is created by the method used to load the data.&lt;/p&gt;&lt;p&gt;With ClientDatasets you have an additional capability, that of defining the metadata at design time. This can be done either by using the Fields Editor to define TField definitions or by using the collection editor for the FieldDefs property to define TFieldDefs. &lt;/p&gt;&lt;p&gt;No matter which design time approach you take (and these are not exclusive options, you can use a combination of TFields and TFieldDefs to define a ClientDataSet’s metadata), you can then right-click the ClientDataSet in the Delphi designer and select Create DataSet to create a design time instance of its data store. &lt;/p&gt;&lt;p&gt;Though the design time definition of a DataTable’s structure is not an option, DataTables support another feature that ClientDataSets do no. Specifically, after you load a DataTable from a query result set at runtime, you can then add additional metadata definitions to configure additional DataColumns. For ClientDataSets, once they have been instantiated and their data store created, no further change can be made to its columns (represented by TField instances).&lt;/p&gt;&lt;p&gt;But before going further, it is worth nothing that this discussion of DataTables (as well as future discussions of DataViews, DataSets, and related ADO.NET classes) apply both to Delphi Prism, Delphi for .NET, as well as other first class .NET classes (such as C# and VB for .NET). However, the syntax of the code samples is slightly different in some cases. Since Delphi Prism is the current (and preferred) Delphi solution for building .NET applications, the examples in this series will use Delphi Prism syntax, that of the Oxygene compiler. &lt;/p&gt;&lt;p&gt;The following code segment demonstrates how to create a ClientDataSet programmatically using its FieldDefs.AddFieldDef method.&lt;/p&gt;&lt;pre&gt;ClientDataSet1 := TClientDataSet.Create(Self);&lt;br /&gt;with ClientDataSet1.FieldDefs do&lt;br /&gt;begin&lt;br /&gt;  Clear;&lt;br /&gt;  with AddFieldDef do&lt;br /&gt;  begin&lt;br /&gt;    Name := 'ID';&lt;br /&gt;    DataType := ftInteger;&lt;br /&gt;  end; //with AddFieldDef do&lt;br /&gt;  with AddFieldDef do&lt;br /&gt;  begin&lt;br /&gt;    Name := 'Name';&lt;br /&gt;    DataType := ftString;&lt;br /&gt;    Size := 30;&lt;br /&gt;  end; //with AddFieldDef do&lt;br /&gt;  with AddFieldDef do&lt;br /&gt;  begin&lt;br /&gt;    Name := 'Date of Birth';&lt;br /&gt;    DataType := ftDate;&lt;br /&gt;  end; //with AddFieldDef do&lt;br /&gt;  with AddFieldDef do&lt;br /&gt;  begin&lt;br /&gt;    Name := 'Active';&lt;br /&gt;    DataType := ftBoolean;&lt;br /&gt;  end; //with AddFieldDef do&lt;br /&gt;end; //with ClientDataSet1.FieldDefs&lt;br /&gt;ClientDataSet1.CreateDataSet;&lt;/pre&gt;&lt;p&gt;Here is another example that uses the FieldDefs.Add method. This example creates a ClientDataSet identical to the one created in the preceding code.&lt;/p&gt;&lt;pre&gt;ClientDataSet1 := TClientDataSet.Create(Self);&lt;br /&gt;with ClientDataSet1.FieldDefs do&lt;br /&gt;begin&lt;br /&gt;  Clear;&lt;br /&gt;  Add('ID',ftInteger, 0, True);&lt;br /&gt;  Add('First Name',ftString, 30);&lt;br /&gt;  Add('Date of Birth',ftDate);&lt;br /&gt;  Add('Active',ftBoolean);&lt;br /&gt;end; //with ClientDataSet1.FieldDefs&lt;br /&gt;ClientDataSet1.CreateDataSet;&lt;/pre&gt;&lt;p&gt;Here is an example of code that creates a .NET DataTable.&lt;/p&gt;&lt;pre&gt;  var DataTable1: DataTable := DataTable.Create;&lt;br /&gt;  var DataColumn1: DataColumn := DataColumn.Create('CustNo',&lt;br /&gt;    System.Type.GetType('System.Int32'));&lt;br /&gt;  DataColumn1.AllowDBNull := False;&lt;br /&gt;  DataColumn1.Unique := True;&lt;br /&gt;  DataTable1.Columns.Add(DataColumn1);&lt;br /&gt;  DataTable1.Columns.Add('FirstName',&lt;br /&gt;    System.Type.GetType('System.String'));&lt;br /&gt;  //Here is another way to define the type&lt;br /&gt;  DataTable1.Columns.Add('LastName', TypeOf(String));&lt;br /&gt;  DataTable1.Columns['LastName'].DefaultValue := 'Not assigned';&lt;/pre&gt;&lt;p&gt;The next article in this series will demonstrate how to populate an in-memory dataset once it has been created.&lt;/p&gt;&lt;p&gt;Copyright © 2009 Cary Jensen. All Rights Reserved&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-432806734047800295?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/432806734047800295/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/11/in-memory-datasets-clientdataset-and.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/432806734047800295'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/432806734047800295'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/11/in-memory-datasets-clientdataset-and.html' title='In-Memory DataSets: ClientDataSet and .NET DataTable Compared: Part 2 Creating In-Memory DataSets'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-5833880755064946365</id><published>2009-11-17T09:02:00.001-08:00</published><updated>2009-11-17T09:02:00.705-08:00</updated><title type='text'>Have You Had An Effortless Delphi Unicode Migration?</title><content type='html'>&lt;p&gt;In my last post (&lt;a title="http://caryjensen.blogspot.com/2009/10/share-your-unicode-migration-story.html" href="http://caryjensen.blogspot.com/2009/10/share-your-unicode-migration-story.html"&gt;http://caryjensen.blogspot.com/2009/10/share-your-unicode-migration-story.html&lt;/a&gt;) I asked Delphi and C++Builder developers to share their Unicode migration success stories. In doing so, I think that I may have implied that converting existing Delphi applications to RAD Studio 2009 or RAD Studio 2010 presented a challenge. This is not always the case. &lt;/p&gt;  &lt;p&gt;The truth is, some applications can be migrated to Unicode versions of RAD Studio with little or no modification. It all depends on the techniques that you have used in your applications.&lt;/p&gt;  &lt;p&gt;I recently presented a five-day Delphi course that covered a wide range of topics, including creating and using DLLs, multithreaded programming, component creation, and basic database development. The target environment for this class was RAD Studio 2009.&lt;/p&gt;  &lt;p&gt;Most of my code samples for this class had been originally created with earlier versions of Delphi, some going back as far as Delphi 1 and 2. While updating this course material to Delphi 2009 I had to migrate more than 60 projects. Of those projects, only a handful of them required modifications associated with Unicode. These were almost exclusively associated with the DLL examples where PChars were being passed as parameters. All the rest simply compiled and ran properly without changes.&lt;/p&gt;  &lt;p&gt;Granted, these projects were very limited in scope, designed specifically to demonstrate a particular feature or technique. As a result, they lack the richness that is normally associated with client applications. Still, given the many different techniques that these code samples represented, it is impressive that most required no modification to run in Delphi 2009.&lt;/p&gt;  &lt;p&gt;Is this typical, or is it the exception? I want to hear from you. Have you moved a Delphi application to RAD Studio 2009 or 2010 with little or no modifications? If so, your story is also important.&lt;/p&gt;  &lt;p&gt;Send me a quick email to &lt;a href="mailto:cjensen@jensendatasystems.com"&gt;mailto:cjensen@jensendatasystems.com&lt;/a&gt; with the subject line Delphi Migration. Tell me a little about the size and scope of the project, and how much effort you migration required. Other Delphi developers will be grateful.&lt;/p&gt;  &lt;p&gt;Copyright © 2009 Cary Jensen. All Rights Reserved&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-5833880755064946365?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/5833880755064946365/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/11/have-you-had-effortless-delphi-unicode.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/5833880755064946365'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/5833880755064946365'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/11/have-you-had-effortless-delphi-unicode.html' title='Have You Had An Effortless Delphi Unicode Migration?'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-352597110982034319</id><published>2009-10-28T10:26:00.001-07:00</published><updated>2009-10-28T10:53:03.599-07:00</updated><title type='text'>Share Your Unicode Migration Story</title><content type='html'>&lt;p&gt;Delphi developers everywhere (and I am including C++Builder developers here), I am asking for your help in preparing a white paper that I hope will serve the greater community for a long time to come. I have been asked to assemble a white paper on Unicode migration for Delphi and C++ developers, and I want to hear your story.&lt;/p&gt;&lt;p&gt;When Senior Director of Delphi Solutions Mike Rozlog originally approached me about putting together this paper, I was enthusiastic. However, there was a slight problem. &lt;/p&gt;&lt;p&gt;While I can easily write about issues concerning the size of PChars or the ins-and-outs of the new UnicodeString type, I felt that this white paper was too important to simply repeat the obvious. Instead, I really wanted to go for an “in the real world this is what you have to do” approach. And while it is a noble goal, it is simply too much for one developer. We each have our specializations, and each of us, if we do run into migration problems, is going to do so in our particular domain.&lt;/p&gt;&lt;p&gt;For example, I am primarily a database developer, and I often work at a pretty high level; querying databases, building user interfaces, generating reports, you know, the bread-and-butter kind of stuff. And as far as Unicode migration goes, I’ve got it easy. Several of my larger applications have converted with few or no problems. &lt;/p&gt;&lt;p&gt;But some of you work much closer to the metal, making extensive use of calls to the operating system API, using sophisticated third-party libraries, manipulating data at the bit and byte level, and generally working in a world where the size of characters and strings matters. If so, your Unicode migration has likely encountered challenges, some incidental and some significant, that you’ve had to solve along the way. It’s these real world stories that I want to hear about.&lt;/p&gt;&lt;p&gt;Now, you might be wondering, why have I been asked to write this white paper, given that I don’t normally have to deal with some of the more serious Unicode migration issues? It’s a valid question, and there is an equally valid answer. Another of my skills, beyond software development, is the ability to communicate clearly and simply about complex topics. With your input, I intend to produce a white paper that is organized, clear, and helpful to those whose Unicode migration is not yet started or complete. And, I want the paper to reflect the wealth of experience of the collective Delphi community on the subject, not just my own experience.&lt;/p&gt;&lt;p&gt;Here is what I am looking for. If you have solved a Unicode-related challenge in converting existing code or techniques to Delphi 2009 or later, I want your input. The basic input that I need is your name and email address (so I can contact you if I have questions), the company you work for (if you are willing to share that), a description of the problem, and a description of the solution. Code samples that demonstrate the problem and solution are preferred (though we do not want anything that is proprietary or non-disclosable).&lt;/p&gt;&lt;p&gt;If your solution is included in the white paper, you will be recognized for your contribution by name, unless you specifically request to remain anonymous.&lt;/p&gt;&lt;p&gt;You can email your contribution to Tim Del Chiaro at &lt;a href="mailto:Tim.DelChiaro@embarcadero.com"&gt;Tim.DelChiaro@embarcadero.com&lt;/a&gt;. Alternatively, you can submit your contribution using the Get Published interface at the Embarcadero Developer Network (EDN). A detailed description of how to submit your contribution can be found in the EDN article located &lt;a href="http://edn.embarcadero.com/article/40018"&gt;http://edn.embarcadero.com/article/40018&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Of course, you can also send questions or contributions to me at &lt;a href="mailto:cjensen@jensendatasystems.com"&gt;cjensen@jensendatasystems.com&lt;/a&gt;. Please include the words “Unicode migration” in the subject line. Please also feel free to contact me if you need help putting together your story. &lt;/p&gt;&lt;p&gt;We are looking to release this white paper as soon as possible, so we necessarily have to set a deadline for contributions. Please submit your Unicode migration story by 5:00pm (GMT-8), November 27&lt;sup&gt;th&lt;/sup&gt;, 2009.&lt;/p&gt;&lt;p&gt;Now, finally, a few words to please the Lawyers. Any Unicode migration contribution you send to me, Tim Del Chiaro, or upload to the Get Published interface, will be interpreted as explicit permission from you for Embarcadero Technologies to include your contribution, or descriptions of it, in the Unicode migration white paper, as well as in any other form, and that you have the right to grant this permission. &lt;/p&gt;&lt;p&gt;Thank you for your consideration. I sincerely hope to hear from many of you with your stories, and look forward to producing a paper that will help all Delphi developers with their Unicode migration challenges.&lt;/p&gt;&lt;p&gt;Copyright © 2009 Cary Jensen. All Rights Reserved&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-352597110982034319?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/352597110982034319/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/10/share-your-unicode-migration-story.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/352597110982034319'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/352597110982034319'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/10/share-your-unicode-migration-story.html' title='Share Your Unicode Migration Story'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-573737848359417196</id><published>2009-09-30T01:04:00.000-07:00</published><updated>2009-09-30T02:48:27.019-07:00</updated><title type='text'>In-Memory DataSets: ClientDataSet and .NET DataTable Compared: Part 1 Overview</title><content type='html'>As some of you know, I have been a big fan of Delphi's ClientDataSets since they were first introduced in Delphi 3 (that's way back in 1997). When .NET shipped, its data access framework, ADO.NET, also included an in-memory dataset, named the DataTable. (.NET also includes the DataSet class, but in most cases, the DataTable class bears the strongest resemblance to the ClientDataSet.)&lt;br /&gt;&lt;br /&gt;Both ClientDataSets and DataTables are in-memory datasets, and as such, share a lot of features in common. On the other hand, they are radically different in a number of interesting ways. In this series of articles, which begins with this one, I will examine the general features of in-memory datasets, and provide a direct comparison between ClientDataSets and .NET DataTables.&lt;br /&gt;&lt;br /&gt;This article begins with a introduction to in-memory datasets in general. In future posts I will provide explicit code examples of how to perform various tasks with these two datasets, including how to create them in code, reading and writing data, sorting, filtering, persisting, navigating, and so on.&lt;br /&gt;&lt;br /&gt;I hope you enjoy.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;strong&gt;Developing with Disconnected Datasets&lt;/strong&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Disconnected datasets are database table-like structures that are stored in memory. These types of datasets are sometimes referred to as cached datasets or in-memory datasets. In this series they will be referred to as in-memory datasets.&lt;br /&gt;&lt;br /&gt;In-memory datasets are structured, high-performance, self-describing data structures temporarily stored in memory. A significant feature of in-memory datasets is that they maintain, and can persist, a change log. The change log permits you to programmatically determine what changes have been made to the data since some point in time, often when the data was originally loaded into the dataset. This information is essential if you need to persist these changes back to an original source, such as a Web service, underlying database, or other persistence mechanism.&lt;br /&gt;&lt;br /&gt;Data persistance in the .NET framework is based on in-memory datasets, and Delphi has included this capability since Delphi 3 in the form of the ClientDataSet.&lt;br /&gt;&lt;br /&gt;This series begins with an overview of the features that make in-memory datasets so useful, including their self-descriptive nature, ability to hold sophisticated relational data structures, their close association with XML, and their persistence and management of change information. How these features are surfaced in both ClientDataSets and .NET datasets is discussed in this section.&lt;br /&gt;&lt;br /&gt;In-memory datasets are the cornerstone of modern software development. Nothing confirms this statement as much as Microsoft's commitment to in-memory datasets as a central aspect of the database framework in .NET, ADO.NET.&lt;br /&gt;&lt;br /&gt;While most developers consider in-memory datasets for the presentation layer in applications, this use represents only a fraction of the possibilities for these powerful data structures. As this series will demonstrate, the characteristics of in-memory datasets make them a valuable tool for many different aspects of application development. The following are the essential feature of in-memory datasets:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;High performance&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Self describing&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Flexible&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Change log managing&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Persistable&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Individually, these characteristics provide a compelling argument for using in-memory datasets in your applications. But it is the combination of these features in a single, easy to use class that makes them so valuable for a wide range of software features. The following sections look at each of these features in greater depth.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;span style="font-size:130%;"&gt;High Performance&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;In-memory datasets reside entirely in RAM (random access memory). Consequently, operations on the data they contain, including searches, filters, and sorts, are very fast. This is particularly true with respect to ClientDataSets, since these can have indexes on this data as well. But even for .NET datasets, which currently support a single index at any given moment (the primary index), data-related operations are many times faster than those that require disk reads (as is the case with a physical database).&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;span style="font-size:130%;"&gt;Self Describing&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;In-memory datasets are formally designed around the concept of a database table. Unlike an array or sequence, whose data elements have a data type, and that's about it, the fields of a data table each have a name, a data type, and sometimes a data size (for example, the size of a text field or precision of a floating point number).&lt;br /&gt;&lt;br /&gt;In addition, the fields of a data table may have constraints, such as a required field constraint, or referential integrity constraints when two or more in-memory tables are related. This information is typically referred to as metadata, which is data about data.&lt;br /&gt;&lt;br /&gt;In ClientDataSets, you access the metadata of a dataset using the Fields property of the dataset, which contains a collection of TField instances. In .NET data tables, you access this information using the Columns property, which contains a collection of DataColumn instances.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;span style="font-size:130%;"&gt;Flexible&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;In-memory datasets are designed to hold nearly any kind of data that might be stored in a physical database. This includes primitive data values, such as integers, strings, real numbers, and date/time values. But it also includes variable length objects, such as memos and Blobs (binary large objects). As a result, an in-memory dataset can hold the pages of a Web site, PDF files, and even executables (.EXEs and .DLLs). If it can be stored in a file on disk, it can be stored in an in-memory dataset (obviously, subject to the limits imposed by your available RAM).&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;span style="font-size:130%;"&gt;Change Log Managing&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Both ClientDataSets and .NET DataTables have a change log. The change log permits you to manage the unresolved changes that have been posted to the dataset's data since you loaded it into memory. This management includes the ability to determined precisely what changes have occurred (which records were inserted, deleted, and field-level modifications), revert changes to their prior state, cancel all changes, or commit those changes permanently, thereby erasing the change log. With ClientDataSets, this change log if held in the Delta property. For .NET DataTables, you use the RowStateFilter of a DataView to access the change log.&lt;br /&gt;&lt;br /&gt;To manage the change log for a ClientDataSet, you use its methods, such as RevertRecord, UndoLastChange, CancelChanges, and ApplyUpdates. In addition, you can use the RecordStatus, StatusFilter, and Fields properties to examine the change log contents.&lt;br /&gt;&lt;br /&gt;With .NET DataTables, you use the methods of the DataTable and DataView classes to control the change log, including NewRow, DeleteRow, AcceptChanges, and RejectChanges. To examine the change log, you use the RowStateFilter and Rows properties.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;span style="font-size:130%;"&gt;Persistable&lt;br /&gt;&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;Of all the features supported by in-memory datasets, the ability to persist state is arguably the most powerful. Not only can you save an in-memory dataset's data, but you can save its change log as well. Specifically, it is possible to save the current state of an in-memory dataset to a file, Web service, or memo field of a database, and then to restore that dataset at a later time to its exact prior state. In short, there is absolutely no difference between the in-memory dataset prior to, and following, its persistence.&lt;br /&gt;&lt;br /&gt;Consider the following scenario: After loading data into memory, and making several edits to an in-memory dataset, that dataset can be written to a file. At a future time, that dataset can be restored from the file, and the edits that were previously performed can be examined and rejected or accepted.&lt;br /&gt;&lt;br /&gt;Furthermore, since the change log is restored to its exact prior state, that information can be used to resolve those edits to the underlying database from which the data was originally loaded. No information is lost during the time that the dataset is in storage, no matter how long its state was persisted.&lt;br /&gt;&lt;br /&gt;Copyright (c) 2009 Cary Jensen. All Rights Reserved&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-573737848359417196?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/573737848359417196/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/09/in-memory-datasets-clientdataset-and.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/573737848359417196'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/573737848359417196'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/09/in-memory-datasets-clientdataset-and.html' title='In-Memory DataSets: ClientDataSet and .NET DataTable Compared: Part 1 Overview'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-1502862099377508108</id><published>2009-08-30T11:58:00.001-07:00</published><updated>2009-08-30T11:58:25.035-07:00</updated><title type='text'>Notifications and the Advantage Database Server</title><content type='html'>&lt;p&gt;The Advantage Database Server (ADS) is a high-performance, low maintenance, remote database server from Sybase iAnywhere. In addition to being one of the fastest database servers available, it is mind-numbingly simple to deploy. This, along with its ability to self-configure, and with no need for a formal database administrator in most situations, makes the Advantage Database Server a favorite with vertical market developers deploying small to medium size applications.&lt;/p&gt;  &lt;p&gt;Over the past decade, ADS has gained one or more impressive new features associated with enterprise-level database servers with each major release. Advantage 6 introduced stored procedures, views, users and groups, and data dictionaries. Advantage 7 added triggers to the mix, while Advantage 8 provided replication and online backup services. &lt;/p&gt;  &lt;p&gt;ADS 9 was no different. Advantage Database Server 9.0 added support for notifications. (Note that the current release is ADS 9.1, which is a free upgrade for 9.0 users.)&lt;/p&gt;  &lt;p&gt;Notifications provide a mechanism for communicating to client applications that something has occurred in the database. For example, notifications can be used to inform a client application that data in a critical table has changed. The client application can then use this information to refresh its view of that table, providing the end-user real-time access to the most current data. Or a notification might be used to signal a client that a record has been added to a special table created for the purpose of communicating messages from the system administrator to the end users. The client can then read the latest message and display it within its interface.&lt;/p&gt;  &lt;p&gt;Client applications subscribe to an event by calling the sp_CreateEvent system stored procedure, to which it passes a string identifying the event of interest. The events themselves are created with calls to the system stored procedure sp_SignalEvent, and can only be executed from within a stored procedure or trigger. &lt;/p&gt;  &lt;p&gt;Like sp_CreateEvent, sp_SignalEvent is passed a string that corresponds to events that one or more clients are expected to subscribe to. The call to sp_SignalEvent is passed an additional parameter that determines whether subscribing clients should be signaled immediately, or only after the current transaction is committed (assuming that the call to sp_SignalEvent was performed within a transaction), permitting the database to signal events that might be rolled back, in which case the client will not receive the notification.&lt;/p&gt;  &lt;p&gt;Clients receive an event by calling either sp_WaitForEvent or sp_WaitForAnyEvent. Both of these procedure calls are synchronous, or blocking. Specifically, the call to either of these methods will not return until an event is signaled by the database, or the call times out. The timeout, which is specified by a parameter passed to the wait procedures, can either be infinite, or limited to a specified number of milliseconds. (Note that the wait call times out immediately if the database has already signaled the event since the last wait call.)&lt;/p&gt;  &lt;p&gt;Because sp_WaitForEvent and sp_WaitForAnyEvent are blocking calls (the call does not complete until a signal is received or the timeout expires, whichever comes first), most developers who need real-time notifications from the database will employ multithreaded techniques. In other words, a call to sp_WaitForEvent or sp_WaitForAnyEvent will almost always be performed by a secondary thread, which is designed to specifically await the notification without interfering with the primary thread of execution.&lt;/p&gt;  &lt;p&gt;Here is an example of a typical notification scenario. Before a client application begins displaying a table that must always display the most current data to the user, the client will subscribe to an event associated with updates to that table. The client does this by calling sp_CreateEvent, passing the string you have created to uniquely identify the event.&lt;/p&gt;  &lt;p&gt;On the server, you create AFTER INSERT, AFTER UPDATE, and AFTER DELETE triggers on the underlying table. From these triggers, you call sp_SignalEvent, again using the same string that the client applications use to subscribe to the event.&lt;/p&gt;  &lt;p&gt;As soon as one of the client applications begins displaying the critical data, the client creates a new thread that then calls either sp_WaitForEvent or sp_WaitForAnyEvent. Code that immediately follows the call to the wait procedure typically tests whether the call has timed out or has been signaled, which can be determined by the values returned by this stored procedure call. &lt;/p&gt;  &lt;p&gt;If the call was signaled, the thread will attempt to lock a synchronization object (such as a critical section or use a monitor to lock an object) and update the display of the data. This synchronization object must also be shared with the thread responsible for the user interface, in order to prevent the multiple threads from interfering with each other.&lt;/p&gt;  &lt;p&gt;Once the display has been updated, the notification thread releases the synchronization object and loops back to another call to the wait procedure.&lt;/p&gt;  &lt;p&gt;While this type of programming requires careful consideration of shared resources, such as the user interface elements that display the data, when done properly, any signals received by the clients can be reacted to immediately as opposed to enduring the delays inherent with polling (the periodic check for a change in some state). &lt;/p&gt;  &lt;p&gt;Like ADS, ALS also supports notifications. However, due to the local server nature of ALS, and the lack of a centralized service, ALS notifications are much less responsive than those supported by ADS.&lt;/p&gt;  &lt;p&gt;Copyright © 2009 Cary Jensen. All Rights Reserved.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-1502862099377508108?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/1502862099377508108/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/08/notifications-and-advantage-database.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/1502862099377508108'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/1502862099377508108'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/08/notifications-and-advantage-database.html' title='Notifications and the Advantage Database Server'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-6215869260230624289</id><published>2009-07-30T14:45:00.001-07:00</published><updated>2009-09-21T11:53:24.017-07:00</updated><title type='text'>Introduction to Language Integrated Query with Delphi Prism, Part 2</title><content type='html'>&lt;p&gt;In part 1 of this 2 part series I provided you with a basic introduction to Language Integrated Query, or LINQ. In this second part I take a look at several specific implementations of LINQ, including LINQ to Objects, LINQ to DataSets, and LINQ to XML. I also have an opportunity to introduce iterator methods, special methods that you implement to return a sequence for use by LINQ queries.&lt;/p&gt;&lt;h2&gt;LINQ to Objects&lt;/h2&gt;&lt;p&gt;LINQ to Objects is any version of LINQ that does not employ a LINQ provider. Consequently, all LINQ queries shown in part 1 of this series are examples of LINQ to Objects.&lt;/p&gt;&lt;p&gt;Here is another example.&lt;/p&gt;&lt;pre&gt;var Customers := GetCustomers;&lt;br /&gt;var query := from c in Customers&lt;br /&gt;  where (c.Age &amp;gt; 10)&lt;br /&gt;  and (c.Age &amp;lt; 40)&lt;br /&gt;  and (c.Active = true&amp;lt;) select c;&lt;br /&gt;for each m in query do&lt;br /&gt;  ResultsList.Items.Add(&lt;br /&gt;    String.Format("Name: {0}, Age={1}",&lt;br /&gt;                  [m.Name, m.Age]));&lt;/pre&gt;&lt;p&gt;Note: If you had wanted to create a query that produced a sequence of objects that only included the Name and Age members, you could have used the technique shown in part 1 where the select clause returned a newly created object (which was an anonymous object in that example, but could have been any compatible type).&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;In the preceding query, a sequence of Customer objects is returned by the GetCustomers function. This particular function is an example of an iterator method. Iterator methods are discussed in the following section.&lt;br /&gt;&lt;br /&gt;Granted, this is a simple example. However, there are many different methods in the .NET framework class library that return arrays, collections, and other queryable data sources. As a result, it is possible to use LINQ to perform a wide variety of useful tasks.&lt;/p&gt;&lt;p&gt;Examples of some of these uses can be found in the Windows SDK for .NET 3.5. For example, there are a number of examples of how to use LINQ to explore files and directories on the local file system. This is possible because the System.IO.Directory.GetFiles method returns an array of strings containing the names of the files in a given directory. Arrays, as you’ve seen, can be used in LINQ queries.&lt;/p&gt;&lt;h2&gt;Interator Methods and Yield Statements&lt;/h2&gt;&lt;p&gt;Iterators are special methods that return a sequence. You declare an iterator by including the iterator keyword in the method signature. Each element in the sequence is specifically returned by the iterator through a call to yield.&lt;/p&gt;&lt;p&gt;This is demonstrated in the following code sample. First, the iterator is declared using the following statement.&lt;/p&gt;&lt;pre&gt;method GetLowDigits: sequence of Integer; iterator;&lt;/pre&gt;&lt;p&gt;Next, the implementation of the iterator includes one or more calls to yield, which, like exit, can be used to return a value (though yield does not cause a return from the method in the same way that exit does).&lt;/p&gt;&lt;pre&gt;method MainForm.GetLowDigits: sequenbce of Integer;&lt;br /&gt;&lt;p&gt;begin&lt;br /&gt;&lt;/p&gt;&lt;p&gt;for i: Integer := 1 to 9 do&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  yield i;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;end;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;The following code demonstrates the use of this iterator. &lt;/p&gt;&lt;pre&gt;var numbers := GetLowDigits;&lt;br /&gt;&lt;p&gt;for each n in numbers do&lt;br /&gt;  MessageBox.Show(n.ToString);&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Iterator methods are not executed when the sequence is assigned to a sequence reference. Specifically, the var declaration and initialization in the preceding code segment did not cause the GetLowDigits iterator to execute. Instead, it was the specific iteration over the sequence in the for each loop that caused the iterator to be executed. Furthermore, the iterator was not executed all at once. Instead, the iterator is executed up until it returns the first element of the sequence, that element is then used by the for each loop, which will then call back to the iterator to get the next value in the sequence, and so on.&lt;/p&gt;&lt;p&gt;The yield keyword can be used to return either a single element of the sequence or a reference to a sequence (which could be an array, a method that returns an array, or other similar reference).&lt;/p&gt;&lt;p&gt;Here is another example of an iterator and a sequence. This code requires a type declaration, which looks like the following.&lt;/p&gt;&lt;pre&gt;Customer = class&lt;br /&gt;&lt;p&gt;private&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  FName: String;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  FAge: Integer;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;public&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  property Name: String read FName write FName;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  property Age: Integer read fAge write FAge;&lt;br /&gt;end;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Here is the declaration of the iterator.&lt;/p&gt;&lt;pre&gt;method GetCustomers: sequence of Customer; iterator;&lt;/pre&gt;&lt;p&gt;And here is the implementation of the iterator.&lt;/p&gt;&lt;pre&gt;method MainForm.GetCustomers: sequence of Customer;&lt;br /&gt;&lt;p&gt;begin&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  yield new Customer(Name:= 'Allan', Age := 10);&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  yield new Customer(Name := 'Bob', Age := 25);&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  yield new Customer(Name := 'Craig', Age := 18);&lt;br /&gt;end;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Now that you understand the iterator, it is easy to see that the following LINQ query, which was introduced in the preceding section, populates the listbox named ResultList with the values Name: Bob, Age=25 and Name: Craig, Age=18.&lt;/p&gt;&lt;p&gt;var Customers := GetCustomers;&lt;br /&gt;var query := from c in Customers&lt;br /&gt;where (c.Age &amp;gt; 10) and (c.Age &amp;lt; 40) and (c.Active = true&amp;lt;) select c;&lt;br /&gt;for each m in query do&lt;br /&gt;  ResultsList.Items.Add(&lt;br /&gt;    String.Format("Name: {0}, Age={1}", [m.Name, m.Age]));&lt;/p&gt;&lt;p&gt;The implementation of the iterator in this case used the named parameters feature of Delphi Prism’s nameless constructors to return each of the customers (which in reality would have probably been populated with data from an external source, such as a database). Also, the Customers type declaration used type inference to determine that Customers variable was a sequence of Customer.&lt;/p&gt;&lt;h2&gt;What is LINQ to DataSets&lt;/h2&gt;&lt;p&gt;LINQ to DataSets is a LINQ provider that permits you to use LINQ queries against DataTables, DataRows, and other classes in the ADO.NET framework. In order to use LINQ to DataSet, you must add the System.Data.DataSetExtensions assembly to the references section of your Delphi Prism project.&lt;/p&gt;&lt;p&gt;This section is designed to provide you with a brief introduction to LINQ to DataSet. As such, it does not go into ADO.NET, which is the data connectivity framework in .NET. &lt;/p&gt;&lt;p&gt;Note: LINQ to DataSet should really probably be called LINQ to DataTable or LINQ to DataRows, or even LINQ to ADO.NET. None of the operations in LINQ to DataSet are performed on instances of the DataSet class. Instead, they are performed on DataTables and DataRows.&lt;/p&gt;&lt;p&gt;As you learned earlier in this article, LINQ queries can be performed on any object that implements the IEnumerable&amp;lt;T&amp;gt; interface. DataTables (and DataRows) do not implement IEnumerable. However, they both have extension methods that support IEnumerable. These are defined in the DataTableExtensions class, which is located in the System.Data.DataSetExtensions namespace (which is why you must add this assembly to your project references folder).&lt;/p&gt;&lt;p&gt;There are only three extension methods in the DataSetExtensions class. These are AsDataView, AsEnumerable, and CopyToDataTable. For DataTables, the key method is AsEnumerable, which returns an enumerable collection of DataRows (records). &lt;/p&gt;&lt;p&gt;This is demonstrated in the following segment code.&lt;/p&gt;&lt;pre&gt;method MainForm.LINQToDataSet_Click(sender: System.Object;&lt;br /&gt;  e: System.EventArgs);&lt;br /&gt;var&lt;br /&gt;  Connection: DataStoreConnection;&lt;br /&gt;  Adapter: DataStoreDataAdapter;&lt;br /&gt;  DataTable1: DataTable;&lt;br /&gt;begin&lt;br /&gt;  Connection := new DataStoreConnection(conStr);&lt;br /&gt;  Connection.Open;&lt;br /&gt;  try&lt;br /&gt;    Adapter := new DataStoreDataAdapter(&lt;br /&gt;      'SELECT * FROM   CUSTOMER', connection);&lt;br /&gt;    DataTable1 := new DataTable;&lt;br /&gt;    DataTable1.TableName := 'Customer';&lt;br /&gt;    Adapter.Fill(DataTable1);&lt;br /&gt;    var query :=&lt;br /&gt;      from cust in DataTable1.AsEnumerable&lt;br /&gt;      where cust['ON_HOLD'].Equals(DbNull.Value)&lt;br /&gt;      and (cust.Field&amp;lt;string&amp;gt;('STATE_PROVINCE') = 'CA');&lt;br /&gt;    for each c in query do&lt;br /&gt;    begin&lt;br /&gt;      ResultsList.Items.Add(&lt;br /&gt;        String.Format('Customer Number: {0}' +&lt;br /&gt;        ' Company Name: {1}', c['Cust_No'].ToString,&lt;br /&gt;        c['Customer'].ToString));&lt;br /&gt;    end;&lt;br /&gt;  finally&lt;br /&gt;    Connection.Close;&lt;br /&gt;  end;&lt;br /&gt;end;&lt;/pre&gt;&lt;p&gt;Once the DataTable has been populated with data from the SQL query, the AsEnumerable method of the DataTable is called, and this object is used in the LINQ query. Interestingly enough, the object returned from the AsEnumerable method is a generic IEnumerable&amp;lt;T&amp;gt;, where the type is a collection of DataRows.&lt;/p&gt;&lt;p&gt;As you consider this code segment, you might conclude that you would be better off using a SQL statement that performs the filtering of the results, rather than use a LINQ query. While that may be true in some instances, if you want to load a DataTable once, and perform numerous queries on the returned value, the LINQ query offers performance benefits. (Note that another alternative is to use a DataView to filter and sort the DataTable results.)&lt;/p&gt;&lt;p&gt;Note that in addition to LINQ to DataSet, the .NET framework provides the LINQ to SQL provider. Unlike LINQ to DataSet, which can be used against any database for which there is a .NET data provider, LINQ to SQL is specifically designed to work only with Microsoft SQL Server. LINQ to Entities is another LINQ provider that can be used against supported databases.&lt;/p&gt;&lt;h2&gt;What is LINQ to XML&lt;/h2&gt;&lt;p&gt;LINQ to XML provides a programming model, similar to XPath, with which you can create, edit, query, and save XML documents. LINQ to XML provides you an alternative to using the XML DOM (document object model), which is what is used by the XMLDocument class in the .NET framework. Like the DOM, LINQ to XML works with the XML document in memory. From there you can read, query, and write data to the XML document, after which you can stream it to a service or write it to a file, if you desire.&lt;/p&gt;&lt;p&gt;As is the case with the LINQ to DataSet topic that precedes this section, this discussion is designed to provide you with a basic introduction to LINQ to XML. It does not, however, cover general XML issues.&lt;/p&gt;&lt;p&gt;In fact, LINQ to XML is far more involved than LINQ to DataSet. This is because LINQ to XML not only supports features necessary for querying, but as mentioned earlier, an entire programming model for working with XML. As a result, this section will cover just enough information to get you started. &lt;/p&gt;&lt;p&gt;Unlike LINQ to DataSet, where most of the functionality is found in extension methods added to DataTable and DataRow, LINQ to XML, found in the System.Xml.Linq namespace, includes a large number of concrete classes that you can use to work with XML documents.&lt;/p&gt;&lt;p&gt;Of these classes, there are three that you are likely to use most often. These are XDocument and XElement, which descend from XNode, and XAttribute, which descends from XObject. (XNode, by the way, also descends from XObject.)&lt;/p&gt;&lt;p&gt;You can create a valid XML document using either XDocument or XElement, though XDocument provides a little more support for this. Specifically, there are certain advanced features of XML documents that you can access through the XDocument class which are unavailable through the XElement class. XAttribute, by comparison, is used to define attributes of XML elements.&lt;/p&gt;&lt;p&gt;But before you get started, at a minimum you will need to add the System.Xml.Linq namespace to your uses clause. In addition, you may have to add one or more of the following additional namespaces, depending on what features your code will use: System.Xml, System.Xml.Schema, System.Xml.XPath, and System.Xml.Xsl.&lt;/p&gt;&lt;h4&gt;Creating XML Documents using LINQ to XML&lt;/h4&gt;&lt;p&gt;As mentioned previously, you can create XML documents using either the XDocument or the XElement class. The following example demonstrates how to create an XML document using the XElement class.&lt;/p&gt;&lt;pre&gt;element := new XElement('customers',&lt;br /&gt;               new XElement('customer',&lt;br /&gt;                 new XAttribute('custno', 1001),&lt;br /&gt;                 new XElement('name', 'John Doe'),&lt;br /&gt;                 new XElement('address',&lt;br /&gt;                   '101 Broadway Avenue'),&lt;br /&gt;                 new XElement('city', 'New York'),&lt;br /&gt;                 new XElement('state', 'NY'),&lt;br /&gt;                 new XElement('zip',&lt;br /&gt;                   '00123')                                            ),&lt;br /&gt;              new XElement('customer',&lt;br /&gt;                  new XAttribute('custno', 1002),&lt;br /&gt;                  new XElement('name', 'John Doe'),&lt;br /&gt;                  new XElement('address',&lt;br /&gt;                    '1001 Main Street'),&lt;br /&gt;                  new XElement('city', 'Los Angeles'),&lt;br /&gt;                  new XElement('state', 'CA'),&lt;br /&gt;                  new XElement('zip', '90123')&lt;br /&gt;              )&lt;br /&gt;          );&lt;br /&gt;element.Save(XmlFileName);&lt;/pre&gt;&lt;p&gt;This code creates an XML file with a root element named customers. The root element has two child elements named customer. Each child element has one attribute and four child elements. The following is how the XML file created by this code looks.&lt;/p&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;br /&gt;&lt;p&gt;&amp;lt;customers&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt; &amp;lt;customer custno="1001"&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;name&amp;gt;John Doe&amp;lt;/name&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;address&amp;gt;101 Broadway Avenue&amp;lt;/address&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;city&amp;gt;New York&amp;lt;/city&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;state&amp;gt;NY&amp;lt;/state&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;zip&amp;gt;00123&amp;lt;/zip&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt; &amp;lt;/customer&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt; &amp;lt;customer custno="1002"&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;name&amp;gt;John Doe&amp;lt;/name&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;address&amp;gt;1001 Main Street&amp;lt;/address&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;city&amp;gt;Los Angeles&amp;lt;/city&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;state&amp;gt;CA&amp;lt;/state&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;  &amp;lt;zip&amp;gt;90123&amp;lt;/zip&amp;gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt; &amp;lt;/customer&amp;gt;&lt;br /&gt;&amp;lt;/customers&amp;gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt; &lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Creating this same file using an XDocument looks similar, though there are differences. &lt;/p&gt;&lt;p&gt;The documentation for LINQ with XML includes a large number of demonstrations of XML definition using declarations like the one provided previously. These declarations, however, are anything but flexible. In other words, they always create the same XML file, which is rarely useful.&lt;/p&gt;&lt;p&gt;The following example, which obtains its data from an ADO.NET data reader, produces an XML file similar in structure to the preceding one, except that its data is entirely based on the results of a SQL query.&lt;/p&gt;&lt;pre&gt;Connection := new DataStoreConnection(conStr);&lt;br /&gt;Connection.Open;&lt;br /&gt;try&lt;br /&gt;  Command := Connection.CreateCommand;&lt;br /&gt;  Command.CommandText := 'select cust_no, customer, ' +&lt;br /&gt;    'address_line1, city, state_province, ' +&lt;br /&gt;    'postal_code from customer';&lt;br /&gt;  DataReader := Command.ExecuteReader;&lt;br /&gt;  try  &lt;br /&gt;    document := new XDocument(&lt;br /&gt;      new XElement('customers'));&lt;br /&gt;    while DataReader.Read do&lt;br /&gt;    begin&lt;br /&gt;      attribute := new XAttribute('custno',&lt;br /&gt;        DataReader.GetString(0));&lt;br /&gt;      element :=&lt;br /&gt;        new XElement('customer',&lt;br /&gt;          new XElement('name', DataReader.GetString(1)),&lt;br /&gt;          new XElement('address',&lt;br /&gt;            DataReader.GetString(2)),&lt;br /&gt;          new XElement('city', DataReader.GetString(3)),&lt;br /&gt;          new XElement('state',&lt;br /&gt;            DataReader.GetString(4)),&lt;br /&gt;          new XElement('zip',&lt;br /&gt;            DataReader.GetString(5))&lt;br /&gt;        );&lt;br /&gt;      element.Add(attribute);&lt;br /&gt;      document.Root.Add(element);                &lt;br /&gt;    end;&lt;br /&gt;  finally&lt;br /&gt;    DataReader.Close;&lt;br /&gt;  end;&lt;br /&gt;  document.Save(XmlFileName);&lt;br /&gt;finally&lt;br /&gt;  connection.Close;&lt;br /&gt;end;&lt;/pre&gt;&lt;h4&gt;Querying with LINQ to XML&lt;/h4&gt;&lt;p&gt;Querying using LINQ to XML is similar to other LINQ queries, though there are classes and methods that you use in LINQ to XML that are not found in other LINQ technologies. In short, once you have a reference to a queryable object (an IEnumerable, a sequence, or other similar construct), you use query statements to retrieve the data you are interested in. The following example demonstrates a LINQ to XML query.&lt;/p&gt;&lt;pre&gt;element := XElement.Load(XmlFileName);&lt;br /&gt;var childList :=&lt;br /&gt;  from el in element.Elements&lt;br /&gt;  where String(el.Element('state')) = 'CA'&lt;br /&gt;  order by String(el.Attribute('custno')) desc&lt;br /&gt;  select new XElement('document',&lt;br /&gt;    new XElement('CustomerName', el.Element('name')) ,&lt;br /&gt;      new XElement('Address', el.Element('address')),&lt;br /&gt;      new XElement('CityStateZip',&lt;br /&gt;        el.Element('city').Value + ', ' +&lt;br /&gt;        el.Element('state').Value + ' ' +&lt;br /&gt;        el.Element('zip').Value));&lt;br /&gt;for each e in childList do&lt;br /&gt;begin&lt;br /&gt;  ResultsList.Items.Add(&lt;br /&gt;   e.Element("CustomerName").Value);&lt;br /&gt;  ResultsList.Items.Add(e.Element("Address").Value);&lt;br /&gt;  ResultsList.Items.Add(&lt;br /&gt;    e.Element('CityStateZip').Value);&lt;br /&gt;  ResultsList.Items.Add(String.Empty);&lt;br /&gt;end;&lt;/pre&gt;&lt;p&gt;This query returns a sequence of XElement references, which have a structure similar to, though different than, the original XElements. For example, the new XElements are not named customer, they are named document. Furthermore, there are no attributes in the new XElements, and there are only three child nodes.&lt;/p&gt;&lt;p&gt;The preceding example demonstrated a query using the XElement class. The following code shows the same basic query, however an XDocument is used in this example.&lt;/p&gt;&lt;pre&gt;document := XDocument.Load(XmlFileName);&lt;br /&gt;var childList :=&lt;br /&gt;  from el in document.Descendants&lt;br /&gt;  where String(el.Element('state')) = 'CA'&lt;br /&gt;  order by String(el.Attribute('custno')) desc&lt;br /&gt;  select new XElement('document',&lt;br /&gt;    new XElement('CustomerName',&lt;br /&gt;      el.Element('name')),&lt;br /&gt;    new XElement('Address', el.Element('address')),&lt;br /&gt;    new XElement('CityStateZip',&lt;br /&gt;      el.Element('city').Value + ', ' +&lt;br /&gt;      el.Element('state').Value + ', ' +&lt;br /&gt;      el.Element('zip').Value));&lt;br /&gt;for each e in childList do&lt;br /&gt;begin&lt;br /&gt;  ResultsList.Items.Add(&lt;br /&gt;    e.Element("CustomerName").Value);&lt;br /&gt;  ResultsList.Items.Add(e.Element("Address").Value);&lt;br /&gt;  ResultsList.Items.Add(&lt;br /&gt;    e.Element('CityStateZip').Value);&lt;br /&gt;  ResultsList.Items.Add(String.Empty);&lt;br /&gt;end;&lt;/pre&gt;&lt;h4&gt;Modifying Exiting XML using LINQ to XML&lt;/h4&gt;&lt;p&gt;Once XML is in memory, you can add elements or attributes, remove elements or attributes, as well as modify the data for elements or attributes. If you load the XML from an existing source, such as a file, make modifications, and then save the XML back to the original source, you have effectively changed the XML.&lt;/p&gt;&lt;p&gt;This is demonstrated in the following code.&lt;/p&gt;&lt;pre&gt;document := XDocument.Load(XmlFileName);&lt;br /&gt;var childList :=&lt;br /&gt;  from el in document.Descendants&lt;br /&gt;  where String(el.Element('state')) = 'CA'&lt;br /&gt;  order by String(el.Attribute('custno')) desc&lt;br /&gt;  select el;&lt;br /&gt;//make changes&lt;br /&gt;for each e in childList do&lt;br /&gt;begin&lt;br /&gt;  e.Element('state').SetValue('California');&lt;br /&gt;end;&lt;br /&gt;//save the changes&lt;br /&gt;document.Save(XmlFileName);&lt;/pre&gt;&lt;p&gt;There is an important characteristic of the preceding code that you should note. Specifically, the select clause of the LINQ to XML query returned a sequence of XElements from the original XDocument. While these represent a possible subset of all XElements returned by the Descendants method of the XDocument, they are still child elements of the original XDocument. &lt;/p&gt;&lt;p&gt;When changes are made to the items in the sequence returned by the query, those changes are actually being made to the corresponding XElements in the XDocument. This is why the changes are preserved when the subsequent call to XDocument.Save is made.&lt;/p&gt;&lt;p&gt;By comparison, if the select clause had created new XElements, similar to how the last query in the preceding section demonstrated, the subsequent changes would have been performed on the newly created objects, which are not child elements of the XDocument.&lt;/p&gt;&lt;h2&gt;Summary&lt;/h2&gt;&lt;p&gt;This two part series has provided you with a brief introduction to LINQ with Delphi Prism. Here you have learned how to construct LINQ Queries, the nature of LINQ to Objects, define and implement iterators, and have been given a preview of LINQ to DataSets and LINQ to XML.&lt;/p&gt;&lt;p&gt;Copyright © 2009 Cary Jensen. All Rights Reserved&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-6215869260230624289?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/6215869260230624289/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/07/introduction-to-language-integrated.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6215869260230624289'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/6215869260230624289'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/07/introduction-to-language-integrated.html' title='Introduction to Language Integrated Query with Delphi Prism, Part 2'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-4559815027305325873</id><published>2009-06-30T05:33:00.000-07:00</published><updated>2009-07-09T15:20:21.618-07:00</updated><title type='text'>Introduction to Language Integrated Query with Delphi Prism: Part 1</title><content type='html'>&lt;p&gt;Language Integrated Query, or LINQ (pronounced link), is a declarative programming language developed by Microsoft for the .NET framework. In a declarative programming language you specify what result you want to achieve without needing to specify how those results are produced. &lt;/p&gt;  &lt;p&gt;SQL SELECT statements are an example of declarative programming. With a given SQL statement you specify what data you want returned. It is up to the SQL engine to determine how that data is derived.&lt;/p&gt;  &lt;p&gt;Traditional Delphi, on the other hand, is categorized as imperative programming. When writing Delphi code, you generally describe in detailed terms how you want the result obtained. You do this using control structures, expressions, and explicit calls to functions and procedures.&lt;/p&gt;  &lt;p&gt;This article is designed to provide you with a general overview of using LINQ with Delphi Prism, the latest .NET development tool from Embarcadero Technologies and RemObjects. For a more detailed discussion of LINQ and its related topics, refer to the latest version of the .NET framework SDK.&lt;/p&gt;  &lt;h2&gt;Overview of LINQ&lt;/h2&gt;  &lt;p&gt;Language Integrated Query comes in a variety of different flavors, depending on the version of the .NET framework you are compiling against. For example, there is LINQ to Objects, LINQ to DataSets, LINQ to SQL, and LINQ to XML. Each of these technologies, which were introduced in .NET 3.0, provides classes you can use in conjunction with LINQ to work with objects in various domains of the .NET framework.&lt;/p&gt;  &lt;p&gt;In addition, some of the major new features that are emerging in .NET also are LINQ related. One example is LINQ to Entities, which is part of the Entity Framework. The Entity Framework is one of the latest database technologies being promoted by Microsoft.&lt;/p&gt;  &lt;p&gt;Turning our attention to the general case of LINQ, LINQ queries can be divided into three basic parts. These are&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;A queryable source of data &lt;/li&gt;    &lt;li&gt;A query definition &lt;/li&gt;    &lt;li&gt;Query execution &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;The following sections describe each of these parts in greater detail. &lt;/p&gt;  &lt;h2&gt;&lt;a name="_Toc228098596"&gt;Queryable Sources of Data&lt;/a&gt;&lt;/h2&gt;  &lt;p&gt;In strictly .NET terms, a queryable source of data is any object that implements the generic IEnumerable&amp;lt;T&amp;gt; interface, which includes List&amp;lt;T&amp;gt; and Dictionary&amp;lt;TKey, TValue&amp;gt;. In Delphi Prism, this also includes any collection that is declared as a sequence, as well as arrays (both fixed and dynamic).&lt;/p&gt;  &lt;p&gt;Some of the LINQ technologies, such as LINQ to DataSet and LINQ to XML, are implemented through LINQ providers, which are support classes that enable LINQ operations. These providers usually include special methods that provide access to an IEnumerable&amp;lt;T&amp;gt; reference based on an object that otherwise does not support this interface. For example, LINQ to DataSet provides the AsEnumerable extension method to the DataTable class.&lt;/p&gt;  &lt;p&gt;The following variable declaration defines a simple queryable data source. In this case, the data source is a sequence.&lt;/p&gt;  &lt;p&gt;var seq: sequence of Integer := [1,2,3,4,5,6,7,8,9];&lt;/p&gt;  &lt;h2&gt;&lt;a name="_Toc228098597"&gt;Query Definitions using Query Syntax&lt;/a&gt;&lt;/h2&gt;  &lt;p&gt;A query definition consists of two parts, a query variable and a query expression. Furthermore, query expressions are defined using one of two techniques. The first is called query syntax, and the second is called method syntax. This section describes query syntax. Method syntax is described later in this article.&lt;/p&gt;  &lt;p&gt;Query expressions are defined using a combination of one or more LINQ statements, comparison operators, extension methods, and value references. At a minimum, a LINQ query includes a from clause, which defines an alias and a data source. Consider the following code segment:&lt;/p&gt;  &lt;p&gt;var numbers: sequence of Integer := [1,2,3,4,5,6,7,8,9];&lt;/p&gt;  &lt;p&gt;var myquery := from c in numbers select c;&lt;/p&gt;  &lt;p&gt;The first line of code defines the data source, and the second line defines the query. The query in this case selects all items from the sequence numbers, as can be seen in the following figure (the code that populates the list box has not been shown yet).&lt;/p&gt;  &lt;p&gt;&lt;a href="http://3.bp.blogspot.com/_oiz-KoL9VE4/SlTHF9UhF6I/AAAAAAAAABU/jbjCD7oaqoM/s1600-h/LINQ1.1.jpg"&gt;&lt;img id="BLOGGER_PHOTO_ID_5356124762024253346" style="display: block; margin: 0px auto 10px; width: 320px; cursor: hand; height: 243px; text-align: center" alt="" src="http://3.bp.blogspot.com/_oiz-KoL9VE4/SlTHF9UhF6I/AAAAAAAAABU/jbjCD7oaqoM/s320/LINQ1.1.jpg" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Figure 1. The LINQExamples project&lt;/p&gt;  &lt;p&gt;The c in the preceding query is an alias, and it is used to reference items in the numbers sequence. The select part of the query, which contrary to SQL standards, appears at the end of the statement, defines what is returned by the query. In this case, the query returns each of the items in the sequence.&lt;/p&gt;  &lt;p&gt;The preceding query really just creates another sequence that is no different from the one that it queried. Most LINQ queries, by comparison, either select subsets of the data, performs data transformations, or other similar operations. The following query includes a where clause that limits the myquery sequence to those numbers between 4 and 8, inclusively. As you can see, the alias is essential for performing this operation.&lt;/p&gt;  &lt;p&gt;var numbers: sequence of Integer := [1,2,3,4,5,6,7,8,9];    &lt;br /&gt;var myquery := from c in numbers where (c &amp;gt;= 4) and (c &amp;lt;= 8) select c;&lt;/p&gt;  &lt;p&gt;In both queries shown so far in this section, the select clause could have been omitted. Specifically, if what we are returning is exactly the same type of item as in the sequence, we can omit the select clause altogether. In other words, omitting the select clause is somewhat similar to a SELECT * clause in a SQL query. The following query, which does not have a select clause, produces the same result as the preceding one.&lt;/p&gt;  &lt;p&gt;var numbers: sequence of Integer := [1,2,3,4,5,6,7,8,9];    &lt;br /&gt;var myquery := from c in numbers where (c &amp;gt;= 4) and (c &amp;lt;= 8);&lt;/p&gt;  &lt;p&gt;The select clause in LINQ is required when you are querying objects with one or more members, and want to only return a subset of its members, or want to return transformed data. For example, the following query returns a sequence of objects whose values are equal to the square of the original values. The objects returned by the select clause, in this case, are anonymous types. (An anonymous type of an object whose type is never explicitly declared, one of many advanced language features supported by Delphi Prism.)&lt;/p&gt;  &lt;p&gt;var numbers: sequence of Integer := [1,2,3,4,5,6,7,8,9];    &lt;br /&gt;var myquery := from c in numbers select new class(Value := c * c);&lt;/p&gt;  &lt;p&gt;LINQ queries can be relatively complicated. In addition to the from, where, and select keywords shown here, Delphi Prism also supports the following LINQ operations: order by, group by, join, with, take, skip, reverse, and distinct. In addition, LINQ queries can make use of lambda expressions, both in the where and select parts (Lambda expressions are discussed briefly later in this article). Finally, similar to SQL, LINQ queries can include subqueries.&lt;/p&gt;  &lt;h2&gt;&lt;a name="_Toc228098598"&gt;Executing LINQ Queries&lt;/a&gt;&lt;/h2&gt;  &lt;p&gt;Defining a query and executing a query are two distinct steps (though as you will learn shortly, Delphi Prism provides a mechanism for combining these operations). Specifically, the query defined in the preceding section identifies what the query will do, but does not execute the query. In LINQ this is referred to as deferred execution.&lt;/p&gt;  &lt;p&gt;You cause the query to be executed by iterating over it using a for each loop. Each execution of the loop brings back another instance of whatever the query returns. For example, if you used a for each loop to iterate over the query shown in the preceding example, each iteration of the loop would return a different instance of the anonymous type. &lt;/p&gt;  &lt;p&gt;The following is the entire code sequence (short as it is), which shows how the Value member of the returned objects are assigned to the list box (which is name ResultList in this example).&lt;/p&gt;  &lt;p&gt;ResultsList.Items.Clear;    &lt;br /&gt;var numbers: sequence of Integer := [1,2,3,4,5,6,7,8,9];     &lt;br /&gt;var myquery := from c in numbers select new class(Value := c * c);     &lt;br /&gt;for each m in myquery do     &lt;br /&gt;&amp;#160; ResultsList.Items.Add(m.Value.ToString);&lt;/p&gt;  &lt;p&gt;When written to the list box, this query result looks like the following.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://2.bp.blogspot.com/_oiz-KoL9VE4/SlTHLGrTSEI/AAAAAAAAABc/uRg1xIawALE/s1600-h/LINQ1.2.jpg"&gt;&lt;img id="BLOGGER_PHOTO_ID_5356124850435082306" style="display: block; margin: 0px auto 10px; width: 320px; cursor: hand; height: 243px; text-align: center" alt="" src="http://2.bp.blogspot.com/_oiz-KoL9VE4/SlTHLGrTSEI/AAAAAAAAABc/uRg1xIawALE/s320/LINQ1.2.jpg" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Figure 2. Data from an anonymous type returns from a LINQ query&lt;/p&gt;  &lt;p&gt;Similar to the alias in the query definition, the for each loop defines a local variable that holds the value of the item that is returned in each iteration. In the preceding code, this variable is named m. In most cases, Delphi Prism uses type inference to determine the type of this variable (which is fortunate in this case, since we used an anonymous type).&lt;/p&gt;  &lt;p&gt;Not all LINQ queries use deferred execution. To cause the results of the query to retrieve immediately, you can use either the IEnumerable methods ToList&amp;lt;TSource&amp;gt; or ToArray&amp;lt;TSource&amp;gt;. An example of this is shown in the following code.&lt;/p&gt;  &lt;p&gt;var numbers: sequence of Integer := [1,2,3,4,5,6,7,8,9];    &lt;br /&gt;var MyResults: Array of Integer :=     &lt;br /&gt;&amp;#160; (from c in numbers where (c &amp;gt;= 4) and (c &amp;lt;= 8)).ToArray();     &lt;br /&gt;for each val in MyResults do     &lt;br /&gt;ResultsList.Items.Add(val.ToString);&lt;/p&gt;  &lt;p&gt;Similarly, if you use any of the grouping operations, such Average, Count, First, and so forth, the query is executed immediately. This is demonstrated in the following code.&lt;/p&gt;  &lt;p&gt;var numbers: sequence of Integer := [1,2,3,4,5,6,7,8,9];    &lt;br /&gt;var MyResults: Double := (from c in numbers).Average();     &lt;br /&gt;MessageBox.Show(MyResults.ToString);&lt;/p&gt;  &lt;h2&gt;&lt;a name="_Toc228098599"&gt;LINQ Query Method Syntax&lt;/a&gt;&lt;/h2&gt;  &lt;p&gt;As mentioned earlier, LINQ query expressions can be defined using both query syntax and method syntax. Method syntax is implemented through extension methods on the System.Linq.Enumerable class, and corresponds roughly to the LINQ query syntax operators. &lt;/p&gt;  &lt;p&gt;The truth is that when you use query syntax in your query expressions, the compiler converts these into method syntax. Another way to put this is that any query that you can write using query syntax can also be defined using method syntax. Interestingly, the opposite is not true. Specifically, there are some queries that you must define using method syntax, as there is no equivalent in query syntax.&lt;/p&gt;  &lt;p&gt;Unlike in query syntax, there are no aliases in method syntax. Instead, the methods are called on your IEnumerable object directly, using dot notation. These operations are typically performed in a single expression. In other words, using dot notation, method syntax sometimes includes a number of method calls, implemented through a chain of calls.&lt;/p&gt;  &lt;p&gt;This is demonstrated in the following code segment, which produces a query identical to the first one listed in the preceding section “Query Definitions using Query Syntax.”&lt;/p&gt;  &lt;p&gt;var myquery := numbers.where(c -&amp;gt; c &amp;gt;= 4).    &lt;br /&gt;&amp;#160; where(c -&amp;gt; c &amp;lt;= 8).orderby(c -&amp;gt; c).select(c -&amp;gt; c);&lt;/p&gt;  &lt;p&gt;The preceding statement is a single statement, even though it has been wrapped onto two lines due to the limits of the column space in this article. Fortunately, this wrapped form can still be compiled by Delphi since its compiler is designed to ignore white space. Also, the arguments of the where, orderby, and select clauses are lambda expressions. Lambda expressions are discussed in the following section.&lt;/p&gt;  &lt;p&gt;As with the query expressions, the select method call here is not necessary, since there is no transformation being performed on the returned items.&lt;/p&gt;  &lt;p&gt;The syntax is a little different when your sequence contains more complex data. This can be seen from the following code, which is the method syntax version of the code segment described in the following section “LINQ to Objects.”&lt;/p&gt;  &lt;p&gt;var Customers := GetCustomers;    &lt;br /&gt;var query := Customers.Where(Customer -&amp;gt; Customer.Age &amp;gt; 10).     &lt;br /&gt;&amp;#160; Where(Customer -&amp;gt; Customer.Age &amp;lt; 40).     &lt;br /&gt;&amp;#160; Where(Customer -&amp;gt; Customer.Active);&lt;/p&gt;  &lt;h2&gt;&lt;a name="_Toc228098600"&gt;Lambda Expressions&lt;/a&gt;&lt;/h2&gt;  &lt;p&gt;Lambda expressions are anonymous functions that can accept zero or more parameters, and typically return a value. The parameters appear on the left-hand side of the lambda operator, and are enclosed in parentheses (unless there are zero or one parameters, in which case the parentheses are optional).&lt;/p&gt;  &lt;p&gt;The parameters, if present, are following by the lambda operator, which is the -&amp;gt; character sequence in Delphi Prism (C# uses =&amp;gt; as the lambda operator). When reading a lambda expression aloud, the lambda operator is spoken “goes to.” As a result, the first expression in the preceding query expression is read “customer goes to customer dot age greater than 10.”&lt;/p&gt;  &lt;p&gt;The expression on the right-hand side of the lambda operator includes either an expression or a statement. This is the value that is returned by the lambda expression (if the statement is an expression lambda). It is possible, however, to include an expression block that does not resolve to an expression. These lambdas are called statement lambdas. Statement lambdas should not be used in method calls when using method syntax. &lt;/p&gt;  &lt;p&gt;In most cases, lambda expressions take advantage of the compiler’s ability to infer the type of the input parameters. &lt;/p&gt;  &lt;h2&gt;Summary&lt;/h2&gt;  &lt;p&gt;This article has provided you with a brief introduction to the composition of language integrated queries. In part 2 of this article, which will appear within a month of this article, numerous examples of LINQ queries are shown, including technologies such as LINQ to Objects, LINQ to DataSets, and LINQ to XML.&lt;/p&gt;  &lt;p&gt;Copyright © 2009 Cary Jensen All Rights Reserved&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-4559815027305325873?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/4559815027305325873/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/06/introduction-to-language-integrated.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/4559815027305325873'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/4559815027305325873'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/06/introduction-to-language-integrated.html' title='Introduction to Language Integrated Query with Delphi Prism: Part 1'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_oiz-KoL9VE4/SlTHF9UhF6I/AAAAAAAAABU/jbjCD7oaqoM/s72-c/LINQ1.1.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-1305099263442227596</id><published>2009-05-29T17:19:00.000-07:00</published><updated>2009-05-30T20:19:47.347-07:00</updated><title type='text'>Keystroke Combinations in Delphi’s Code Editor</title><content type='html'>&lt;p&gt;There are many advantages to being a Delphi developer. Not only has the language kept pace with improvements added to other languages, such as C#, but it has maintained a remarkable consistency over its many years, first as Turbo Pascal, and later as Delphi. For example, routines originally written for Turbo Pascal in the 1980s often compile in the latest version of Delphi with little or no changes. There are not many languages you can say that about.&lt;br /&gt;&lt;br /&gt;Delphi developers have also benefited from Delphi’s strong IDE (integrated development environment). From Delphi 1 through RAD Studio 2009, Delphi’s IDE has provided developers with state-of-the-art features that support and improve the overall development experience.&lt;br /&gt;&lt;br /&gt;Unfortunately, not all of the capabilities of Delphi’s IDE are well known. Consider this. Did you know that Delphi can record (Ctrl+Shift+R) and playback (Ctrl+Shift+P) a series of keystrokes? It’s my impression that at least half of Delphi developers do not know this. But it is a feature that has been available in Delphi since Delphi version 1.&lt;br /&gt;&lt;br /&gt;Part of the problem is that the various keystrokes supported by Delphi’s IDE have been notoriously hard to find in Delphi’s help. Some of them have never been documented. For example, one of my favorite keystroke combinations is Ctrl+Spacebar, which invokes the Argument Value List drop down. Whenever you are entering the value of an expression, for example, the left side of an assignment statement or a parameter of a function, pressing Ctrl+Spacebar displays a list of the possible symbols that might satisfy the expression. This feature, which was added in Delphi 3, appeared in the Delphi 4 help files only. It wasn’t in Delphi 3’s help, and has been missing in action since Delphi 5.&lt;br /&gt;&lt;br /&gt;Another interesting item to note is that, although Delphi’s IDE has undergone major revisions in the past few years with the introduction of the Galileo IDE, the keystrokes available in the IDE, and the editor specifically, have not changed much at all. In particular, nearly every keystroke combination that was available in Delphi 1 still works in Delphi 2009.&lt;br /&gt;&lt;br /&gt;But there is still the problem of documentation. When I inspected the most recent help that ships with RAD Studio 2007, I found that just over half of the editor’s keystroke combinations appeared in the help. The others were nowhere to be found.&lt;br /&gt;&lt;br /&gt;And this brings us to the essence of this article. I have attempted to collect here, in Table 1, every editor keystroke combination that I am aware of. These keystroke combinations are for the default key mapping, which is used by most Delphi developers. If you are using one of the other key mappings, some of these combinations will not work, but many will.&lt;br /&gt;&lt;br /&gt;I also want to admit that most of this table’s contents was derived from Delphi’s help files. In other words, while I compiled this table, I did not write most of the entries. Some entries appear exactly as they did in the help files, but I wrote some as well.&lt;br /&gt;&lt;br /&gt;Furthermore, I edited many of the help file entries, either to make corrections or to simplify the description. I also removed entries that were in the help files that either didn’t work correctly, or appeared to not work at all. Finally, I didn’t include many keystrokes that are not code editor specific. For instance, I did not include debugging related keystrokes.&lt;br /&gt;&lt;br /&gt;Permit me to list these main points again: &lt;/p&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;These keystroke combinations are only for the code editor. I did not include debugger keystroke combinations or general IDE keystroke combinations &lt;/li&gt;&lt;br /&gt;&lt;li&gt;These keystroke combinations are for the default keybinding. I made no attempt to include keystroke combinations for other keybindings &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Many of these descriptions are derived from some version of Delphi’s documentation&lt;/li&gt;&lt;br /&gt;&lt;li&gt;I listed these combinations in alphabetical order. Yes, there are many other alternative orders that would have been useful as well, but I opted for an alphabetical order for simplicity&lt;/li&gt;&lt;br /&gt;&lt;li&gt;I know that there must be some valid keystroke combinations that are missing (I specifically omitted those that did not appear to work). If you know of valid editor keystroke combinations that apply to the default keybinding that are missing, feel free to email those to me at CaryBlog@JensenDataSystems.com. I will try to update this table periodically with your contributions &lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;p&gt;So here, for your consideration and enjoyment, is a zip file that contains a PDF of a nearly complete list of the keystrokes supported by Delphi’s editor. This list is presented in alphabetical order, by key. When two key or key combinations perform the same task, they both appear in the left column. &lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;a href="http://www.JensenDataSystems.com/pdf/Cary Jensen's Delphi Editor Keystroke Table.zip" target="_blank"&gt;Cary Jensen's Delphi Editor Keystroke Table&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Copyright (c) 2009 Cary Jensen. All Rights Reserved.&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-1305099263442227596?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/1305099263442227596/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/05/keystroke-combinations-in-delphis-code.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/1305099263442227596'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/1305099263442227596'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/05/keystroke-combinations-in-delphis-code.html' title='Keystroke Combinations in Delphi’s Code Editor'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-3102823871579100924</id><published>2009-05-03T17:54:00.000-07:00</published><updated>2009-05-03T18:01:29.128-07:00</updated><title type='text'>An Advantage for Delphi Developers</title><content type='html'>&lt;div&gt;Here's a trivia question: What was the name of Delphi during its original beta test?&lt;br /&gt;&lt;br /&gt;The answer is Delphi (but you probably knew that already). But why did the Delphi development team pick such an odd name for their ground breaking, component-based heir to their Pascal development tool? The answer is related to databases.&lt;br /&gt;&lt;br /&gt;Delphi is name of both the city and temple in Greece where people would travel to speak to the Oracle. And although Delphi can work with just about any database you can think of, the point was that it was designed from the beginning to be a great environment for developing database applications.&lt;br /&gt;&lt;br /&gt;It’s ironic, then, that while Delphi’s name alludes to ORACLE, one of the most popular SQL-based relational database management systems (RDBMS), the foundations of database access in Delphi is navigational, not set oriented. Specifically, the original data access mechanism in Delphi was the Open Database Application Programming Interface, or ODAPI.&lt;br /&gt;&lt;br /&gt;ODAPI, which would later be referred to simply as the Borland Database Engine, or BDE, was a direct outgrowth of the Paradox Engine. The Paradox file format, like many of its file-based cousins, is index based. In other words, database tables are treated as an ordered sequence of records (rows), whose order can be changed by changing which index is used to access the records. These indexes can also be used to filter records of a single table, as well as perform joins between tables having compatible indexes.&lt;br /&gt;&lt;br /&gt;Data Access in Delphi&lt;br /&gt;&lt;br /&gt;That Delphi’s database roots are well grounded in the navigational model is immediately obvious if you consider the TDataSet class, which is the base class for all data access components in Delphi. This class includes properties and methods for a variety of navigational operations. For example, for selecting indexes (IndexName), setting ranges (SetRange), finding records based on indexed values (FindKey, FindNearest), creating relational joins using indexes (MasterSource and MasterFields), and navigating records in index order (First, Next, Prior, MoveBy, and so forth).&lt;br /&gt;&lt;br /&gt;While the benefits of SQL-based data access are well-known, navigational data access has its advantages as well. Specifically, the navigational model better suits the development of intuitive and user-friendly interfaces.&lt;br /&gt;&lt;br /&gt;For example, with a navigational interface, it is possible to display all of the records from a database table (or a query result set), and permit the user to navigate these records freely. This navigation may even include incremental search (always an end user favorite). And, these features are available whether the underlying table has a couple of hundred records, or millions.&lt;br /&gt;&lt;br /&gt;By comparison, nearly all set-based databases, such as MS SQL Server and ORACLE, are not designed to provide navigational access. As a result, searching for a record in a result set means refining an underlying WHERE clause in a SQL query to reduce the resulting result set to a manageable size. The idea of opening a result set with millions of records and permitting a user to freely browse it is unthinkable.&lt;br /&gt;&lt;br /&gt;Here is where the Advantage Database Server really shines. Not only is it a high-performance, low maintenance database server that supports optimized SQL queries, its index-based architecture permits it to provide data access options normally only found in a file-based database (such as Paradox, dBase, or MS Access), while providing all the benefits of a transaction-based remote database server.&lt;br /&gt;&lt;br /&gt;But there is more - a lot more. To begin with, database developers creating native Delphi applications have access to Advantage Delphi Components, a component set that implements the native TDataSet interface for access to ADS. (For Delphi developers using Delphi 7 and earlier, the Advantage TDataSet Descendant provides the same features.)&lt;br /&gt;&lt;br /&gt;These components provide a near seamless replacement for BDE components, but go even further. For example, while the BDE supports filters, they are not optimized. With Advantage Delphi Components, so long as your tables have the appropriate indexes, filters are fully optimized. It is possible to set a filter on a 20 million record table and have the result in a fraction of a second.&lt;br /&gt;&lt;br /&gt;For Delphi for .NET, as well as for Delphi Prism developers, Advantage offers the Advantage Data Provider for .NET. This ADO.NET provider not only supports all features defined by ADO.NET, but it even supports optimized, server-side cursors. Specifically, while all .NET data providers support a forward navigating, read-only DbDataReader descendant, the AdsExtendedReader class, which is also a DbDataReader descendant, supports bi-directional navigation, optimized server-side filters, read/write support, as well as pessimistic locking. You cannot find better .NET data provider anywhere.&lt;br /&gt;&lt;br /&gt;General Overview of the Advantage Database Server&lt;br /&gt;&lt;br /&gt;The Advantage Database Server is a high-performance, relational database server that simultaneously supports optimized set-based SQL, as well as a blinding-fast, index-based navigational operations. Its low per-seat cost, ease of deployment, and very low maintenance requirements makes it particularly well suited for vertical market applications, especially those where the deployed sites lack the IT infrastructure required to maintain normal database servers.&lt;br /&gt;&lt;br /&gt;There is another reason why Advantage is so popular with vertical market developers. In addition to the Advantage Database Server, Sybase iAnywhere publishes the Advantage Local Server (ALS), a free, file-server based, five-user database that sports an interface identical to ADS, providing a seamless upgrade path to ADS. For those customers for whom cost is an issue, you can deploy your applications using ALS. Later, if the needs of the customers grow, or they want the stability of a transaction supporting, remote database server, migrating can be as simple as installing ADS (and this installation takes less than five minutes in most cases).&lt;br /&gt;&lt;br /&gt;Advantage is a Full-Featured Database&lt;br /&gt;&lt;br /&gt;While the benefits of ADS mentioned so far in this article provide a compelling case for building database applications with ADS and Delphi, there is much more to the story. The remainder of this article provides you with a brief overview of the many features that makes ADS a strong contender for just about any database development environment. All of these features, with the exception of replication, online backup, and support for class 4 Java drivers, are also supported by ALS.&lt;br /&gt;&lt;br /&gt;At the time of this writing, the current version of ADS is version 9.1. While some of the features defined below have been available since version 6.0, many have been either introduced or enhanced in later versions.&lt;br /&gt;&lt;br /&gt;Advantage Comes in Many Flavors&lt;br /&gt;&lt;br /&gt;There is not just one Advantage Database Server, there are several. For Linux and Windows servers, there are both 32-bit and a 64-bit versions of ADS. If you are running Novel, there is an Advantage Database Server NLM (Netware Loadable Module). The 64-bit versions of ADS were introduced in ADS 9.0.&lt;br /&gt;&lt;br /&gt;An Easy to Use Data Architect&lt;br /&gt;&lt;br /&gt;While you can control all aspects of your database design and management through Advantage SQL (or even the Advantage Client Engine), most of the time, you will want to design and configure your Advantage databases and data dictionaries using the Advantage Data Architect. The Advantage Data Architect is shown in Figure 1.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_oiz-KoL9VE4/Sf490ILlxTI/AAAAAAAAAAg/IWc4gPNXgDc/s1600-h/Figure+1.png"&gt;&lt;img id="BLOGGER_PHOTO_ID_5331766974611834162" style="FLOAT: left; MARGIN: 0px 10px 10px 0px; WIDTH: 320px; CURSOR: hand; HEIGHT: 275px" alt="" src="http://2.bp.blogspot.com/_oiz-KoL9VE4/Sf490ILlxTI/AAAAAAAAAAg/IWc4gPNXgDc/s320/Figure+1.png" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Figure 1: The Advantage Data Architect&lt;br /&gt;&lt;br /&gt;The Advantage Data Architect provides you with a rich graphical interface for working with Advantage objects. For example, you can use the Advantage Data Architect to create data dictionaries and database tables, create and configure users and groups, define stored procedures, create views, write user defined functions, and execute queries against your database objects.&lt;br /&gt;&lt;br /&gt;The Advantage Data Architect also comes with an invaluable SQL debugger. This debugger helps you step through your SQL queries, SQL stored procedures, SQL triggers, and user defined functions. It supports inspection of local variables, multiple breakpoints, live editing of your SQL queries, and more.&lt;br /&gt;&lt;br /&gt;A Multitude of Data Access Options&lt;br /&gt;&lt;br /&gt;Advantage supports one of the widest arrays of data access mechanisms in the industry. In addition to the Advantage Delphi Components, TDataSet Descendant, Advantage Data Provider for .NET, and Java class 4 drivers already mentioned, Sybase iAnywhere publishes a range of additional drivers. These include an ODBC driver, an OLE DB Provider, a PHP driver, a DBD (Perl) driver, a Clipper Replaceable Database Driver (RDD), and a Crystal Reports driver.&lt;br /&gt;&lt;br /&gt;In addition, some third party vendors provider their own Advantage drivers. For example, Alaska Software publishes the ADS-Database Engine for their Xbase++ language, an object-oriented language based on Clipper.&lt;br /&gt;&lt;br /&gt;Data Dictionaries with Database Wide Security&lt;br /&gt;&lt;br /&gt;Advantage first introduced data dictionary support in 2000 with the release of Ads 6.0. Data dictionaries provide a number of valuable features, some of which are associated with security. For example, with data dictionaries you can define users and groups, which permit you to define which database objects (including tables, fields, stored procedures, views, and so forth), individual users, or groups of users, have access to. This access can include full access, read/write access, readonly access, or no access.&lt;br /&gt;&lt;br /&gt;Furthermore, data dictionaries enable table encryption to be applied across the entire database, without having to encrypt and decrypt tables on a table-by-table basis. In addition, encryption can be extended to network and Internet communications. Specifically, Advantage data can be transparently encrypted using 160-bit encryption before being moved across your networks, or even the Internet. All you need to do is configure your data dictionary for the encryption option you want. There is no need to manually perform encryption and decryption in your client applications.&lt;br /&gt;&lt;br /&gt;Stored Procedures, User Defined Functions, and Views&lt;br /&gt;&lt;br /&gt;Stored procedures and user defined functions are routines that you create for your data dictionaries that can be used by any applications that access your Advantage data. For stored procedures, these routines can be written using DLLs (a Delphi project template is supplied), COM objects, or .NET managed assemblies. Stored procedures can also be written using SQL Scripts (SQL persistent stored modules, or PSMs). SQL Scripts are the only option for writing used defined functions (UDFs).&lt;br /&gt;&lt;br /&gt;Views are also based on SQL. However, views are SQL SELECT statements that you define in your data dictionary, and then call, as though they were a database table, from your client applications.&lt;br /&gt;&lt;br /&gt;Support for Constraints&lt;br /&gt;&lt;br /&gt;Advantage supports a range of constraint options. The most prominent of these is referential integrity (RI) constraints. Though they should be used judicially, RI constraints provide you with a mechanism to ensure the relational integrity between two or more related tables.&lt;br /&gt;&lt;br /&gt;Advantage also provides both table-level and field-level constraints. These constraints define rules that ensure that data entered into your tables meets criteria that you specify, without your having to embody these rules in each of your client applications.&lt;br /&gt;&lt;br /&gt;Online Full and Incremental Backup&lt;br /&gt;&lt;br /&gt;In version 8.0 Advantage introduced online back. Online backup permits you to create a copy of your data, which you can use in the case of a catastrophic failure of your server.&lt;br /&gt;&lt;br /&gt;Advantage supports two modes of online backup. A full backup creates a complete backup of your data each time it is run. Incremental back, by comparison, maintains a single backup, but updates it with the incremental changes that were detected since the previous back was completed.&lt;br /&gt;&lt;br /&gt;Database Replication&lt;br /&gt;&lt;br /&gt;Advantage also introduced database replication in ADS 8.0. With replication, changes to your database are propagated to one or more servers. Replication in Advantage uses a publish/subscribe model. As a result, replication can be performed between two or more Advantage servers using either unidirectional or bi-directional modes.&lt;br /&gt;&lt;br /&gt;Replication in ADS 8.0 was performed on a whole record basis. In ADS 9.0, it became possible to filter replication in order to replicate only certain fields of a table.&lt;br /&gt;&lt;br /&gt;Triggers and Notifications&lt;br /&gt;&lt;br /&gt;Triggers, which were added in ADS 7.0, and notifications, which were added in ADS 9.0, provide you with the option to react to changes that occur in your databases programmatically. Triggers, which, like stored procedures, can be written as DLLs, COM objects, .NET managed code, or SQL scripts, are routines that execute in response to a change occurring to a record in an underlying table, such as an insertion, modification, or deletion. In addition, triggers can be defined to trigger before the operation (permitting you to prevent it), during the operation (permitting you to implement it), or after the operation (allowing you to respond to it).&lt;br /&gt;&lt;br /&gt;Notifications provide a mechanism for communicating to a client that something has occurred in the database. For example, notifications can be used to inform a client application that data in a critical table has changed. The client application can then use this information to refresh its view of that table, providing the end user with the most current data. Or, a notification might be used to signal a client that a record has been added to a special table created for the purpose of communicating messages from the system administrator to the end users. The client can then read the latest message and display it within its interface.&lt;br /&gt;&lt;br /&gt;Conclusion&lt;br /&gt;&lt;br /&gt;The Advantage Database Server stands alone in the world of relational database servers (RDBMs). It not only supports the navigational model, but also provides optimized support for SQL operations. This navigational support makes it a perfect match with Delphi’s data access model. This, combined with its advanced features, very low maintenance, high performance, and ease of deployment, make it an ideal database server for a wide range of applications.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Copyright (c) 2009 Cary Jensen. All Rights Reserved.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-3102823871579100924?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/3102823871579100924/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/05/advantage-for-delphi-developers.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3102823871579100924'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3102823871579100924'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/05/advantage-for-delphi-developers.html' title='An Advantage for Delphi Developers'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_oiz-KoL9VE4/Sf490ILlxTI/AAAAAAAAAAg/IWc4gPNXgDc/s72-c/Figure+1.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6222893893652665733.post-3998125228801347456</id><published>2009-04-11T17:54:00.001-07:00</published><updated>2009-04-27T01:52:21.486-07:00</updated><title type='text'>Each Journey Begins with One Step</title><content type='html'>To blog or not to blog. That’s been the question.&lt;br /&gt;&lt;br /&gt;And it’s an odd one, at least for me. Those of you who know me are all too aware that I write a fair bit (some people would characterize the amount I write as a lot). At last count, I’ve published 20 books (co-authored with my wife, Loy Anderson), about 80 software courses, hundreds of magazine articles, and many Web articles. But I haven’t blogged until now (at least about technical issues, but that’s another topic for another venue).&lt;br /&gt;&lt;br /&gt;It’s amazing, really, that it’s taken until now for me to place hands on keyboard and get back to a regular schedule of technical writing. Heaven knows I enjoy it. In fact, I consider myself an author first, before any other endeavor, though I technically spend more time writing code than writing about coding.&lt;br /&gt;&lt;br /&gt;The real question is why haven’t I started before now. And the answer is simpler than you’d think. I didn’t want writing to become a chore.&lt;br /&gt;&lt;br /&gt;Back in the days of Delphi Informant, and even before that, I wrote a regular monthly column. Monthly is nice. You get a whole 30 days to get your act together. And even if you wait until the last minute, once it’s done, you’ve got a breather before you need to get back to the keyboard and pound out another one. No, monthly is not a chore.&lt;br /&gt;&lt;br /&gt;Lately I’ve been writing regularly for the Software Development Magazine, which is published by the Software Development Network, one of the larger European user groups, based in the Netherlands. That magazine is published four times a year. Again, not a chore.&lt;br /&gt;&lt;br /&gt;My real worry was that if I started blogging technical material, I would commit to too frequent a cycle. And though that might work for a while, I would soon find myself worrying about scraping together enough time to keep the pace. Eventually, I would slack off, and this would lead to guilt, which would make matters worse.&lt;br /&gt;&lt;br /&gt;Over time, I would occasionally post a new blog, and with each post, my shortcomings would be staring me in the face: April 2009 (8), May 2009 (6), June 2009 (2), September 2009 (1) April 2010 (1). It would be unbearable.&lt;br /&gt;&lt;br /&gt;But I’m over it now. I’ve been on the blogging sidelines for too long, and I’m now ready to get into the game.&lt;br /&gt;&lt;br /&gt;And, as so not to make it a chore, I am going to initially commit to a rather light schedule. I hope, on average, to post one to two technical blogs each month. Scattered between these will be the odd observation or announcement, such as the upcoming Multithreading Master Class that I am presenting with Marco Cantù as a Power Workshop at DelphiLive! on May 13, 2009 in San Jose, California. (See &lt;a href="http://www.delphilive.com/"&gt;http://www.delphilive.com/&lt;/a&gt; for more details. The Power Workshop description can be found at &lt;a href="http://www.delphilive.com/conferences/delphi_live/workshops/"&gt;http://www.delphilive.com/conferences/delphi_live/workshops/&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;I have been looking forward to this. I hope you have, too.&lt;br /&gt;&lt;br /&gt;Copyright (C) 2009 Cary Jensen. All Rights Reserved.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6222893893652665733-3998125228801347456?l=caryjensen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://caryjensen.blogspot.com/feeds/3998125228801347456/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://caryjensen.blogspot.com/2009/04/each-journey-begins-with-one-step.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3998125228801347456'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6222893893652665733/posts/default/3998125228801347456'/><link rel='alternate' type='text/html' href='http://caryjensen.blogspot.com/2009/04/each-journey-begins-with-one-step.html' title='Each Journey Begins with One Step'/><author><name>Cary Jensen</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_oiz-KoL9VE4/SiJ_n1m8tgI/AAAAAAAAAAs/ElzhjsD1910/S220/CaryJensen.jpg'/></author><thr:total>0</thr:total></entry></feed>
