views:

513

answers:

6

I often need to design a dialog in Delphi/C++Builder that allows various properties of an object to be modified, and the code to use it typically looks like this.

Dialog.Edit1.Text := MyObject.Username;
Dialog.Edit2.Text := MyObject.Password;
// ... many more of the same

if (Dialog.ShowModal = mrOk) 
begin
  MyObject.Username := Dialog.Edit1.Text;
  MyObject.Password := Dialog.Edit2.Text;
  // ... again, many more of the same
end;

I also often need similar code for marshalling objects to/from xml/ini-files/whatever.

Are there any common idioms or techniques for avoiding this kind of simple but repetitive code?

A: 

Delphi at least have 'With', though it doesn't solve the problem completely.

if (Dialog.ShowModal = mrOk) 
begin
  with MyObject do
  begin
    Username := Dialog.Edit1.Text;
    Password := Dialog.Edit2.Text;
    // ... again, many more of the same
  end;
end;

And builder AFAIK has nothing alike.

akalenuk
+2  A: 

Here's my variation on this. What I did, having got fed up with the same repetitive code, was to name all the edit boxes according to the XML node names I wanted, then iterate around the components and output their values. The XML code should be obvious, and I only have an edit and checkbox, but you should be able to see the idea.

procedure TfrmFTPSetup.LoadFromXML(szFileName : string);
var
xComponent : TComponent;
nLoop : Integer;
xMainNode : TXmlNode;
xDocument : TNativeXml;
begin
inherited;

xDocument := TNativeXml.Create;
try
 xDocument.LoadFromFile(szFileName);
 xMainNode := xml_ChildNodeByName(xDocument.Root, 'options');
 for nLoop := 0 to ComponentCount - 1 do
 begin
  xComponent := Components[nLoop];
  if xComponent is TRzCustomEdit then
  begin
   (xComponent as TRzCustomEdit).Text := xMainNode.AttributeByName[xComponent.Name];
  end;
  if xComponent is TRzCheckBox then
  begin
   (xComponent as TRzCheckBox).Checked := xml_X2Boolean(xMainNode.AttributeByName[xComponent.Name], false);
  end;
 end;
finally
 FreeAndNil(xDocument);
end;
 end;

   procedure TfrmFTPSetup.SaveToXML(szFileName : string);
var
xComponent : TComponent;
nLoop : Integer;
xMainNode : TXmlNode;
xDocument : TNativeXml;
begin
inherited;

xDocument := TNativeXml.CreateName('ftpcontrol');
try
 xMainNode := xml_ChildNodeByNameCreate(xDocument.Root, 'options');
 for nLoop := 0 to ComponentCount - 1 do
 begin
  xComponent := Components[nLoop];
  if xComponent is TRzCustomEdit then
  begin
   xMainNode.AttributeByName[xComponent.Name] := (xComponent as TRzCustomEdit).Text;
  end;
  if xComponent is TRzCheckBox then
  begin
   xMainNode.AttributeByName[xComponent.Name] := xml_Boolean2X((xComponent as TRzCheckBox).Checked);
  end;
 end;

 xDocument.XmlFormat := xfReadable;
 xDocument.SaveToFile(szFileName);
finally
 FreeAndNil(xDocument);
end;
 end;
mj2008
That's nice. So once I marshal to/from XML, I can use that as an intermediate betweem the dialogs and the objects. Now all I need to do is automate the XML marshalling...
Roddy
I see that you're using raize components.. In RC5 you have a TRzPropertyStore, which lets you persist property values in an .ini file. After you've configured which properties to persist, all you need to do is call RzPropertyStore1.Load() and RzPropertyStore1.Save()
Wouter van Nifterick
+3  A: 

well, something that I feel completely invaluable is the GExperts plugin wizard "Reverse Statement" which is invoked after installing GExperts by pressing Shift + ALT + R

What it does is automatically switch the assignments around for the highlighted block. For example:

edit1.text := dbfield.asString;

becomes

dbField.asString := edit1.text;

Not exactly what your looking for, but a huge time saver when you have a large number of assignments.

skamradt
Thanks! I have GExperts installed but have never found this before. Best of all, it works for c++Builder as well.
Roddy
A: 

Binding controls to data works well in Delphi, but unfortunately only when that data resides in a TDataSet descendant. You could write a TDataSet descendant that uses an object for data storage, and it turns out that one such thing already exists. See link below... This implementation appears to only work with collections of objects (TCollection or TObjectList), not single objects.

http://www.torry.net/pages.php?id=563 - search the page for for "Snap Object DataSet"

I have no personal experience with this, but it would be very useful if it works and especially if it would also work with single object instances, such as a property on a data module...

Jozz
A: 

Look up "mediator pattern". It's a GoF design pattern, and in their book the GoF in fact motivate this design pattern with a somewhat similar situation to what you're describing here. It aims at solving a different problem -- coupling -- but I think you have this problem too anyhow.

In short, the idea is to create a dialog mediator, an extra object that sits in between all the dialog widgets. No widget knows about any other widget, but each widget does know the mediator. The mediator knows all widgets. When one widget changes it informs the mediator; the mediator then informs the relevant widgets about this. For example, when you click OK the mediator may inform other widgets about this event.

This way each widgets takes care of events and actions related to itself only. The mediator takes care of the interaction between all widgets, so all this "boilerplate" code is split over all widgets, and the "residue" which is global to all widgets is the interaction, and it is the responsibility of the mediator.

wilhelmtell
In Delphi, your "dialog mediator" is effectively the TDialog. And I assume you mean controls when you say "widgets"?
Roddy
I suppose so, yes. Widgets are the GUI elements. Buttons, sliders, list boxes, windows, views, everything GUI. In this context, I'm talking about buttons, list boxes and other things a dialog box can hold.
wilhelmtell
+1  A: 

It's not considered good practice to access properties of visual components on a form. It is considered better to have seperate properties. In the example above you would have username and password properties with get and set methods.

For example:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
  private
    function GetPassword: string;
    function GetUsername: string;
    procedure SetPassword(const Value: string);
    procedure SetUsername(const Value: string);
  public
    property Password: string read GetPassword write SetPassword;
    property Username: string read GetUsername write SetUsername;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function TForm1.GetPassword: string;
begin
 Result := Edit2.Text;
end;

function TForm1.GetUsername: string;
begin
 Result := Edit1.Text;
end;

procedure TForm1.SetPassword(const Value: string);
begin
  Edit2.Text := Value;
end;

procedure TForm1.SetUsername(const Value: string);
begin
  Edit1.Text := Value;
end;

end.

This means you can change the visual components on the form without having it affecting the calling code.

Another option would be to pass the object as a property to the dialog;

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TUserObject = class(TObject)
  private
   FPassword: string;
   FUsername: string;
  public
   property Password: string read FPassword write FPassword;
   property Username: string read FUsername write FUsername;
  end;

  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    btnOK: TButton;
    procedure btnOKClick(Sender: TObject);
  private
    FUserObject: TUserObject;
    procedure SetUserObject(const Value: Integer);
  public
    property UserObject: Integer read FUserObject write SetUserObject;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnOKClick(Sender: TObject);
begin
 FUserObject.Username := Edit1.Text;
 FUserObject.Password := Edit2.Text;
 ModalResult := mrOK;
end;

procedure TForm1.SetUserObject(const Value: Integer);
begin
 FUserObject := Value;
 Edit1.Text := FUserObject.Username;
 Edit2.Text := FUserObject.Password;
end;

end.

Hope that helps.

dcraggs