如何将我的应用程序置于前台?

24

我知道这是个糟糕的想法,因为当一个应用程序窃取输入焦点时,我不喜欢它,但这仅适用于纯个人使用,并且我希望它发生;它不会干扰任何事情。

(对于好奇的人:我正在NetBeans中运行单元测试,生成日志文件。当我的后台应用程序看到日志文件的时间戳更改时,我希望它解析日志文件并前置显示结果)。

这个问题没有帮助,谷歌搜索也没有。似乎BringToFront()很长时间以来都没有起作用,我找不到任何其他可以起作用的替代方法。

有什么想法吗?


1
你不能这样做。Windows XP 有一个解决方法可以允许它,但是之后的版本禁止了它。 - Ken White
2
@Mawg,你测试了我的解决方案吗? - whosrdaddy
@whosrdaddy 是的,你的解决方案对我有效(对我来说已经足够了)。正如Ken所说,它可能不适用于其他人。我希望我能把答案授予给你,但我认为Ken回答了实际提出的问题,即使你给了我个人想要的解决方案。希望你不介意。我会给你在这个问题中发布的所有内容点赞。 - Mawg says reinstate Monica
1
@Mawg - 出于好奇,您是否曾经测试过我在帖子中提供的解决方案? - Sertac Akyuz
2
@Mawg - 这不是关于接受(如果已经有答案,我不会发布答案),而是关于反馈 - 只是“是的,它有效”或“不,这不起作用”。上面的评论/问题不是第一个,它的前任留在那里几天,然后我删除了它,并在确定您在线时问了另一个问题(这个问题也保留了五天以上)。 - Sertac Akyuz
显示剩余3条评论
8个回答

39

这是一个看起来很简单的方法,经过测试适用于多个系统,包括XP、Server2003、Vista、Server2008和W7。测试应用程序在标准(或管理员)帐户下运行,可以在前台写字时从记事本窃取输入焦点。

var
  Input: TInput;
begin
  ZeroMemory(@Input, SizeOf(Input));
  SendInput(1, Input, SizeOf(Input)); // don't send anyting actually to another app..
  SetForegroundWindow(Handle);

如果需要,您可以进一步调整它,例如,对于最小化的应用程序等。


2
谢谢!在Windows 8.1中也可以工作。 - Albert Wiersch
当窗口被隐藏时,它可以很好地工作,但无法恢复已最小化到任务栏的窗口。 - AlainD

20
你无法可靠地执行此操作。Windows XP有一种解决方法可以实现,但自那以后的版本大多数都禁止了此功能(请参阅下面的注释)。MSDN文档中SetForegroundWindow的备注部分明确指出了这一点:

系统限制哪些进程可以设置前景窗口。 只有满足以下条件之一的进程才能设置前景窗口:

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

应用程序不能在用户使用其他窗口时将窗口强制置于前景。 相反,Windows切换到窗口任务栏按钮以通知用户。

请注意上述引用文档的最后一段,特别是第一句话。

问题在于

这仅用于个人使用且我希望它发生; 它不会干扰任何事情。

任何应用程序都可以尝试执行相同的操作。 “纯个人使用”如何让它知道是你个人而不只是任何应用程序? :-)

注意:我已经尝试过所有我能想到的方法,以便在单击帮助工具栏按钮时使IDE文档显示在前景中,但我能得到的只有闪烁的任务栏按钮(而我作为用户希望它这样做)。


+1(还有您上面的评论;-) 显然,我会抱着希望,在几个小时内保持开放状态,然后再授予奖项。就像我说的那样,我希望有一些像缩小到系统托盘然后恢复,或者隐藏然后再次显示这样的鲜为人知的技巧 - 但是我尝试过的所有方法都不起作用 :-( 这是很多工作,只是为了避免Alt-Tab(嗯,也许如果我有一个“启动器”应用程序,然后启动一个“进程”应用程序。新应用程序会在前台启动吗?) - Mawg says reinstate Monica
你可以使用AlwaysOnTop样式,虽然它可能会影响其他应用程序的友好性,但它可以吸引用户的注意力。不过,像Growl/Snarl这样的特殊提醒服务可能更适合它。 - Arioch 'The

15

我在我的一个应用程序中正在做类似的事情,这个函数适用于我在xp/vista/w7中使用:

function ForceForegroundWindow(hwnd: THandle): Boolean;
const
  SPI_GETFOREGROUNDLOCKTIMEOUT = $2000;
  SPI_SETFOREGROUNDLOCKTIMEOUT = $2001;
var
  ForegroundThreadID: DWORD;
  ThisThreadID: DWORD;
  timeout: DWORD;
begin
  if IsIconic(hwnd) then ShowWindow(hwnd, SW_RESTORE);

  if GetForegroundWindow = hwnd then Result := True
  else
  begin
    // Windows 98/2000 doesn't want to foreground a window when some other
    // window has keyboard focus

    if ((Win32Platform = VER_PLATFORM_WIN32_NT) and (Win32MajorVersion > 4)) or
      ((Win32Platform = VER_PLATFORM_WIN32_WINDOWS) and
      ((Win32MajorVersion > 4) or ((Win32MajorVersion = 4) and
      (Win32MinorVersion > 0)))) then
    begin
      Result := False;
      ForegroundThreadID := GetWindowThreadProcessID(GetForegroundWindow, nil);
      ThisThreadID := GetWindowThreadPRocessId(hwnd, nil);
      if AttachThreadInput(ThisThreadID, ForegroundThreadID, True) then
      begin
        BringWindowToTop(hwnd); // IE 5.5 related hack
        SetForegroundWindow(hwnd);
        AttachThreadInput(ThisThreadID, ForegroundThreadID, False);
        Result := (GetForegroundWindow = hwnd);
      end;
      if not Result then
      begin
        // Code by Daniel P. Stasinski
        SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, @timeout, 0);
        SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, TObject(0),
          SPIF_SENDCHANGE);
        BringWindowToTop(hwnd); // IE 5.5 related hack
        SetForegroundWindow(hWnd);
        SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, TObject(timeout), SPIF_SENDCHANGE);
      end;
    end
    else
    begin
      BringWindowToTop(hwnd); // IE 5.5 related hack
      SetForegroundWindow(hwnd);
    end;

    Result := (GetForegroundWindow = hwnd);
  end;
end;

另一个解决方案是不窃取焦点,只是将窗口置于z缓冲区中最顶部:

procedure ForceForegroundNoActivate(hWnd : THandle);

begin
 if IsIconic(Application.Handle) then
  ShowWindow(Application.Handle, SW_SHOWNOACTIVATE);
 SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOMOVE);
 SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOMOVE);
end;

1
这在非管理员的Win7上无法工作,也不能在运行于Win2008域的机器上正常工作,所以它并不总是有效,这就是为什么我给出了我的答案。当然,我(以及操作系统制造商本身)知道什么呢? - Ken White
2
@KenWhite,这个函数用于终端用户应用程序,他们使用XP和W7(UAC开启),我在受限环境中没有遇到任何问题。 - whosrdaddy
1
这里有一件事情让我困扰: SystemParametersInfo(SPI_SET...。设定系统级别的参数是一个好主意吗?在UAC环境下,标准用户能够做到吗? - kobik
2
顺便提一下,SystemParametersInfo() 的第三个参数是一个 Pointer,所以代码应该使用 nil 而不是 TObject(0) - Remy Lebeau
1
我会点赞这个回答,除非有人能够“证明”这段代码不起作用(我自己还没有测试过)。@Ken,你是否确实在非管理员身份下使用Win7测试了这段代码? - kobik
显示剩余7条评论

8
假设没有其他StayOnTop应用程序在运行,您可以暂时将表单的FormStyle设置为fsStayOnTop,然后执行应用程序恢复和BringToFront操作,然后将表单样式改回原来的样式。
tmpFormStyle := Application.MainForm.FormStyle;
Application.MainForm.FormStyle := fsStayOnTop;
Application.Restore; // optional
Application.BringToFront;
Application.MainForm.FormStyle := tmpFormStyle;

需要注意的是,此方法仅在没有其他置顶应用程序的情况下才有效。


5
function WinActivate(const AWinTitle: string): boolean;
var
  _WindowHandle: HWND;
begin
  Result := false;
  _WindowHandle := FindWindow(nil, PWideChar(AWinTitle));
  if _WindowHandle <> 0 then
  begin
    if IsIconic(_WindowHandle) then
      Result := ShowWindow(_WindowHandle, SW_RESTORE)
    else
      Result := SetForegroundWindow(_WindowHandle);
  end;
end;

if WinActivate(self.Caption) then
  ShowMessage('This works for me in Windows 10');

5

这应该可以在Windows7+8操作系统中工作。唯一的要求是应用程序本身可以调用函数。使用外部应用程序将另一个进程的窗口设置为前台更加棘手。

Application.Restore; // unminimize window, makes no harm always call it
SetWindowPos(self.Handle, HWND_NOTOPMOST,0,0,0,0, SWP_NOMOVE or SWP_NOSIZE);
SetWindowPos(self.Handle, HWND_TOPMOST,0,0,0,0, SWP_NOMOVE or SWP_NOSIZE);
SetWindowPos(self.Handle, HWND_NOTOPMOST,0,0,0,0, SWP_SHOWWINDOW or SWP_NOMOVE or SWP_NOSIZE);

编辑 好的,我已经发现一个问题。应用程序被置于前台,但焦点仍然保留在原始应用程序中。使用这个答案来解决问题,它是相当复杂的方法,但是复制粘贴就可以了。在调用ForceForegroundWindow(wnd)之前,您可能需要调用Application.Restore来取消最小化窗口。

https://stackoverflow.com/a/5885318/185565

3
您可以编写一个可执行文件来解决这个限制。例如,您可以创建一个简单的非可视应用程序(基本上是控制台应用程序,但没有{$APPTYPE CONSOLE}),其唯一目的是将您想要的窗口置于前台。
当您需要将窗口置于前台时,您的监视后台应用程序通过命令行调用(例如ShellExecute)调用辅助应用程序。由于此应用程序正在成为前台进程,因此它能够将其他窗口置于前台(SetForeGroundWindow)。您可以使用FindWindow获取目标窗口的句柄,或在启动辅助应用程序时传递适当的参数。

-2

Form.bringtofront; ?

应该可以。 如果你把它放在计时器里,它会一直保持在最前面。


Q已经写了"_BringToFront()没有起作用",而你甚至没有明确指出这应该在哪个Windows版本中可靠地工作。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接