Winapi 如何监视当前有键盘焦点的窗口

Winapi 如何监视当前有键盘焦点的窗口,winapi,mfc,Winapi,Mfc,有没有办法跟踪哪个窗口当前有键盘焦点。我可以为每个窗口处理WM_SETFOCUS,但我想知道是否有另一种更简单的方法(即某个地方的单个消息处理程序) 我可以在MFC中使用OnIdle()并调用GetFocus(),但这似乎有点不太妥当。Win32怎么样?因此,从您对问题的措辞来看,我推断您希望有一个事件处理程序,在焦点在窗口之间切换时调用它。你希望得到通知,而不是投票 事实上,我并不认为从OnIdle调用GetFocus是一种黑客行为——当然是轮询,但它是低开销轮询,没有副作用——但如果您真的想

有没有办法跟踪哪个窗口当前有键盘焦点。我可以为每个窗口处理WM_SETFOCUS,但我想知道是否有另一种更简单的方法(即某个地方的单个消息处理程序)


我可以在MFC中使用OnIdle()并调用GetFocus(),但这似乎有点不太妥当。

Win32怎么样?

因此,从您对问题的措辞来看,我推断您希望有一个事件处理程序,在焦点在窗口之间切换时调用它。你希望得到通知,而不是投票

事实上,我并不认为从OnIdle调用GetFocus是一种黑客行为——当然是轮询,但它是低开销轮询,没有副作用——但如果您真的想跟踪这一点,它可能是您的最佳选择。具体来说,您可以安装CBT钩子(WH_CBT)并侦听HCBT_SETFOCUS通知

当Windows将焦点设置为任何窗口时,Windows使用此钩子代码调用WH_CBT钩子。对于特定于线程的挂钩,窗口必须属于线程。如果filter函数返回TRUE,则焦点不会改变

您还可以使用WH_CALLWNDPROC钩子来监听WM_SETFOCUS消息


根据您是将其设置为全局钩子还是应用程序本地,您可以跨系统上的所有窗口跟踪焦点,或者仅跟踪进程拥有的窗口。

您可以监视事件的消息


嗯,这可能不是很优雅。。。但是您可以很容易地检索当前的焦点控件。所以你可以考虑设置一个定时器,每1/2秒询问一次定时器“当前的焦点在哪里?”然后你可以观察变化。下面是Delphi代码示例;它应该很容易适应,因为真正的工作是在WindowsAPI调用中

<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>

函数TForm1.GetCurrentHandle:整数;
变量
activeWinHandle:HWND;
focusedThreadID:DWORD;
开始
//返回当前焦点控件的Windows句柄
结果:=0;
activeWinHandle:=GetForegroundWindow;
focusedThreadID:=GetWindowThreadProcessID(activeWinHandle,nil);
如果AttachThreadInput(GetCurrentThreadID,focusedThreadID,true),则开始
尝试
结果:=GetFocus;
最后
AttachThreadInput(GetCurrentThreadID,focusedThreadID,false);
结束;
结束//如附
结束;
程序TForm1.Timer1Timer(发送方:TObject);
开始
//如果句柄已更改,则发出通知
//(此代码由计时器触发)
CurrentHandle:=GetCurrentHandle;
如果CurrentHandle之前的Handle,则开始
标签1.标题:=“上次焦点更改发生@”+DateTimeToStr(现在);
结束;
PreviousHandle:=CurrentHandle;
结束;

使用.Net Framework 3.5有一种简单的方法:库UI Automation提供了一个事件焦点更改,每次焦点更改到新控件时都会触发该事件焦点

样本:

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

private void OnFocusChanged(object sender, AutomationFocusChangedEventArgs e)
{
    AutomationElement focusedElement = sender as AutomationElement;
    //...
}
这个api实际上在幕后使用windows钩子来实现这一点。但是,您必须使用.Net Framework…


有一个窗口焦点跟踪器示例

如果你是在.net 3.5中编程,olorin提到的自动化软件包是目前为止最简单的,但是要小心在一个本身有UI的程序中使用它,至少如果UI是在WPF中完成的——焦点跟踪挂钩会被自己应用程序中的事件弄糊涂,并很快锁定UI。我给她发了一封信。我在使用传统的Windows窗体UI时没有发现同样的问题。当然,你可以将跟踪代码放在一个单独的控制台应用程序中,并使用某种ipc来传输你需要的信息


使用Interop从C#访问Whu CBT Windows钩子的诱人替代方案是行不通的——。

我知道如何获得焦点窗口,我正在寻找一种在焦点更改时在单个位置收到通知的方法。我在任何地方都找不到源代码的下载,而且SDK的安装中也没有包含该源代码。有人知道我该如何得到它吗?焦点追踪者似乎已经移到这里了:(抱歉,我没有足够的声誉将此作为对上述相关答案的评论)。