Доброго дня всем!
Не так давно столкнулся с необходимостью работы с базой данных из FMX-приложения.
Те, кто уже «щупал» Delphi XE, должны быть в курсе, про отсутствие в FMX таких любимых VCL как:
И если проблема с DBGrid решается вполне интуитивно, визуальным биндингом, то с TDBLoockupComboBox не всё так однозначно.
Во всяком случае гугл не смог мне подсказать ничего толкового.
Проблему я решил; хочу поделиться решением с сообществом, возможно, кому-то пригодится.
Итак, начнем с того, что набросаем демо-проект.
БД для простоты возьмем SQLite.
Создадим главную табличку, второстепенную, и таблицу связи между ними.
Ну и накидаем чуток записей для теста.
tbl_main, tbl_status, main_st.
Далее подготовим проект в Delphi.
Главная форма, форма изменения статуса, модуль для работы с БД (DataModule).
На форму модуля данных кинем необходимые компоненты для работы с данными.
Предпочитаю FireDAC, поэтому накидал TFDPhysSQLiteDriverLink, TFDGUIxWaitCursor, TFDConnection и три TFDQuery.
На главную форму положим TGrid, растянем на всю форму.
На форму редактирования статуса положим TComboBox, он и будет у нас служить для выбора статуса.
По двойному клику на гриде главной формы, будем открывать форму редактирования статуса.
Также в датасет грида добавим lookup-поле «Status», которое будет ссылаться на датасет qStMain:
И будет подставлять в колонку грида наименование статуса, связанного с этой записью.
Теперь перейдем к датасету, который будет наполнять комбо-бокс:
Начальная подготовка завершена.
Перейдем к самому интересному — форме изменения статуса.
Выглядеть она будет так:

Подготовка формы.
Самое первое что нужно сделать — привязать датасет к комбо-боксу.
для этого нужно выбрать комбо-бокс в дизайнере и щелкнуть дважды на строке LiveBindings в инспекторе объектов.
В выпавшем меню выбрать «Bind Visually»:

После этого, в нижней части, отобразится окно привязок:

Для лукапа к БД, нам понадобится промежуточное поле.
В данном случае хорошо подходит свойство Tag, комбо-бокса.
В данном поле мы будем хранить текущее значение id выбранной строки датасета.
Поэтому тыкаем в три точки и выбираем Tag для отображения:

Не забываем прописать наш datamodule в uses формы.
После этого датасеты появляются в возможных биндах.

В биндинге протягиваем связи из полей датасета в комбо-бокс.
name тащим в Item.Text, id тащим в Item.LookupData.
Это приводит к автоматическому созданию на форме TBindSourceDB, TBindingsList и TLinkFillControlToField.

Уже на данный момент, если мы не забудем сделать open датасету, при открытии формы, в комбо-боксе будет список значений из датасета.

Казалось бы — все замечательно, но есть два момента.
Решим первую проблему.
Создадим в форме метод Init(), в который передадим id статуса в выбранной строке.
Соответственно, имея id, мы можем сделать locate в датасете и спозиционироваться на записи.
Данное действие не ведет к каким либо изменениям в комбо-боксе, поэтому нужно и в нем выбрать нужное значение.
Постойте, но ведь в гриде у нас нет id статуса!
Можно конечно добавить его в запрос, но мы не ищем легких путей.
Вернемся к главной форме и гриду.
На событие OnDblClick грида, повесим отображение формы изменения статуса:
Теперь по двойному клику на строке грида, будет открываться форма изменения статуса, с комбо-боксом, спозиционированном на нужном статусе.
Осталась проблема два. Какой бы статус мы не выбирали в комбо-боксе, датасет, привязанный к нему, не будет двигать курсор.
Решение проблемы два.
Я много гуглил на эту тему, но так и не нашел решения.
Пришлось придумать его самому.
Кладем на форму выбора статуса TPrototypeBindSource.
В окне «Structure», раскрываем список у данного объекта и жмем ПКМ->«AddItem» на строке «FieldDefs»:

Выбираем его тип «ftInteger» — это ж id, и называем его соответственно:

В LiveBindings Designer протягиваем связи от ComboBox1.SelectedValue к PrototypeBindSource1.id, затем от PrototypeBindSource1.id к ComboBox1.Tag.

Как ни странно, из ComboBox1.SelectedValue мы не можем получить id.
А вот после проведенной процедуры, из ComboBox1.Tag — можем!
Кто не верит — может поместить на форму label и писать в него значение Tag комбо-бокса, в момент onChange.
Или попытаться добыть id любым другим способом.
Итак, в итоге у нас есть id записи в датасете, на этом можно и ограничится, вычитывая значение поле Tag, после закрытия модальной формы.
Но это снова не наш метод.
Надо переместить курсор датасета, привязанного к комбо-боксу, к выбранной записи.
Если посмотреть на окно «Structure», то можно заметить появившийся элемент «LinkPropertyToFieldTag»:

Нажмем на него и создадим обработчик события onAssignedValue:
Теперь, при выборе строки в комбо-боксе, датасет тоже двигает курсор!
И мы можем с полным правом дополнить метод OnDblClick грида главной формы:
Запускаем, проверяем, радуемся!
Надеюсь, данная информация поможет кому-то обойти ограничения, накладываемые fmx.
PS:
Не так давно столкнулся с необходимостью работы с базой данных из FMX-приложения.
Те, кто уже «щупал» Delphi XE, должны быть в курсе, про отсутствие в FMX таких любимых VCL как:
- TDBGrid
- TDBLoockupComboBox
И если проблема с DBGrid решается вполне интуитивно, визуальным биндингом, то с TDBLoockupComboBox не всё так однозначно.
Во всяком случае гугл не смог мне подсказать ничего толкового.
Проблему я решил; хочу поделиться решением с сообществом, возможно, кому-то пригодится.
Итак, начнем с того, что набросаем демо-проект.
БД для простоты возьмем SQLite.
Создадим главную табличку, второстепенную, и таблицу связи между ними.
Ну и накидаем чуток записей для теста.
tbl_main, tbl_status, main_st.
Структура
-- Table: tbl_main CREATE TABLE tbl_main ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR( 255 ) ); INSERT INTO [tbl_main] ([id], [name]) VALUES (1, 'Запись1'); INSERT INTO [tbl_main] ([id], [name]) VALUES (2, 'Запись2'); INSERT INTO [tbl_main] ([id], [name]) VALUES (3, 'Запись3'); INSERT INTO [tbl_main] ([id], [name]) VALUES (4, 'Запись4'); -- Table: tbl_status CREATE TABLE tbl_status ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, name VARCHAR( 255 ) ); INSERT INTO [tbl_status] ([id], [name]) VALUES (1, 'Новый'); INSERT INTO [tbl_status] ([id], [name]) VALUES (2, 'Обработан'); INSERT INTO [tbl_status] ([id], [name]) VALUES (3, 'Проведен'); INSERT INTO [tbl_status] ([id], [name]) VALUES (4, 'Отложен'); -- Table: main_st CREATE TABLE main_st ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, id_main INTEGER NOT NULL REFERENCES tbl_main ( id ) ON DELETE CASCADE ON UPDATE CASCADE, id_status INTEGER NOT NULL REFERENCES tbl_status ( id ) ON DELETE CASCADE ON UPDATE CASCADE ); INSERT INTO [main_st] ([id], [id_main], [id_status]) VALUES (1, 1, 2); INSERT INTO [main_st] ([id], [id_main], [id_status]) VALUES (2, 2, 1); INSERT INTO [main_st] ([id], [id_main], [id_status]) VALUES (3, 3, 2); INSERT INTO [main_st] ([id], [id_main], [id_status]) VALUES (4, 4, 3);
Далее подготовим проект в Delphi.
Главная форма, форма изменения статуса, модуль для работы с БД (DataModule).
На форму модуля данных кинем необходимые компоненты для работы с данными.
Предпочитаю FireDAC, поэтому накидал TFDPhysSQLiteDriverLink, TFDGUIxWaitCursor, TFDConnection и три TFDQuery.
На главную форму положим TGrid, растянем на всю форму.
На форму редактирования статуса положим TComboBox, он и будет у нас служить для выбора статуса.
По двойному клику на гриде главной формы, будем открывать форму редактирования статуса.
Запрос для грида
Грид будет наполняться с помощью датасета FDQuery1.
select id, name from tbl_main
Также в датасет грида добавим lookup-поле «Status», которое будет ссылаться на датасет qStMain:
Запрос для лукапа в гриде
select ms.id, ms.id_main, ms.id_status, s.name from main_st ms join tbl_status s on s.id = ms.id_status
И будет подставлять в колонку грида наименование статуса, связанного с этой записью.
Теперь перейдем к датасету, который будет наполнять комбо-бокс:
Запрос для TComboBox
Комбо-бокс будет наполняться с помощью датасета qStatuses.
select id, name from tbl_status
Начальная подготовка завершена.
Перейдем к самому интересному — форме изменения статуса.
Выглядеть она будет так:

Подготовка формы.
Самое первое что нужно сделать — привязать датасет к комбо-боксу.
для этого нужно выбрать комбо-бокс в дизайнере и щелкнуть дважды на строке LiveBindings в инспекторе объектов.
В выпавшем меню выбрать «Bind Visually»:

После этого, в нижней части, отобразится окно привязок:

Для лукапа к БД, нам понадобится промежуточное поле.
В данном случае хорошо подходит свойство Tag, комбо-бокса.
В данном поле мы будем хранить текущее значение id выбранной строки датасета.
Поэтому тыкаем в три точки и выбираем Tag для отображения:

Не забываем прописать наш datamodule в uses формы.
После этого датасеты появляются в возможных биндах.

В биндинге протягиваем связи из полей датасета в комбо-бокс.
name тащим в Item.Text, id тащим в Item.LookupData.
Это приводит к автоматическому созданию на форме TBindSourceDB, TBindingsList и TLinkFillControlToField.

Уже на данный момент, если мы не забудем сделать open датасету, при открытии формы, в комбо-боксе будет список значений из датасета.

Казалось бы — все замечательно, но есть два момента.
- При открытии неплохо бы спозиционировать датасет и комбо-бокс на той записи, по которой мы щелкнули в гриде.
- При выборе значения в комбо-боксе, курсор не перемещается по датасету, а ведь надо!
Решим первую проблему.
Создадим в форме метод Init(), в который передадим id статуса в выбранной строке.
Соответственно, имея id, мы можем сделать locate в датасете и спозиционироваться на записи.
Данное действие не ведет к каким либо изменениям в комбо-боксе, поэтому нужно и в нем выбрать нужное значение.
procedure TfrmStChange.init(id: string); begin with DM do begin if qStatuses.Active then qStatuses.Close; qStatuses.Open(); qStatuses.Locate('id', id, []); ComboBox1.Tag := id.ToInteger; ComboBox1.ItemIndex := ComboBox1.Items.IndexOf (qStatuses.FieldByName('NAME').AsString); end; end;
Постойте, но ведь в гриде у нас нет id статуса!
Можно конечно добавить его в запрос, но мы не ищем легких путей.
Вернемся к главной форме и гриду.
На событие OnDblClick грида, повесим отображение формы изменения статуса:
procedure TfrmMain.Grid1DblClick(Sender: TObject); var frm: TfrmStChange; begin frm := TfrmStChange.Create(self); DM.qStMain.Locate('id_main', DM.FDQuery1.FieldByName('id').AsString, []); frm.init(DM.qStMain.FieldByName('id_status').AsString); frm.ShowModal; frm.Free; end;
Теперь по двойному клику на строке грида, будет открываться форма изменения статуса, с комбо-боксом, спозиционированном на нужном статусе.
Осталась проблема два. Какой бы статус мы не выбирали в комбо-боксе, датасет, привязанный к нему, не будет двигать курсор.
Решение проблемы два.
Я много гуглил на эту тему, но так и не нашел решения.
Пришлось придумать его самому.
Кладем на форму выбора статуса TPrototypeBindSource.
В окне «Structure», раскрываем список у данного объекта и жмем ПКМ->«AddItem» на строке «FieldDefs»:

Выбираем его тип «ftInteger» — это ж id, и называем его соответственно:

В LiveBindings Designer протягиваем связи от ComboBox1.SelectedValue к PrototypeBindSource1.id, затем от PrototypeBindSource1.id к ComboBox1.Tag.

Как ни странно, из ComboBox1.SelectedValue мы не можем получить id.
А вот после проведенной процедуры, из ComboBox1.Tag — можем!
Кто не верит — может поместить на форму label и писать в него значение Tag комбо-бокса, в момент onChange.
Или попытаться добыть id любым другим способом.
Итак, в итоге у нас есть id записи в датасете, на этом можно и ограничится, вычитывая значение поле Tag, после закрытия модальной формы.
Но это снова не наш метод.
Надо переместить курсор датасета, привязанного к комбо-боксу, к выбранной записи.
Если посмотреть на окно «Structure», то можно заметить появившийся элемент «LinkPropertyToFieldTag»:

Нажмем на него и создадим обработчик события onAssignedValue:
procedure TfrmStChange.LinkPropertyToFieldTagAssignedValue(Sender: TObject; AssignValueRec: TBindingAssignValueRec; const Value: TValue); begin with DM do begin qStatuses.Locate('id', ComboBox1.Tag.ToString, []); end; end;
Теперь, при выборе строки в комбо-боксе, датасет тоже двигает курсор!
И мы можем с полным правом дополнить метод OnDblClick грида главной формы:
procedure TfrmMain.Grid1DblClick(Sender: TObject); var frm: TfrmStChange; begin frm := TfrmStChange.Create(self); DM.qStMain.Locate('id_main', DM.FDQuery1.FieldByName('id').AsString, []); frm.init(DM.qStMain.FieldByName('id_status').AsString); frm.ShowModal; if (frm.ModalResult = mrOk) then begin DM.qStMain.Edit; DM.qStMainid_status.AsInteger := DM.qStatusesid.AsInteger; DM.qStMain.Post; refresh;//метод обновления грида, простой close\open датасета end; frm.Free; end;
Запускаем, проверяем, радуемся!
Надеюсь, данная информация поможет кому-то обойти ограничения, накладываемые fmx.
PS:
- С радостью приму любую конструктивную критику.
- Если кто-то знает метод лучше — расскажите, посыплю голову пеплом и признаю неправоту.
- Демо-проект написан только для демо, любые режущие глаз названий объектов считать случайными и не относящимися к делу.
- Ну и не судите строго, если что...
