...

понедельник, 18 августа 2014 г.

[Из песочницы] DBLookupComboBox в FireMonkey, или костыль для рыжей обезьяны

Доброго дня всем!

Не так давно столкнулся с необходимостью работы с базой данных из 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





Начальная подготовка завершена.

Перейдем к самому интересному — форме изменения статуса.

Выглядеть она будет так:

image


Подготовка формы.


Самое первое что нужно сделать — привязать датасет к комбо-боксу.

для этого нужно выбрать комбо-бокс в дизайнере и щелкнуть дважды на строке LiveBindings в инспекторе объектов.

В выпавшем меню выбрать «Bind Visually»:

image


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

image


Для лукапа к БД, нам понадобится промежуточное поле.

В данном случае хорошо подходит свойство Tag, комбо-бокса.

В данном поле мы будем хранить текущее значение id выбранной строки датасета.

Поэтому тыкаем в три точки и выбираем Tag для отображения:

image


Не забываем прописать наш datamodule в uses формы.

После этого датасеты появляются в возможных биндах.

image


В биндинге протягиваем связи из полей датасета в комбо-бокс.

name тащим в Item.Text, id тащим в Item.LookupData.


Это приводит к автоматическому созданию на форме TBindSourceDB, TBindingsList и TLinkFillControlToField.

image


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

image


Казалось бы — все замечательно, но есть два момента.



  • При открытии неплохо бы спозиционировать датасет и комбо-бокс на той записи, по которой мы щелкнули в гриде.

  • При выборе значения в комбо-боксе, курсор не перемещается по датасету, а ведь надо!


Решим первую проблему.

Создадим в форме метод 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»:

image


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

image


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

image


Как ни странно, из ComboBox1.SelectedValue мы не можем получить id.

А вот после проведенной процедуры, из ComboBox1.Tag — можем!

Кто не верит — может поместить на форму label и писать в него значение Tag комбо-бокса, в момент onChange.

Или попытаться добыть id любым другим способом.


Итак, в итоге у нас есть id записи в датасете, на этом можно и ограничится, вычитывая значение поле Tag, после закрытия модальной формы.

Но это снова не наш метод.

Надо переместить курсор датасета, привязанного к комбо-боксу, к выбранной записи.


Если посмотреть на окно «Structure», то можно заметить появившийся элемент «LinkPropertyToFieldTag»:

image


Нажмем на него и создадим обработчик события 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:



  • С радостью приму любую конструктивную критику.

  • Если кто-то знает метод лучше — расскажите, посыплю голову пеплом и признаю неправоту.

  • Демо-проект написан только для демо, любые режущие глаз названий объектов считать случайными и не относящимися к делу.

  • Ну и не судите строго, если что...


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий