JDK-8231564 : setMaximizedBounds is broken with large display scale and multiple monitors
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 8u221,12.0.2,13,14
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2019-09-23
  • Updated: 2021-03-05
  • Resolved: 2020-01-30
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 13 JDK 15
11.0.8-oracleFixed 13.0.4Fixed 15 b10Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Java version from 9 to 13.

A DESCRIPTION OF THE PROBLEM :
With the follow conditions the method Frame.setMaximizedBounds() produce wrong results:
* Windows 10
* 2 monitors or more
* display scale larger 100%, for example 125%
* the monitor with the smaller resoultion must be the "main display". For example main:1280*1024 and other:1920*1080
* the app window is on the other, larger, not the main monitor.

There is no continuation in the behavior of the method. It look like that with some height value in the setted bounds there is a different calculation.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test program on a system with the given conditions and press the "toggle" button. The red cross is not in the middle of the display.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
We expected a maximize window size (not full screen). This means without the size of the task bar.
ACTUAL -
Actual the windows size is approx. 50% larger as the the monitor display. It look like some time it used the wrong GraphicsConfiguration.

---------- BEGIN SOURCE ----------
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Rectangle;

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

public class UndecoratedJFrame extends JFrame {

    public static void main( String[] args ) {
        UndecoratedJFrame frame = new UndecoratedJFrame();
        frame.setVisible( true );
    }

    @Override
    public void setExtendedState( int state ) {
        if( state == MAXIMIZED_BOTH ) {
            // calculate setMaximizedBounds
            GraphicsConfiguration graphicsConfiguration = getGraphicsConfiguration();
            Rectangle screenBounds = graphicsConfiguration.getBounds();
            Insets in = getToolkit().getScreenInsets( graphicsConfiguration );

            Rectangle maxBounds = new Rectangle( in.left, in.top, screenBounds.width - in.left - in.right, screenBounds.height - in.top - in.bottom );
            setMaximizedBounds( maxBounds );
            super.setExtendedState( MAXIMIZED_BOTH ); 

            // since Java 9 we need calculate the scale factor because setMaximizedBounds reduce the resulting bounds with the display scale factor
            Rectangle bounds = getBounds();
            if( bounds.width != maxBounds.width || bounds.height != maxBounds.height ) { 
                double factorX = (double)maxBounds.width / bounds.width;
                double factorY = (double)maxBounds.height / bounds.height;
                maxBounds = new Rectangle( in.left, in.top, (int)Math.round( screenBounds.width * factorX ) - in.left - in.right, (int)Math.round( screenBounds.height * factorY ) - in.top - in.bottom );
                setMaximizedBounds( maxBounds );
                super.setExtendedState( ICONIFIED ); // state must change that the new MaximizedBounds is used from the GUI 
                SwingUtilities.invokeLater( () -> { 
                    UndecoratedJFrame.super.setExtendedState( MAXIMIZED_BOTH );
                } );
            }
        } else {
            super.setExtendedState( state );
        }
    }

    /**
     * Create a test frame
     */
    UndecoratedJFrame() {
        setUndecorated( true );
        setTitle( "setMaximizedBounds Test" );
        setDefaultCloseOperation( DISPOSE_ON_CLOSE );

        // find a position in the other monitor
        for( GraphicsDevice gd : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices() ) {
            GraphicsConfiguration gc = gd.getDefaultConfiguration();
            Rectangle gcBounds = gc.getBounds();
            if( gcBounds.x != 0 || gcBounds.y != 0 ) {
                setBounds( gcBounds.x + 100, gcBounds.y + 100, 250, 150 );
                break;
            }
        }

        // we use 2 buttons instead a full L&F to make it simple
        // Toggle button
        JButton button = new JButton( "Toggle" );
        button.addActionListener( ( e ) -> {
            if( getExtendedState() == MAXIMIZED_BOTH ) {
                setExtendedState( NORMAL );
            } else {
                setExtendedState( MAXIMIZED_BOTH );
            }
        } );
        getContentPane().add( button, BorderLayout.WEST );
        // Close Button 
        button = new JButton( "Close" );
        button.addActionListener( ( e ) -> {
            dispose();
        } );
        getContentPane().add( button, BorderLayout.EAST );
        getContentPane().add( new JComponent() {
            @Override
            public void paint( Graphics g ) {
                super.paint( g );
                // draw a red cross to see the size of the window
                g.setColor( Color.RED );
                g.drawLine( 0, 0, getWidth(), getHeight() );
                g.drawLine( 0, getHeight(), getWidth(), 0 );
            }
        }, BorderLayout.CENTER );
    }
}

---------- END SOURCE ----------

FREQUENCY : always



Comments
Submitter confirmed that the issue is resolved when checked with 11.0.8.
21-07-2020

Fix request (13u) Fixed by the same patch as JDK-8176359
09-06-2020

Submitter: CAP_member_17 Members: http://wiki.se.oracle.com/display/JavaSE/CAP+Members+Matrix���
29-04-2020

Initial response from submitter on verifying the fix with JDK 15: The problem is larger now. First I have test it with my standard screen configuration. The larger monitor (right) is the main monitor (1). [Screenshot attached - ondimfomdcecfggo.png] If I start the test program then it is on the left monitor (2). If I click on the toogle button from the test then it jump to the main monitor (1) but only with the size of the second, smaller monitor (2). If I change back it to the normal view then it also jump to the left, smaller monitor. I think a call to setExtendedState( MAXIMIZED_BOTH ) should never change the monitor of the window.
28-04-2020

Submitter has confirmed the fix with this final response and they urged to document the behaviour change: With your suggested changes it work better with Java 15 but break with Java 8. On Java 8 the window is in the Nirvana. With the follow changes it work for us with Java 8, 11 and 15. int major = Version.getJavaVersion().getMajor(); if( major <= 8 ) { screenBounds.x = 0; screenBounds.y = 0; } else if( major >= 15 ){ AffineTransform tx = graphicsConfiguration.getDefaultTransform(); in.left /= tx.getScaleX(); in.right /= tx.getScaleX(); in.top /= tx.getScaleY(); in.bottom /= tx.getScaleY(); } Rectangle maxBounds = new Rectangle( screenBounds.x + in.left, screenBounds.y + in.top, screenBounds.width - in.left - in.right, screenBounds.height - in.top - in.bottom ); This behaviour change should be documented. The fix is ok for us.
28-04-2020

JDK team's response for the behavior observed by Submitter: If we are talking about the testcase from the description of JDK-8231564, then the described behavior is expected since the the test request exactly this location: ======================================= GraphicsConfiguration graphicsConfiguration = getGraphicsConfiguration(); Rectangle screenBounds = graphicsConfiguration.getBounds(); Insets in = getToolkit().getScreenInsets( graphicsConfiguration ); Rectangle maxBounds = new Rectangle( in.left, in.top, screenBounds.width - in.left - in.right, screenBounds.height - in.top - in.bottom ); setMaximizedBounds( maxBounds ); super.setExtendedState(MAXIMIZED_BOTH); ======================================= Note that "maxBounds" uses the target screenBounds in the calculation of "width" and "height" only, but always set coordinate on the main screen for "x" and "y". I think it should be changed to: Rectangle maxBounds = new Rectangle(screenBounds.x+in.left, screenBounds.y+in.top, screenBounds.width - in.left - in.right, screenBounds.height - in.top - in.bottom);
28-04-2020

Fix request (11u) This is fixed by the same patch as 8176359, which I'm downporting, too. So nothing to do here.
20-04-2020

URL: https://hg.openjdk.java.net/jdk/jdk/rev/2777408b8281 User: psadhukhan Date: 2020-02-11 08:55:22 +0000
11-02-2020

URL: https://hg.openjdk.java.net/jdk/client/rev/2777408b8281 User: serb Date: 2020-01-30 06:47:13 +0000
30-01-2020

For the record, an example above has a bug: - Rectangle maxBounds = new Rectangle( in.left, in.top, screenBounds.width - in.left - in.right, screenBounds.height - in.top - in.bottom ); +Rectangle maxBounds = new Rectangle( screenBounds.x+in.left, screenBounds.y+in.top, screenBounds.width - in.left - in.right, screenBounds.height - in.top - in.bottom );
26-01-2020

Verified this with Windows 10 and mentioned resolution requirements (dual monitor) and could confirm the results w.r.t the method Frame.setMaximizedBounds(). The windows size always appears approx. 50% larger as the the monitor display. Result: ======== 8: Fail 8u221: Fail 12.0.2: Fail 13: Fail 14 ea b16: Fail To verify, run the attached test program with specified monitor configurations.
27-09-2019