Native window decorations: fixed broken maximizing window (under special conditions) when restoring frame state at startup (issue #283)

This commit is contained in:
Karl Tauber
2021-05-13 12:10:11 +02:00
parent 359eedf773
commit eea341fb33
7 changed files with 338 additions and 34 deletions

View File

@@ -51,10 +51,13 @@ FlatLaf Change Log
(issue #322) (issue #322)
- IntelliJ Themes: Fixed background colors of DesktopPane and DesktopIcon in all - IntelliJ Themes: Fixed background colors of DesktopPane and DesktopIcon in all
themes. themes.
- Native window decorations: Fixed occasional double window title bar when - Native window decorations:
creating many frames or dialogs. (issue #315) - Fixed slow application startup under particular conditions. (e.g. incomplete
- Native window decorations: Fixed slow application startup under particular custom JRE) (issue #319)
conditions. (e.g. incomplete custom JRE) (issue #319) - Fixed occasional double window title bar when creating many frames or
dialogs. (issue #315)
- Fixed broken maximizing window (under special conditions) when restoring
frame state at startup.
- Linux: Fixed/improved detection of user font settings. (issue #309) - Linux: Fixed/improved detection of user font settings. (issue #309)

View File

@@ -312,16 +312,7 @@ class FlatWindowsNativeWindowBorder
return; return;
// remove the OS window title bar // remove the OS window title bar
if( window instanceof JFrame && ((JFrame)window).getExtendedState() != 0 ) { updateFrame( hwnd, (window instanceof JFrame) ? ((JFrame)window).getExtendedState() : 0 );
// In case that the frame should be maximized or minimized immediately
// when showing, then it is necessary to defer ::SetWindowPos() invocation.
// Otherwise the frame will not be maximized or minimized.
// This occurs only if frame.pack() was no invoked.
EventQueue.invokeLater( () -> {
updateFrame( hwnd );
});
} else
updateFrame( hwnd );
} }
void uninstall() { void uninstall() {
@@ -333,7 +324,7 @@ class FlatWindowsNativeWindowBorder
private native long installImpl( Window window ); private native long installImpl( Window window );
private native void uninstallImpl( long hwnd ); private native void uninstallImpl( long hwnd );
private native void updateFrame( long hwnd ); private native void updateFrame( long hwnd, int state );
private native void showWindow( long hwnd, int cmd ); private native void showWindow( long hwnd, int cmd );
// invoked from native code // invoked from native code

View File

@@ -292,6 +292,11 @@ public class FlatWindowsNativeWindowBorder
WM_NCRBUTTONUP = 0x00A5, WM_NCRBUTTONUP = 0x00A5,
WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320; WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320;
// WM_SIZE wParam
private static final int
SIZE_MINIMIZED = 1,
SIZE_MAXIMIZED = 2;
// WM_NCHITTEST mouse position codes // WM_NCHITTEST mouse position codes
private static final int private static final int
HTCLIENT = 1, HTCLIENT = 1,
@@ -320,6 +325,7 @@ public class FlatWindowsNativeWindowBorder
private Window window; private Window window;
private final HWND hwnd; private final HWND hwnd;
private final BaseTSD.LONG_PTR defaultWndProc; private final BaseTSD.LONG_PTR defaultWndProc;
private int wmSizeWParam = -1;
private int titleBarHeight; private int titleBarHeight;
private Rectangle[] hitTestSpots; private Rectangle[] hitTestSpots;
@@ -338,16 +344,7 @@ public class FlatWindowsNativeWindowBorder
defaultWndProc = User32Ex.INSTANCE.SetWindowLong( hwnd, GWLP_WNDPROC, this ); defaultWndProc = User32Ex.INSTANCE.SetWindowLong( hwnd, GWLP_WNDPROC, this );
// remove the OS window title bar // remove the OS window title bar
if( window instanceof JFrame && ((JFrame)window).getExtendedState() != 0 ) { updateFrame( (window instanceof JFrame) ? ((JFrame)window).getExtendedState() : 0 );
// In case that the frame should be maximized or minimized immediately
// when showing, then it is necessary to defer ::SetWindowPos() invocation.
// Otherwise the frame will not be maximized or minimized.
// This occurs only if frame.pack() was no invoked.
EventQueue.invokeLater( () -> {
updateFrame();
});
} else
updateFrame();
} }
void uninstall() { void uninstall() {
@@ -358,16 +355,31 @@ public class FlatWindowsNativeWindowBorder
User32Ex.INSTANCE.SetWindowLong( hwnd, GWLP_WNDPROC, defaultWndProc ); User32Ex.INSTANCE.SetWindowLong( hwnd, GWLP_WNDPROC, defaultWndProc );
// show the OS window title bar // show the OS window title bar
updateFrame(); updateFrame( 0 );
// cleanup // cleanup
window = null; window = null;
} }
private void updateFrame() { private void updateFrame( int state ) {
// Following SetWindowPos() sends a WM_SIZE(SIZE_RESTORED) message to the window
// (although SWP_NOSIZE is set), which would prevent maximizing/minimizing
// when making the frame visible.
// AWT uses WM_SIZE wParam SIZE_RESTORED to update JFrame.extendedState and
// removes MAXIMIZED_BOTH and ICONIFIED. (see method AwtFrame::WmSize() in awt_Frame.cpp)
// To avoid this, change WM_SIZE wParam to SIZE_MAXIMIZED or SIZE_MINIMIZED if necessary.
if( (state & JFrame.ICONIFIED) != 0 )
wmSizeWParam = SIZE_MINIMIZED;
else if( (state & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH )
wmSizeWParam = SIZE_MAXIMIZED;
else
wmSizeWParam = -1;
// this sends WM_NCCALCSIZE and removes/shows the window title bar // this sends WM_NCCALCSIZE and removes/shows the window title bar
User32.INSTANCE.SetWindowPos( hwnd, hwnd, 0, 0, 0, 0, User32.INSTANCE.SetWindowPos( hwnd, hwnd, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE ); SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
wmSizeWParam = -1;
} }
/** /**
@@ -391,6 +403,11 @@ public class FlatWindowsNativeWindowBorder
fireStateChangedLaterOnce(); fireStateChangedLaterOnce();
break; break;
case WM_SIZE:
if( wmSizeWParam >= 0 )
wParam = new WPARAM( wmSizeWParam );
break;
case WM_DESTROY: case WM_DESTROY:
return WmDestroy( hwnd, uMsg, wParam, lParam ); return WmDestroy( hwnd, uMsg, wParam, lParam );
} }

View File

@@ -47,9 +47,9 @@ JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder
extern "C" extern "C"
JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_00024WndProc_updateFrame JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_00024WndProc_updateFrame
( JNIEnv* env, jobject obj, jlong hwnd ) ( JNIEnv* env, jobject obj, jlong hwnd, jint state )
{ {
FlatWndProc::updateFrame( reinterpret_cast<HWND>( hwnd ) ); FlatWndProc::updateFrame( reinterpret_cast<HWND>( hwnd ), state );
} }
extern "C" extern "C"
@@ -68,6 +68,9 @@ jmethodID FlatWndProc::fireStateChangedLaterOnceMID;
HWNDMap* FlatWndProc::hwndMap; HWNDMap* FlatWndProc::hwndMap;
#define java_awt_Frame_ICONIFIED 1
#define java_awt_Frame_MAXIMIZED_BOTH (4 | 2)
//---- class FlatWndProc methods ---------------------------------------------- //---- class FlatWndProc methods ----------------------------------------------
FlatWndProc::FlatWndProc() { FlatWndProc::FlatWndProc() {
@@ -76,6 +79,7 @@ FlatWndProc::FlatWndProc() {
obj = NULL; obj = NULL;
hwnd = NULL; hwnd = NULL;
defaultWndProc = NULL; defaultWndProc = NULL;
wmSizeWParam = -1;
} }
HWND FlatWndProc::install( JNIEnv *env, jobject obj, jobject window ) { HWND FlatWndProc::install( JNIEnv *env, jobject obj, jobject window ) {
@@ -120,7 +124,7 @@ void FlatWndProc::uninstall( JNIEnv *env, jobject obj, HWND hwnd ) {
::SetWindowLongPtr( hwnd, GWLP_WNDPROC, (LONG_PTR) fwp->defaultWndProc ); ::SetWindowLongPtr( hwnd, GWLP_WNDPROC, (LONG_PTR) fwp->defaultWndProc );
// show the OS window title bar // show the OS window title bar
updateFrame( hwnd ); updateFrame( hwnd, 0 );
// cleanup // cleanup
env->DeleteGlobalRef( fwp->obj ); env->DeleteGlobalRef( fwp->obj );
@@ -145,10 +149,29 @@ void FlatWndProc::initIDs( JNIEnv *env, jobject obj ) {
initialized = 1; initialized = 1;
} }
void FlatWndProc::updateFrame( HWND hwnd ) { void FlatWndProc::updateFrame( HWND hwnd, int state ) {
// Following SetWindowPos() sends a WM_SIZE(SIZE_RESTORED) message to the window
// (although SWP_NOSIZE is set), which would prevent maximizing/minimizing
// when making the frame visible.
// AWT uses WM_SIZE wParam SIZE_RESTORED to update JFrame.extendedState and
// removes MAXIMIZED_BOTH and ICONIFIED. (see method AwtFrame::WmSize() in awt_Frame.cpp)
// To avoid this, change WM_SIZE wParam to SIZE_MAXIMIZED or SIZE_MINIMIZED if necessary.
FlatWndProc* fwp = (FlatWndProc*) hwndMap->get( hwnd );
if( fwp != NULL ) {
if( (state & java_awt_Frame_ICONIFIED) != 0 )
fwp->wmSizeWParam = SIZE_MINIMIZED;
else if( (state & java_awt_Frame_MAXIMIZED_BOTH) == java_awt_Frame_MAXIMIZED_BOTH )
fwp->wmSizeWParam = SIZE_MAXIMIZED;
else
fwp->wmSizeWParam = -1;
}
// this sends WM_NCCALCSIZE and removes/shows the window title bar // this sends WM_NCCALCSIZE and removes/shows the window title bar
::SetWindowPos( hwnd, hwnd, 0, 0, 0, 0, ::SetWindowPos( hwnd, hwnd, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE ); SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
if( fwp != NULL )
fwp->wmSizeWParam = -1;
} }
LRESULT CALLBACK FlatWndProc::StaticWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { LRESULT CALLBACK FlatWndProc::StaticWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
@@ -176,6 +199,11 @@ LRESULT CALLBACK FlatWndProc::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, L
fireStateChangedLaterOnce(); fireStateChangedLaterOnce();
break; break;
case WM_SIZE:
if( wmSizeWParam >= 0 )
wParam = wmSizeWParam;
break;
case WM_DESTROY: case WM_DESTROY:
return WmDestroy( hwnd, uMsg, wParam, lParam ); return WmDestroy( hwnd, uMsg, wParam, lParam );
} }

View File

@@ -25,7 +25,7 @@ class FlatWndProc
public: public:
static HWND install( JNIEnv *env, jobject obj, jobject window ); static HWND install( JNIEnv *env, jobject obj, jobject window );
static void uninstall( JNIEnv *env, jobject obj, HWND hwnd ); static void uninstall( JNIEnv *env, jobject obj, HWND hwnd );
static void updateFrame( HWND hwnd ); static void updateFrame( HWND hwnd, int state );
private: private:
static int initialized; static int initialized;
@@ -40,6 +40,7 @@ private:
jobject obj; jobject obj;
HWND hwnd; HWND hwnd;
WNDPROC defaultWndProc; WNDPROC defaultWndProc;
int wmSizeWParam;
FlatWndProc(); FlatWndProc();
static void initIDs( JNIEnv *env, jobject obj ); static void initIDs( JNIEnv *env, jobject obj );

View File

@@ -34,10 +34,10 @@ JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder
/* /*
* Class: com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc * Class: com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc
* Method: updateFrame * Method: updateFrame
* Signature: (J)V * Signature: (JI)V
*/ */
JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_00024WndProc_updateFrame JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_00024WndProc_updateFrame
(JNIEnv *, jobject, jlong); (JNIEnv *, jobject, jlong, jint);
/* /*
* Class: com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc * Class: com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc

View File

@@ -0,0 +1,264 @@
/*
* Copyright 2021 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.testing;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.function.Consumer;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import com.formdev.flatlaf.FlatDarkLaf;
/**
* @author Karl Tauber
*/
public class FlatWindowMaximizeTest
{
public static void main( String[] args ) {
System.out.println( "Java version: " + System.getProperty( "java.version" ) );
// Windows Laf
try {
UIManager.setLookAndFeel( "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" );
} catch( Exception ex ) {
ex.printStackTrace();
}
testMaximize( JFrame.MAXIMIZED_BOTH );
// testMaximize( JFrame.ICONIFIED );
// testMaximize( JFrame.ICONIFIED | JFrame.MAXIMIZED_BOTH );
// testMaximize( JFrame.NORMAL );
// FlatLaf
FlatDarkLaf.setup();
testMaximize( JFrame.MAXIMIZED_BOTH );
// testMaximize( JFrame.ICONIFIED );
// testMaximize( JFrame.ICONIFIED | JFrame.MAXIMIZED_BOTH );
// testMaximize( JFrame.NORMAL );
System.exit( 0 );
}
private static void testMaximize( int state ) {
System.out.println();
System.out.println( "---- " + state + " - " + UIManager.getLookAndFeel().getClass().getSimpleName() + " ----" );
// only maximize
testMaximize( "MAX", state, frame -> {
frame.setExtendedState( state );
} );
// pack/size before maximize
testMaximize( "pack MAX", state, frame -> {
frame.pack();
frame.setExtendedState( state );
} );
testMaximize( "size MAX", state, frame -> {
frame.setSize( 1000, 500 );
frame.setExtendedState( state );
} );
// pack/size after maximize
testMaximize( "MAX pack", state, frame -> {
frame.setExtendedState( state );
frame.pack();
} );
testMaximize( "MAX size", state, frame -> {
frame.setExtendedState( state );
frame.setSize( 1000, 500 );
} );
// pack and size before maximize
testMaximize( "pack size MAX", state, frame -> {
frame.pack();
frame.setSize( 1000, 500 );
frame.setExtendedState( state );
} );
testMaximize( "size pack MAX", state, frame -> {
frame.setSize( 1000, 500 );
frame.pack();
frame.setExtendedState( state );
} );
// pack/size before maximize and size/pack after maximize
testMaximize( "pack MAX size", state, frame -> {
frame.pack();
frame.setExtendedState( state );
frame.setSize( 1000, 500 );
} );
testMaximize( "size MAX pack", state, frame -> {
frame.setSize( 1000, 500 );
frame.setExtendedState( state );
frame.pack();
} );
// pack and size after maximize
testMaximize( "MAX size pack", state, frame -> {
frame.setExtendedState( state );
frame.setSize( 1000, 500 );
frame.pack();
} );
testMaximize( "MAX pack size", state, frame -> {
frame.setExtendedState( state );
frame.pack();
frame.setSize( 1000, 500 );
} );
// 1. create invisible frame
// 2. create dialog with invisible frame as owner
// 3. pack dialog, which invokes frame.addNotify()
// 4. show frame
testMaximize( "MAX dialog.pack", state, true, frame -> {
frame.setExtendedState( state );
JDialog dialog = new JDialog( frame );
dialog.pack(); // this invokes frame.addNotify()
} );
}
private static void testMaximize( String msg, int expectedState, Consumer<JFrame> testFunc ) {
testMaximize( msg, expectedState, false, testFunc );
}
private static void testMaximize( String msg, int expectedState, boolean showLater, Consumer<JFrame> testFunc ) {
JFrame[] pFrame = new JFrame[1];
EventQueue.invokeLater( () -> {
JFrame frame = new JFrame( "test" );
frame.setName( msg );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
// addWindowListener( frame );
// addComponentListener( frame );
((JComponent) frame.getContentPane()).registerKeyboardAction( e -> {
System.exit( 0 );
}, KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0, false ), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
JButton button = new JButton( msg );
button.setName( "button - " + msg );
// addComponentListener( button );
frame.add( button );
frame.setLocation( 100, 50 );
testFunc.accept( frame );
if( !showLater )
frame.setVisible( true );
pFrame[0] = frame;
} );
try {
if( showLater ) {
Thread.sleep( 500 );
EventQueue.invokeLater( () -> {
pFrame[0].setVisible( true );
} );
}
Thread.sleep( 500 );
EventQueue.invokeAndWait( () -> {
int state = pFrame[0].getExtendedState();
System.out.printf( " %-15s: %d %s\n", msg, state, (state != expectedState ? " FAILED" : "") );
} );
} catch( Exception ex ) {
ex.printStackTrace();
}
}
@SuppressWarnings( "unused" )
private static void addWindowListener( JFrame frame ) {
frame.addWindowListener( new WindowListener() {
@Override
public void windowOpened( WindowEvent e ) {
print( "windowOpened", e.getWindow().getName() );
}
@Override
public void windowClosing( WindowEvent e ) {
print( "windowClosing", e.getWindow().getName() );
}
@Override
public void windowClosed( WindowEvent e ) {
print( "windowClosed", e.getWindow().getName() );
}
@Override
public void windowIconified( WindowEvent e ) {
print( "windowIconified", e.getWindow().getName() );
}
@Override
public void windowDeiconified( WindowEvent e ) {
print( "windowDeiconified", e.getWindow().getName() );
}
@Override
public void windowActivated( WindowEvent e ) {
print( "windowActivated", e.getWindow().getName() );
}
@Override
public void windowDeactivated( WindowEvent e ) {
print( "windowDeactivated", e.getWindow().getName() );
}
} );
frame.addWindowStateListener( e -> {
print( "windowStateChanged", e.getOldState() + " -> " + e.getNewState() + " " + e.getWindow().getName() );
} );
}
@SuppressWarnings( "unused" )
private static void addComponentListener( Component comp ) {
comp.addComponentListener( new ComponentListener() {
@Override
public void componentResized( ComponentEvent e ) {
Component c = e.getComponent();
print( "componentResized", c.getName() + " " + c.getWidth() + "," + c.getHeight() );
}
@Override
public void componentMoved( ComponentEvent e ) {
Component c = e.getComponent();
print( "componentMoved", e.getComponent().getName() + " " + c.getX() + "," + c.getY() );
}
@Override
public void componentShown( ComponentEvent e ) {
print( "componentShown", e.getComponent().getName() );
}
@Override
public void componentHidden( ComponentEvent e ) {
print( "componentHidden", e.getComponent().getName() );
}
} );
}
private static void print( String key, String value ) {
System.out.printf( " %-20s %s\n", key, value );
}
}