Как я могу контролировать, какое окно в настоящее время имеет фокус клавиатуры

Есть ли способ отслеживать, какое окно в настоящее время имеет фокус клавиатуры. Я мог обрабатывать WM_SETFOCUS для каждого окна, но мне интересно, есть ли альтернативный, более простой метод (т.е. Один обработчик сообщения где-нибудь).

Я мог бы использовать OnIdle() в MFC и вызывать GetFocus(), но это кажется немного взломанным.

Ответы

Ответ 1

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

На самом деле я не думаю, что вызов GetFocus из OnIdle - это большая часть взлома - уверена, что это опрос, но это низкий опрос без побочных эффектов - но если вы действительно хотите отслеживать это, Windows Hooks, вероятно, ваш лучший выбор. В частности, вы можете установить крючок CBT (WH_CBT) и прослушать уведомление HCBT_SETFOCUS.

Windows вызывает крюк WH_CBT с помощью этого кода hook, когда Windows собирается установить фокус в любое окно. В случае крючков, специфичных для потока, окно должно принадлежать потоку. Если функция фильтра возвращает TRUE, фокус не изменяется.

Вы также можете использовать крючок WH_CALLWNDPROC и прослушивать сообщение WM_SETFOCUS.

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

Ответ 3

Существует простой способ использования .Net Framework 3.5: библиотека Автоматизация пользовательского интерфейса позволяет изменить фокус событий, который срабатывает при каждом изменении фокуса на новый элемент управления.

Страница в MSDN

Пример:

public void SubscribeToFocusChange()
{
    AutomationFocusChangedEventHandler focusHandler 
       = new AutomationFocusChangedEventHandler(OnFocusChanged);
    Automation.AddAutomationFocusChangedEventHandler(focusHandler);
}

private void OnFocusChanged(object src, AutomationFocusChangedEventArgs e)
{
    AutomationElement focusedElement = sender as AutomationElement;
    //...
}

Этот api на самом деле использует крючки для Windows за кулисами, чтобы сделать это. Однако вы должны использовать .Net Framework...

Ответ 4

Если вы программируете в .net 3.5, пакет автоматизации olorin является самым легким, но остерегайтесь использовать его в программе, которая сама имеет пользовательский интерфейс, по крайней мере, если пользовательский интерфейс выполняется в WPF - фокусы отслеживания фокуса путаются событиями в собственном приложении и быстро блокируют пользовательский интерфейс. Я отправил MS на отчет об ошибке. Я не заметил ту же проблему, используя традиционный пользовательский интерфейс Windows Forms. Конечно, вы могли бы поместить код отслеживания в отдельное консольное приложение и использовать какой-то ipc для передачи необходимой вам информации.

Заманчивая альтернатива использования Interop для доступа к WH_CBT Windows Hook с С# не будет работать - единственные глобальные перехватчики, с которыми вы можете перейти с С#, - это мыши и клавиатуры.

Ответ 5

Вы можете отслеживать сообщения для события WM_ACTIVATE.

ref

Ответ 6

Ну, это может быть не очень изящно... но вы можете легко получить текущий сфокусированный элемент управления. Таким образом, вы можете подумать о настройке таймера, который запрашивает каждые полсекунды или около того "Где находится текущий фокус?"... Затем вы можете наблюдать изменения. Пример кода Delphi приведен ниже; его довольно легко адаптировать, поскольку настоящая работа выполняется в вызовах Windows API.

<snip>

function TForm1.GetCurrentHandle: integer;
var
  activeWinHandle: HWND;
  focusedThreadID : DWORD;
begin
  //return the Windows handle of the currently focused control
  Result := 0;
  activeWinHandle := GetForegroundWindow;
  focusedThreadID := GetWindowThreadProcessID(activeWinHandle,nil);
  if AttachThreadInput(GetCurrentThreadID,focusedThreadID,true) then begin
    try
      Result := GetFocus;
    finally
      AttachThreadInput(GetCurrentThreadID, focusedThreadID, false);
    end;
  end;  //if attached
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  //give notification if the handle changed
  //(this code gets fired by a timer)
  CurrentHandle := GetCurrentHandle;
  if CurrentHandle <> PreviousHandle then begin
    Label1.Caption := 'Last focus change occurred @ ' + DateTimeToStr(Now);
  end;
  PreviousHandle := CurrentHandle;
end;

<snip>