Delphi Clinic C++Builder Gate Delphi Notes Weblog Delphi for .NET Prism for .NET
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #23
See Also: other Dr.Bob Examines columns or Delphi articles

An earlier version of this article originally appeared in Delphi Developer (September 2001). Copyright Pinnacle Publishing, Inc. All rights reserved.

Delphi 6 XML Data Binding
Delphi 6 contains a lot of new and enhanced XML support. With Delphi 6, new XML features have been added in the form of XML document programming, XML Data Binding Wizard, XML Mapper and BizSnap (SOAP/XML Web Services). This is the third in a series of articles about Delphi 6 XML support. Last time, I introduced "plain" XML document programming, this time we'll go one step further with native XML Data Binding in Delphi 6 (and next time we'll finish with another coverage of the XML Mapper tool).

XML Document Programming
The "plain" XMLDocument component offers us ways to work with and edit the contents of an XML Document. However, last month we also saw that we can only work with unnamed ChildNodes (of type IXMLNode), and we have to know the names of the attributes (child nodes) ourselves (although you there is a FindAttribute method available), meaning no real compile-time error checking either! Fortunately, Delphi wouldn't be Delphi if the support could be made a little bit more intuitive. Using context-sensitive structure information from the XML document itself. This is possible using the XML Data Binding Wizard of Delphi 6.

XML Data Binding
The XML Data Binding wizard can be found in the Object Repository and can be used to generate interfaces and classes that offer a simpler way to access and update data stored in XML data files (such as ClientDataSet XML data files or ADO XML data files or any validated XML document containing data like our Clinic.xml file from last month that we'll use again this month). For the XML examples, we use the little XML file again that contains information about my forthcoming Delphi 6 Clinics. Now, Delphi 6 Enterprise, do File | New - Other and select the XML Data Binding Wizard from the Object Repository.

XML Data Binding Wizard icon

The Wizard has three pages. The first page of the XML Data Binding Wizard is used to specify the document or schema (in this case our Clinic.xml document), and looks as follows:

XML Data Binding Wizard

In here, you can specify a schema or XML document to be used in the other steps. The Options Dialog, which can be shown in any page of the Wizard, can be used to specify the Code Generation options as well as a Data Type map (we'll get back to the Options Dialog after the next step). The second page of the XML Data Binding Wizard is used to indicate how the wizard should represent each element (i.e. what kind of code should be generated). For the XML document of last time, the second page looks as follows:

XML Data Binding Wizard

We can see the complex types ClinicsType and ClinicType as well as the single simple type String that being used in our XML document. Note that this page doesn't require any further action from our part. After you've seen the list of Simple and Complex Types that are used by the XML document, it may be time to click on the Options button to get the Options Dialog, which looks as follows:

XML Data Binding Wizard

Here you can modify the generated code (like Get_ and Set_ prefix for the getter and setter methods - which are only used to implement the more useful properties, as we'll see later in this article), as well as the data type map (we only use the String type, but in case you use other types - or a type not present yet, you may want to edit this list).

The third page of the XML Data Binding Wizard is used to confirm all your choices and generate the interfaces and classes for you. You can also decide to store the settings in an external file as well (like Clinic.xdb). For our example Clinic.xml file, the third page looks as follows:

XML Data Binding Wizard

The resulting settings - stored in Clinic.xdb - are as follows, specifying that we have a Clinics element of type ClinicsType that contains a sequence of Clinic elements of type ClinicType:

  <?xml version="1.0"?>
  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xdb="http://www.borland.com/schemas/delphi/6.0/XMLDataBinding">
    <xs:element name="Clinics" type="ClinicsType"/>
    <xs:complexType name="ClinicsType">
      <xs:annotation>
        <xs:appinfo xdb:docElement="Clinics"/>
      </xs:annotation>
      <xs:sequence>
        <xs:element name="Clinic" type="ClinicType" maxOccurs="unbounded"/>
      </xs:sequence>
    </xs:complexType>
    <xs:complexType name="ClinicType">
      <xs:sequence>
        <xs:element name="Title" type="xs:string"/>
        <xs:element name="Date" type="xs:string"/>
        <xs:element name="Topics" type="xs:string"/>
      </xs:sequence>
      <xs:attribute name="No" type="xs:string"/>
    </xs:complexType>
  </xs:schema>
It also specifies the Clinic element of type ClinicType as being a complex type that contains a sequence of string elements (Title, Date and Topics) as well as a No attribute, also of type string.

Generated Code
The generated interfaces and classes can be used directly in our applications. Unfortunately, Delphi 6 Enterprise (contains a minor error that) sometimes produces an Access Violation at this point. Just try again and it will work.
The interface section of the (somewhat lengthy) generated source code is as follows (saved to file Clinic.pas):

  {****************************************************}
  {                                                    }
  {              Delphi XML Data Binding               }
  {                                                    }
  {         Generated on: 2001/11/07 00:37:00          }
  {       Generated from: D:\D6Clinic\src\Clinic.xml   }
  {   Settings stored in: D:\D6Clinic\src\Clinic.xdb   }
  {                                                    }
  {****************************************************}
  unit Clinic;
  interface
  uses xmldom, XMLDoc, XMLIntf;

  type

  { Forward Decls }

    IXMLClinicsType = interface;
    IXMLClinicType = interface;

  { IXMLClinicsType }

  IXMLClinicsType = interface(IXMLNodeCollection)
    ['{06723E03-662D-11D5-81CE-00104BF89DAD}']
    { Property Accessors }
    function Get_Clinic(Index: Integer): IXMLClinicType;
    { Methods & Properties }
    function Add: IXMLClinicType;
    function Insert(const Index: Integer): IXMLClinicType;
    property Clinic[Index: Integer]: IXMLClinicType
      read Get_Clinic; default;
  end;

  { IXMLClinicType }

  IXMLClinicType = interface(IXMLNode)
    ['{06723E04-662D-11D5-81CE-00104BF89DAD}']
    { Property Accessors }
    function Get_No: WideString;
    function Get_Title: WideString;
    function Get_Date: WideString;
    function Get_Topics: WideString;
    procedure Set_No(Value: WideString);
    procedure Set_Title(Value: WideString);
    procedure Set_Date(Value: WideString);
    procedure Set_Topics(Value: WideString);
    { Methods & Properties }
    property No: WideString read Get_No write Set_No;
    property Title: WideString read Get_Title write Set_Title;
    property Date: WideString read Get_Date write Set_Date;
    property Topics: WideString read Get_Topics write Set_Topics;
  end;

  { Forward Decls }

    TXMLClinicsType = class;
    TXMLClinicType = class;

  { TXMLClinicsType }

  TXMLClinicsType = class(TXMLNodeCollection, IXMLClinicsType)
  protected
    { IXMLClinicsType }
    function Get_Clinic(Index: Integer): IXMLClinicType;
    function Add: IXMLClinicType;
    function Insert(const Index: Integer): IXMLClinicType;
  public
    procedure AfterConstruction; override;
  end;

  { TXMLClinicType }

  TXMLClinicType = class(TXMLNode, IXMLClinicType)
  protected
    { IXMLClinicType }
    function Get_No: WideString;
    function Get_Title: WideString;
    function Get_Date: WideString;
    function Get_Topics: WideString;
    procedure Set_No(Value: WideString);
    procedure Set_Title(Value: WideString);
    procedure Set_Date(Value: WideString);
    procedure Set_Topics(Value: WideString);
  end;

  { Global Functions }

  function GetClinics(Doc: IXMLDocument): IXMLClinicsType;
  function LoadClinics(const FileName: WideString): IXMLClinicsType;
  function NewClinics: IXMLClinicsType;
There are two interface types here: IXMLClinicsType and IXMLClinicType. And at the same time, there are two classes that implement these interfaces: TXMLClinicsType and TXMLClinicType. And to get everything started, there are three global functions: GetClinics (to get the root element), LoadClinics (to load it from an external XML file) and NewClinics (to start a new document - in memory).

Usage
Using the generated Clinic.pas unit is easy: just like last time, we only need an XMLDocument component (found on the Internet tab), but this time we no longer have to work with untyped IXMLNodes, but we can extract the IXMLClinicsType directly from the XMLDocument using the GetClinics function. But in case you missed last month's article on using the plain XMLDocument component, here are the step by step instructions again:

  1. Start a new Delphi 6 project
  2. Add the unit Clinic.pas to the uses clause of the main unit
  3. Drop a XMLDocument component on the main form
  4. Point the FileName property to Clinic.xml
  5. write the following code in the OnCreate event handler
  procedure TForm1.FormCreate(Sender: TObject);
  var
    Clinics: IXMLClinicsType;
  begin
    Clinics := GetClinics(XMLDocument1);
  end;
Of course, it would be more useful to place the Clinics variable in the Form declaration itself, so you can use the Clinics interface during the lifetime of your main form. Using the IXMLClinicsType is now much easier than using the plain XMLDocument as we did last month. We can now get individual Clinic elements from Clinics using the Get_Clinic method, add Add or Insert (at a specific location) a new Clinic element in the list. In order to get the first Clinic item, we can use Clinics.Clinic and the Getter and Setter methods. However, like real Delphi users, it's much easier to directly use the No, Title, Date and Topics properties to list the individual fields, as follows:
  procedure TForm1.ButtonGetClick(Sender: TObject);
  var
    Clinic: IXMLClinicType;
  begin
    Clinic := Clinics.Clinic[0];
    EditNo.Text := Clinic.No;
    EditTitle.Text := Clinic.Title;
    EditDate.Text := Clinic.Date;
    EditTopics.Text := Clinic.Topics
  end;
As you can see in the generated Clinic.pas unit, the Getter and Setter methods are just used to implement the properties, and have no real benefit over using the properties (in fact, I always feel that using the properties results in much clearer code). Besides, Delphi 6 code insight already makes sure that we only see the properties and not the getter and setter methods (another welcome new feature of Delphi 6). If you compare that to the "plain" XML Document programming we did last month (using only the XMLDocument component - with no Code Insight support or compile time error checking), then you'll see why the XML Data Binding is so much more convenient to use.

As another example, to Add a new Clinic to the end of the list, just call Add and make sure to save the result, which is an interface of type IXMLClinicType (an individual Clinic item). We can used the saved interface to set the individual fields (No, Title, Date and Topics) as follows:

  procedure TForm1.ButtonAddClick(Sender: TObject);
  begin
    with Clinics.Add do
    begin
      No := '2001-2-8;  // 8th Clinic of the 2nd series of 2001
      Title := 'Special Kylix 2 Clinic';
      Date := '2001/12/21';
      Topics := 'Kylix 2 New Features'
    end
  end;
This insert a new clinic - namely the special Kylix 2 Clinic that I've done on December 21st 2001 in Eindhoven, The Netherlands.
Finally, since we didn't set the AutoSave option (part of the XMLDocument.Options flags), we have to make sure that we actually save the modified or new contents of the XML Document. Although the interface was extracted from the XMLDocument component, we've still only made changes to the XMLDocument component. And in order to save these changes, we only have to call the XMLDocument.Save method as follows:
  procedure TForm1.FormDestroy(Sender: TObject);
  begin
    XMLDocument1.SaveToFile;
  end;
That's all. Again a very convenient way to work with XML documents, if you ask me. It keeps on getting better and better...

Next Time
Although the XML Data Binding adds code insight support to our XML document programming, there are still some places that feel a bit "unfinished". Like the way we have to navigate through the set of individual Clinic elements (from the set of Clinics). It sure would help if we had some kind of real data binding (to data-aware components, I mean, not just to strings). In fact, there is such an option, but for that we have to use the even more advanced Delphi 6 XML Mapper, which is a topic for another day (so stay tuned)...

For more recent information on Delphi for Win32 XML Programming, check out my Delphi 2010 XML, SOAP & Web Services courseware manual.


This webpage © 2001-2010 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.