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

 

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