В первой статье, посвященной работе с LiveBindings были рассмотрены простенькие примеры того как и где может использоваться связывание любых данных с визуальными компонентами на форме. Собственно всё, что от нас требовалось — правильно составить выражение для обеспечения связи и «виртуозно» им воспользоваться =). Думаю, что для первого знакомства с механизмом LiveBindings подобных примеров было достаточно.
Сегодня я решил сделать ещё один небольшой пробный шаг в использовании LiveBindings и сделать связь свойств своего объекта с компонентами на форме. Посмотрим как это можно реализовать, а заодно и напишем пару выражений для связи самостоятельно безо всяких редакторов.
Прежде, чем приступим непосредственно к работе над тестовой программой, рассмотрим подробнее решаемую задачу. Итак, есть модуль в котором описан класс, скажем вот такой:
ype TBlogInfo = class private fName: string; fURL: string; fSubscribers: integer; procedure SetName(const Value: string); procedure SetSubscribers(const Value: integer); procedure SetURL(const Value: string); public constructor Create; destructor Destroy; override; property Name: string read FName write SetName; property URL: string read FURL write SetURL; property Subscribers: integer read FSubscribers write SetSubscribers; end;
Как видите — это не компонент и на форму его не уложить. По ходу работы программы, например, в момент её запуска создается объект:
[...] MyBlog:=TBlogInfo; [...]
Каким образом можно связать свойства этого объекта с визуальными компонентами на форме? Для того, чтобы определить связи в design-time нам нужны именно наследники от TComponent, а наш класс — от TObject и связь вроде бы не создать.
Но на деле способ связывания есть. И причём воспользоваться им можно как в run-time так и в design-time. Называется этот способ — использование компонента TBindScope. На вкладке LiveBindings палитры компонентов вы можете найти два компонента — TBindscope и TBindScopeDB. Второй используется для создания связей между записями базы данных и визуальными компонентами. Нас же сегодня интересует более простая реализация компонента — TBindScope.
В числе прочих, у компонента TBindScope имеется следующее замечательное свойство:
property DataObject: TObject read FDataObject write SetDataObject;
Записав в него наш объект мы получим как раз возможность обеспечивать связи между свойствами нашего объекта и компонентами на форме. И вот теперь, определившись с вводной, начнем писать нашу программку. Открываем Delphi XE2, создаем новое VCL-приложение и укладываем на форму следующие компоненты:
- BindingsList — в нем будем хранить наши выражения для связей
- BindScope — этот компонент будет держать в свойстве DataObject экземпляр нашего класса
- 3 Edit’а для ввода/вывода информации
- 2 Button’а — для загрузки/сохранения данных
- 3 label’а
type TBlogInfo = class private fName: string; fURL: string; fSubscribers: integer; procedure SetName(const Value: string); procedure SetSubscribers(const Value: integer); procedure SetURL(const Value: string); public constructor Create; destructor Destroy; override; property Name: string read FName write SetName; property URL: string read FURL write SetURL; property Subscribers: integer read FSubscribers write SetSubscribers; end; implementation {$R *.dfm} { TBlogInfo } constructor TBlogInfo.Create; begin inherited Create; fName:='WebDelphi'; fURL:='http://wwww.webdelphi.ru'; fSubscribers:=355; end; destructor TBlogInfo.Destroy; begin inherited; end; procedure TBlogInfo.SetName(const Value: string); begin FName := Value; ShowMessage('Новое название: '+Value); end; procedure TBlogInfo.SetSubscribers(const Value: integer); begin FSubscribers := Value; ShowMessage('Новое количество подписчиков: '+IntToStr(Value)); end; procedure TBlogInfo.SetURL(const Value: string); begin FURL := Value; ShowMessage('Новый URL: '+Value); end;
Теперь переходим к главному — обеспечиваем связь между нашим классом и компонентами на форме. Как я уже сказал выше, сделать мы это можем как в design-, так и в run-time. Вначале рассмотрим второй вариант и создадим выражение связи для одного из свойств класса, например для Name. Пишем такую процедуру:
procedure TForm13.CreateExpression(AControlComponent: TComponent; AControlProperty, ASourceProperty: string; ABindScope: TBindScope; ABindingsList: TBindingsList); var Expression: TBindExpression; begin {создаем выражение} Expression := TBindExpression.Create(self); {назначаем компонент для отображения информации} Expression.ControlComponent := AControlComponent; {Указываем в какое свойство выводить данные} Expression.ControlExpression := AControlProperty; {указываем точку доступа - тут должен содержаться наш класс} Expression.SourceComponent := ABindScope; {Тут указываем свойство КЛАССА, которое будет выводится в AControlProperty} Expression.SourceExpression := ASourceProperty; {Обеспечиваем двухстороннюю связь - будем и читать и писать свойство} Expression.Direction := TExpressionDirection.dirBidirectional; {Назначаем список} expression.BindingsList := ABindingsList; end;
Думаю, комментариев в коде должно быть достаточно, чтобы понять, что делается в методе — тут мы обеспечиваем двухстороннюю связь между свойством нашего объекта и каким-либо визуальным компонентом. В нашем приложении эта процедура будет использоваться так:
CreateExpression(edName,'Text','Name',BindScope1,BindingsList1);
Так мы создаем связь между свойством Name нашего объекта и свойством Text у Edit’а. Теперь напишем ещё одну вспомогательную процедурку, которая будет заниматься тем, что будет записывать в BindScope наш объект и создавать необходимые связи:
procedure TForm13.InitializeBinding(ABlogInfo: TBlogInfo); begin {сначала создаем необходимые выражения} CreateExpression(edName,'Text','Name',BindScope1,BindingsList1); CreateExpression(edAddress,'Text','URL',BindScope1,BindingsList1); CreateExpression(edSubscribers,'Text','Subscribers',BindScope1,BindingsList1); {потом пишем свойства} BindScope1.DataObject:=ABlogInfo; end;
Теперь сделаем так, чтобы при запуске программы на форму выводились дефолтные значения свойств объекта:
procedure TForm13.FormCreate(Sender: TObject); begin BlogInfo:=TBlogInfo.Create; InitializeBinding(BlogInfo); end;
Теперь, если вы запустите программу, то в Edit’ы выпишутся значения свойств, которые были присвоены в момент создания объекта — прямая связь обеспечена. Следующий шаг — обеспечение обратной связи, т.е. сделать так, чтобы информация из Edit’ов записывалась в свойства. Пишем следующий обработчик кнопки «Сохранить»:
procedure TForm13.Button2Click(Sender: TObject); var F: TIniFile; begin {оповещаем об изменениях} BindingsList1.Notify(edName,''); BindingsList1.Notify(edAddress,''); BindingsList1.Notify(edSubscribers,''); {дли примера скидываем новые значения в INI-файлик} F:=TIniFile.Create(ExtractFilePath(Application.ExeName)+'Object.ini'); try F.WriteString('Object','Name',BlogInfo.Name); F.WriteString('Object','URL',BlogInfo.URL); F.WriteInteger('Object','Subscribers',BlogInfo.Subscribers); finally F.Free end; end;
Как видите, здесь нет ни одного явного присваивания значений свойствам класса, типа:
BlogInfo.Name:='Any name';
Такие операции будут обеспечены механизмом LiveBindings после соответствующего оповещения. Ну и на кнопке «Загрузка» будет такой код:
procedure TForm13.Button1Click(Sender: TObject); begin BindScope1.DataObject := BlogInfo; end;
После окончания работы не забываем убраться:
procedure TForm13.FormDestroy(Sender: TObject); begin BlogInfo.Free; end;
Теперь программа полностью готова к работе. Можете запустить её и убедиться, что двухсторонняя связь полностью поддерживается, свойства читаются и пишутся.
Что же касается указания связей в design-time, то достаточно посмотреть на процедуру CreateExpression, чтобы, воспользовавшись редактором выражений у BindingsList написать те же самые выражения.
И, осталось разобрать ещё один момент, касающийся сегодняшней темы — что произойдет, если мы вовремя не создадим объект?. Давайте проверим. Комментируем следующую строку:
procedure TForm13.FormCreate(Sender: TObject); begin //BlogInfo:=TBlogInfo.Create; - типо забыли создать объект InitializeBinding(BlogInfo); end;
Убираем всё, что касается записи в INI-файл, т.к. там 100% будет AV. Запускаем программу и наблюдаем, что никаких исключений нет ни при загрузке, ни при нажатии кнопок — просто Edit’ы не получают данных из объекта, который в данный момент у нас равен nil. Вот такой интересный механизм LiveBindings. Ну, а почему не возникло исключений мы рассмотрим в следующий раз.
Хорошая заметка, спасибо! А есть ли возможность сделать биндинг списка объектов? Например к обычному Grid
Andrey Durow, списки тоже можно связывать. Я правда, до этого ещё не дошел, но, думаю, что заметка на эту тему в блоге появится
замечательная заметка :) webdelphi за сегодня открыл мне глаза на xe2, потому что первое впечатление было совсем грустное. про liveBindings как раз думал на днях.. наткнулся на реализацию под Android и думал было «может сделать под D7…», а тут такое :) спасибо!
Очень интересует биндинг списка объектов, например через Grid или TreeView
Delphist, меня тоже интересует..ну сейчас решил поразбираться с Firemonkey, а там вроде без биндинга никак — может и до списков скоро доберусь
[…] в двух постах: "Delphi XE2. Знакомство с LiveBinding" и "Delphi XE2. LiveBindings для объектов", так что повторяться не будем по поводу того, что […]
Немного не понял обратную связь, а точнее не вижу никакой реакции на
BindScope1.DataObject := BlogInfo;
по нажатию кнопки «Загрузить» — что должно произойти визуально? (программного все понятно)
«Немного не понял обратную связь, а точнее не вижу никакой реакции на
BindScope1.DataObject := BlogInfo;
по нажатию кнопки «Загрузить» — что должно произойти визуально? (программного все понятно)»
Данные из пропертей BlogInfo должны перелиться в проперти соответ. компонентов на форме.
(класс TBindScope — это компонент адаптер для TBlogInfo)
[…] произвольных объектов (как это делалось в Delphi XE2 см. эту статью) и эта связь отображалась/редактировалась в […]
Просто пляска святого вита. Столько писанины c сомнительным эффектом. Не легче ли прямые связи прописывать?