如何把一个窗口置于前台?

96

我们有一个Java应用程序,当远程控制机制在应用程序中激活某些东西时,需要将其引入到前台。

为了实现这一点,在表示我们应用程序帧的类的调用方法(继承自JFrame)中,我们已经意识到以下实现:

setVisible(true);
toFront();

在 Windows XP 下,第一次调用此功能时可以正常工作,但第二次只会导致任务栏中的选项卡闪烁,窗口框架不再置于前台。Win2k 的情况也是如此。在 Vista 上似乎能够正常工作。

你有什么想法吗?


你有这个行为的示例吗? - OscarRyz
3
正确的答案是在EDT上使用invokeLater调用toFront()方法。下面包含了一个简单的答案,但它不是被接受的答案。不过,这个简单的答案确实有效。完美无缺。 - Erick Robertson
我知道这很旧,但这在OSX上也会发生。 - ferdil
我遇到了这个问题,但下面的答案似乎都没有解决它。我确定这是由于Windows不允许我在应用程序中为我的第一个窗口“窃取”焦点引起的。 - Craig Warren
12个回答

73
一个可能的解决方案是:
java.awt.EventQueue.invokeLater(new Runnable() {
    @Override
    public void run() {
        myFrame.toFront();
        myFrame.repaint();
    }
});

8
也许最好一开始就把所有的UI代码放在invokeLater里面?;) - java.is.for.desktop.indeed
2
在Java 7和KDE 4.9.5上,这对我没有起作用。窗口仍然会隐藏在其他程序下面。帮助我的是改变将窗口置于前台的顺序。与其隐藏一个窗口并显示第二个窗口,不如先显示第二个窗口,然后再隐藏第一个窗口(JFrame)。 - Lekensteyn
1
适用于在应用程序中运行Java 1.8的Windows 10。 - Elliott
什么是反方法? - Cardinal System

38

我在Ubuntu下(Java 1.6.0_10)遇到了带一个JFrame置于最前面的问题。我唯一能解决这个问题的方法是提供一个WindowListener。具体来说,当调用toFront()时,我必须将我的JFrame设置为始终保持在最上层,并提供windowDeactivated事件处理程序,以便调用setAlwaysOnTop(false)


因此,以下是可以放置在基本JFrame中的代码,该JFrame用于导出所有应用程序框架。

@Override
public void setVisible(final boolean visible) {
  // make sure that frame is marked as not disposed if it is asked to be visible
  if (visible) {
      setDisposed(false);
  }
  // let's handle visibility...
  if (!visible || !isVisible()) { // have to check this condition simply because super.setVisible(true) invokes toFront if frame was already visible
      super.setVisible(visible);
  }
  // ...and bring frame to the front.. in a strange and weird way
  if (visible) {
      toFront();
  }
}

@Override
public void toFront() {
  super.setVisible(true);
  int state = super.getExtendedState();
  state &= ~JFrame.ICONIFIED;
  super.setExtendedState(state);
  super.setAlwaysOnTop(true);
  super.toFront();
  super.requestFocus();
  super.setAlwaysOnTop(false);
}

当需要显示或将窗口置于最前时,请调用frame.setVisible(true)

自从我升级到Ubuntu 9.04后,似乎没有必要使用WindowListener来调用super.setAlwaysOnTop(false) --可以观察到,这段代码已经移到了toFront()setVisible()方法中。

请注意,setVisible()方法应始终在EDT上调用。


以下是与此相关的编程问题:https://dev59.com/cXE95IYBdhLWcg3wbtXO。谢谢! - rogerdpack
因为找不到 setDisposed() 方法,所以我编译不通过。 - ka3ak
1
@ka3ak 这是一个受保护的 setter,可以在建议的 JFrame 基类中引入,以跟踪 frame 被处理的情况。方法 dispose() 需要被重写,并调用 setDisposed(true)。这并不是对每个人都必需的。 - 01es
1
当我使用JWindow时,.setAlwaysOnTop(true);是唯一有效的选项。 - DGolberg
setAlwaysOnTop(true) 是我在 Windows 10 下运行它的唯一方法 - 谢谢! - Hartmut Pfarr

23

Windows有防止窗口抢占焦点的功能;相反,它会闪烁任务栏图标。在XP中,默认情况下启用此功能(我所知道唯一更改它的地方是使用TweakUI,但是某个地方有一个注册表设置)。在Vista中,他们可能已经更改了默认设置和/或通过开箱即用的UI公开了用户可访问的设置。

自Windows 2K以来,防止窗口强制置于前台并获取焦点是一项功能(对此,我感激不尽)。

话虽如此,我有一个小的Java应用程序,用于提醒我记录工作中的活动,并且它会每30分钟(当然可以配置)使自己成为活动窗口。它在Windows XP下始终表现出色,并且从未闪烁标题栏窗口。它使用以下代码,在UI线程中由定时器事件触发调用:

if(getState()!=Frame.NORMAL) { setState(Frame.NORMAL); }
toFront();
repaint();

(第一行是恢复窗口如果最小化...实际上,如果窗口最大化也会恢复,但我从来没有这样做过。)

虽然我通常将此应用程序最小化,但它经常只是在我的文本编辑器后面。而且,正如我所说的,它总是有效的。

我有一个关于你的问题可能是什么的想法 - 也许你在 setVisible() 调用时存在竞争条件。当调用 toFront() 时,窗口实际上必须被显示出来才有效;我以前遇到过使用 requestFocus() 时出现这个问题。您可能需要在窗口激活事件的 UI 监听器中放置 toFront() 调用。

2014-09-07: 在某个时间点,上述代码停止工作了,可能是在 Java 6 或 7 上。经过一些调查和实验,我不得不更新代码以覆盖窗口的 toFront 方法(与上面的修改代码结合使用):

setVisible(true);
toFront();
requestFocus();
repaint();

...

public @Override void toFront() {
    int sta = super.getExtendedState() & ~JFrame.ICONIFIED & JFrame.NORMAL;

    super.setExtendedState(sta);
    super.setAlwaysOnTop(true);
    super.toFront();
    super.requestFocus();
    super.setAlwaysOnTop(false);
}

截至Java 8_20版本,该代码似乎能够正常工作。


1
+1 支持不允许 Windows 窃取焦点。当我在文档中输入时,我讨厌这种情况发生。 - Ken Paul
1
我完全同意您反对窃取焦点的观点,但在这种特定情况下,用户希望应用程序置于前台。但更改注册表设置并改变整个Windows行为是不合适的。 - boutta
我猜测 super.setAlwaysOnTop(false); 的作用是让窗口不总是置顶,这是为了消除我们之前设置的 true 以将窗口置于前台,对吗?我问这个问题是因为在您的代码中,窗口在我的情况下仍然总是置顶,显然我不想要这样。在 Windows 10 上运行 jre1.8.0_66。 - Bram Vanroy
@Bram:是的,没错。我正在同一版本的Java和Windows上运行代码,但它并不总是在其他窗口之上。也许没有必要设置总在最前,但我认为否则Windows只会在某些情况下闪烁标题栏。 - Lawrence Dol
嗯,奇怪。你能看一下我链接到这个答案的类似问题吗?也许那段代码更清楚地显示了问题:http://stackoverflow.com/questions/34637597/bring-jframe-window-to-the-front - Bram Vanroy
@Bram:啊哈。我确实见过这种情况发生。我最好的猜测是,如果当前窗口在调用“toFront”和实际显示之间接收到用户输入,它将会做出你所描述的行为;输入焦点仍然停留在它后面的窗口上。当我编码时,我也遇到过这种情况。有时候很烦人,因为新窗口看起来已经获得了焦点。我甚至遇到过键盘输入被发送到两个窗口的情况。我所看到的情况看起来像是Windows中的一个错误,因为它只在Win 10中开始发生,并且似乎是试图阻止焦点窃取而出现了问题。 - Lawrence Dol

12

这里有一个真正有效的方法(在Windows Vista上测试通过):D

   frame.setExtendedState(JFrame.ICONIFIED);
   frame.setExtendedState(fullscreen ? JFrame.MAXIMIZED_BOTH : JFrame.NORMAL);
全屏变量表示您是否希望应用程序以全屏或窗口模式运行。
这不会闪烁任务栏,但可靠地将窗口置于前台。

感谢提供 setExtendedState 的提示。我将其与 toFront() 和 repaint() 解决方案一起使用,即使窗口被最小化,也可以将其置于前景。 - rob
1
确认:这个解决方案在WindowsXP中有效,使用toFront会导致任务栏中的闪烁消息。谢谢! - Eric Lindauer

5
我测试了你的答案,只有Stefan Reich的回答对我有效。虽然我无法将窗口恢复到之前的状态(最大化/正常)。我发现这个变异更好:
view.setState(java.awt.Frame.ICONIFIED);
view.setState(java.awt.Frame.NORMAL);

那应该使用 setState 而不是 setExtendedState

5

你好,我在 Fedora KDE 14 中使用您提供的所有方法都无法生效。我有一个不太正规的方法可以让窗口置于前台,等待 Oracle 解决此问题。

import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.InputEvent;

public class FrameMain extends javax.swing.JFrame {

  //...
  private final javax.swing.JFrame mainFrame = this;

  private void toggleVisible() {
    setVisible(!isVisible());
    if (isVisible()) {
      toFront();
      requestFocus();
      setAlwaysOnTop(true);
      try {
        //remember the last location of mouse
        final Point oldMouseLocation = MouseInfo.getPointerInfo().getLocation();

        //simulate a mouse click on title bar of window
        Robot robot = new Robot();
        robot.mouseMove(mainFrame.getX() + 100, mainFrame.getY() + 5);
        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);

        //move mouse to old location
        robot.mouseMove((int) oldMouseLocation.getX(), (int) oldMouseLocation.getY());
      } catch (Exception ex) {
        //just ignore exception, or you can handle it as you want
      } finally {
        setAlwaysOnTop(false);
      }
    }
  }

  //...

}

而且,在我的Fedora KDE 14中,这个功能完美运作 :-)


有点hacky,对我们有效,但仅限于第一次调用:-)。(Kubuntu 12.04)-其他解决方案失败了。 - user85155
这是我在Windows Server 2012 R2上遇到的一个问题的唯一解决方案,即当打开一个JFrame(登录)时,直到用户点击它之前它才会获得焦点。 - glenneroo

4
这个简单的方法在Windows 7上对我非常有效:
    private void BringToFront() {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                if(jFrame != null) {
                    jFrame.toFront();
                    jFrame.repaint();
                }
            }
        });
    }

2
repaint() 不是必需的,invokeLater() 已经完成了。谢谢。 - Matthieu

3

我发现最简单的方法是没有跨平台不一致性的:

setVisible(false); setVisible(true);


1
虽然会导致一些闪烁,但还是很简单好用的 :) - rogerdpack
我的后台进程无法运行。如果从前台进程调用,窗口在第一次刷新时会变成白色。无法用于屏幕截图。 - DragonLord
可以通过检查窗口是否被图标化来避免闪烁。 - totaam

2
当你使用.toFront()方法将JFrame窗口置于前台时,无论是在Windows还是Linux中,规则都是一样的:
- 如果现有应用程序的窗口当前是焦点窗口,则焦点会切换到请求的窗口。 - 如果不是,则该窗口仅在任务栏中闪烁。
但是:
- 新窗口会自动获得焦点。
因此,我们可以利用这一点!想要将一个窗口置于前台,该怎么做呢?如下所示:
1. 创建一个空的非目的窗口。 2. 显示它。 3. 等待它在屏幕上显示(setVisible方法可以实现)。 4. 在显示后,为实际想要聚焦的窗口请求焦点。 5. 隐藏空窗口并销毁它。
或者,在Java代码中实现:
// unminimize if necessary
this.setExtendedState(this.getExtendedState() & ~JFrame.ICONIFIED);

// don't blame me, blame my upbringing
// or better yet, blame java !
final JFrame newFrame = new JFrame();
newFrame.add(new JLabel("boembabies, is this in front ?"));

newFrame.pack();
newFrame.setVisible(true);
newFrame.toFront();

this.toFront();
this.requestFocus();

// I'm not 100% positive invokeLater is necessary, but it seems to be on
// WinXP. I'd be lying if I said I understand why
SwingUtilities.invokeLater(new Runnable() {
  @Override public void run() {
    newFrame.setVisible(false);
  }
});

在Win7上无法工作,两个窗口都闪烁(如果我不隐藏第二个)。 - NateS
有创意。在Win7上,当它被覆盖时并未作为我的后台进程工作。新框架没有置顶。使用旧版的JDK 6u21。 - DragonLord

1
为了避免窗口在从隐藏状态返回到可见状态时失去焦点,只需要执行以下操作:
setExtendedState(JFrame.NORMAL);

就像这样:
defaultItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showWindow();
                setExtendedState(JFrame.NORMAL);
            }
});

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