пятница, 4 июня 2010 г.

Опасности игр с фокусом во время обработки сообщения WM_KILLFOCUS

Это перевод The dangers of playing focus games when handling a WM_KILLFOCUS message. Автор: Реймонд Чен.

В прошлом году я указал, что сообщение WM_KILLFOCUS - не подходящее место для проверок корректности ввода. А вот ещё один пример, как игры с фокусом во время обработки WM_KILLFOCUS могут создать хаос.

Рассмотрим edit-контрол, который отображает подсказки всплывающим балуном (balloon tip). Например, элементы для ввода паролей могут предупреждать вас, когда вы пытаетесь набрать пароль с включенным CapsLock. Что вы, вероятно, захотите при этом сделать - так это убирать подсказку, когда пользователь переходит на другой элемент управления, потому что нет смысла предупреждать пользователя о чём-то, что он не использует. Вы можете захотеть реализовать это, изменив обработчик сообщений edit-контрола примерно так:

case Msg of
  ...
  WM_KILLFOCUS:
    if hwndBalloonTip <> 0 then
    begin
      DestroyWindow(hwndBalloonTip);
      hwndBalloonTip := 0;
    end;
  ...
end;
inherited;

Когда вы запустите этот код, он будет работать прекрасно... но только если пользователь не щёлкнет на самой подсказке - тогда каретка edit-а (такая мигающая штучка внутри) исчезнет. Что же произошло?

А произошло то, что вы нарушили процесс смены фокуса ввода уничтожением окна, которому передают фокус! Процесс смены фокуса происходит так:
  • Установить фокус ввода в новое окно.
  • Отправить сообщение WM_KILLFOCUS в бывшее сфокусированное окно (если есть).
  • Отправить сообщение WM_SETFOCUS в новое сфокусированное окно (если есть).
Но на втором шаге вы уничтожили новое окно для фокуса. Когда сфокусированное окно уничтожается, менеджер окон пытается найти новое окно для фокусировки, и в нашем случае он находит для этого сам edit-контрол. Это запускает рекурсивную смену фокуса, сообщая edit-у, что он теперь снова имеет фокус.

Давайте посмотрим на выполнение кода в этом сценарии.
  • Установить фокус ввода в подсказку.
  • Отправить сообщение WM_KILLFOCUS в edit-контрол.
  • В ответ на WM_KILLFOCUS ваш код уничтожает подсказку.
    • Оконный менеджер устанавливает фокус ввода на edit-контрол.
    • Нет никого, кому можно было бы послать WM_KILLFOCUS.
    • WM_SETFOCUS отправляется edit-у.
    • Edit принимает WM_SETFOCUS.
  • Edit заканчивает обработку WM_KILLFOCUS (оригинальный обработчик принимает это сообщение).
  • Отправляется WM_SETFOCUS подсказке - неудачно (окно подсказки уничтожено).
Теперь вы видите проблему?

Посмотрите на поток сообщений, который приходит в оконную процедуру edit-контрола:
  • WM_SETFOCUS (от вложенного цикла смены фокуса)
  • WM_KILLFOCUS (от исходной смены фокуса)
С точки зрения edit-контрола: сначала он получил фокус, а потом его потерял. Поэтому нет и каретки, т.к. edit-контрол показывает каретку только, если он сфокусирован, а ваши игры с фокусировкой привели к тому, что edit-контрол не считает, что он имеет фокус ввода.

Есть несколько способов избавиться от этого хаоса.

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

Комментариев нет:

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

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.