这是教授Java Frame有关Windows Aero Snap功能的唯一方法吗?

29

如果我将一个被 Aero 吸附到屏幕左侧的 JFrame 最小化,方法是通过点击 Windows WindowDecoration 的最小化按钮,并通过 Alt-Tab 或在 Windows 任务栏中单击它来取消最小化,那么该框架会被正确还原并吸附到左侧。非常好!

但是,如果我通过

setExtendedState( getExtendedState() | Frame.ICONIFIED );

当你将鼠标悬停在Windows任务栏上查看预览时,它显示的位置是错误的。 通过Alt-Tab或单击Windows任务栏将其最小化后,该框架会以错误的位置和大小恢复。框架边界是“未捕捉”的值,如果您将框架从屏幕边缘拖动,则Windows通常会记住这些值以便恢复。
Bug的屏幕录制:

enter image description here

我的结论是,Java不知道AeroSnap并向Windows提供了错误的边界。(例如,Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_VERT));返回false。)
这是我修复此错误的方法:
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Point;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

/**
 * Fix for the "Frame does not know the AeroSnap feature of Windows"-Bug.
 *
 * @author bobndrew 20160106
 */
public class SwingFrameStateWindowsAeroSnapBug extends JFrame
{
  Point     location = null;
  Dimension size     = null;


  public SwingFrameStateWindowsAeroSnapBug( final String title )
  {
    super( title );
    initUI();
  }

  private void initUI()
  {
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    setLayout( new FlowLayout() );
    final JButton minimize = new JButton( "Minimize" );
    final JButton maximize = new JButton( "Maximize" );
    final JButton normal = new JButton( "Normal" );
    add( normal );
    add( minimize );
    add( maximize );
    pack();
    setSize( 200, 200 );


    final ActionListener listener = actionEvent ->
    {
      if ( actionEvent.getSource() == normal )
      {
        setExtendedState( Frame.NORMAL );
      }
      else if ( actionEvent.getSource() == minimize )
      {
        //Size and Location have to be saved here, before the minimizing of an AeroSnapped WindowsWindow leads to wrong values:
        location = getLocation();
        size = getSize();
        System.out.println( "saving location (before iconify) " + size + " and " + location );

        setExtendedState( getExtendedState() | Frame.ICONIFIED );//used "getExtendedState() |" to preserve the MAXIMIZED_BOTH state

        //does not fix the bug; needs a Window-Drag after DeMinimzing before the size is applied:
        //          setSize( size );
        //          setLocation( location );
      }
      else if ( actionEvent.getSource() == maximize )
      {
        setExtendedState( getExtendedState() | Frame.MAXIMIZED_BOTH );
      }
    };

    minimize.addActionListener( listener );
    maximize.addActionListener( listener );
    normal.addActionListener( listener );

    addWindowStateListener( windowEvent ->
    {
      System.out.println( "oldState=" + windowEvent.getOldState() + "  newState=" + windowEvent.getNewState() );

      if ( size != null && location != null )
      {
        if ( windowEvent.getOldState() == Frame.ICONIFIED )
        {
          System.out.println( "Fixing (possibly) wrong size and location on de-iconifying to " + size + " and " + location + "\n" );
          setSize( size );
          setLocation( location );

          //Size and Location should only be applied once. Set NULL to avoid a wrong DeMinimizing of a following Windows-Decoration-Button-Minimize!
          size = null;
          location = null;
        }
        else if ( windowEvent.getOldState() == (Frame.ICONIFIED | Frame.MAXIMIZED_BOTH) )
        {
          System.out.println( "Set size and location to NULL (old values: " + size + " and " + location + ")" );
          //Size and Location does not have to be applied, Java can handle the MAXIMIZED_BOTH state. Set NULL to avoid a wrong DeMinimizing of a following Windows-Decoration-Button-Minimize!
          size = null;
          location = null;
        }
      }

    } );
  }


  public static void main( final String[] args )
  {
    SwingUtilities.invokeLater( new Runnable()
    {
      @Override
      public void run()
      {
        new SwingFrameStateWindowsAeroSnapBug( "AeroSnap and the Frame State" ).setVisible( true );
      }
    } );
  }
}

这种方法似乎适用于Windows7下的所有情况,但感觉对窗口管理进行了太多的处理。由于某些原因,我避免在Linux或MacOS下测试它 ;-) 有没有更好的方法让AeroSnap和Java框架一起工作?

编辑:

我已在Oracle提交了一个错误报告:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8147840


1
如果还没有的话,您应该将此作为JDK错误提交。 - user1803551
1
小提示:如果您使用 JFrame.ICONIFIED 而不是 Frame.ICONIFIED 等,您可以删除 Frame 导入。 - user1803551
1
看起来这个问题在JDK 9中已经被修复了(http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8037575)。 - user1803551
2个回答

7

有没有更好的方法让AeroSnap和Java框架一起工作?

没有更好的方法。直接设置扩展状态会绕过操作系统对其进行的处理。

如果您查看JFrame#setExtendedState的源代码,您将看到它调用FramePeersetState方法。JDK的JFrame实现FramePeer接口是WFramePeer类,该类将其setState方法声明为native。因此,除非Oracle采取措施或使用本机代码(见下文),否则您将毫无办法。

幸运的是,您不一定需要使用事件侦听器和缓存边界。隐藏和显示框架就足以“重置”大小为最小化之前的大小:

public class AeroResize extends JFrame {

    public AeroResize(final String title) {

        super(title);
        initUI();
    }

    private void initUI() {

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new FlowLayout());
        final JButton minimize = new JButton("Minimize");
        final JButton maximize = new JButton("Maximize");
        final JButton normal = new JButton("Normal");
        add(normal);
        add(minimize);
        add(maximize);
        pack();

        minimize.addActionListener(e -> {
            setVisible(false);
            setExtendedState(getExtendedState() | JFrame.ICONIFIED);
            setVisible(true);
//          setLocation(getLocationOnScreen()); // Needed only for the preview. See comments section below.
        });
    }

    public static void main(final String[] args) {

        SwingUtilities.invokeLater(() -> new AeroResize("AeroSnap and the Frame State").setVisible(true));
    }
}

尽管如此,这样做会有一个副作用,即无法给出帧内容的详细预览:

enter image description here

使用本地代码的解决方案

如果您愿意使用JNA,那么您可以完全模拟本地平台的最小化。您需要在构建路径中包含jna.jarjna-platform.jar

import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HWND;

public class AeroResize extends JFrame {

    public AeroResize(final String title) {

        super(title);
        initUI();
    }

    private void initUI() {

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new FlowLayout());
        final JButton minimize = new JButton("Minimize");
        final JButton maximize = new JButton("Maximize");
        final JButton normal = new JButton("Normal");
        add(normal);
        add(minimize);
        add(maximize);
        pack();

        minimize.addActionListener(e -> {
            HWND windowHandle = new HWND(Native.getComponentPointer(AeroResize.this));
            User32.INSTANCE.CloseWindow(windowHandle);
        });
    }

    public static void main(final String[] args) {

        SwingUtilities.invokeLater(() -> new AeroResize("AeroSnap and the Frame State").setVisible(true));
    }
}

这很容易理解。您可以获取窗口的指针,然后在其上使用本地的CloseWindow(实际上是最小化,想想也知道)。请注意,我写的极简方式会导致第一次按下按钮时出现小延迟,因为加载了User32实例。您可以在启动时加载它以避免此第一次延迟。

感谢这里被接受的答案


很遗憾,你的“隐藏和显示框架”源代码仅适用于已捕捉的框架状态。如果在最小化之前框架位于屏幕中央,则它将始终恢复到左上角。而在正确恢复已捕捉框架的同时,它会失去先前捕捉的位置和大小(通常会在取消捕捉时恢复)。 - bobndrew
@bobndrew "如果窗口在最小化之前位于屏幕中央,则始终会将其恢复到左上角。" 我不理解这种行为。我的窗口始终能正确地恢复(位置和大小)。 - user1803551
启动您的AeroResize.java。Frame显示在x=0,y=0处。将Frame移动到x=400,y=400。不要将其捕捉到边缘。单击“最小化”-JButton。任务栏预览(带有图标作为内容)显示Frame位于x=0,y=0处。取消最小化Frame,它将显示在x=0,y=0处。原因是您的代码行setLocation(getLocationOnScreen()); //仅用于预览Frame设置为x=-32000,y=-32000(导致x=0,y=0)。这也会发生在Frame捕捉到屏幕右边框的情况下。 - bobndrew
1
@bobndrew 我试着再多尝试一下。预览位置过于依赖本地操作,我无法可靠地使其正确。不过我添加了一个本地代码解决方案。 - user1803551
@bobndrew,你提交了这个bug吗?如果是的话,请在你的问题或评论中发布链接。 - user1803551
显示剩余6条评论

0

这似乎是一个Swing的bug。Bug数据库上的报告:

JDK-7029238 : 当窗体被捕捉时,componentResized未被调用

在这个报告中,该bug无法重现,现在你遇到了同样的bug(我认为它是相同的,或者至少相关),也许现在重新打开这个报告是一个好时机。(我没有找到任何其他关于此问题的参考资料,所以我认为它还没有被修复)


1
也许这些错误是相关的,但你发现的那个看起来更像是JDK-8016356中附加的截图。这里有一个视频。我的错误与“通过编程方式最小化而失去捕捉状态”更相关。 - bobndrew
哦,好的,那我很抱歉。我想我的英语比我想象中还要差。 - Mayuso

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