#c #delphi #modal-dialog #boilerplate
Вопрос:
Мне часто приходится создавать диалоговое окно в Delphi/C Builder, которое позволяет изменять различные свойства объекта, и код для его использования обычно выглядит так.
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;
Мне также часто нужен аналогичный код для сортировки объектов в/из xml/ini-файлов/чего угодно.
Существуют ли какие-либо общие идиомы или методы, позволяющие избежать такого рода простого, но повторяющегося кода?
Комментарии:
1. «Диалог. Правка1. Текст := Мой объект. Имя пользователя» — это не совсем хорошая инкапсуляция. Несмотря на то, что это только добавляет больше кода котельной, добавление свойства имени пользователя в TDialog значительно повышает гибкость (например, «Диалог. Имя пользователя := MyObject. Имя пользователя»).
2. @onnodb: Я обычно кодирую это как дополнительный метод в своем классе диалоговых окон, и мои TEdits получают разумные имена, поэтому обычно это выглядит так: Имя пользователя. Текст := Мой объект. Имя пользователя; — и имя пользователя TEdit в идеале должно быть «защищено», а не опубликовано, но этого не может быть…
Ответ №1:
ну, что-то, что я чувствую совершенно бесценным, — это мастер плагинов GExperts «Обратный оператор», который вызывается после установки GExperts нажатием Shift ALT R
Что он делает, так это автоматически переключает назначения для выделенного блока. Например:
edit1.text := dbfield.asString;
становится
dbField.asString := edit1.text;
Не совсем то, что вы ищете, но огромная экономия времени, когда у вас большое количество заданий.
Комментарии:
1. Спасибо! У меня установлены GExperts, но я никогда не находил этого раньше. Лучше всего то, что это работает и для c Builder.
Ответ №2:
Вот моя вариация на этот счет. Что я сделал, устав от одного и того же повторяющегося кода, так это назвал все поля редактирования в соответствии с именами узлов XML, которые я хотел, затем перебрал компоненты и вывел их значения. XML-код должен быть очевиден, и у меня есть только правка и флажок, но вы должны быть в состоянии увидеть идею.
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;
Комментарии:
1. Это мило. Поэтому, как только я перейду в/из XML, я смогу использовать его в качестве промежуточного звена между диалоговыми окнами и объектами. Теперь все, что мне нужно сделать, это автоматизировать сортировку XML…
2. Я вижу, что вы используете компоненты raize.. В RC5 у вас есть хранилище TRzPropertyStore, которое позволяет сохранять значения свойств в файле .ini. После того, как вы настроили, какие свойства должны сохраняться, все, что вам нужно сделать, это вызвать RzPropertyStore1.Load() и RzPropertyStore1.Save()
Ответ №3:
Доступ к свойствам визуальных компонентов формы не считается хорошей практикой. Считается, что лучше иметь отдельные свойства. В приведенном выше примере у вас будут свойства имени пользователя и пароля с помощью методов get и set.
Например:
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.
Это означает, что вы можете изменять визуальные компоненты формы, не влияя на вызывающий код.
Другим вариантом было бы передать объект в качестве свойства в диалоговое окно;
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.
Надеюсь, это поможет.
Ответ №4:
У Дельфи, по крайней мере, есть «С», хотя это не решает проблему полностью.
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;
И у строителя АФАИКА нет ничего похожего.
Ответ №5:
Привязка элементов управления к данным хорошо работает в Delphi, но, к сожалению, только тогда, когда эти данные находятся в потомке TDataSet. Вы могли бы написать потомок TDataSet, который использует объект для хранения данных, и оказывается, что одна такая вещь уже существует. Смотрите ссылку ниже… Эта реализация, по-видимому, работает только с коллекциями объектов (TCollection или TObjectList), а не с отдельными объектами.
http://www.torry.net/pages.php?id=563 — найдите на странице «Набор данных объектов привязки».
У меня нет личного опыта в этом, но было бы очень полезно, если бы это работало, и особенно если бы это также работало с отдельными экземплярами объектов, такими как свойство в модуле данных…
Ответ №6:
Найдите «шаблон посредника«. Это шаблон дизайна GoF, и в их книге GoF на самом деле мотивирует этот шаблон дизайна несколько похожей ситуацией на то, что вы описываете здесь. Он направлен на решение другой проблемы-сцепления-но я думаю, что у вас все равно есть эта проблема.
Короче говоря, идея состоит в том, чтобы создать посредника диалога, дополнительный объект, который находится между всеми виджетами диалоговых окон. Ни один виджет не знает о каком-либо другом виджете, но каждый виджет знает посредника. Посредник знает все виджеты. Когда один виджет изменяется, он сообщает об этом посреднику; затем посредник сообщает об этом соответствующим виджетам. Например, при нажатии кнопки ОК посредник может сообщить другим виджетам об этом событии.
Таким образом, каждый виджет заботится о событиях и действиях, связанных только с ним самим. Посредник заботится о взаимодействии между всеми виджетами, поэтому весь этот «шаблонный» код разбит на все виджеты, а «остаток», который является глобальным для всех виджетов, — это взаимодействие, и это ответственность посредника.
Комментарии:
1. В Delphi вашим «посредником диалога» фактически является TDialog. И я предполагаю, что вы имеете в виду элементы управления, когда говорите «виджеты»?
2. Я полагаю, что да, да. Виджеты-это элементы графического интерфейса. Кнопки, ползунки, списки, окна, представления, все графическое. В этом контексте я говорю о кнопках, списках и других вещах, которые может содержать диалоговое окно.