Ввод с клавиатуры и фокус
Как Вы уже знаете, ввод с клавиатуры направляется потоком необработанного ввода (RTT) в очередь виртуального ввода какого-либо потока, но только не в окно RIT по мещает события от клавиатуры в очередь потока безотносительно конкретному окну. Когда поток вызывает GetMessage, событие от клавиатуры извлекается из очереди и перенаправляется окну (созданному потоком), на котором в данный момент сосре доточен фокус ввода(рис 27-2)
Чтобы направить клавиатурный ввод в другое окно, нужно указать, в очередь ка кого потока RIT должен помещагь события от клавиатуры, а также "сообщить" пере менным состояния ввода потока, какое окно будет находиться в фокусе Одним вызо вом SetFocus эти задачи не решить Если в данный момент ввод от RIT получает поток 1, то вызов SetFocus с передачей описателей окон А, В или С приведет к смене фокуса.
Окно, теряющее фокус, убирает используемый для обозначения фокуса прямоуголь ник или гасит курсор ввода, а окно, получающее фокус, рисует такой прямоугольник или показывает курсор ввода.
Рис. 27-2. RIT направляет пользовательский ввод с клавиатуры в очередь виртуального ввода только одного из потоков единовременно
Предположим, однако, что поток 1 по-прежнему получает ввод от RIT и вызывает SetFocus, передавая ей описатель окна E. В этом случае система не дает функции что либо сделать, так как окно, на которое Вы хотите перевести фокус, не использует очередь виртуального ввода, подключенную в данный момент к RIT. Когда поток 1 выполнит этот вызов, на экране не произойдет ни смены фокуса, ни каких-либо из менений
Возьмем другую ситуацию: поток 1 подключен к RIT, а поток 2 вызывает SetFocus, передавая ей описатель окна E. На этот раз значения переменных локального состо яния ввода потока 2 изменяются так, что - когда RIT в следующий раз направит со бытия от клавиатуры этому потоку — ввод с клавиатуры получит окно E. Этот вызов не заставит RIT направить клавиатурный ввод в очередь виртуального ввода потока 2
Так как фокус теперь сосредоточен на окне F, потока 2, оно получает сообщение WM_SETFOCUS Если окно E — кнопка, на нем появляется прямоугольник, обознача ющий фокус, и в результате на экране могут появиться два окна с такими прямоуголь никами (окна А и E). Сами понимаете, это вряд ли кому понравится. Поэтому вызы вать SetFocus следует с большой осторожностью — чтобы не создавать подобных си туаций. Вызов SetFocus безопасен, только если Ваш поток подключен к RIT
Кстати, если Вы переведете фокус на окно, которое, получив сообщение WM_SET FOCUS, показывает курсор ввода, не исключено одновременное появление на экране нескольких окон с таким курсором. Это тоже вряд ли кого обрадует.
Когда фокус переводится с одного окна на другое обычным способом (например, щелчком окна), теряющее фокус окно получает сообщение WM_KILLFOCUS. Если окно, получающее фокус, принадлежит другому потоку, переменные локального со стояния ввода потока, который владеет окном, теряющим фокус, обновляются так, чтобы показать: окон в фокусе нет. И вызов GetFocus возвращает при этом NULL, зас тавляя поток считать, что окон в фокусе нет.
Функция SetActiveWindow активизирует в системе окно верхнего уровня и перево дит на него фокус:
HWND SetActiveWindow(HWND hwnd);
Как и SetFocus, эта функция ничего не делает, если поток вызывает ее с описате лем окна, созданного другим потоком.
Функцию SetActiveWindow дополняет GetActiveWindow:
HWND GetActiveWindow();
Она работает так же, как и GetFocus, но возвращает описатель активного окна, указанного в переменных локального состояния ввода вызывающего потока. Так что, если активное окно принадлежит другому потоку, функция возвращает NULL.
Есть и другие функции, влияющие на порядок размещения окон, их статус (актив но или неактивно) и фокус:
BOOL BringWindowToTop(HWND hwnd);
BOOL SetWindowPos( HWND hwnd, HWND hwndInsertAfter, int x, int у, int cx, int су, UINT fuFlags);
Обе эти функции работают одинаково (фактически BringWmdowToTop вызывает SetWindowPos, передавая ей HWND_TOP во втором параметре). Когда поток, вызыва ющий любую из этих функций, не связан с RIT, они ничего не делают. В ином случае (когда поток связан с RIT) система активизирует указанное окно Обратите внимание, что здесь не имеет значения, принадлежит ли это окно вызвавшему потоку. Окно ста новится активным, а к RIT подключается тот поток, который создал данное окно, Кроме того, значения переменных локального состояния ввода обоих потоков обнов ляются так, чтобы отразить эти изменения.
Иногда потоку нужно вывести свое окно на передний план. Например, Вы запла нировали какую-то встречу, используя Microsoft Outlook. Где-то за полчаса до назна ченного времени Outlook выводит на экран диалоговое окно с напоминанием о встре че. Если поток Outlook не связан с RIT, это диалоговое окно появится под другими окнами, и Вы его не увидите. Поэтому нужен какой-то способ, который позволил бы привлекать внимание к определенному окну, даже если в данный момент пользова тель работает с окном другого приложения.
Вот функция, которая выводит окно на передний план и подключает его поток к RIT:
BOOL SetForegroundWindow(HWND hwnd);
Одновременно система активизирует окно и переводит на него фокус. Функция, парная SetForegroundWindow:
HWND GetForegroundWindow();
Она возвращает описатель окна, находящегося сейчас на переднем плане.
В болсс ранних версиях Windows функция SetForegroundWindow срабатывала все гда. То есть поток, вызвавший ее, всегда мог перевести указанное окно на передний план (даже если оно было создано другим потоком). Однако разработчики стали зло употреблять этой функцией и нагромождать окна друг на друга. Представьте, я пишу журнальную статью, и вдруг выскакивает окно с сообщением о завершении печати. Если бы я не смотрел на экран, то начал бы вводить текст не в свой документ, а в это окно. Еще больше раздражает, когда пытаешься выбрать команду в меню, а на экране появляется какое-то окно и закрывает меню.
Чтобы прекратить всю эту неразбериху, Microsoft сделала SetForegroundWindow чуть поумнее. В частности, эта функция срабатывает, только если вызывающий поток уже подключен к RIT или если поток, связанный с RIT R данный момент, не получал ввода на протяжении определенного периода (который задается функцией System ParametersInfo и значением SPI_SETFOREGROUNDLOCKTIMEOUT). Кроме того, SetFore groundWindow терпит неудачу, когда активно какое-нибудь меню
Если SetForegroundWindow нс удается переместить окно па передний план, то его кнопка на панели задач начинает мигать. Заметив это, пользователь будет в курсе, что окно требует его внимания Чтобы выяснить, в чем дело, пользователю придется ак тивизировать это окно вручную. Управлять режимом мигания окна позволяет функ ция SystemParameterslnfo со значением SPI_SETFOREGROUNDFLASHCOUNT.
Из-за такого поведения SetForegroundWindow в систему встроено несколько новых функций. Первая из них, AllowSetForegroundWindow, разрешает потоку указанного процесса успешно вызвать SetForegroundWindow, но только если и вызывающий ее поток может успешно вызвать SetForegroundWindow Чтобы любой процесс мог выво дить окно «поверх» остальных окон, открытых Вашим потоком, передайте в парамет ре dwProcessId значение ASFW_ANY (определенное как -1):
BOOL AllowSetForegroundWindow(DWORD dwProcessId);
Кроме того, можно полностью заблокировать работу SetForegroundWindow, вызвав LockSetForegroundWindow:
BOOL LockSetForegroundWindow(UINT uLockCode);
В параметре uLockCode она принимает либо LSFW_LOCK, либо LSFW_UNLOCK. Данная функция вызывается системой, когда на экране активно какое-нибудь систем ное меню, — чтобы никакое окно не могло его закрыть. (Поскольку меню Start не явля ется встроенным, то при его открытии Windows Explorcr сам вызывает эти функции.)
Система автоматически снимает блокировку с функции SetForegroundWindow, когда пользователь нажимает клавишу Alt или активизирует какое-либо окно. Так что при ложение не может навечно заблокировать SetForegroundWindow.
Другой аспект управления клавиатурой и локальным состоянием ввода связан с массивом синхронного состояния клавиш (synchronous key state array). Этот массив включается в переменные локального состояния ввода каждого потока. В то же время массив асинхронного состояния клавиш (asynchronous key state array) — только один, и он разделяется всеми потоками. Эти массивы отражают состояние всех клавиш на данный момент, и функция GetAsyncKeyState позволяет определить, нажата ли сейчас заданная клавиша:
SHORT GetAsyncKeyState(int nVirtKey);
Параметр nVirtKey задает код виртуальной клавиши, состояние которой нужно проверить. Старший бит результата определяет, нажата в данный момент клавиша (1) или нет (0). Я часто пользовался этой функцией, определяя при обработке сообще ния, отпустил ли пользователь основную (обычно левую) кнопку мыши. Передав зна чение VK_LBUTTON, я ждал, когда обнулится старший бит Заметьте, что GetAsyncKey State всегда возвращает 0 (не нажата), если ее вызывает другой поток, а не тот, кото рый создал окно, находящееся сейчас в фокусе ввода.
Функция GetKeyState отличается от GetAsyncKeyState тем, что возвращает состоя ние клавиатуры на момент, когда из очереди потока извлечено последнее сообщение от клавиатуры:
SHORT GetKeyState(int nVirtKey);
Эту функцию можно вызвать в любой момент; для нее невяжно, какое окно в фокусе.