Monday, April 23, 2012

How to: on the fly create runtime data aware controls

There are many situations where you have a dataset with unknown fields structure or you just want to popup a small form and present to user some specific fields from the dataset , ie the fields need to be edited after an unsuccessful validation.

So, here is a simple function i have been using for long time ago to create data aware controls on panels, dialogs, scrollboxes:

uses StdCtrls, DB, DbCtrls, TypInfo;

var
  Label_W: integer = 200; {@ Width of the label controls}
  Control_W: integer = 500; {@ Max width of edit controls}
  Ctrl_Ident: integer = 2; {@ Distance between controls (horz & vert)}

function CreateDatasetEditor(
         COwner: TComponent; {@ The owner of control, it will be responsible for destruction}
         WParent: TWinControl; {@ The parent window where controls will live in}
         DSource: TDataSource; {@ TDataSource to be associated with controls}
         const Names, {@ Array of field names to use
               (optional, empty array will use all fields from TDataSource.Dataset}
               Labels: array of string; {@ Array of labels to use
               (optional, empty cells will use field.DisplayLabel }
         X: integer; Y: integer {X,Y coordinates in WParent to start positioning controls}
         ): TRect; {@ Result TRect used to place controls}

var i, j, iHigh: integer;
    c, ic : TControl;
    s: string;
    fld: TField;
    iL,iT: integer;
    Fields: TFields;
    Canvas: TControlCanvas;

 {@ Create a label control}
 procedure CreateDBLabel(ForField: TField; LabelText: string);
 begin
    with TLabel.Create(COwner) do begin
     Parent := WParent;
     AutoSize := False;
     Left := iL + Ctrl_Ident; Inc(iT,Ctrl_Ident); Top:=iT;
     Width := Label_W;
     WordWrap := False;
     if LabelText<>'' then
        Caption := LabelText
     else
        Caption := ForField.DisplayLabel;
     Alignment := taRightJustify;
     AutoSize := True;
     Transparent := True;
     end;
 end;

 {@ Create editing data aware control}
 function CreateEditField(ForFld: TField; sLabel: string): TControl;
 var w, h: integer;
 begin
  {@ Create edit control's associated label}
  CreateDBLabel(ForFld, sLabel);

  {@ Create actual data aware control based on filed info}
  if (ForFld.DataType in [ftBoolean]) then begin
      Result := TDBCheckBox.Create(nil);
      end
  else
  if (ForFld.DataType in [ftMemo, ftFmtMemo]) then begin
      Result := TDBMemo.Create(nil);
      Result.Width := Control_W;
      end
  else
  if (ForFld.FieldKind = fkLookup) then begin
      Result := TDBLookupComboBox.Create(nil);
      end
  else
      begin
      Result := TDBEdit.Create(nil);
      end;

  {@ Insert created control to COwner component hierarchy (for destruction puproses)}
  COwner.InsertComponent(Result);
  {@ Set control parent, width and other properties}
  Canvas.Control := Result;
  Result.Parent := WParent;
  Result.Enabled := not ForFld.ReadOnly;
  case ForFld.DataType of
    ftWord, ftSmallInt, ftInteger, ftAutoInc, ftLargeint: w := Canvas.TextWidth('###,###,###,###,###')+25;
    ftCurrency, ftFloat: w := Canvas.TextWidth('###,###,###,###,##0.00')+25;
    else
    w := ForFld.DisplayWidth * Canvas.TextWidth('W')+50;
    h := Canvas.TextWidth('Wq')+3;
    end;
  if not (ForFld.DataType in [ftMemo, ftFmtMemo]) then
     if w > Control_W then Result.Width := Control_W else Result.Width := w;
  {@ Connect control to DataSource & Field}
  TypInfo.SetOrdProp(Result,'DataSource',LongInt(DSource));
  TypInfo.SetPropValue(Result,'DataField',ForFld.FieldName);
  {@ Final adjustment of control width}
  if Result.Width > Control_W then Result.Width := Control_W;
 end;

 {@ Position a control in sequence}
 procedure PositControl(c: TControl);
 begin
  c.Left := iL + Ctrl_Ident*2 +Label_W; c.Top:=iT; Inc(iT,c.Height);
  Result.Bottom := iT;
  if Result.Right < c.BoundsRect.Right then
    Result.Right := c.BoundsRect.Right;
 end;

begin
 if not Assigned(DSource.DataSet) then Exit;
 Fields := DSource.DataSet.Fields;
 iL:=X;iT:=Y;
 Result.Left := X;
 Result.Top := Y;
 Canvas := TControlCanvas.Create;
 try
 iHigh := High(Labels);
 if Length(Names) > 0 then
    begin // Create controls from Names array
    j:=High(Names);
    for i:=0 to j do begin
      fld := Fields.FindField(Names[i]);
      if Assigned(Fld) then begin
        s:='';
        if (i<=iHigh) then s := Labels[i];
        c := CreateEditField(Fld,s);
        if Assigned(c) then
           PositControl(c);
        end;
      end;
    end
 else
    begin //Create controls from dataset.fields
    j:=Fields.Count-1;
    for i:=0 to j do
      begin
      s:='';
      if (i<=iHigh) then s := Labels[i];
      c := CreateEditField(Fields[i],s);
      if Assigned(c) then
         PositControl(c);
      end;
    end;
 finally Canvas.Free;
 end;
end;


Have fun developing, because development is fun!


No comments:

Post a Comment