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:
- Windows: Fixed possible deadlock with TabbedPane in window title area in
"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
when resizing window. (issue #907)

View File

@@ -309,6 +309,8 @@ public class FlatWindowsNativeWindowBorder
WM_ENTERSIZEMOVE = 0x0231,
WM_EXITSIZEMOVE = 0x0232,
WM_DPICHANGED = 0x02E0,
WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320;
// WM_SIZE wParam
@@ -501,6 +503,22 @@ public class FlatWindowsNativeWindowBorder
isMoving = true;
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:
// do not erase background while the user is moving the window,
// 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);
}
/**
* 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.
*/
@@ -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 -------------------------------------------------
@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)
workflow. Then the produced Artifacts ZIP was downloaded, signed DLLs with
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;
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:
// do not erase background while the user is moving the window,
// 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.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import com.formdev.flatlaf.FlatClientProperties;
@@ -74,6 +76,7 @@ public class FlatWindowDecorationsTest
}
private List<Image> images;
private Timer refreshStateTimer;
FlatWindowDecorationsTest() {
initComponents();
@@ -132,6 +135,49 @@ public class FlatWindowDecorationsTest
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 ) {
@@ -653,6 +699,9 @@ debug*/
typeUtilityRadioButton = new JRadioButton();
typeSmallRadioButton = new JRadioButton();
showRectanglesCheckBox = new JCheckBox();
JPanel hSpacer1 = new JPanel(null);
JLabel frameStateLabel = new JLabel();
frameStateField = new JLabel();
menuBar = new JMenuBar();
JMenu fileMenu = new JMenu();
JMenuItem newMenuItem = new JMenuItem();
@@ -1092,6 +1141,16 @@ debug*/
showRectanglesCheckBox.setSelected(true);
showRectanglesCheckBox.addActionListener(e -> showRectangles());
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 ========
{
@@ -1341,6 +1400,7 @@ debug*/
private JRadioButton typeUtilityRadioButton;
private JRadioButton typeSmallRadioButton;
private JCheckBox showRectanglesCheckBox;
private JLabel frameStateField;
private JMenuBar menuBar;
// JFormDesigner - End of variables declaration //GEN-END:variables
}

View File

@@ -637,6 +637,27 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"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 ) {
"location": new java.awt.Point( 0, 0 )
"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"
# flatlaf-natives-jna
jna = "net.java.dev.jna:jna:5.12.1"
jna-platform = "net.java.dev.jna:jna-platform:5.12.1"
jna = "net.java.dev.jna:jna:5.15.0"
jna-platform = "net.java.dev.jna:jna-platform:5.15.0"
# junit
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }