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).
Надеюсь статья получилась вполне читабельна и изложение доходчиво.