Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Полигон
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  17:50[Войти] | [Зарегистрироваться]

ComboBox с автозавершением (AutoComplete) по подстроке

Андрей Чистяков
дата публикации 19-11-2008 10:59

ComboBox с автозавершением (AutoComplete) по подстроке

Постановка задачи

В интернете достаточно много информации по реализации функции автозавершения вводимого в TextBox или ComboBox текста — так, как это сделано в диалоговом окне "Пуск / Выполнить". Данную функцию можно реализовать с помощью интерфейсов IAutoComplete/IAutoComplete2. Эти интерфейсы позволяют формировать список вариантов автозавершения, подбирая те строки, которые начинаются с введенного в поле редактирования текста, но не позволяют выбирать те строки, которые содержат введенный текст в любом месте строки (см. рис. 1) — во всяком случае в интернете я не нашел информации по этому вопросу. Поэтому пришлось делать свое выпадающее окно автозавершения.


Рисунок1

В целом выпадающее окно должно вести себя точно так же, как и стандартное выпадающее окно ComboBox'а (это окно создается системой, его класс называется "ComboLBox"):

  1. ComboBox не должен терять фокус при выпадении окна.
  2. Окно должно пропадать, когда фокус уходит с ComboBox'а; когда происходит переключение на другое окно; когда пользователь двигает форму, содержащую ComboBox.
  3. Выпадающее окно не должно получать фокус, т.е. когда пользователь выбирает какой-либо пункт списка автозавершения, либо просто прокручивает этот список мышью, заголовок формы, содержащей ComboBox, должен оставаться активным.

Второй пункт реализуется достаточно просто. Пункты первый и третий взаимосвязаны, и их я реализовать долгое время не мог. Родное выпадающее окно комбобокса (ComboLBox) судя по всему работает с применением захвата ввода от мыши (SetCapture/ReleaseCapture). Когда я попытался сделать свое выпадающее окно аналогичным образом, практически все получилось как задумано (комбобокс не терял фокус, форма не становилась неактивной), за единственным исключением — не работала полоса прокрутки в списке (в вопросе ¹ 57188 на Круглом столе тоже обсуждается эта проблема). Наконец, решение было найдено здесь. На основе этой статьи был сделан компонент TACComboBox. Тема показалась мне актуальной, поэтому я решил опубликовать результаты своих изысканий.

Описание компонента

Компонент имеет следующие published-свойства:

property ACItems: TStrings; — список вариантов автозавершения; из строк, содержащихся в этом списке формируется список автозавершения, в зависимости от значения свойства ACType.

type TAutoCompleteType = (actSimple, actSubString, actCustom);

property ACType: TAutoCompleteType; — определяет алгоритм, по которому заполняется список автозавершения.

  • при ACType=actSimple в список автозавершения помещаются строки из ACItems, которые начинаются с введенного в поле комбобокса текста;
  • при ACType=actSubString в список автозавершения помещаются строки из ACItems, которые содержат введенный в поле комбобокса текст;
  • при ACType=actCustom для каждого элемента списка ACItems вызывается обработчик события OnCheckString, в котором определяется, следует ли добавлять очередной элемент в список автозавершения. Если обработчик устанавливает параметр AddString в True, элемент добавляется, если в False — не добавляется.

В принципе, для формирования списка автозавершения можно не пользоваться списком, заданным в свойстве ACItems. Для этого нужно написать наследника класса TACComboBox, в котором переопределить protected-метод PrepareACStrings. В параметре AText этому методу передается введенный в поле комбобокса текст; метод должен заполнить список FDropDown.Items (подробнее см. пример, прилагаемый к статье).

Опишу некоторые детали реализации компонента; подробные комментарии есть в исходниках к статье.

  1. Итак, делаем выпадающее окно. Возможность изменения размеров окна автозавершения, как это сделано в IAutoComplete, мне не нужна, поэтому в качестве выпадающего окна вполне подойдет обычный ListBox.
  2. type
      TDropDownListBox = class(TListBox)
      protected
        procedure CreateParams(var Params: TCreateParams); override;
      end;
    
    procedure TDropDownListBox.CreateParams(var Params: TCreateParams);
    begin
    inherited CreateParams(Params);
    Params.ExStyle:=WS_EX_TOOLWINDOW;
    Params.WndParent:=GetDesktopWindow;
    Params.Style:=WS_CHILD or WS_BORDER or WS_CLIPSIBLINGS or WS_OVERLAPPED or WS_VSCROLL;
    end;
    

    Такой ListBox будет работать именно так, как нужно: не ловит фокус, при этом полоса прокрутки работает.

  3. Объявляем наследника класса TComboBox:
  4. type
      TACComboBox = class(TComboBox)
      private
        FDropDown: TDropDownListBox;
      public
        procedure ShowAC;
        procedure HideAC(ApplySelection: Boolean);
      end;
    

    FDropDown — ссылка на окно автозавершения, создается/разрушается в конструкторе/деструкторе TACComboBox, подробнее см. исходники, прилагаемые к статье.

    Метод ShowAC показывает выпадающее окно:

    procedure TACComboBox.ShowAC;
    var P: TPoint; Cnt: Integer;
    begin
    // если текст в комбобоксе ='', то прячем окно автозавершения (если оно было показано)
    if Text='' then
      begin
      HideAC(False);
      exit;
      end;
    
    // заполняем список автозавершения (подробнее см. исходники)
    PrepareACStrings(Text);
    
    Cnt:=FDropDown.Items.Count;
    // если подходящих вариантов автозавершения нет, прячем окно автозавершения
    if Cnt=0 then
      begin
      HideAC(False);
      exit;
      end;
    // будем показывать список автозавершения такого размера, чтобы в нем
    // помещалось не более пяти строк. Если вариантов автозавершения больше пяти,
    // будет показана вертикальная полоса прокрутки
    if Cnt>5 then Cnt:=5;
    
    FDropped:=True;
    
    // если было показано "родное" выпадающее окно комбобокса, прячем его
    SendMessage(Handle, CB_SHOWDROPDOWN, 0, 0);
    
    // показываем окно автозавершения под комбобоксом. Вообще говоря,
    // было бы правильным сделать, чтобы это окно показывалось над комбобоксом,
    // если комбобокс находится слишком близко к нижнему краю экрана
    P.X:=1;
    P.Y:=Height-1;
    P:=ClientToScreen(P);
    SetWindowPos(FDropDown.Handle, HWND_TOPMOST, P.X, P.Y, Width-GetSystemMetrics(SM_CXVSCROLL)-2,
      Cnt*FDropDown.ItemHeight+2, SWP_SHOWWINDOW);
    end;
    

    Метод HideAC прячет список автозавершения. Если ApplySelection=False, окно просто прячется, если True — прячется, а выбранная строка помещается в комбобокс:

    procedure TACComboBox.HideAC(ApplySelection: Boolean);
    var I: Integer;
    begin
    ShowWindow(FDropDown.Handle, SW_HIDE);
    if ApplySelection then
      begin
      I:=FDropDown.ItemIndex;
      if I<>-1 then
        begin
        Text:=FDropDown.Items[I];
        SelectAll;
        end;
      end;
    FDropped:=False;
    end;
    

  5. Нужно, чтобы при перемещении формы, на которой лежит комбобокс, окно автозавершения пряталось. Это реализуется путем подмены оконной процедуры формы и перехватом сообщений WM_WINDOWPOSCHANGING/WM_WINDOWPOSCHANGED. Мне самому такое решение не очень нравится, возможно, форма рассылает какие-то нотификационные сообщения дочерним контролам, уведомляя их об изменении своего положения, но я таких сообщений не нашел.

Остальные методы классов на мой взгляд понятны, подробности см. в исходниках.

Отмазки

Отмазка №1. Все это затевалось в основном для того, чтобы сделать AutoComplete с возможностью подбора вариантов по подстроке. Я исходил из предположения, что интерфейсы IAutoComplete/IAutoComplete2 этого сделать не могут. Если окажется, что они таки могут, и я изобрел очередной велосипед в — простите великодушно, не кидайте в меня камень, лучше киньте ссылку, по которой есть такая информация. Впрочем, даже в этом случае, надеюсь, из моей реализации все равно можно извлечь какую-то пользу — например, можно в списке вариантов подсвечивать набранный текст, как это показано на рисунке в начале статьи.

Отмазка №2. В принципе, можно в качестве выпадающего окна использовать не только листбокс. На мой взгляд, тема компонентов типа "поле ввода + кнопка + выпадающее окно" достаточно актуальна. Я попробовал на скорую руку вместо листбокса использовать календарь, на первый взгляд все работало как задумано. Но, с другой стороны, возможны и проблемы, связанные с тем, что фокус остается в комбобоксе и не переходит к выпадающему окну, поэтому как будет себя вести, например, TStringGrid с активным InplaceEdit'ом в роли выпадающего окна, я предсказывать не берусь.

Отмазка №3. Данные исходики я рассматриваю скорее как первый набросок, чем как окончательный вариант компонента. Всесторонне обкатать компонент в боевом проекте я еще не успел, поэтому наверняка где-то что-то можно добавить/оптимизировать.

Исходники

К статье прилагаю архив. Компонент находится в файле _ACCombo.pas, устанавливается обычным образом (Component/Install component…). Демо-проект не требует установки компонента в IDE, там компонент создается в Run-Time, поэтому можно просто открыть .dpr и нажать F9, чтобы посмотреть, как оно работает. Исходники на Delphi 7, но, насколько мне известно, я не использовал никаких особенностей компилятора данной версии, которые помешали бы пользоваться компонентом в других версиях Delphi.

Также, в соответствии с правилами Королевства, следует упомянуть и поблагодарить Владимира Папаева — для примера мне нужна была функция для представления числа прописью, я воспользовался его разработкой.

К статье прилагаются файлы:




Смотрите также материалы по темам:
[TComboBox]

 Обсуждение материала [ 10-08-2016 13:50 ] 11 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования