15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Автозаполнение.

Автор: Paul DiLascia

Скачать исходник к статье - 21 Кб (Компилятор: Visual C++ 6.0)

Каждый раз, набирая что-нибудь в строке "Адрес" интернет эксплорера, выпадает список со ссылками, удовлетворяющими тому, что Вы уже набрали в адресной строке. При этом наиболее удовлетворяющая ссылка уже выделена и на ней достаточно нажать Enter:

Делается это при помощи интерфейса IAutoComplete. IAutoComplete связан с IEnumString - универсальным интерфайсом для получения списка строк. Дасточно передать в объект IAutoComplete указатель на Ваш получатель строк и дескриптор окошка редактирования или выпадающего списка и он сделает всё остальное самостоятельно. IAutoComplete2 служит для того, чтобы задать какие-либо специфические опции.

Однако не всё так просто. IAutoComplete присутствует только в Windows 2000. COM-объект, который содержит в себе IAutoComplete (CLSID_IAutoComplete), живёт только в пятой версии shell32.dll, которая поставляется с Windows 2000, но никак не с Windows 95, Windows 98, или Windows NT 4.0. Поэтому, чтобы автозаполнение в Вашем приложение работало на всех операционках, прийдётся отказаться от данной технологии.

Итак, давайте сперва посмотрим на класс CAutoComplete (см. прилагаемый к статье исходник), который был создан мною специально для этой цели. Никаких COM или shell32.dll, а всего лишь класс и файл .cpp, которые можно добавить в своё приложение или DLL и при том работающих на любой версии Windows:

CAutoComplete не содержит в себе всех возможностей IAutoComplete. Например, IAutoComplete имеет строку форматирования, которая срабатывает при нажатии пользователем Ctrl+Enter. Строка форматирования, это строка sprintf, которую Windows использует для трансляции пользовательского ввода. Например, если строка форматирования равна "http://www.%s.com" и пользователь ввёл "woowoo", то IAutoComplete подставит http://www.woowoo.com. В основном такая возможность ориентирована на Internet Explorer, поэтому я не стал усложнать класс CAutoComplete.

Для того, чтобы продемонстрировать работу класса, я сделал небольшое тестовое приложение ACTest (исходник которого тоже содержится в прилагаемом архиве). ACTest это диалоговое приложение, в главном диалоге которого присутствует окошко редактирования, выпадающий список (combobox) и два экземпляра CAutoComplete:

class CMyDialog : public CDialog {
protected:
    CAutoComplete m_acEdit;  // для окошка редактирования
    CAutoComplete m_acCombo; // для выпадающего списка (combobox)
•••
};

Для использования CAutoComplete, необходимо инициализировать каждый экземпляр с указателем на окно (окошко редактирования или список), а затем добавить несколько строк. CMyDialog делает это в OnInitDialog:

// в CMyDialog::OnInitDialog
m_acCombo.Init(GetDlgItem(IDC_COMBO1));
m_acEdit.Init(GetDlgItem(IDC_EDIT1));
static LPCTSTR STRINGS[] = {
    "alpha",
    "alphabet",
    •••
    NULL
};
for (int i=0; STRINGS[i]; i++) {
    m_acCombo.GetStringList().Add(STRINGS[i]);
    m_acEdit.GetStringList().Add(STRINGS[i]);
}

С этого момента ACTest больше не использует окошко редактирования или список для чего-нибудь ещё (так как это всего лишь демонстрационное приложение). Для получения элементов управления диалога я использую CWnd::GetDlgItem. В реальном приложении, вероятнее всего Вы воспользуетесь членами класса m_wndEdit и m_wndCombo для передачи их адресов после сабклассинга SubclassDlgItem.

Как работает класс CAutoComplete ? Основная идея заключается в том, что CAutoComplete наследуется от CSubclassWnd, который позволяет различным объектам перехватывать сообщения посылаемые окну. CSubclassWnd работает по стандартному принципу сабклассинга окон, инсталируя оконную процедуру (WindowProc). Когда приложение вызывает CAutoComplete::Init, то CAutoComplete в свою очередь вызывает CSubclassWnd::HookWindow, которая сабкласит окно. CSubclassWnd::HookWindow "присоединяет" (используя механизм наподобие MFC) объект CSubclassWnd к окну, чтобы сообщения, адресованные окну, сперва попадали в виртуальную функцию CSubclassWnd::WindowProc. В CAutoComplete эта функция переопределена для обработки интересующих нас сообщений:

// Переопределяем CSubclassWnd::WindowProc
LRESULT CAutoComplete::WindowProc(...)
{
    if (/* EN_CHANGE or CBN_EDITCHANGE */))) {
        // try to complete
    }
    return CSubclassWnd::WindowProc(...);
}

Обратите внимание, что CAutoComplete это объект не наследованный от CWnd. Он наследован от CSubclassWnd, который в свою очередь наследуется от CObject. После обработки сообщения (или не обработки), CAutoComplete вызывает CSubclassWnd::WindowProc, которая передает сообщение по его законному пути через изначальную оконную процедуру различным картам сообщений с обработчиками, ожидающими, чтобы обработать это сообщение.

Дальше всё очень просто. В виртуальной функции OnComplete производится сравнение того, что ввёл пользователь с внутренним списком и, если по необходимости корректируется вводимая информация. При этом для комбобокса показывается выпадающий список.

Так же присутствуют некоторые тонкости. Например, когда CAutoComplete перехватывает EN_CHANGE (изменение окошка редактирования) или CBN_ EDITCHANGE, то перед тем как вызвать SetWindowText для помещения в окошко редактирования нового текста, класс должен сам отключиться. Иначе, SetWindowText сгенерирует другое уведомление CHANGE и элемент управления погрязнет в бесконечном цикле самосгенерированных EN_ или CBN_CHANGE сообщений.

Тонкость номер два. Предположим, что пользователь вводит "al", которое CAutoComplete дополняет до "alpha", подсвечивая при этом "pha". Теперь пользователь нажимает Backspace, чтобы удалить "pha". Нам естевственно не хотелось бы возвращаться назад и по-новой сравнивать с alpha. Бедный пользователь так и не поймёт, почему Backspace не работает. Решение заключается в том, чтобы игнорировать сравнение, если пользователький ввод сокращается, а не увеличивается. Для этого я добавил виртуальную функцию IgnoreCompletion. CAutoComplete производит автодобавление только если эта функция возвращает FALSE. На мой взгляд алгоритм этой функции немного некорректный, поэтому я зделал эту функцию виртуальной, чтобы Вы могли спосойно её переопределять.

И в заключении хотелось бы привести сравнение CAutoComplete с IAutoComplete. Цель приведённой таблицы не в том, чтобы сравнить что лучше, а в том, чтобы выбрать что нужнее:

CAutoComplete
IAutoComplete
Собственный механим используя C++/MFC COM объект в shell32.dll
Работает на всех платформах Win32 только Windows 2000
Довольно просто можно изменить определённые возможности Ничего уже не изменишь
Не имеет строки формата Ctrl + Enter строка формата типа www.%s.com
Не поддерживает реестр
По умолчанию строки хранит в реестре
Нет необходимости в получателе строк (IEnumString) Необходимо использовать IEnumString
Вы имеете полный контроль Вы ограничены тем, что поддерживается