这是教 Java 框架一些关于 Windows 的 Aero Snap 功能的唯一方法吗?

Is this the only way to teach a Java Frame something about the Aero Snap feature of Windows?

如果我通过单击 Windows WindowDecoration 的最小化按钮将 Aero 捕捉到屏幕左侧的 JFrame 最小化,然后通过 Alt-Tabbing 或单击它取消最小化在 Windows 任务栏中,框架被正确地恢复到左侧。好!

但是如果我将框架最小化

setExtendedState( getExtendedState() | Frame.ICONIFIED );

将鼠标悬停在 Windows 任务栏上查看预览,它显示框架的位置错误。 通过 Alt-Tabbing 或在 Windows 任务栏中单击它取消最小化后,框架将恢复到这个错误的位置和大小。框架边界是 "unsnapped" 值,如果您将框架拖离 ScreenBorder,Windows 通常会记得恢复该值。

Bug 的屏幕录制:

我的结论是,Java 不了解 AeroSnap 并将错误的边界传递给 Windows。 (例如 Toolkit.getDefaultToolkit().isFrameStateSupported( Frame.MAXIMIZED_VERT ) ); returns 错误。)

这是我对错误的修复:

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 下的所有情况,但感觉在 window-管理方面太过混乱了。由于某种原因,我避免在 Linux 或 MacOS 下进行测试 ;-)

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


编辑:

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

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

JDK-7029238 : componentResized not called when the form is snapped

在此报告中无法重现错误,现在您遇到了相同的错误(我认为它是相同的,或者至少相关),也许是re-open此报告的好时机。 (我没有找到任何其他参考资料,所以我认为它还没有被修复)

Is there a better way to let AeroSnap and Java Frames work together?

好不了多少。直接设置扩展状态会绕过 OS 的设置处理。

如果您查看 JFrame#setExtendedState 的源代码,您会发现它调用了 FramePeersetState 方法。 JDK 对 FramePeer 接口的 JFrame 实现是 WFramePeer class,它将其 setState 方法声明为 native .因此,除非 Oracle 对此采取措施或您使用本机代码(见下文),否则您会很不走运。

幸运的是,您不必为事件侦听器和缓存边界发疯。隐藏和显示框架足以 "reset" 缩小到最小化前的大小:

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

虽然这确实有一个 side-effect 没有提供框架内容的详细预览:

使用本机代码的解决方案

如果你愿意使用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));
    }
}

这是不言自明的。你得到一个指向 window 的指针,并在其上使用原生的 CloseWindow (实际上最小化,看图)。请注意,我编写它的简约方式会在第一次按下按钮时造成一个小的延迟,因为 User32 实例已加载。您可以在启动时加载它以避免这种 first-time 延迟。

归功于 the accepted answer here