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

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

评论

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

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

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

当我启动一个应用程序,切换到其他操作并且新的应用程序开始接收半个句子时,这尤其令人讨厌。弹出对话框时,您甚至无意中将其关闭,甚至看不到消息,这更令人烦恼,因为您碰巧在键入句子时按了空格键或Enter键。

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

#1 楼

如果不对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代码。一位同事实际上做了一些其他研究,并介绍了该方法。在SO上讨论了该问题:https://stackoverflow.com/questions/7430864/windows-7-prevent-application-from-losing-focus

评论


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

–TomášZato-恢复莫妮卡
15年3月4日在15:04

@TomášZato:不知道。我自己并未积极使用此代码。

–霍赫斯塔普勒
2015年3月4日15:53

我正在尝试至少将其编译为C ++(然后从Java注入/删除已编译的DLL)。但这也不是很好。我不想在这里在评论中讨论它,但是如果您实际上可以帮助我使其正常运行,我将非常优雅!我创建了一个聊天室,如果可以使用,我会在此处发布评论操作方法:chat.stackexchange.com/rooms/21637/…

–TomášZato-恢复莫妮卡
2015年3月4日15:56

#2 楼

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

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

所以, SetForegroundWindow或类似的API函数存在某些问题...

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

如果还不够(另一个bug的API调用?),您可以走得更远,并进行一些API监视以查看发生了什么,然后您只需在执行完每个过程后就将API调用挂钩您可以摆脱所有弄乱前台的电话。但是,具有讽刺意味的是,Microsoft不鼓励这样做。

评论


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

–塔玛拉·维斯曼(Tamara Wijsman)
2012年3月24日10:47



安装程序(基于InnoSetup)将启动其他进程和可能的其他(隐藏)设置,但我不知道它们基于的安装程序创建者。

–丹尼尔·贝克♦
2012年3月26日在15:00

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

– Endolith
2012年6月6日18:06

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

–塔玛拉·维斯曼(Tamara Wijsman)
2012年6月6日19:31

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

– Endolith
2012年6月6日20:59



#3 楼

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

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

Update:据EndangeredMassa称,TweakUI在Windows 7上不起作用。

评论


tweakui与Windows 7兼容吗?

–法兰克斯特
2010年1月13日,9:18

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

–西蒙·史蒂文斯(Simon P Stevens)
2010年1月13日在15:51

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

–濒临灭绝的马萨
10-10-15在19:19

@EndangeredMassa那是哪个注册表项?

–n611x007
2012年11月4日21:49



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

–foo
13年10月10日在11:33

#4 楼

我认为可能存在一些混淆,因为有两种“窃取焦点”的方式:(1)窗口进入前台,(2)窗口接收击键。
这里提到的问题可能是第二个是Windows在没有用户请求或许可的情况下将焦点放在前台而成为焦点。
这里的讨论必须在XP和7之间进行。
Windows XP
在XP中,有一个注册表黑客使XP与Windows 7的作用相同,可以防止应用程序窃取焦点:

使用regedit转到:HKEY_CURRENT_USER\Control Panel\Desktop
Double-单击ForegroundLockTimeout并将其值以十六进制设置为30d40
按OK并退出regedit。
重新启动PC以使更改生效。

Windows 7
(下面的讨论同样也适用于XP。)
请理解,Windows无法完全阻止应用程序窃取焦点并保持功能。
例如,如果du环回文件副本,您的防病毒软件就会检测到可能的威胁
,并想弹出一个窗口询问您要采取的措施,如果该窗口被阻止了
,那么您将永远无法理解为什么复制永远不会终止。
在Windows 7中,只能对Windows本身的行为进行一种修改,即
使用MS-Windows焦点跟随鼠标注册表黑客,其中焦点和/或激活总是转到光标下方的窗口。可以添加延迟以避免应用程序在整个桌面上弹出。
请参阅本文:Windows 7-鼠标悬停使窗口处于活动状态-启用。
否则,必须检测并消除有罪程序:
如果这始终是与焦点相同的应用程序,则该应用程序
被编程为获取焦点,并且可以通过禁用它来从计算机启动或使用提供的某些设置来防止这种情况的发生。通过该应用程序来避免这种行为。
您可以使用VB代码中包含的VBS脚本
,该脚本可以识别谁在窃取焦点,而作者曾使用该脚本将罪魁祸首识别为打印机软件的“呼唤”更新程序。
衡量何时所有其他方法都失败了,并且如果您发现此应用程序编程错误,请
将其最小化,并希望它不会因此而浮出水面。
最小的更强形式是托盘通过使用
最佳免费应用程序最小化程序中列出的免费产品之一。
按照绝望的顺序,最后的想法是通过使用诸如台式机或Dexpot之类的产品来虚拟破坏台式机,
,然后在默认桌面以外的其他桌面上工作。
[编辑]
由于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


评论


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

– Endolith
2012年6月9日12:10



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

– Synetech
2012年6月12日下午4:46

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

–harrymc
2012年6月12日下午6:21

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

– Synetech
2012年6月12日15:06

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

– Synetech
2012年6月12日18:31

#5 楼

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

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

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

要处理其他窃取焦点的方法,需要进行更多测试,而我d感谢您对发生这种情况的任何反馈。

编辑:源代码现已可用。

评论


我尝试了该应用程序。似乎需要以admin身份启动,否则它将崩溃。 ViveProSettings.exe也导致它崩溃了,所以我杀死了该进程。一旦我解决了这些问题,也许它确实可以工作(从那以后就再也没有发现焦点被窃取了!),尽管我也无法通过关闭它来进行反测试,因为它也在退出时崩溃了:)。我没有抱怨,谢谢您的工作!我只想弄清楚如何使其可用。我已通过电子邮件发送给您详细信息。

–Piedone
20 Mar 24 '20 at 14:24

#6 楼

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


一天发生几次,某些应用程序通过弹出来窃取活动窗口的焦点。这种
可能由于多种原因而发生,例如,当我提取文件或完成传输时

在大多数情况下
并不重要,但是有时我写的是
文章,这不仅意味着
我必须再次输入一些单词,而且
我也失去了专心,并且必须单击以重新获得焦点。

Pro Reviewer网站上有关于如何防止这种情况发生的提示。
/>防止窃取焦点的最简单方法是使用Tweak UI,它具有
名为“防止应用程序窃取焦点”的设置。
选中此选项可以防止
其他应用程序突然弹出,
窃取您当前正在使用的窗口的焦点。

仅当应用程序
被使用时,此方法才起作用。最小化之前。而不是
失去焦点,它会闪烁
次,次数可以在Tweak UI的同一菜单中定义
。如果您不想使用Tweak UI,则可以在Windows注册表中更改设置。

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


评论


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

–濒临灭绝的马萨
10-10-15在19:18

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

– Michael Paulukonis
2014年2月24日在16:03



#7 楼

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


“系统限制可以设置前台窗口的进程。一个进程只能设置前台窗口如果满足以下条件之一:


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

https://docs.microsoft.com/zh-cn/windows/desktop/api/winuser/nf-winuser- allowetforegroundwindow


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

显然,这需要两个进程之间进行一些协调,但是它确实可以工作,如果您这样做是为了实现一个单实例应用程序,该应用程序将所有Explorer单击启动重定向到现有应用程序实例,那么您将已经有了一个(例如)命名管道来协调所有内容。