Delphi. Combobox и база данных. Редактирование записей.
10.05.2017Часто, в ответ на вопрос «как связать комбобокс с БД» Вы услышите — «используй dbLookupComboBox».
Возможно это действительно наиболее удачный вариант, но в силу моей к нему неприязни, я использую обычный ComboBox практически всегда.
Да, действительно, он не всегда удобен, если информацию о работе с ним получать только из книг по Delphi. Ведь в них, обычно, рассматриваются только самые просте способы и методы работы с ним.
В своих проектах я использую его на формах добавления или редактирования записи в базе данных.
Не буду описывать его свойства — это Вы и так можете найти в книгах или на просторах интернета, — благо форумы пестрят подобными заголовками.
В этой статье рассмотрим конкретную реализацию.
База данных выступает в качестве хранилища информации, которую мы будем добавлять или редактировать.
ComboBox, в данном описании, выступает как сомпонент позволяющий выбрать запись из таблицы-справочника для внесения информации в другую таблицу, либо как элемент выступающий в роли отображающего сохраненнную информацию на форме редактирования..
В данном примере для внесения записи и для ее редактирования применяется одна и та же форма, открываемая с различными входными параметрами.
На скринах показано много элементов формы, но я вам покажу работу на примере двух ComboBox-ов. Двух — по той причине что тут они связаны между собой и выбор элемента в первом, изменяет набор данных во втором и при редактировании записи второго ComboBox (связанного с первым по условию) это требует дополнительных операций.
На приведенном изображении фокус установлен на боксе с помощью которого, в данном случае, оператор выбирает назначение рельса (форма открыта в режиме добавления новой записи).

При этом Combobox «Тип оборудования» не содержит никакой информации, поскольку еще не сделан выбор назначения.
После того как назначение будет выбрано, список типов будет заполнен и доступен для выбора необходимой записи и в нем будут отображены только те типы (в данном случае рельса) которые связаны с выбранным назначением. Теперь оператор может выбрать нужный ему тип. Данные связаны.

Теперь о реализации.
Для боксов «Назначение» и «Тип» в БД существуют справочные таблицы, из которых и выполняется наполнение боксов.

Активация формы просходит из другой (родительской) формы, при которой и передается некий параметр определяющий роли элементов.
procedure TForm1.BitBtn1Click(Sender: TObject); begin oper:='addr'; form2.ShowModal; end;
Определили роль действий как «Добавление записи» и открыли форму, скрины которой показаны выше.
Как работает Form2. Вот что выполняется при открытии формы:
procedure TForm2.FormShow(Sender: TObject);
begin
// наполнить боксы данными из справочников
//1. бокс "назначение рельса"
NaznBox.Items.Clear;
zquery1.Close;
zquery1.sql.Clear;
with zquery1 do
begin
sql.Add('select * from sprav_nazn');
open;
end;
while not zquery1.Eof do begin
NaznBox.Items.AddObject(ZQuery1.fieldbyname('nazn').AsString, TObject(ZQuery1.FieldByName('pk').AsInteger));
ZQuery1.Next;
end;
Поскольку форма может быть запущена больше одного раза , то Combobox необходимо очистить от существуюещго набора, ведь набор данных мог и поменяться, — что и сделано командой NaznBox.Items.Clear;
Далее обращаемся к БД для формирования текущего набора элементов бокса. Процедура стандартная и не вижу необходимости ее описывать. Однако стоит остановиться на части кода отображенном в цикле while.
Здесь из набора данных формируем объекты Combobox-а взяв в качестве оборажемого текста, хранящееся в таблице БД имя (здесь «назначение» — поле ‘nazn’), а ключ выбранной записи помещаем в TObject этого Item.
Combobox наполнен необходимыми значениями. Теперь наполним второй, связанный с первым по смыслу.
Следует заметить: поскольку в данный момент рассматриваем форму как оболочку для внесения новой записи — то бокс содержащий типы, в данном случае рельсов, наполняется только после выбора назначения. И вот как я это делаю:
Так как существует связка между назначеним и типом, то необходимо сначала получить значение связующего звена. Связку можно понять внимательно взгянув на скрины таблиц БД.
// событие на смену значения в боксе "Назначение"...
procedure TForm2.NaznBoxChange(Sender: TObject);
begin
tr:=0;
// получить ключ выбранной записи и запросить fk для нее
NaznBox.Tag:=integer(NaznBox.Items.Objects[NaznBox.ItemIndex]);
nr:= NaznBox.Tag;
// в переменную взяли ключ выбранной записи, помещенный при формировании бокса в TObject
// предварительно считав его в Tag Combobox-а
// для исключения ошибки при случайной прокрутки бокса колесом мыши
// и выполняем запрос к таблице для получения вторичного ключа записи
infoquery.Close;
infoquery.sql.Clear;
with infoquery do
begin
sql.Add('select * from sprav_nazn where pk= :pPk');
Params[0].Value:= nr;
open;
end;
fknr:=infoquery.FieldValues['fk']; // получили код назначения рельса или элемента
// формирование бокса типов используя вторичный ключ первого бокса.
TipBox.Items.Clear;
zquery2.Close;
zquery2.sql.Clear;
with zquery2 do
begin
sql.Add('select * from sprav_tip where nazn_key= :pNaz');
Params[0].Value:= fknr;
open;
end;
while not zquery2.Eof do begin
TipBox.Items.AddObject(ZQuery2.fieldbyname('tip').AsString,
TObject(ZQuery2.FieldByName('pk_tr').AsInteger));
ZQuery2.Next;
end;
end;
После чего становится доступным выбор позиции во втором боксе.
На этом прекращаю рассмотрение формы в режиме добавления записи. Сам процесс записи данных в БД должен быть Вам известен.
И приступим к рассмотрению наиболее интересной части статьи, а именно позиционирование Combobox-а на нужную запись при редактировании.
Далее в тексте описания и примерах кода Вы увидите обращение к элементу DBGridEh — это табличное отображение набора данных которые будем редактированить.
Сам грид и процесс его наполнения показывать не буду — это тема для отдельной статьи, но процесс получения значений из него частино будет приведен и если у читателя будет желание получить информацию о реализации отображения табличных данных в рамках этой статьи — дополню ее или оформлю ссылкой на отдельную запись.
Как я уже отметил, на основной форме имеется некий DBGridEh с набором данных. Для редактирования записи ее нужно сначала выбрать, выполнив клик мышью на нужной строке.
Используя событие OnMouseDown DBGridEh-а получаю ключ записи (поле ‘pk’), которая будет радактироваться. Ключ так же содержится в таблице, но столбец с этой информацие скрыт, поскольку для пользователя он, как правило, не нужен.
// selpkr - переменная типа Integer
selpkr:=DBGridEh1.SelectedRows.DataSet.FieldByName('pk').AsInteger;
Теперь обрабатываем нажатие кнопки «Редактировать»
procedure TForm1.BitBtn2Click(Sender: TObject); begin oper:='editr'; form2.ShowModal; end;
Передали форме form2 информацию о том, что она должна быть открыта в режиме редактирования записи…
Зная этот параметр отрываю форму активируя следующий набор операций:
procedure TForm2.FormShow(Sender: TObject);
begin
//....
if oper='editr' then begin
relsinfo;
end;
//...
end;
Здесь мной использован вызов процедуры, поскольку в своей программе вызов этот (редактирование значений) я допускаю делать в различных блоках программы.
И вот так выглядит сама процедура:
procedure relsinfo; // процедура выбора данных по рельсу для редактирования
var i:integer;
begin
// известно значение ключа рельса в БД - selpkr - по нему выбрать все и заполнить поля формы..
// 1. Выбираем и наполняем основные параметры рельса EditInfoQuery
form2.EditInfoQuery.Close;
form2.EditInfoQuery.SQL.Clear;
with form2.EditInfoQuery do
begin
sql.Add('select * from railslist where pk= :pPk');
Params[0].Value:= selpkr;
open;
end;
nr:=form2.EditInfoQuery.FieldValues['key_nazn'];
tr:=form2.EditInfoQuery.FieldValues['key_tip'];
zr:=form2.EditInfoQuery.FieldValues['key_z'];
dl:=form2.EditInfoQuery.FieldValues['dl'];
......................
Выбрал значения текущих параметров для всех полей в переменные
Ведь при выполнении обновления записи потребуются они все (дабы не писать кучу условий..)
Далее выбираю позицию для бокса «назначение рельса» в соответсвии с записью в БД
// перебираем все значения Combobox-а "Назначение", пока не найдем то, ключ которого совпадает с хранимым в БД.
for i:=0 to form2.NaznBox.Items.Count-1 do begin
form2.NaznBox.ItemIndex:=i;
if integer(form2.NaznBox.Items.Objects[form2.NaznBox.ItemIndex])=nr
then
// когда нужное значение найдено, - получаю значения поля-сввязки для наполнения бокса типов
begin
form2.infoquery.Close;
form2.infoquery.sql.Clear;
with form2.infoquery do
begin
sql.Add('select * from sprav_nazn where pk= :pPk');
Params[0].Value:= nr;
open;
end;
fknr:=form2.infoquery.FieldValues['fk']; // код назначения рельса или элемента
// и после получения связующего значения - наполняю бокс типов оборудования...
form2.TipBox.Items.Clear;
form2.zquery2.Close;
form2.zquery2.sql.Clear;
with form2.zquery2 do
begin
sql.Add('select * from sprav_tip where nazn_key= :pNaz');
Params[0].Value:= fknr;
open;
end;
while not form2.zquery2.Eof do begin
form2.TipBox.Items.AddObject(form2.ZQuery2.fieldbyname('tip').AsString,
TObject(form2.ZQuery2.FieldByName('pk_tr').AsInteger));
form2.ZQuery2.Next;
end;
// прерываю цикл перебора значений бокса "Назначение", т.к. дальнейшая проверка не нужна.
break;
end; end;
Когда у нас есть наполненный по условию связки бокс типов оборудования, — остается последняя операция: Выбираю позицию для бокса «тип рельса» в соответсвии с записью в БД
for i:=0 to form2.TipBox.Items.Count-1 do begin
form2.TipBox.ItemIndex:=i;
if integer(form2.TipBox.Items.Objects[form2.TipBox.ItemIndex])=tr
then break; end;
// Далее выполняется наполнение оставщихся элементов формы и завершается код процедуры:
end;
В сети Вы найдете и другие реализации форм редактирования на Delphi, но поскольку ни одна из них меня не устроила по своему функционалу — я написал код, который и представил на Ваше рассмотрение. Пусть Вас не смущают значения NaznBox и TipBox — это обычные Combobox-ы…
Далее выполняется стандартная процедура обновления записи в таблице базы данных (в данном примере это таблица railslist).
Надеюсь статья получилась вполне читабельна и изложение доходчиво.