Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #5
See Also: other Dr.Bob Examines columns or Delphi articles

Custom InternetExpress Components
This article is about writing custom InternetExpress components. The code works with Delphi 5 Enterprise and most likely also with C++Builder 5 Enterprise (or at least without too many problems). As for Kylix, I guess we have to wait and see what's compatible and what's not when Kylix really ships...

Delphi custom component building usually consist of deriving from TComponent and implementing the desired behaviour. The most difficult choice often takes place at the start: which component to derive from (TControl, TWinControl, TCustomControl or a plain TEdit?). For InternetExpress components, this story is a bit different. The main reason for that is not merely the fact that we can't even see the InternetExpress components on a component palette (we can't see TControl, TWinControl or TCustomControl either), but mainly the fact that the InternetExpress components implement interfaces as well. The fact that the inner workings are almost undocumented sure helps. Not. Which is why I decided - after some exploration of my own - to share some of my findings here.

As most of you probably know, Delphi/C++Builder 5 InternetExpress components appear on the Web Page Editor only. This means that it isn't easy to get an overview (or feeling) of how many there are, or which ones we still miss. The Web Page Editor doesn't even show them all at once, but only the relevant components, based on the selected (parent) component. Initially, we can select base forms like a DataForm and a QueryForm (or a LayoutGroup, which can be used for specific layout options - I usually ignore this feature). A DataForm generates an HTML form for displaying data and the controls that manipulate the data or submit updates. Sub-component we can to DataForms are TDataGrid (data in a multi-record grid) or TFieldGroup (a set of controls each of which represents a single field from a single record. In addition, we can add a TDataNavigator (a set of buttons to navigate through data or post updates), or a TAppyUpdatesButton to apply updates back to the Web server. Each of these items contains subitems to represent individual fields or buttons. A QueryForm generates an HTML form for displaying or reading application-defined values. We can use this form for displaying and submitting parameter values. Sub-components we can add to QueryForms are TQueryFieldGroup (display application-defined values) or TQueryButtons (a set of buttons to submit or reset those values). Each of these items contains subitems to represent individual values or buttons.

The goal of this article is to show how we can write our own InternetExpress custom components that can be used in either (or both) a DataForm or a QueryForm. Before we start, the most important unit for our explorations is the MidItems unit. It contains the definition of the TWebForm component, the Form that should ends up containing all the InternetExpress-aware components, such as TDataGrid, TDataNavigator, TFieldGroup and sub-sub-components. There are two sub-classes derived from TWebForm: TDataForm and TQueryForm. In fact, TQueryForm is derived from TCustomQueryForm, which is derived from TWebForm but also implements IQueryForm. The latter is an interface with no methods, and is probably merely used to distinguish a DataForm with a QueryForm at run-time (otherwise I have no explanation why someone would use an empty interface in the first place).

The real sub-components can also be found in the MidItems unit. These include the TFieldText, TTextColumn and TQueryText (among others). Their ancestor class is TWebTextInput, which is the base class of our custom InternetExpress components, and has a number of interesting methods we should override and implement. The most interesting method are ControlContents and AddAttributes which are used to generate the HTML that defines our control in the resulting webpage. This is what makes a RadioButton a radio button, and a CheckBox a check box. Speaking of the latter, developers who've tried to work with InternetExpress and the Web Page Editor may have noticed that we have just about every HTML control available, except for check boxes. I wonder why, but that doesn't matter (at least I have a nice example to write about in this InternetExpress custom components article).

Latest News: John Kaster of Borland DevRel told me the reason they didn't implement checkbox support (yet). The state of the checkbox when off is indeterminate -- no value is passed back UNLESS the checkbox is turned on. Borland hasn't found a solution they like, yet, and you'll be facing the same problem with my component.

The TWebTextInput AddAttributes method is defined as follows:

  procedure TWebTextInput.AddAttributes(var Attrs: string);
  begin
    AddQuotedAttrib(Attrs, 'NAME', GetHTMLControlName);
    AddIntAttrib(Attrs, 'SIZE', DisplayWidth);
    AddIntAttrib(Attrs, 'TABINDEX', TabIndex);
    AddQuotedAttrib(Attrs, 'STYLE', Style);
    AddQuotedAttrib(Attrs, 'CLASS', StyleRule);
    AddCustomAttrib(Attrs, Custom);
  end;
Without showing the AddQuotesAttrib and the AttIntAttib methods as well, I think it's safe to assume that you'll accept the fact that the AddAttributes method puts the HTML control name (a lookup function), the DisplayWidth, TabIndex, Style and StyleRule in a single output string Attrs, that is used by the ControlContents to generate the complete HTML code we need. In order to add special HTML type controls, we need to override the ControlContent method and use the Attrs as returned by the AddAttributes method above.

TWebCheckBox
For our simple WebCheckBox component, we need to descend from TWebTextInput, and override one method: the ControlContents. The definition and implementation of this new class is as follows:

  type
    TWebCheckbox = class(TWebTextInput)
    protected
      function ControlContent(Options: TWebContentOptions): string; override;
    published
      property DisplayWidth;
      property ReadOnly;
      property Caption;
      property CaptionAttributes;
      property CaptionPosition;
      property TabIndex;
      property Style;
      property Custom;
      property StyleRule;
    end;

  implementation

  function TWebCheckbox.ControlContent(Options: TWebContentOptions): string;
  var
    Attrs: string;
  begin
    AddAttributes(Attrs);
    Result := Format('<INPUT TYPE=CHECKBOX %0:s>', [Attrs]);
  end;
It's as simple as that, and after we register this component - in a special way, see the end of this article - it'll become visible in the Web Page Editor (as possible child component of DataForms), so we can use it right away in our InternetExpress applications. Apart from showing up as child of a DataForm, however, this component does not show as possible child of QueryForms. For that, we need to do something special...

TQueryCheckBox
In order to make an InternetExpress component work for QueryForms instead of DataForms, we need to add an interface definition to it, and implement it (of course). Specifically, we can derive a new TQueryCheckBox from out previous TWebCheckBox, and implement the IQueryField interface to result in a new InternetExpress custom component that can be added to QueryForms. Before we show the actual new component, let's first look at the IQueryField interface, which is defined as follows:

  type
    IQueryField = interface(IHTMLField)
    ['{7C321115-FCFB-11D2-AFC3-00C04FB16EC3}']
      function GetParamName: string;
      procedure SetParamName(AParamName: string);
      function GetText: string;
      procedure SetText(const Value: string);
      property ParamName: string read GetParamName write SetParamName;
      property Text: string read GetText write SetText;
    end;
This means that any class that implements the IQueryField interface needs to implement the methods specified in the interface. The definition (and implementation) of the new TQueryCheckBox class is as follows (note that we only implement the GetText/SetText methods and not the ParamName methods):
  type
    TQueryCheckbox = class(TWebCheckbox, IQueryField)
    private
      FText: string;
    protected
      function GetText: string;
      procedure SetText(const Value: string);
    public
      class function IsQueryField: Boolean; override;
    end;

  implementation

  class function TQueryCheckbox.IsQueryField: Boolean;
  begin
    Result := True;
  end;

  function TQueryCheckbox.GetText: string;
  begin
    Result := FText;
  end;

  procedure TQueryCheckbox.SetText(const Value: string);
  begin
    FText := Value;
  end;
Apart from the IQueryField interface, the class method IsQueryField makes sure that any component can "introspect" the TQueryCheckBox component and see that it's meant for QueryForms only.

Registration
Apart from the TWebCheckBox and TQueryCheckBox (which weren't too hard to implement, were they?), there's one last task that's a bit more complex than usual: registering the two components. Since we're not dealing with regular components that appear on the component palette, it shouldn't come as a surprise that we need a special registration routine. In this case, that's procedure RegisterWebComponents, which takes an array of InternetExpress components and an optional (component?) editor. To register our two new custom InternetExpress components, we simply need to call:

  procedure Register;
  begin
    RegisterWebComponents([TWebCheckBox,TQueryCheckBox]);
  end;
Put everything in a package, and you have the first two useful new custom InternetExpress components available for daily use. I've written a few more, including some JavaScript-powered buttons, but that's a story for some other time...


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