Evitando o diálogo Boilerplate em Delphi e /ou C ++
-
08-07-2019 - |
Pergunta
Muitas vezes, preciso projetar uma caixa de diálogo no construtor Delphi/C ++ que permite que várias propriedades de um objeto sejam modificadas, e o código para usá -lo normalmente se parece com isso.
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;
Eu também geralmente preciso de um código semelhante para marcar objetos para/de xml/ini-files/o que for.
Existem idiomas ou técnicas comuns para evitar esse tipo de código simples, mas repetitivo?
Solução
Bem, algo que me sinto completamente inestimável é o Gexperts Assistente de plug -in "declaração reversa", que é invocada após a instalação do GEXPERTS, pressionando o Shift + Alt + R
O que ele faz é alternar automaticamente as atribuições para o bloco destacado. Por exemplo:
edit1.text := dbfield.asString;
torna-se
dbField.asString := edit1.text;
Não é exatamente o que você está procurando, mas um enorme economizador de tempo quando você tem um grande número de tarefas.
Outras dicas
Aqui está minha variação sobre isso. O que eu fiz, tendo sido cansado do mesmo código repetitivo, foi nomear todas as caixas de edição de acordo com os nomes de nó XML que eu queria, depois itera em torno dos componentes e produzir seus valores. O código XML deve ser óbvio, e eu só tenho uma edição e uma caixa de seleção, mas você deve ver a ideia.
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;
Não é considerado uma boa prática acessar propriedades de componentes visuais em um formulário. É considerado melhor ter propriedades separadas. No exemplo acima, você teria propriedades de nome de usuário e senha com métodos GET e Set.
Por exemplo:
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.
Isso significa que você pode alterar os componentes visuais no formulário sem que ele afete o código de chamada.
Outra opção seria passar o objeto como uma propriedade para a caixa de diálogo;
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.
Espero que ajude.
Delphi pelo menos tem 'com', embora não resolva completamente o problema.
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;
E o construtor Afaik não tem nada parecido.
Os controles de ligação aos dados funcionam bem em Delphi, mas infelizmente somente quando esses dados residem em um descendente de TDataset. Você pode escrever um descendente do TDataSet que usa um objeto para armazenamento de dados, e acontece que já existe uma dessas coisas. Veja o link abaixo ... Esta implementação parece funcionar apenas com coleções de objetos (TCollection ou TobjectList), não objetos únicos.
http://www.torry.net/pages.php?id=563 - Pesquise a página para "DATASET DE ESTRABILIDADE
Não tenho experiência pessoal com isso, mas seria muito útil se funcionasse e, especialmente, se também funcionasse com instâncias de objetos únicos, como uma propriedade em um módulo de dados ...
Olho para cima "Padrão do mediador". É um padrão de design do GOF e, em seu livro, o GoF de fato motiva esse padrão de design com uma situação um tanto semelhante ao que você está descrevendo aqui. Ele visa resolver um problema diferente - acoplamento - mas acho que você tem Esse problema também.
Em suma, a idéia é criar um mediador de diálogo, um objeto extra que fica entre todos os widgets de diálogo. Nenhum widget conhece nenhum outro widget, mas cada widget conhece o mediador. O mediador conhece todos os widgets. Quando um widget muda, ele informa o mediador; O mediador então informa os widgets relevantes sobre isso. Por exemplo, quando você clica em OK, o mediador pode informar outros widgets sobre este evento.
Dessa forma, cada widgets cuida apenas de eventos e ações relacionadas a si mesmo. O mediador cuida da interação entre todos os widgets; portanto, todo esse código "Boilerplate" é dividido em todos os widgets, e o "resíduo" que é global para todos os widgets é a interação e é de responsabilidade do mediador.