防止应用窃取焦点


191

有什么解决方案可以防止应用程序从活动窗口窃取焦点?

当我启动一个应用程序,切换到其他操作并且新的应用程序开始接收半个句子时,这尤其令人讨厌。


9
@Ivo Windows 7以我为例,但我认为对于SuperUser,所有Windows版本都是相关的
svandragt

3
主持人将以下问题合并:superuser.com/questions/199821/…与当前问题合并。这是错误的,当前问题的答案不适用于Windows 7,因此不应合并。到目前为止,我在Windows 7中找不到该问题的解决方案
Alex Angelico

17
这是我使用过的每个GUI的头号宠物之一。您正在打字并责备,一些让人眼花lee乱的对话框夺走了焦点,而您一半的击键却移到了其他地方。您可能认为窗口系统的实现者会在几十年前就弄清楚了这一点。如果窗口中有活动,请延迟新窗口的显示。例如,在当前焦点窗口中单击最后一个按钮或击键三到四秒钟后,才在GUI上弹出任何内容。h!
卡兹(Kaz)2012年

24
This is especially annoying when I'm starting an application, switch to do something else and the new application starts receiving half a sentence of text.弹出对话框时,您更无聊了,因为您碰巧按下了SpaceEnter输入了一个句子而无意中将其关闭,甚至看不到消息。
Synetech 2014年

3
实际上,这不仅仅是令人讨厌的方式,我想这是安全风险。当您输入密码并获取输入时,没有什么可以阻止应用程序弹出。
克里斯·皮科克

Answers:


51

没有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;

从该片段中可以看出,我的研究还集中在我不喜欢的用户界面行为的其他方面。

我试图解决此问题的方法是将DLL加载到每个新进程中,并挂接导致另一个窗口被激活的API调用。
最后一部分很简单,这要归功于出色的API挂钩库。我使用了非常好的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;
}

从我当时的测试来看,这非常有效。除了将DLL加载到每个新进程中的部分。正如人们可能想象的那样,这一点也不为过。那时我使用了AppInit_DLLs方法(这还远远不够)。

基本上,这很好。但是我从来没有时间写一些可以将我的DLL 正确注入新进程的东西。花在这上面的时间大大掩盖了偷窃焦点给我带来的烦恼。

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


您认为您的解决方案可以移植到Java吗?我一直在搜索并提出问题,但没有发现任何问题。也许我可以使用Java导入钩子库本身jne
托马什Zato

@TomášZato:不知道。我本人并没有积极使用此代码。
Der Hochstapler'3

我正在尝试至少将其编译为C ++(然后从Java注入/删除已编译的DLL)。但这也不是很好。我不想在这里在评论中讨论它,但是如果您实际上可以帮助我使其正常运行,我将非常优雅!我创建了一个聊天室,如果我得到那个工作我会后评论如何做到这一点的位置:chat.stackexchange.com/rooms/21637/...
托马什Zato

23

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

但是,文档很烂,他们互相追逐,并找到解决方法

因此,SetForegroundWindow或类似的API函数发生了一些问题。

真正正确执行此操作的唯一方法是制作一个小型应用程序,该应用程序会定期调用LockSetForegroundWindow,从而实际上禁止了对我们的错误API函数的任何调用。

如果这还不够(另一个错误的API调用?),您可以走得更远,并进行一些API监视以查看发生了什么,然后您只需在每个进程上挂接API调用,之后就可以摆脱任何混乱的调用前景。但是,具有讽刺意味的是,Microsoft不鼓励这样做。


3
在Windows 7中,有人可以复制此用例吗?考虑到人们宁愿遇到相反的情况(例如,我经常发现要求Windows隐藏在当前窗口的后面),而我还没有看到在Windows 7中发生这种情况,因此编写应用程序会很烦人,但却无法测试一下。此外,如Microsoft所述,Windows 7不再应该发生这种情况。充其量,人们发现它只能偶然地切换键盘的焦点,此API调用可以解决此问题,但我不知道如何测试它是否真正起作用。 。
塔玛拉Wijsman

1
安装程序(基于InnoSetup)启动其他进程和可能的其他(隐藏)安装程序,但我不知道它们基于的安装程序创建者。
丹尼尔·贝克

6
@TomWijsman:打开regedit,搜索一些找不到的随机文本。进入另一个应用程序并开始输入。搜索完成后,regedit将窃取焦点。
endlith 2012年

1
@endolith:不可重现,不过请在此处使用Windows 8 Replase Preview。您正在使用什么操作系统?就我而言,它只是在底部突出显示了应用程序,却丝毫不打断我的浏览……
Tamara Wijsman 2012年

21
是的,Win7 Pro 64位。而且对于高级进程而言,焦点窃取甚至更糟,因为它们会在不应该按时按您按下<Enter>的位置,并且您告诉它意外地软管系统。任何事情不能夺走焦点。
endlith 2012年

18

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

尽管这是一场持续不断的武器战争,所以我不知道它是否适用于一切。

更新:根据EndangeredMassa的说法,TweakUI在Windows 7上不起作用。


2
tweakui与Windows 7兼容吗?
弗兰克斯特

@弗兰克斯特。不知道,对不起,我怀疑可能不是。下载并尝试。如果您这样做,请举报,每个人都知道。
西蒙·史蒂文斯

5
即使使用TweakUI设置的注册表设置在Win7上也不起作用。
EndangeredMassa 2010年

@EndangeredMassa那是哪个注册表项?
n611x007

2
注册表项是HKEY_CURRENT_USER \ Control Panel \ Desktop \ ForegroundLockTimeout(以毫秒为单位)。是的,它不再适用于Windows 7。
foo,

14

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

这里提到的问题可能是第二个问题,其中一个窗口通过将其自身置于前台来声明焦点-未经用户请求或许可。

讨论必须在XP和7之间进行。

Windows XP

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

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

Windows 7的

(以下讨论也同样适用于XP。)

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

在Windows 7中,只能对Windows本身的行为进行一种修改,即使用MS-Windows焦点跟随鼠标注册表hack,其中焦点和/或激活始终移至光标所在的窗口。可以添加一个延迟,以避免应用程序在整个桌面上弹出。
请参阅本文:Windows 7-鼠标悬停使窗口处于活动状态-启用

否则,必须检测并消除有罪程序:如果这始终是引起焦点的同一应用程序,则对该应用程序进行编程以使其成为焦点,并可以通过禁止它从计算机启动来防止这种情况发生,或者使用该应用程序提供的某些设置来避免此行为。

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

如果所有其他方法都失败了,并且您发现了这个编程不良的应用程序,那么一个绝望的措施就是将其最小化,并希望这样不会使自己走在前头。通过使用“ 最佳免费应用程序最小化程序”中列出的免费产品之一,可以对托盘进行 最小化

绝望的最后一个想法是通过使用诸如DesktopsDexpot之类的产品来虚拟破坏桌面,并在默认桌面之外的其他桌面上进行工作。

[编辑]

由于微软已经淘汰了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

48
“如果此窗口被阻止,那么您将永远无法理解为什么副本永远不会终止”。正确的行为是使用闪烁的任务栏图标(或者可能是气球弹出窗口或烤面包机通知等)通知用户。使用拦截其击键的窗口来打断用户,这意味着他们告诉防病毒软件随机采取一项措施或另一项措施。绝对不是做事情的好方法。
endlith 2012年

1
“如果此窗口被阻止,那么您将永远无法理解为什么副本永远不会终止”。正确的行为是使用闪烁的任务栏图标通知用户... 有时,我单击按钮或正在运行的程序中的某些东西会导致创建新的模态对话框(例如,打开文件),但是在创建对话框之前,我切换到另一个程序。结果,该对话框被隐藏,并且无法切换到其他程序,并且无法关闭该对话框。它的任务栏按钮均无效Alt-Tab。仅强制将对话框显示在最前面。
Synetech 2012年

1
@Synetech:有时,非前端对话框的唯一解决方案是终止任务。Windows中的焦点算法确实很糟糕。
harrymc

2
@harrymc,我永远不必求助于杀死其中一个应用程序。我只运行我的窗口操作程序(WinSpy ++很好地完成了这个技巧),然后将窗口隐藏在前面,然后可以关闭卡住的对话框,然后重新显示隐藏的窗口。这不方便,但是比杀死这两个过程要好。
Synetech 2012年

1
@harrymc,不是真的;杀死一个应用程序并丢失内容只会增加工作量,如果它是一个模式对话框(锁定了父窗口并且没有任务栏按钮),那么它将不会出现在Alt+Tab列表中,以我的经验,打开模态对话框并不总是(永远不会?)显示带有的模态对话框Alt+Tab,特别是如果对话框从未更改过以获取焦点。:-|
Synetech 2012年

2

Ghacks有一个可能的解决方案:

一天发生几次,某些应用程序通过弹出来窃取活动窗口的焦点。例如,当我提取文件或完成传输时,可能由于多种原因会发生这种情况。在大多数情况下发生这种情况并不重要,但有时我正在写一篇文章,这不仅意味着我必须再次输入一些单词,而且还意味着我失去了专注力,必须单击以重新获得焦点。

专业点评网站有关于如何防止这种情况的发生小费。防止窃取焦点的最简单方法是使用Tweak UI,该UI的设置称为“防止应用程序窃取焦点”。选中此选项可防止其他应用程序突然弹出并占用您当前正在使用的窗口的焦点。

这仅在以前最小化应用程序时有效。它不会闪烁焦点,而是闪烁多次,可以在Tweak UI的同一菜单中定义多次。如果您不想使用Tweak UI,则可以在Windows注册表中更改设置。

导航到注册表项HKEY_CURRENT_USER>控制面板>桌面,然后将ForegroundLockTimeout值更改为30d40(十六进制)或200000(十进制)。按键ForeGroundFlashCount定义了一个窗口闪烁的次数,以警告用户0表示无限制。


20
XP之后的任何操作系统都无法使用此功能。该注册表值已经设置为该值(默认情况下,我相信),并且仍然无法正常工作。
EndangeredMassa 2010年

1
仅次于我在Windows 7(64位)上进行的抢焦点(最终激活时为VS 2012,例如),并且上述注册表建议已经就位。此回答中的技术确认:superuser.com/a/403554/972
Michael Paulukonis 2014年

2

受到Der Hochstapler的回答的启发,我决定编写一个DLL注入器,该注入器可与64位和32位进程一起使用,并防止焦点转移到Windows 7或更高版本上:https : //blade.sk/stay-focused/

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

从我的测试来看,到目前为止效果很好。但是,问题似乎不仅限于应用程序调用SetForegroundWindow。例如,当创建一个新窗口时,它会自动进入前台,这也会干扰用户键入另一个窗口。

为了处理窃取焦点的其他方法,需要进行更多测试,对于在这种情况下出现的任何反馈,我将不胜感激。


0

在您以编程方式从另一个进程激活,最大化和集中该进程的主窗口之后,我想出了如何阻止TaskBar闪烁新激活的目标窗口。首先,是否允许此操作有很多限制。

“系统限制了哪些进程可以设置前台窗口。只有在满足以下条件之一的情况下,进程才能设置前台窗口:

  • 该过程是前台过程。
  • 该过程由前台过程启动。
  • 该进程收到了最后一个输入事件。
  • 没有前台进程。
  • 前台进程正在调试。
  • 前景未锁定(请参见LockSetForegroundWindow)。
  • 前台锁定超时已到期(请参阅SystemParametersInfo中的SPI_GETFOREGROUNDLOCKTIMEOUT)。
  • 没有菜单处于活动状态。

https://docs.microsoft.com/zh-CN/windows/desktop/api/winuser/nf-winuser-allowsetforegroundwindow

因此,如果控制进程在前台,则可以通过使用目标进程的进程ID调用AllowSetForegroundWindow来暂时使另一个进程完全窃取前台。然后,目标进程可以使用其自己的窗口句柄调用SetForegroundWindow本身,它将起作用。

显然,这需要在两个过程之间进行一些协调,但是它确实可以工作,并且如果要执行此操作以实现将所有Explorer单击启动重定向到现有应用程序实例的单实例应用程序,那么您已经有一个(例如)命名管道来协调事物。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.