Всем привет! Сегодня я буду рассказывать про DBComboBox, но если правда не совсем о нем. Я расскажу о том как заменить его. Суть работы следующая: вы наверное поняли DBComboBox – компонент работы с базами данных. И казалось, по идеи вы должны, указать просто DataSource и поля которые, хотите запихать в список, и будет вам выпадающий список с вариантами. НО когда я разбирался не так давно с этим компонентом, я так и не смог получить желаемый результат.
Настроил DBComboBox, и в поле Text записалось первое значение из выборки. При нажатии на «галочку» список пустой. Есть еще момент: при вводе значения в поле (при этом должен составиться список подходящих значений) происходит обновление поля в базе. Т.е. получается, что я заношу новое значение в список — компонент просто обновлял строку в базе на введенное мной значение. Вот так можно описать суть проблемы с DBComboBox.
Первым методом решения проблемы работы с DBComboBox было предложение следующей схемы реализации.

как заменить DBComboBox
Алгоритм замены DBComboBox

Слабые места.
— При слабом соединении (то есть например будет подключение к серверу через VPN, подключение в интернету через модем). Представим себе ситуацию пользователь вводит длинное слово при каждом введенном символе будет отослан запрос в базу и пока будет получен ответ, в цикле сформирован список — это все время.
— При частом обращении в список Items компонент начинает глючить. Причем глюк ловил и на Delphi 7 и на Delphi XE 4.
По перечисленным причинам пришлось отказать от данной реализации. НО спасибо товарищу и учителю Format_C_eft (ЧЕРЕДНИЧЕНКО О.Г.). Код собственно подсмотрен у него.
Для новой реализации используем следующие компоненты: TListBox и TEdit. Сам алгоритм оставляем тем же. Длина строки =>3 символов, это что не делать холостые запросы в базу, когда под условие выборки будет подходить много записей. А это лишняя нагрузка на соединение с севером, а следовательно потеря времени для конечного пользователя. Путем экспериментов пришли к тому, что данное количество будет минимальным и достаточным условием для нормальной работы. Данную реализацию опробовали на версии 2G мобильного интернета, в принципе она и сейчас работает, причем уже в течении более года. За все время не было замечено серьезных глюков.

реализации своего DBcombox
Эскиз реализации своего DBcombox

Как вы можете видеть на картинке, TListBox у нас отвечает за отображение списка вариантов, а TEdit за поле ввода. Между вариантами можно перемещаться стрелками. При выборе элемента, текст копируется в поле ввода. Вот так получается типа DBComboBox, только круче. Ну собственно перейдём к коду.
Приведу вам пример на основе поля выбора гражданства. В базе есть табличка с данными, из нее мы будем брать варианты для списка. Внизу статьи я приведу ссылки на скачивание бэкапа таблицы.
Начнем с TListBox

procedure TForm1.ListStateKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 if Key=VK_UP then //Это стрелка «вверх» чтоб при нажатии фокус перескакивал на поле ввода
  begin
     EditState.SetFocus;
  end;
end;

procedure TForm1.ListStateKeyPress(Sender: TObject;
  var Key: Char);
begin
   if  Key = chr(13) then //При нажатии на Enter
   begin
    if (Length(EditState.Text)<>0) then
      begin
       ListState.Enabled := False; //Деактивируем список
       ListState.Visible := False; //Прячем список
       EditState.Color:=clWhite; //Делаем фон поля ввода белым
       EditState.SetFocus; //Передаем фокус
       Exit;
      end;
   end;
end; 

procedure TForm1.ListStateClick(Sender: TObject);
begin
 EditState.Text := ListState.Items.Strings[ListState.ItemIndex]; //В поле ввода заносим выбранный //элемент списка (Причем если даже будете по списку перемещаться с помощью стрелок это //событие все равно будет отрабатывать.)
end;

Все пояснения уже есть в коде и выше я рассматривал алгоритм. Поэтому без лишних слов переходим к дальнейшему описания решению проблемы с DBComboBox.
Далее привожу код для TEdit…

procedure TForm1.EditStateKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 if Key=VK_DOWN then //Стрелка «ВНИЗ» переводит курсор на список вариантов если он есть
  begin
    if ListState.Visible=True then
    begin
     ListState.SetFocus;
    end;
  end;
end;

procedure TForm1.EditStateKeyPress(Sender: TObject;
  var Key: Char);
begin
  if (Length(EditState.Text)<3) then //Если В поле ввода количество символов меньше 3
  Begin
    ListState.Clear;  //Очищаем и скрываем, деактивируем список вариантов
    ListState.Enabled := False;
    ListState.Visible := False;
  End;
end;

procedure TForm1.EditStateKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
Var
  i,pos : Integer;
sql:String;
begin //( Key>47)- что отсекать служебные символы
  if (Key>47) and (Length(EditState.Text) >= 3) then //Если больше либо равно 3 символам
  Begin
  EditState.ClearSelection;
  Pos := Length(EditState.Text);

  sql:='SELECT state FROM spr_oksm WHERE state LIKE '+C+EditState.Text+'%'+C+' limit 10';

  Query.Filtered := False;
  Query.SQL.Clear;
  Query.SQL.Add(sql);
  Query.Open;

  if Query.FieldByName('state').IsNull = False then
  Begin
    EditState.Color := clWhite;
    EditState.Text := Query.fieldByName('state').AsString;
    EditState.SelStart := pos;
    EditState.SelLength := Length(EditState.Text)-1;

    // Заполнение listBox
    if Query.RecordCount > 1  then //Количество вариантов больше одного
    Begin
      ListState.Items.Clear;
      Query.First;
        for I := 0 to Query.RecordCount -1 do
        begin
         ListState.Items.Add(Query.fieldbyName('state').asstring);
         Query.Next;
        end;
      ListState.Enabled := True;
      ListState.Visible := True;
    End
    Else
    Begin
      ListState.Items.Clear;
      ListState.Enabled := False;
      ListState.Visible := False;
    End;
    // Заполнение ListBox
   End
   Else
   Begin
     EditState.Color := clRed;
     ListState.Items.Clear;
     ListState.Enabled := False;
     ListState.Visible := False;
   End;
  End;
end;

Вот собственно и все что я хотел написать на данную тему. Код можете применять, не забываем указывать ссылку на мой сайт. Можете пользоваться кнопками социальных сетей, чтобы поделиться со всеми данным материалом.
Вот ссылка на бэкап справочника кодов стран. Можете потренироваться на нем. В заключение могу сказать про выше описанную схему — я применяю для выведения справочника имен и отчеств, и прошу заметить табличка это немаленькая. Тем не менее, все работает довольно не плохо.
Подписывайтесь на обновления по почте. Вверху есть сcылочка.
С уважение, ваш Shinobi.
P.S. Комментируйте ваше мнение важно для меня, как для автора и человека который делится своим опытом. Есть возможность подписки на обновление комментариев на почту.

7 комментариев

  1. Павел

    Заметил что у вас на сайте, очень часто перемешивается код на Delphi и SQL. У меня тоже есть такая беда. Я сейчас все методы которые обращаются к базе, стараюсь вынести в отдельный DM, и получать оттуда результат через вызов соответствующих процедур.
    И ещё, я не знаю как с вашими компонентами, но многие запросы если их просто открыть и потом обратится к RecordCount могут вернуть неверное количество, т.к. обычно не происходит выборка всех данных. Приходится делать Last и затем получать RecordCount, хотя может мне просто не повезло с компонентом или у него была неправильная настройка.

    Ответить
    • admin_shinobi

      Описанной вами проблемы не разу не встречал…А по поводу перемешивания SQL и кода Delhi. Я нашел следующие решение: Например мне нужно данные выборки из двух трех таблиц, да это еще цветочки, а например там будут вложенные запросы и недавно писал запрос с тремя связными таблицами, да еще и три варианта запроса связные через UNION..При этом у меня проект динамичный,и условия часто меняются чуть ли не раз в неделю. Поэтому я пошел простым путем…Берем запрос запихиваем его в представление и все… В коде просто вызываешь обычным запросом «SELECT * FROM `представление`».
      Плюс данного подхода в том что если изменится логика запроса или название таблиц не важно не надо будет переписывать код программы, а просто меняешь запрос в представлении. Я поже разовью эту тему как я реализовал свой проект из статики, (все запросы были прописаны прям в коде) и сделал это в динамике. Работать стало одно удовольствие, не нужно искать где в коде была ошибка, а просто работаешь с базой.
      Да еще один маленький плюс, «обновление» долетает до конечного пользователя в секунды. По сравнению с тем что ему не надо обновлять программу. Вот как-то так…

      Ответить
      • Павлен

        SELECT * FROM `представление`- хороший вариант. И я очень много видел таких подходов. Работает на ура. Однако главное не злоупотреблять, когда вместо представления используется хранимка, причём с достаточно сложной логикой. Всё хранится в базе. Работает может и быстро, но если нужно что-то изменить то это сделать сложно.

        Ответить
        • admin_shinobi

          Да влюбом подходе есть свои минусы. Главное выбрать меньшее зло. По поводу хранимок их тоже использую, так как очень часто проблемы с логикой типа…при таком вот документе вот это должно писать в xml,при таком вот это…ноесли то операция такая то третье….Кроче без ящика не разберешься. На это случай я использую сохранении как на ввод, тоесть набор данных не меняется меняется в основном то что показывать а что нет. Так и на выходе, я подготовливаю данные для формирование xml за счет сохраненок.
          Еще одну фишку подсмотрел 1С-ников у них есть такое понятие как справочник (казалось бы бред на фига он нужен.) А со справочниками работа стала вообще очень приятной,например у меня несколько форм для ввода данных, данные нужно проверять прям намести, что огранить ввод не корректных данных…(Пользаки вообще криворукие, так комп некоторые видят во второй раз в жизни). Раньше я прописывал логику ввода прям коде в событии KeyPress, переход между полями делал на нажатие Enter. Короче это была грамозкая, и не управляемая хуйня…Сделал табличку (справочник далее), в ней написал типа название масок, сами макси, текст предупреждение. Название например «!FAM» и маска это регулярное выражение. Все.
          Схема следующая открывается, форма с полями ввода. В память в массив, записывается значение имен масок, маска,текст предупреждения. На каждом поле в событии OnExit, висит небольшой код который передает функции название, макси, та возвращает регулярное выражение,текст предупреждения из массива. Регулярка проверяется, все хорошо гасим balonhint осветляем поле ввода, все плохо вызываем функцию отображения подсказки, в аргументах передаем ссылку на поле ввода и текст предупреждения, подсвечиваем поле красным.
          Данный пример самый просто, для того чтоб объяснить схему, я много времени убил например на документы. Чтоб составить нормальный справочник который будет учитывать 90% всех случаев. С широким функционалом. Если интересно сегодня же могу сесть написать на эту тему статью.
          В выводе я хотел бы отметить на данный момент я использую три метода отделения SQL от DElphi, и создания динамичной логики.
          — Представления, для создания необходимого набора данных из нескольких таблиц
          — Сохраненки, для управления занесение данных и логикой выгрузки
          — Справочники, для динамичного редактирования всяких списков вариантов и проверок.
          Ну и если чесно, для автоматизации некоторых процесов использую триггеры. Так проще кода код выполняет сама СУБД да еще и на стороне сервака а не клиента.

          Ответить
  2. Павлен

    Комментарий как бальзам на душу:) Я до этого тоже долго доходил. Иногда хранение какой-то логики в базе это реально удобно. Например хранение регулярок для проверки поля.
    По поводу проверки корректности данных, это вообще была бы отдельная тема для статьи. И как это грамотно показать.
    Сейчас стараюсь использовать следующий подход — Поле становится красным и над ним высвечиваю Лэйбл с текстом «Ошибка: значение поля только в диапазоне 1..50», к примеру. И данные берутся из базы, где хранится подобная форма.
    А раньше были ужасные ShowMessage 🙁
    Но как к примеру быть когда, у нас есть табличные данные, вот это бывает проблематично. например, оператор вводит данные для пациента по количеству и коду услуг. Потом выясняет что пациент будет оплачивать по ДМС, и выбирает компанию, а у этой компании нету некоторых услуг в договоре. Вот тут уже приходится придумывать, как наглядно показать оператору, что он не может распечатать наряд на оплату. Сорри, что получилось длинно, но проблема достаточно наболевшая: (

    Ответить
    • admin_shinobi

      Да интересная тема…У меня таких задач то же постоянно всплывают. Например, когда писал форму первичной проверки данных там вводятся ФИО,СНИЛС, Паспортные данные. А у меня документы в справочнике для граждан РФ разбиты на две под категории «до 14» и «после 14». Так как поля у меня все открытые могла возникнуть ситуация когда человек не введет ДР а сразу возмется за документы, а как мне без ДР определить возраст, если это паспорт РФ то просрочен документ или нет. Если ему больше 14 обязательно должен быть паспорт. Тоесть ситуация двоякая. Пришлось идти по пути меньшего сопровтивления…Просто поставил заглушку не ведина валидная дата рождения, курсор перекидываю на это поле и вывожу посказку типа введи ДР, не могу дальше рабоать без нее…
      Короче долго думая я пришел к тому, что полную свободу пользаку, физически не могу дать. Поэтому типа есть какие обязательные требования или данные которые выполняются в первую очередь. Не удобно…кому плевать..Потому как сегодня не удобно,а завтра привыкают. Я избавляюсь от кучи гемороя.
      ShowMessage -раньше тоже часто пользовал, сейчас в основном подсказки (balonhint), иногда для подтверждения что типа я случайно нажала кнопку задаю вопрос с помощью MessageBox. Там же при проверки я написал такой ход, пользак ввел данные нажимает кнопку проверить идет проверка по трем ключам ФИО ДР в разных комбинациях, документы, снилс и еслия могу 100% идентифировать человека сразу перехожу к следущем данных, если нет в в табличку под кнопкой вываливаются варианты. И тут прикол я когда начинал писать проект думал как защититься от кривых рук, типа не ту запись выбрала, не чайно…Кроче нажимает «Страховать», есть вариаты выдаю InputText, типа напрянги извилины и введи номер записи которой ты сейчас хочешь рабоать и все.. Как показала практика в несколько лет метод оказался действенным.

      Ответить
    • admin_shinobi

      А по поводу наболешего не перживай….Мое желание вести этот блог и выставлять свой корявый код и идеи на всеобщее обозрение — это возможность получить обратную связь. Коментарии, не вазжно похвальные она, жалуются на своих тупых пользаков,и разносят меня..Важно что это обрпатная связь и только ради это стоит писать эти статьи и занимать этим. Причем мне раньше казалось написать статью фигня….Сейчас в голове куча идей, но неуспеваю все написать, иногда над сать рабоаешь почти два дня. Потом еще товарищ пепречитывает и снова правишь помарки. ТАк что я только блогадарен всем кто тут коментирует мою бездарность))))

      Ответить

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.