24

I know all the reasons why it is a bad idea. I dislike it if an application steals input focus, but this is for purely personal use and I want it to happen; it will not disturb anything.

(for the curious: I am running unit tests in NetBeans, which generate a log file. When my background application sees the log file's timestamp change I want it to parse the log file and come to the front to show the results).

This question did not help, nor did googling. It seems that BringToFront() hasn't worked for a long time and I can't find any alternative that does.

Any ideas?

4

8 回答 8

37

Here's something simple that seems to work, tested with multiple boxes consisting of XP, Server2003, Vista, Server2008, W7. Test application ran with a standard (or admin) account, stole the input focus from notepad while writing in the foreground.

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

You can tweak it further f.i. for a minimized app or such if required.

于 2012-10-18T08:40:07.913 回答
20

You can't do this reliably. Windows XP had a workaround that would allow it, but versions since then prohibit it for the most part (see note below). This is expressly stated in the remarks section of the MSDN documentation on SetForegroundWindow:

The system restricts which processes can set the foreground window. A process can set the foreground window only if one of the following conditions is true:

  • The process is the foreground process.
  • The process was started by the foreground process.
  • The process received the last input event.
  • There is no foreground process.
  • The foreground process is being debugged.
  • The foreground is not locked (see LockSetForegroundWindow).
  • The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
  • No menus are active.

An application cannot force a window to the foreground while the user is working with another window. Instead, Windows flashes the taskbar button of the window to notify the user.

Note the final paragraph of the quoted documentation, especially the first sentence.

The problem with

this is for purely personal use and I want it to happen; it will not disturb anything.

is that any application could try to do the same thing. How does "purely personal use" tell it's you personally and not just any application? :-)

Note: I've tried everything I can think of to get the IDE's documentation to show in the foreground when I click the help toolbutton, but all I can get is the flashing taskbar button (and I as the user want it to do so).

于 2012-10-18T02:42:21.610 回答
15

I am doing something similar in one of my applications and this function works for me in 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;

Another solution is not to steal focus and just put the window TOPMOST in the z buffer:

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;
于 2012-10-18T07:56:51.050 回答
6

Assuming that there are no other StayOnTop applications running you can temporarily set your forms FormStyle to be fsStayOnTop and then do an Application Restore and BringToFront and then change the formstyle back to whatever it was.

tmpFormStyle := Application.MainForm.FormStyle;
Application.MainForm.FormStyle := fsStayOnTop;
Application.Restore; // optional
Application.BringToFront;
Application.MainForm.FormStyle := tmpFormStyle;

Again, this only works if there are no other stayontop applications.

于 2015-12-11T22:55:32.040 回答
5

This should work even in Windows7+8 OS. Only requirement is an application itself can call functions. It's trickier to use an external app to set another process's window front.

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

Edit Ok I found out one problem with this. Application is brought to front but focus is kept in an original application. Use this answer to fix the problem, its quite complex method but copypaste does the trick. Before calling ForceForegroundWindow(wnd) you may have to call Application.Restore unminimizing a window. https://stackoverflow.com/a/5885318/185565

于 2013-12-20T16:11:12.517 回答
3

You could write an Executable to workaround that limitation. Take for example a simple (non-visual) application (basically a console app but without the {$APPTYPE CONSOLE}) with the only purpose to bring your desired window to front.

Your monitoring background application calls the helper app via command line call (e.g. ShellExecute) whenever your bringtofront action is needed. Since this app is becoming the foreground process, it is then able to bring other windows to front (SetForeGroundWindow). You could use FindWindow to get a handle on your target window or pass appropriate parameters when starting your helper app.

于 2012-10-18T07:43:28.477 回答
3
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');
于 2015-10-07T04:34:08.877 回答
-2

Form.bringtofront; ?

Should work. If you put it in a timer it will stay at front.

于 2012-10-25T17:51:40.263 回答