Windows: fixed wrong layout in maximized frame after changing screen scale factor (issue #904)
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
Native Libraries / Natives (macos) (push) Has been cancelled
Native Libraries / Natives (ubuntu) (push) Has been cancelled
Native Libraries / Natives (windows) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled

Windows binaries built and signed locally in clean workspace
This commit is contained in:
Karl Tauber
2024-11-17 19:41:54 +01:00
parent ee9e238592
commit c37712b0f0
10 changed files with 173 additions and 2 deletions

View File

@@ -10,6 +10,8 @@ FlatLaf Change Log
- FlatLaf window decorations: - FlatLaf window decorations:
- Windows: Fixed possible deadlock with TabbedPane in window title area in - Windows: Fixed possible deadlock with TabbedPane in window title area in
"full window content" mode. (issue #909) "full window content" mode. (issue #909)
- Windows: Fixed wrong layout in maximized frame after changing screen scale
factor. (issue #904)
- Linux: Fixed continuous cursor toggling between resize and standard cursor - Linux: Fixed continuous cursor toggling between resize and standard cursor
when resizing window. (issue #907) when resizing window. (issue #907)

View File

@@ -309,6 +309,8 @@ public class FlatWindowsNativeWindowBorder
WM_ENTERSIZEMOVE = 0x0231, WM_ENTERSIZEMOVE = 0x0231,
WM_EXITSIZEMOVE = 0x0232, WM_EXITSIZEMOVE = 0x0232,
WM_DPICHANGED = 0x02E0,
WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320; WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320;
// WM_SIZE wParam // WM_SIZE wParam
@@ -501,6 +503,22 @@ public class FlatWindowsNativeWindowBorder
isMoving = true; isMoving = true;
break; break;
case WM_DPICHANGED:
LRESULT lResult = User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam );
// if window is maximized and DPI/scaling changed, then Windows
// does not send a subsequent WM_SIZE message and Java window bounds,
// which depend on scale factor, are not updated
boolean isMaximized = User32Ex.INSTANCE.IsZoomed( hwnd );
if( isMaximized ) {
MyRECT r = new MyRECT( new Pointer( lParam.longValue() ) );
int width = r.right - r.left;
int height = r.bottom - r.top;
User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, WM_SIZE, new WPARAM( SIZE_MAXIMIZED ), MAKELPARAM( width, height ) );
}
return lResult;
case WM_ERASEBKGND: case WM_ERASEBKGND:
// do not erase background while the user is moving the window, // do not erase background while the user is moving the window,
// otherwise there may be rendering artifacts on HiDPI screens with Java 9+ // otherwise there may be rendering artifacts on HiDPI screens with Java 9+
@@ -818,6 +836,13 @@ public class FlatWindowsNativeWindowBorder
return (low & 0xffff) | ((high & 0xffff) << 16); return (low & 0xffff) | ((high & 0xffff) << 16);
} }
/**
* Same implementation as MAKELPARAM(l, h) macro in winuser.h.
*/
private LPARAM MAKELPARAM( int low, int high ) {
return new LPARAM( MAKELONG( low, high ) );
}
/** /**
* Same implementation as RGB(r,g,b) macro in wingdi.h. * Same implementation as RGB(r,g,b) macro in wingdi.h.
*/ */
@@ -937,6 +962,23 @@ public class FlatWindowsNativeWindowBorder
} }
} }
//---- class MyRECT -------------------------------------------------------
@FieldOrder( { "left", "top", "right", "bottom" } )
public static class MyRECT
extends Structure
{
public int left;
public int top;
public int right;
public int bottom;
public MyRECT( Pointer pointer ) {
super( pointer );
read();
}
}
//---- class MENUITEMINFO ------------------------------------------------- //---- class MENUITEMINFO -------------------------------------------------
@FieldOrder( { "cbSize", "fMask", "fType", "fState", "wID", "hSubMenu", @FieldOrder( { "cbSize", "fMask", "fType", "fState", "wID", "hSubMenu",

View File

@@ -19,3 +19,32 @@ The DLLs were built on a GitHub server with the help of GitHub Actions. See:
[Native Libraries](https://github.com/JFormDesigner/FlatLaf/actions/workflows/natives.yml) [Native Libraries](https://github.com/JFormDesigner/FlatLaf/actions/workflows/natives.yml)
workflow. Then the produced Artifacts ZIP was downloaded, signed DLLs with workflow. Then the produced Artifacts ZIP was downloaded, signed DLLs with
FormDev Software code signing certificate and committed the DLLs to Git. FormDev Software code signing certificate and committed the DLLs to Git.
## Development
To build the library on Windows using Gradle, (parts of)
[Visual Studio Community
2022](https://visualstudio.microsoft.com/downloads/)
needs to be installed. After downloading and running `VisualStudioSetup.exe` the
**Visual Studio Installer** is installed and started. Once running, it shows the
**Workloads** tab that allows you to install additional packages. Either choose
**Desktop development with C++**, or to save some disk space switch to the
**Single Components** tab and choose following components (newest versions):
- MSVC v143 - VS 2022 C++ x64/x86 Buildtools
- MSVC v143 - VS 2022 C++ ARM64/ARM64EC Buildtools
- Windows 11 SDK
Note that the Visual Studio Installer shows many similar named components for
MSVC. Make sure to choose exactly those components listed above.
Using
[Build Tools for Visual Studio 2022](https://visualstudio.microsoft.com/downloads/#remote-tools-for-visual-studio-2022)
(installs only compiler and SDKs) instead of
[Visual Studio Community
2022](https://visualstudio.microsoft.com/downloads/)
does not work with Gradle.
[Visual Studio Code](https://code.visualstudio.com/) with **C/C++** extension
can be used for C++ code editing.

View File

@@ -288,6 +288,23 @@ LRESULT CALLBACK FlatWndProc::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, L
isMoving = true; isMoving = true;
break; break;
case WM_DPICHANGED: {
LRESULT lResult = ::CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam );
// if window is maximized and DPI/scaling changed, then Windows
// does not send a subsequent WM_SIZE message and Java window bounds,
// which depend on scale factor, are not updated
bool isMaximized = ::IsZoomed( hwnd );
if( isMaximized ) {
RECT* r = reinterpret_cast<RECT*>( lParam );
int width = r->right - r->left;
int height = r->bottom - r->top;
::CallWindowProc( defaultWndProc, hwnd, WM_SIZE, SIZE_MAXIMIZED, MAKELPARAM( width, height ) );
}
return lResult;
}
case WM_ERASEBKGND: case WM_ERASEBKGND:
// do not erase background while the user is moving the window, // do not erase background while the user is moving the window,
// otherwise there may be rendering artifacts on HiDPI screens with Java 9+ // otherwise there may be rendering artifacts on HiDPI screens with Java 9+

View File

@@ -25,6 +25,8 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.TitledBorder; import javax.swing.border.TitledBorder;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
@@ -74,6 +76,7 @@ public class FlatWindowDecorationsTest
} }
private List<Image> images; private List<Image> images;
private Timer refreshStateTimer;
FlatWindowDecorationsTest() { FlatWindowDecorationsTest() {
initComponents(); initComponents();
@@ -132,6 +135,49 @@ public class FlatWindowDecorationsTest
fullWindowContentButtonsBoundsField.setEnabled( bounds != null ); fullWindowContentButtonsBoundsField.setEnabled( bounds != null );
} ); } );
} }
if( window instanceof Frame ) {
AtomicInteger lastState = new AtomicInteger( -1 );
AtomicReference<Window> lastFullScreenWindow = new AtomicReference<>();
refreshStateTimer = new Timer( 500, e -> {
Frame frame = (Frame) window;
int state = frame.getExtendedState();
Window fullScreenWindow = window.getGraphicsConfiguration().getDevice().getFullScreenWindow();
if( state != lastState.get() || fullScreenWindow != lastFullScreenWindow.get() ) {
lastState.set( state );
lastFullScreenWindow.set( fullScreenWindow );
String s = "";
if( (state & Frame.ICONIFIED) != 0 )
s += "iconified ";
if( (state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH )
s += "maximized ";
else {
if( (state & Frame.MAXIMIZED_HORIZ) != 0 )
s += "maximized-horizontal ";
if( (state & Frame.MAXIMIZED_VERT) != 0 )
s += "maximized-vertical ";
}
if( fullScreenWindow == window )
s += "full-screen ";
if( s.isEmpty() )
s = "normal";
frameStateField.setText( s );
}
} );
refreshStateTimer.start();
}
}
@Override
public void removeNotify() {
super.removeNotify();
if( refreshStateTimer != null ) {
refreshStateTimer.stop();
refreshStateTimer = null;
}
} }
private void updateDecorationStyleRadioButtons( JRootPane rootPane ) { private void updateDecorationStyleRadioButtons( JRootPane rootPane ) {
@@ -653,6 +699,9 @@ debug*/
typeUtilityRadioButton = new JRadioButton(); typeUtilityRadioButton = new JRadioButton();
typeSmallRadioButton = new JRadioButton(); typeSmallRadioButton = new JRadioButton();
showRectanglesCheckBox = new JCheckBox(); showRectanglesCheckBox = new JCheckBox();
JPanel hSpacer1 = new JPanel(null);
JLabel frameStateLabel = new JLabel();
frameStateField = new JLabel();
menuBar = new JMenuBar(); menuBar = new JMenuBar();
JMenu fileMenu = new JMenu(); JMenu fileMenu = new JMenu();
JMenuItem newMenuItem = new JMenuItem(); JMenuItem newMenuItem = new JMenuItem();
@@ -1092,6 +1141,16 @@ debug*/
showRectanglesCheckBox.setSelected(true); showRectanglesCheckBox.setSelected(true);
showRectanglesCheckBox.addActionListener(e -> showRectangles()); showRectanglesCheckBox.addActionListener(e -> showRectangles());
add(showRectanglesCheckBox, "cell 0 3"); add(showRectanglesCheckBox, "cell 0 3");
add(hSpacer1, "cell 2 3 2 1");
//---- frameStateLabel ----
frameStateLabel.setText("Frame state:");
add(frameStateLabel, "cell 2 3 2 1,alignx right,growx 0");
//---- frameStateField ----
frameStateField.setText("n/a");
frameStateField.setFont(frameStateField.getFont().deriveFont(frameStateField.getFont().getStyle() | Font.BOLD));
add(frameStateField, "cell 2 3 2 1,alignx right,growx 0");
//======== menuBar ======== //======== menuBar ========
{ {
@@ -1341,6 +1400,7 @@ debug*/
private JRadioButton typeUtilityRadioButton; private JRadioButton typeUtilityRadioButton;
private JRadioButton typeSmallRadioButton; private JRadioButton typeSmallRadioButton;
private JCheckBox showRectanglesCheckBox; private JCheckBox showRectanglesCheckBox;
private JLabel frameStateField;
private JMenuBar menuBar; private JMenuBar menuBar;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables
} }

View File

@@ -637,6 +637,27 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3" "value": "cell 0 3"
} ) } )
add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) {
name: "hSpacer1"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3 2 1"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "frameStateLabel"
"text": "Frame state:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3 2 1,alignx right,growx 0"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "frameStateField"
"text": "n/a"
"font": new com.jformdesigner.model.SwingDerivedFont( null, 1, 0, false )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3 2 1,alignx right,growx 0"
} )
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 ) "location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 960, 570 ) "size": new java.awt.Dimension( 960, 570 )

View File

@@ -46,8 +46,8 @@ glazedlists = "com.glazedlists:glazedlists:1.11.0"
netbeans-api-awt = "org.netbeans.api:org-openide-awt:RELEASE112" netbeans-api-awt = "org.netbeans.api:org-openide-awt:RELEASE112"
# flatlaf-natives-jna # flatlaf-natives-jna
jna = "net.java.dev.jna:jna:5.12.1" jna = "net.java.dev.jna:jna:5.15.0"
jna-platform = "net.java.dev.jna:jna-platform:5.12.1" jna-platform = "net.java.dev.jna:jna-platform:5.15.0"
# junit # junit
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }