Думаю, что у каждого был тот день, когда вместо обычного «брауза» по файлу, вы решили отображать «рукопашную» очередь. В стандартном «браузе» на раз два активируется «поиск по первым буквам». При использовании же очереди стандартного средства нет. В этой статье рассказывается о том, каким образом можно организовать поиск по первым буквам в рукопашной очереди.
Задача
У нас есть лист бокс, в котором отображается очередь. Поиск будет осуществляться по определенному полю очереди. Желательно, чтобы данное поле было отсортировано.
Когда фокус находится на лист боксе необходимо отлавливать нажатия клавиш. Составлять из набора клавиш «строку» и искать в очереди совпадение с этой строкой. При совпадении осуществить позиционирование в лист боксе на заданную запись.
При нажатии на клавишу «Delete» необходимо уменьшить строку поиска на 1 символ.
При различных перемещениях (например, на запись вверх, на самую последнюю запись и т.п.) необходимо очищать локатор.
Усложним себе задачу еще и тем, что по истечении некоторого времени локатор также должен быть очищен (так, например, работает поиск в Проводнике).
В качестве локатора также может выступать entry-поле. В этом случае позиционирование в очереди происходит при уходе с этого поля.
Реализация
По моему мнению, в реализации поиска существует только одна проблема. Заключается она в том … как же отловить нажатия клавиш в лист боксе. Я знаю, что некоторые сейчас подумали о выставлении AlertKey для набора буквенно-цифровых клавиш и последующей обработки Event:AlertKey. Как я думаю, это плохое решение.
Хорошее решение 🙂 это обработка события WM_CHAR, которое возникает как раз тогда, когда нажимается клавиша. WM_CHAR это событие, которое посылает операционная система. Для обработки таких событий используется сабклассинг.
Если вас все еще пугает это слово, то, я думаю, стоит пересилить себя и заставить прочитать статью «Снова сабклассинг», понять раз и навсегда что это и начать использовать.
Итак, сабклассим лист бокс. Ловим WM_CHAR, а wParam содержит код нажатой клавиши.
Далее составляем строку поиска. Обращаю ваше внимание на то, что в качестве строки поиска лучше использовать тип CSTRING. Допустим вы набираете «пет» для поиска фамилии «Петров». Строка поиска при наборе буквы «п» будет содержать «п», при наборе «е» — «пе», при наборе «т» — «пет».
После каждого нажатия необходимо искать совпадение строки поиска с данными в очереди. Я просто перебираю всю очередь и сравниваю содержимое поля с строкой поиска, используя UPPER и SUB. При первом совпадении я прекращаю поиск, запоминая порядковый номер найденной записи. Для того чтобы установить указатель на заданную запись, необходимо просто выполнить команду select(?ListBox, порядковый номер).
Таймер
Для того, чтобы очищать локатор по истечении некоторого времени необходимо использовать таймер. Таймер в Кларионе принято устанавливать на окно. После установки можно обрабатывать событие EVENT:Timer. Можно установить только один таймер.
Используя функции API можно устанавливать таймер на только на окно, но и на другие контролы (которые в терминологии ОС также являются окнами). «Установить таймер на окно» означает, что это окно сможет принимать событие WM_TIMER. Таких таймеров можно установить несколько, а не один. Каждый таймер имеет свой идентификатор-номер. Когда Кларион устанавливает таймер, то установленный таймер имеет номер 1. Я думаю, что вряд ли кому-нибудь придет в голову устанавливать таймер на лист-бокс, но на всякий случай, пусть идентификатор нашего таймера будет 2 🙂
Для установки таймера используется функция SetTimer, ее прототип:
SetTimer(ULONG hWnd,ULONG nIDEvent,ULONG uElapse,LONG lpTimerFunc),ULONG,PASCAL
hWnd – хэндл окна, на который устанавливается таймер
nIDEvent – идентификатор таймера
uElapse – время «срабатывания» таймера в миллисекундах
lpTimerFunc – адрес callback-функции, которая будет вызываться при срабатывании таймера
Параметр lpTimerFunc может быть равен нулю, в этом случае при срабатывании таймера посылается событие WM_TIMER окну hWnd. Как вы понимаете «срабатывание» — это истечение времени uElapse.
Как обычно, то что создали подлежит уничтожению, поэтому используем KillTimer.
Таким образом процедура сабклассинга лист бокса помимо обработки WM_CHAR будет заниматься также обработкой WM_TIMER. При нажатии на клавишу запоминается время нажатия, используя обычную команду clock(), а по WM_TIMER смотрим разницу между последним нажатием и текущим временем. Если эта разница больше определенной величины, то очищаем строку поиска.
Entry-поле
Мне кажется не совсем логичным использовать entry- или какое либо другое поле для быстрого поиска по очереди. Если вы хотите отображать что набираете – используйте string-поле. Хотя и этого много. В «Проводнике», например, или в ACDSee нет такой подсказки. Entry-поле используется в стандартных браузах для «быстрого прыжка». Так как стандартные браузы обычно настроены на постраничную загрузку, то при использовании быстрого поиска можем получить хорошие тормоза (нажали букву «п», подгрузилась страница всех на букву «п», нажали «е» — подгрузилась страница всех на «пе» и т.д., т.е. происходит постоянная подкачка страниц, тормоза можно получить на сети при использовании большого справочника в несколько десятков тысяч наименований). Если в этой ситуации использовать entry-поле, в котором сразу набрали «петро», то будет подгружена всего одна страница – все, что начинается на «петро».
Очередь же ничего не подгружает. Я не проверял данный поиск на больших объемах, но, как мне кажется, тормозов не будет. К тому же, не совсем понятно зачем грузить в очередь для просмотра большие объемы данных. Всегда можно ограничиться определенной выборкой.
Если все таки возникнет желание использовать entry-поле 🙂
Все что нужно, это после ухода с этого поля (я предполагаю нажатие клавиши TAB) осуществить позиционирование в лист-боксе. Для этого я использую команду REGISTEREVENT, которая говорит, что при возникновении определенного события необходимо выполнить заданную процедуру.
Пример
Пример собран на C6.3 9050 ABC.
Представленный класс локатора будет работать и в Legacy, необходимо только правильно определить _ABCLinkMode_,_ABCDllMode_.
Для просмотра примера необходимо зарегистрировать шаблон dp_class.tpl – шаблон для написания классов.
Класс локатора использует класс xCallbackClass для сабклассинга (xCBClass.clw, xCBClass.inc). Необходимо правильно определить _XCBCLinkMode_, _XCBCDllMode_.
Скачать пример