防止应用程序窃取焦点

sva*_*agt 204 windows window-focus

是否有任何解决方案可以防止应用程序从活动窗口窃取焦点?

当我启动一个应用程序,切换到做其他事情并且新应用程序开始接收半句文本时,这尤其令人讨厌。

Der*_*ler 53

如果不对Windows 内部进行大量操作,这是不可能的,您需要克服它。

在日常计算机使用中,有时在操作系统允许您执行另一项操作之前执行一项操作非常重要。为此,它需要将您的注意力锁定在某些窗口上。在 Windows 中,对这种行为的控制主要留给您使用的各个程序的开发人员。

在谈到这个话题时,并不是每个开发人员都会做出正确的决定。

我知道这很令人沮丧和烦人,但你不能吃蛋糕也不能吃。在您的日常生活中可能有很多情况,您可以将焦点移到某个 UI 元素或请求焦点保持锁定在它上面的应用程序。但是大多数应用程序在决定现在谁是领导者时都有些平等,而且系统永远不会完美。

不久前,我对一劳永逸地解决这个问题进行了广泛的研究(但失败了)。我的研究结果可以在烦恼项目页面上找到。

该项目还包括一个应用程序,该应用程序通过调用反复尝试获取焦点:

switch( message ) {
  case WM_TIMER:
    if( hWnd != NULL ) {
      // Start off easy
      // SetForegroundWindow will not move the window to the foreground,
      // but it will invoke FlashWindow internally and, thus, show the
      // taskbar.
      SetForegroundWindow( hWnd );

      // Our application is awesome! It must have your focus!
      SetActiveWindow( hWnd );

      // Flash that button!
      FlashWindow( hWnd, TRUE );
    }
    break;
Run Code Online (Sandbox Code Playgroud)

正如我们从这段代码中看到的,我的研究还集中在我不喜欢的用户界面行为的其他方面。

我试图解决这个问题的方法是将一个 DLL 加载到每个新进程中,并挂钩导致另一个窗口被激活的 API 调用。
最后一部分很简单,这要归功于很棒的 AP​​I 挂钩库。我使用了非常棒的mhook 库

#include "stdafx.h"
#include "mhook-2.2/mhook-lib/mhook.h"

typedef NTSTATUS( WINAPI* PNT_QUERY_SYSTEM_INFORMATION ) ( 
  __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,     
  __inout    PVOID SystemInformation, 
  __in       ULONG SystemInformationLength, 
  __out_opt  PULONG ReturnLength    
);

// Originals
PNT_QUERY_SYSTEM_INFORMATION OriginalFlashWindow   = 
  (PNT_QUERY_SYSTEM_INFORMATION)::GetProcAddress( 
  ::GetModuleHandle( L"user32" ), "FlashWindow" );

PNT_QUERY_SYSTEM_INFORMATION OriginalFlashWindowEx = 
  (PNT_QUERY_SYSTEM_INFORMATION)::GetProcAddress( 
  ::GetModuleHandle( L"user32" ), "FlashWindowEx" );

PNT_QUERY_SYSTEM_INFORMATION OriginalSetForegroundWindow = 
  (PNT_QUERY_SYSTEM_INFORMATION)::GetProcAddress( 
  ::GetModuleHandle( L"user32" ), "SetForegroundWindow" );

// Hooks
BOOL WINAPI
HookedFlashWindow(
  __in  HWND hWnd,
  __in  BOOL bInvert
  ) {
  return 0;
}

BOOL WINAPI 
HookedFlashWindowEx(
  __in  PFLASHWINFO pfwi
  ) {
  return 0;
}

BOOL WINAPI 
HookedSetForegroundWindow(
  __in  HWND hWnd
  ) {
  // Pretend window was brought to foreground
  return 1;
}


BOOL APIENTRY 
DllMain( 
  HMODULE hModule,
  DWORD   ul_reason_for_call,
  LPVOID  lpReserved
  ) {
  switch( ul_reason_for_call ) {
    case DLL_PROCESS_ATTACH:
      Mhook_SetHook( (PVOID*)&OriginalFlashWindow,         HookedFlashWindow );
      Mhook_SetHook( (PVOID*)&OriginalFlashWindowEx,       HookedFlashWindowEx );
      Mhook_SetHook( (PVOID*)&OriginalSetForegroundWindow, HookedSetForegroundWindow );
      break;

    case DLL_PROCESS_DETACH:
      Mhook_Unhook( (PVOID*)&OriginalFlashWindow );
      Mhook_Unhook( (PVOID*)&OriginalFlashWindowEx );
      Mhook_Unhook( (PVOID*)&OriginalSetForegroundWindow );
      break;
  }
  return TRUE;
}
Run Code Online (Sandbox Code Playgroud)

从我当时的测试来看,这很好用。除了将 DLL 加载到每个新进程中的部分。正如人们所想象的那样,没有什么可以掉以轻心的。我当时使用了AppInit_DLLs方法(这根本不够)。

基本上,这很好用。但是我从来没有时间写一些东西来正确地将我的 DLL 注入到新进程中。而在这方面投入的时间在很大程度上掩盖了焦点窃取给我带来的烦恼。

除了 DLL 注入问题,还有一个焦点窃取方法,我在 Google Code 上的实现中没有涉及。一位同事实际上做了一些额外的研究并涵盖了该方法。在 SO 上讨论了这个问题:https : //stackoverflow.com/questions/7430864/windows-7-prevent-application-from-losing-focus


Tam*_*man 24

在 Windows 7 中,ForegroundLockTimeout不再检查注册表项,您可以使用 Process Monitor 进行验证。事实上,在 Windows 7 中,它们不允许您更改前景窗口。去阅读它的详细信息,它甚至从 Windows 2000 开始就存在了。

然而,文档很糟糕,他们互相追逐并找到解决方法

所以,有一些错误SetForegroundWindow,或者类似的 API 函数......

真正正确地做到这一点的唯一方法是制作一个定期调用的小型应用程序LockSetForegroundWindow,实际上禁用对我们有缺陷的 API 函数的任何调用。

如果这还不够(另一个有问题的 API 调用?),您可以更进一步进行一些API 监控以查看发生了什么,然后您只需将 API 调用挂接到每个进程上,然后您就可以摆脱任何混乱的调用前景。然而,具有讽刺意味的是,微软不鼓励这样做......

  • 是的,Win7 Pro 64 位。对于提升的进程来说,焦点窃取甚至更糟,因为它们会在不应该时捕获您按下的 <Enter> 键,而您却告诉它意外地对系统进行软管冲洗。没有什么应该*永远*能够窃取焦点。 (25认同)
  • @TomWijsman:打开regedit,搜索一些找不到的随机文本。进入另一个应用程序并开始输入。搜索完成后,regedit 将窃取焦点。 (7认同)
  • **有人在 Windows 7 中有可重现的用例吗?** 鉴于人们宁愿经历相反的情况(例如,我经常发现要求将 Windows 隐藏在我当前的窗口后面)而且我还没有看到这种情况发生在 Windows 7 中,编写应用程序但无法对其进行测试会非常烦人。此外,正如微软所说,Windows 7 不应再发生这种情况。人们充其量发现它只能偶然切换键盘的焦点,这个 API 调用会解决这个问题,但我不知道如何测试它是否真的有效。 . (3认同)

Sim*_*ens 18

TweakUI 中有一个选项可以执行此操作。它可以防止可疑的软件开发人员用来强制专注于他们的应用程序的大多数常见技巧。

不过,这是一场持续的军备战争,所以我不知道它是否适用于所有情况。

更新:根据EndangeredMassa 的说法,TweakUI 不适用于 Windows 7。

  • 即使使用 TweakUI 设置的注册表设置在 Win7 上也不起作用。 (5认同)
  • 与 windows 7 兼容吗? (2认同)
  • 注册表项是 HKEY_CURRENT_USER\Control Panel\Desktop\ForegroundLockTimeout(以毫秒为单位)。是的,它不再适用于 Windows 7。 (2认同)

har*_*ymc 16

我相信可能存在一些混淆,因为有两种“窃取焦点”的方法:(1)窗口进入前台,以及(2)窗口接收按键。

这里提到的问题可能是第二个,其中 windows 通过将自己置于前台来声明焦点 - 无需用户请求或许可。

这里的讨论必须在 XP 和 7 之间分开。

视窗 XP

在 XP 中有一个注册表黑客,它使 XP 在防止应用程序窃取焦点方面与 Windows 7 的工作方式相同:

  1. 使用 regedit 转到:HKEY_CURRENT_USER\Control Panel\Desktop
  2. 双击ForegroundLockTimeout并将其十六进制值设置为30d40
  3. 按 OK 并退出 regedit。
  4. 重新启动您的 PC 以使更改生效。

Windows 7的

(下面的讨论也主要适用于 XP。)

请理解,Windows 无法完全阻止应用程序窃取焦点并保持功能。例如,如果在文件复制过程中,您的防病毒软件检测到可能存在的威胁,并希望弹出一个窗口要求您采取行动,如果此窗口被阻止,那么您将永远无法理解为什么复制永远不会终止。

在 Windows 7 中,Windows 本身的行为只能进行一种修改,即使用MS-Windows focus-follows-mouse Registry hacks,其中焦点和/或激活始终指向光标下的窗口。可以添加延迟以避免应用程序在桌面上弹出。
请参阅此文章:Windows 7-鼠标悬停使窗口处于活动状态-启用

否则,必须检测并中和有罪的程序:如果这始终是获得焦点的同一个应用程序,则该应用程序被编程为获得焦点,并且可以通过禁用它从计算机启动来防止这种情况发生,或者使用该应用程序提供的一些设置来避免这种行为。

您可以使用VB 代码中包含的 VBS 脚本,该脚本可识别谁在窃取焦点,作者使用该脚本将罪魁祸首识别为打印机软件的“回拨”更新程序。

当所有其他方法都失败时,如果您发现了这个编程错误的应用程序,那么一个绝望的措施是将其最小化,并希望它不会因此而出现在前面。一种更强大的最小化形式是使用Best Free Application Minimizer 中列出的免费产品之一对托盘进行 最小化

绝望中的最后一个想法是通过使用诸如DesktopsDexpot 之类的产品来虚拟地破坏您的桌面,并在另一个桌面而不是默认桌面上完成您的工作。

[编辑]

由于 Microsoft 已停用 Archive Gallery,以下是转载的上述 VB 代码:

Declare Auto Function GetForegroundWindow Lib "user32.dll" () As Integer
Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As Integer, ByRef procid As Integer) As UInteger
 
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Me.RichTextBox1.AppendText("Starting up at " & Now & vbCrLf)
    End Sub
 
    Private Sub GoingAway(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Deactivate, Me.LostFocus
 
        Dim hwnd As Integer = GetForegroundWindow()
        ' Note that process_id will be used as a ByRef argument
        ' and will be changed by GetWindowThreadProcessId
        Dim process_id As Integer = 1
        GetWindowThreadProcessId(hwnd, process_id)
 
        If (process_id <> 1) Then
            Dim appExePath As String = Process.GetProcessById(process_id).MainModule.FileName() 
            Me.RichTextBox1.AppendText("Lost focus at " & Now & " due to " & appExePath & vbCrLf)
        Else
            Me.RichTextBox1.AppendText("Lost focus due to unknown cause.")
        End If
 
    End Sub
Run Code Online (Sandbox Code Playgroud)

  • “如果这个窗口被阻止,那么你永远不会明白为什么副本永远不会终止”那不是真的。正确的行为是用闪烁的任务栏图标(或者可能是气球弹出或烤面包机通知或其他东西)通知用户。用拦截用户击键的窗口打断用户意味着他们告诉防病毒软件随机采取一种或另一种操作。绝对不是做事的好方法。 (53认同)
  • @harrymc,我永远不必求助于杀死其中一个应用程序。我只是运行我的窗口操作程序([WinSpy++](http://www.catch22.net/software/winspy-17) 效果很好)并将窗口隐藏在前面,然后我可以消除卡住的对话框,然后重新显示隐藏的窗口。这并不方便,但总比杀死其中一个进程要好。 (2认同)

bla*_*ade 5

Der Hochstapler 的回答的启发,我决定编写一个 DLL 注入器,它适用于 64 位和 32 位进程,并防止在 Windows 7 或更高版本上窃取焦点:https : //blade.sk/stay-focused/

它的工作方式是SetWinEventHook监视新创建的窗口(使用),并将与 Der Hochstapler 的 DLL 非常相似的 DLL 注入到窗口进程中(如果尚不存在)。它卸载 DLL 并在退出时恢复原始功能。

从我的测试来看,到目前为止它运行良好。然而,这个问题似乎不仅仅是应用程序调用SetForegroundWindow. 例如,当创建一个新窗口时,它会自动进入前台,这也会干扰用户在另一个窗口中键入内容。

为了处理其他的焦点窃取方法,需要更多的测试,我很感激任何关于它发生的场景的反馈。

编辑:源代码现在可用。