Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JFrame is not maximized correctly when using custom window decorations #129

Closed
grimlock81 opened this issue Jul 15, 2020 · 13 comments
Closed
Labels
custom window decorations Related to using FlatLaf custom window decorations multi-screen Related to using multiple screens
Milestone

Comments

@grimlock81
Copy link

grimlock81 commented Jul 15, 2020

I've found an issue when using custom window decorations on Windows 10 when programmatically maximizing the JFrame by calling frame.setExtendedState(Frame.MAXIMIZED_BOTH);

The resulting JFrame's dimensions fits the whole screen including covering the Windows task bar. This is inconsistent with how Windows applications are displayed in Maximized - they don't cover the task bar. The behaviour of subsequent calls to frame.setExtendedState(Frame.MAXIMIZED_BOTH); depends on whether the Maximize button (next to the Close button) has been clicked at least once in the same session or not:

  • If the Maximize button has been clicked, then the call correctly sets the dimensions of the JFrame to fit the screen but doesn't cover the task bar
  • If the Maximise button has never been clicked, then the call incorrectly sets the dimensions to cover the task bar.

This is my test code, I am running on AdoptOpenJDK 11.0.7

import com.formdev.flatlaf.FlatIntelliJLaf;

import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;

/**
 *
 * @author grimlock81
 */
public class TestExtendedState {
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            startGUI();
        });
    }

    private static void startGUI() {
        JFrame.setDefaultLookAndFeelDecorated(true);
        try {
            UIManager.setLookAndFeel(new FlatIntelliJLaf());
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }

        JFrame frame = new JFrame();

        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        fileMenu.add("Open"); // fake
        fileMenu.add("Exit"); // fake
        JMenu editMenu = new JMenu("Set");
        editMenu.add(new AbstractAction("Extended State to Normal") {
            @Override
            public void actionPerformed(ActionEvent evt) {
                frame.setExtendedState(Frame.NORMAL);
            }
        });
        editMenu.add(new AbstractAction("Extended State to Maximized Both") {
            @Override
            public void actionPerformed(ActionEvent evt) {
                frame.setExtendedState(Frame.MAXIMIZED_BOTH);
            }
        });
        menuBar.add(fileMenu);
        menuBar.add(editMenu);

        frame.setJMenuBar(menuBar);
        frame.setTitle("Testing Extended State");
        frame.add(new JScrollPane(new JTree()));
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
        frame.setExtendedState(Frame.MAXIMIZED_BOTH);
    }
}

After looking through FlatTitlePane.java I can understand why the behaviour is correct after pressing the Maximize button, because frame.setmaximizedBounds(Rectangle) is called inside the maximize() method and sets the right dimensions including accounting for the task bar. Does that mean that Windows is incorrectly setting the maximized bounds at the start? Should FlatLAF set the correct maximized bounds at the start to override Windows?

@grimlock81 grimlock81 changed the title When using custom window decorations, the JFrame is not maximized correctly A JFrame is not maximized correctly when using custom window decorations Jul 15, 2020
@grimlock81 grimlock81 changed the title A JFrame is not maximized correctly when using custom window decorations JFrame is not maximized correctly when using custom window decorations Jul 16, 2020
DevCharly added a commit that referenced this issue Jul 16, 2020
@DevCharly
Copy link
Collaborator

@grimlock81 thx for reporting.

I've implemented a fix in master branch. Please give it a try.
If you don't want build FlatLaf yourself, use the latest snapshot:
https://github.com/JFormDesigner/FlatLaf#snapshots

A side effect of following code, which applies maximized bounds, is that WindowStateListener.windowStateChanged(e) is invoked multiple times.

frame.setExtendedState( oldExtendedState & ~Frame.MAXIMIZED_BOTH );
frame.setExtendedState( oldExtendedState );

@grimlock81
Copy link
Author

@DevCharly Thanks I built a copy locally and confirms that has fixed the problem.

However I have discovered a similar issue that happens if the extended state is called before the JFrame is visible. So in my test code swap the last two lines:

frame.setVisible(true);
frame.setExtendedState(Frame.MAXIMIZED_BOTH);

to

frame.setExtendedState(Frame.MAXIMIZED_BOTH);
frame.setVisible(true);

What happens in this case is that the windows covers the task bar and the Restore/Maximise button is showing the Maximise icon. Pressing this button does nothing visibly. Only after the window is set to the NORMAL state, either programmatically, double clicking on the title pane, or dragging the title pane downwards does this button behave as expected.

@kirill-grouchnikov
Copy link

I had similar code in Substance, and it was causing issues with maximizing frames across multiple screens that had different sizes and resolutions.

@DevCharly
Copy link
Collaborator

@kirill-grouchnikov and did you find a solution?

I've tested the maximizing code only on Windows 10 with two screens (primary 4K, secondary FullHD) at various scaling factors and with various Java versions (8 - 15). The problem was that Java 8 behaves differnt to Java 9-14 on scaled screens. And Java 15+ again is different to previous versions since some scaling issues were fixed in Java 15.

There are a lot of comments in the maximizing code that describe the differences:

protected void updateMaximizedBounds() {
Frame frame = (Frame) window;
// set maximized bounds to avoid that maximized window overlaps Windows task bar
// (if not running in JBR and if not modified from the application)
Rectangle oldMaximizedBounds = frame.getMaximizedBounds();
if( !hasJBRCustomDecoration() &&
(oldMaximizedBounds == null ||
Objects.equals( oldMaximizedBounds, rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) )
{
GraphicsConfiguration gc = window.getGraphicsConfiguration();
// Screen bounds, which may be smaller than physical size on Java 9+.
// E.g. if running a 3840x2160 screen at 200%, screenBounds.size is 1920x1080.
// In Java 9+, each screen can have its own scale factor.
//
// On Java 8, which does not scale, screenBounds.size of the primary screen
// is identical to its physical size. But when the primary screen is scaled,
// then screenBounds.size of secondary screens is scaled with the scale factor
// of the primary screen.
// E.g. primary 3840x2160 screen at 150%, secondary 1920x1080 screen at 100%,
// then screenBounds.size is 3840x2160 on primary and 2880x1560 on secondary.
Rectangle screenBounds = gc.getBounds();
int maximizedX = screenBounds.x;
int maximizedY = screenBounds.y;
int maximizedWidth = screenBounds.width;
int maximizedHeight = screenBounds.height;
if( !SystemInfo.IS_JAVA_15_OR_LATER ) {
// on Java 8 to 14, maximized x,y are 0,0 based on all screens in a multi-screen environment
maximizedX = 0;
maximizedY = 0;
// scale maximized screen size to get physical screen size for Java 9 to 14
AffineTransform defaultTransform = gc.getDefaultTransform();
maximizedWidth = (int) (maximizedWidth * defaultTransform.getScaleX());
maximizedHeight = (int) (maximizedHeight * defaultTransform.getScaleY());
}
// screen insets are in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8243925)
// and except for Java 8 on secondary screens where primary screen is scaled
Insets screenInsets = window.getToolkit().getScreenInsets( gc );
// maximized bounds are required in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8231564 and
// https://bugs.openjdk.java.net/browse/JDK-8176359)
// and except for Java 8 on secondary screens where primary screen is scaled
Rectangle newMaximizedBounds = new Rectangle(
maximizedX + screenInsets.left,
maximizedY + screenInsets.top,
maximizedWidth - screenInsets.left - screenInsets.right,
maximizedHeight - screenInsets.top - screenInsets.bottom );
if( !Objects.equals( oldMaximizedBounds, newMaximizedBounds ) ) {
// change maximized bounds
frame.setMaximizedBounds( newMaximizedBounds );
// remember maximized bounds in client property to be able to detect
// whether maximized bounds are modified from the application
rootPane.putClientProperty( "_flatlaf.maximizedBounds", newMaximizedBounds );
}
}
}

(feel free to copy code to Substance if useful)

DevCharly added a commit that referenced this issue Jul 18, 2020
…ly maximizing window before showing window (issue #129)
@DevCharly
Copy link
Collaborator

@grimlock81 many thx.
This is now fixed in master branch.

@DevCharly DevCharly added this to the 0.39 milestone Jul 18, 2020
@kirill-grouchnikov
Copy link

Thanks @DevCharly. I'll try this when I get to a two-monitor setup next time - with FlatLaf code, and then porting it to Substance.

@grimlock81
Copy link
Author

@grimlock81 many thx.
This is now fixed in master branch.

@DevCharly I got the latest changes, rebuilt my local copy, but doesn't seemed to have fixed the problem.

@grimlock81
Copy link
Author

grimlock81 commented Jul 21, 2020

Found another issue this one rather more serious. Let me first explain my monitor setup. I have 2 monitors:

  • Primary Monitor 1920x1080. On the right hand side
  • Secondary Monitor 1280x1024. One the left hand side.

I launch my test application on the Primary Monitor maximised. I then drag the frame to the Secondary Monitor and press the maximise button. What results is a little hard to describe but it seems to reposition the frame continuously flickering between the two monitors. From what I can discern it loops between setting the location to 0,0 on Secondary Monitor then 0,0 on Primary Monitor then back to the original position on the Secondary Monitor where I had dragged the frame to and then back to 0,0 Secondary Monitor ad infinitum. The size of the frame doesn't change, it will remain the same size.

Other info:

  • Exactly same issue if programmatically calling setExtendedState(Frame.MAXIMIZED_BOTH)
  • Pressing maximise button when the frame is in the Primary Monitor behaves correctly.
  • Setting setVisible(true) before or after setExtendedState(Frame.MAXIMIZED_BOTH) doesn't make a difference in this case.

Running FlatLaf 0.39-SNAPSHOT on Windows 10 using AdoptOpenJDK 11.0.8.

@DevCharly
Copy link
Collaborator

@grimlock81 Is scaling used on one of the screens? (see Windows Settings)

Looks like that some fixes from Java 15 were backported to AdoptOpenJDK 11.0.8.
Does it work with AdoptOpenJDK 11.0.7?

If you remove the '!' in following line, is the problem gone with AdoptOpenJDK 11.0.8?

@grimlock81
Copy link
Author

grimlock81 commented Jul 21, 2020

Is scaling used on one of the screens? (see Windows Settings)

Neither monitor using scaling. They are both set to "100% (Recommended)"

Does it work with AdoptOpenJDK 11.0.7?

Yes it works with AdoptOpenJDK 11.0.7.

If you remove the '!' in following line, is the problem gone with AdoptOpenJDK 11.0.8?

The problem remains after removing the '!' with AdoptOpenJDK 11.0.8

As another test I tried FlatLaf 0.37 and 0.38 with AdoptOpenJDK 11.0.8. Both had the same behaviour - while the flicking doesn't happen the 'maximised' frame is placed on the Primary Monitor with the dimensions of the Secondary Monitor.

Looks like that some fixes from Java 15 were backported to AdoptOpenJDK 11.0.8.

Is it this one? https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8176359

DevCharly added a commit that referenced this issue Jul 22, 2020
…d 13.0.4, which has fixes backported from Java 15 (issue #129)
@DevCharly
Copy link
Collaborator

Is it this one? https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8176359

Yes, and https://bugs.openjdk.java.net/browse/JDK-8176359 and https://bugs.openjdk.java.net/browse/JDK-8231564.

The flicker should be fixed with latest commit in master branch.
And maximizing bounds should be correct with AdoptOpenJDK 11.0.8 (or Java 15).

However noticed a new issue with Java 8 - 14 (without above fixes) if the primary screen has smaller resolution than the secondary screen. Then maximizing on the secondary (larger) screen gives the window the size of the primary (smaller) screen.

DevCharly added a commit that referenced this issue Jul 23, 2020
…StateListener in case of behavior changes in Java (issue #129)
@grimlock81
Copy link
Author

grimlock81 commented Jul 27, 2020

@DevCharly My work replaced my secondary monitor with a new one that is the same dimensions and resolution so I am unable to repeat the exact same tests I originally had.

This is the behaviour I am experiencing now from my tests, using the current master branch:
For setVisible(true) called before setExtendedState(Frame.MAXIMIZED_BOTH)

  • Frame maximized correctly with taskbar visible
  • Moving the frame to secondary monitor and pressing maximize button results in the frame maximized on the primary screen.
  • In Windows settings, changing the main display to the secondary screen and in the same test session pressing the maximize button results in the frame maximized on the (new) primary screen, regardless of whether the frame is originally located. i.e. maximize always maximizes to what Windows currently recognises as the primary screen.
  • When in maximized state, pressing the restore button restores the frame back to its location on the original monitor.
  • Flickering no longer occurs.

For setExtendedState(Frame.MAXIMIZED_BOTH) called before setVisible(true)

  • Frame maximized covering the taskbar
  • The rest of the behaviour is exactly the same as described in the first test above.

I also have re-tested with the version before the latest commits 38d853b and 5a2c067 and found that the flickering doesn't happen when the resolutions are the same. The default recommended resolutions for both monitors are 1920x1080. When I change my secondary monitor to 1280x1024 and re-run the test, the flickering occurs again. I tried setting the secondary monitor with other resolutions and the flickering still happens, the only exception being if I make the two monitors have the same resolution. e.g. set both to 1280x1024

@DevCharly DevCharly added custom window decorations Related to using FlatLaf custom window decorations multi-screen Related to using multiple screens labels Oct 5, 2020
@DevCharly
Copy link
Collaborator

Closing because since FlatLaf 1.1 (native window decorations on Windows 10) this maximizing code is no longer used.
FlatLaf invokes Win32 API ShowWindow( hwnd, SW_MAXIMIZE ) and Windows maximizes the window (including animation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
custom window decorations Related to using FlatLaf custom window decorations multi-screen Related to using multiple screens
Projects
None yet
Development

No branches or pull requests

3 participants