ADDITIONAL SYSTEM INFORMATION :
Microsoft Windows [Version 10.0.17134.765]
openjdk version "11.0.3" 2019-04-16
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.3+7)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.3+7, mixed mode)
A DESCRIPTION OF THE PROBLEM :
Window content is shrunk on rescaling after moving to 125% DPI screen. This happens due to window's border change by the system on moving it to screen with higher DPI setting - border becomes bigger, so there's less space for content inside window, and it becomes shrunk. Due to that, ScrollPane's scroll bar is being shown, which should not happen.
I have observed that window can be in intermediate state when it's not rescaled by JavaFX, but rescaled by Window: border is bigger, but content stays the same as on 100% DPI screen - due to WinWindow#notifyMoving. In that state it can be easily observed that content is shrunk by few pixels, when border (including title bar) becomes bigger.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Preconditions:
Two screens environment:
- left side monitor 100% DPI,
- right side monitor 125% DPI.
Application starts on left monitor by default.
Test case:
1. Startup provided test case application.
2. Move window to the center of second monitor with 125% DPI.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
After 2. content should look the same as on 1st monitor - no scroll bar visible.
ACTUAL -
After 2. Scroll bar is visible, because content is shrunk by bigger window's border.
---------- BEGIN SOURCE ----------
package com.example;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class HiDpiIssueSample
{
public static void main( String[] args )
{
System.err.println( Runtime.version().toString() );
Application.launch( MainFx.class, args );
}
public static class MainFx extends Application
{
private static final int LABEL_SIZE = 250;
@Override
public void start( final Stage primaryStage ) throws Exception
{
final var labelSp = createLabel();
labelSp.setBackground(
new Background( new BackgroundFill( Color.RED, CornerRadii.EMPTY, Insets.EMPTY ) ) );
final var sp = new ScrollPane( labelSp );
sp.setFitToHeight( true );
sp.setFitToWidth( true );
final var labelTop = createLabel();
labelTop.setBackground(
new Background( new BackgroundFill( Color.GREEN, CornerRadii.EMPTY, Insets.EMPTY ) ) );
final VBox vBox = new VBox( labelTop, sp );
VBox.setVgrow( labelTop, Priority.ALWAYS );
VBox.setVgrow( sp, Priority.ALWAYS );
final Scene aScene = new Scene( vBox );
primaryStage.setScene( aScene );
primaryStage.sizeToScene();
primaryStage.show();
}
private Label createLabel()
{
return new Label( LABEL_SIZE + "px square min/pref" )
{
@Override
protected double computeMinWidth( final double height )
{
return snapSizeX( LABEL_SIZE );
}
@Override
protected double computeMinHeight( final double width )
{
return snapSizeY( LABEL_SIZE );
}
@Override
protected double computePrefHeight( final double width )
{
return snapSizeY( LABEL_SIZE );
}
@Override
protected double computePrefWidth( final double height )
{
return snapSizeX( LABEL_SIZE );
}
};
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
package de.psi.pjf.wsm.app.util;
import java.util.Objects;
import java.util.Optional;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.stage.Stage;
import javafx.stage.Window;
public final class FxWindowHiDpiWorkaround
{
private static final String FX_HIDPI_WORKAROUND_KEY = "FxWindowHiDpiWorkaround";
private static final int WINDOW_INSETS_TOP = 31;
private static final int WINDOW_INSETS_LEFT = 8;
private static final int WINDOW_INSETS_BOTTOM = 8;
private static final int WINDOW_INSETS_RIGHT = 8;
private Window window;
private final ChangeListener< Number > outputScaleXListener =
( observable, oldValue, newValue ) -> updateRendererScaleX( oldValue, newValue );
private final ChangeListener< Number > outputScaleYListener =
( observable, oldValue, newValue ) -> updateRendererScaleY( oldValue, newValue );
private FxWindowHiDpiWorkaround( final Window aWindow )
{
window = Objects.requireNonNull( aWindow );
}
private void uninstall()
{
window.outputScaleXProperty().removeListener( outputScaleXListener );
window.outputScaleYProperty().removeListener( outputScaleYListener );
window = null;
}
private void installListeners()
{
window.outputScaleXProperty().addListener( outputScaleXListener );
window.outputScaleYProperty().addListener( outputScaleYListener );
}
private void updateRendererScaleY( final Number aOldValue, final Number aNewValue )
{
if( aOldValue == null || aNewValue == null )
{
return;
}
if( window instanceof Stage && ( (Stage)window ).isMaximized() )
{
return;
}
final double height = window.getHeight();
if( isValidSize( height ) )
{
updateRendererScaleYImpl( aOldValue, aNewValue, height );
}
}
private void updateRendererScaleYImpl( final Number aOldValue, final Number aNewValue,
final double aHeight )
{
final int topBotInsets = WINDOW_INSETS_TOP + WINDOW_INSETS_BOTTOM;
final double previous = Math.ceil( topBotInsets * aOldValue.doubleValue() );
final double current = Math.ceil( topBotInsets * aNewValue.doubleValue() );
final double deltaY = current - previous;
window.setHeight( Math.ceil( aHeight ) + deltaY );
}
private void updateRendererScaleX( final Number aOldValue, final Number aNewValue )
{
if( aOldValue == null || aNewValue == null )
{
return;
}
if( window instanceof Stage && ( (Stage)window ).isMaximized() )
{
return;
}
final double width = window.getWidth();
if( isValidSize( width ) )
{
updateRendererScaleXImpl( aOldValue, aNewValue, width );
}
}
/**
* If window is showing directly on HiDPI panel, it <code>aSize</code> is {@link Double#NaN}.
*/
private boolean isValidSize( final double aSize )
{
return aSize > 0 && !Double.valueOf( aSize ).isNaN();
}
private void updateRendererScaleXImpl( final Number aOldValue, final Number aNewValue,
final double aWidth )
{
final int leftRightInsets = WINDOW_INSETS_LEFT + WINDOW_INSETS_RIGHT;
final double previous = Math.ceil( leftRightInsets * aOldValue.doubleValue() );
final double current = Math.ceil( leftRightInsets * aNewValue.doubleValue() );
final double deltaX = current - previous;
window.setWidth( Math.ceil( aWidth ) + deltaX );
}
public static void install()
{
Window.getWindows().addListener( (ListChangeListener< ? super Window >)change -> {
while( change.next() )
{
change.getAddedSubList().forEach( FxWindowHiDpiWorkaround::installWindow );
change.getRemoved().forEach( FxWindowHiDpiWorkaround::uninstallWindow );
}
} );
}
private static void uninstallWindow( final Window aWindow )
{
final FxWindowHiDpiWorkaround fxWindowHiDpiWorkaround =
(FxWindowHiDpiWorkaround)aWindow.getProperties().remove( FX_HIDPI_WORKAROUND_KEY );
if( fxWindowHiDpiWorkaround == null )
{
return;
}
fxWindowHiDpiWorkaround.uninstall();
}
private static void installWindow( final Window aWindow )
{
if( aWindow == null )
{
return;
}
if( getFxHiDpiWorkaround( aWindow ).isPresent() )
{
return;
}
final FxWindowHiDpiWorkaround fxWindowHiDpiWorkaround = new FxWindowHiDpiWorkaround( aWindow );
fxWindowHiDpiWorkaround.installListeners();
aWindow.getProperties().put( FX_HIDPI_WORKAROUND_KEY, fxWindowHiDpiWorkaround );
}
private static Optional< FxWindowHiDpiWorkaround > getFxHiDpiWorkaround( final Window aWindow )
{
if( aWindow == null )
{
return Optional.empty();
}
return Optional
.ofNullable( (FxWindowHiDpiWorkaround)aWindow.getProperties().get( FX_HIDPI_WORKAROUND_KEY ) );
}
}
FREQUENCY : always
Microsoft Windows [Version 10.0.17134.765]
openjdk version "11.0.3" 2019-04-16
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.3+7)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.3+7, mixed mode)
A DESCRIPTION OF THE PROBLEM :
Window content is shrunk on rescaling after moving to 125% DPI screen. This happens due to window's border change by the system on moving it to screen with higher DPI setting - border becomes bigger, so there's less space for content inside window, and it becomes shrunk. Due to that, ScrollPane's scroll bar is being shown, which should not happen.
I have observed that window can be in intermediate state when it's not rescaled by JavaFX, but rescaled by Window: border is bigger, but content stays the same as on 100% DPI screen - due to WinWindow#notifyMoving. In that state it can be easily observed that content is shrunk by few pixels, when border (including title bar) becomes bigger.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Preconditions:
Two screens environment:
- left side monitor 100% DPI,
- right side monitor 125% DPI.
Application starts on left monitor by default.
Test case:
1. Startup provided test case application.
2. Move window to the center of second monitor with 125% DPI.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
After 2. content should look the same as on 1st monitor - no scroll bar visible.
ACTUAL -
After 2. Scroll bar is visible, because content is shrunk by bigger window's border.
---------- BEGIN SOURCE ----------
package com.example;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class HiDpiIssueSample
{
public static void main( String[] args )
{
System.err.println( Runtime.version().toString() );
Application.launch( MainFx.class, args );
}
public static class MainFx extends Application
{
private static final int LABEL_SIZE = 250;
@Override
public void start( final Stage primaryStage ) throws Exception
{
final var labelSp = createLabel();
labelSp.setBackground(
new Background( new BackgroundFill( Color.RED, CornerRadii.EMPTY, Insets.EMPTY ) ) );
final var sp = new ScrollPane( labelSp );
sp.setFitToHeight( true );
sp.setFitToWidth( true );
final var labelTop = createLabel();
labelTop.setBackground(
new Background( new BackgroundFill( Color.GREEN, CornerRadii.EMPTY, Insets.EMPTY ) ) );
final VBox vBox = new VBox( labelTop, sp );
VBox.setVgrow( labelTop, Priority.ALWAYS );
VBox.setVgrow( sp, Priority.ALWAYS );
final Scene aScene = new Scene( vBox );
primaryStage.setScene( aScene );
primaryStage.sizeToScene();
primaryStage.show();
}
private Label createLabel()
{
return new Label( LABEL_SIZE + "px square min/pref" )
{
@Override
protected double computeMinWidth( final double height )
{
return snapSizeX( LABEL_SIZE );
}
@Override
protected double computeMinHeight( final double width )
{
return snapSizeY( LABEL_SIZE );
}
@Override
protected double computePrefHeight( final double width )
{
return snapSizeY( LABEL_SIZE );
}
@Override
protected double computePrefWidth( final double height )
{
return snapSizeX( LABEL_SIZE );
}
};
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
package de.psi.pjf.wsm.app.util;
import java.util.Objects;
import java.util.Optional;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.stage.Stage;
import javafx.stage.Window;
public final class FxWindowHiDpiWorkaround
{
private static final String FX_HIDPI_WORKAROUND_KEY = "FxWindowHiDpiWorkaround";
private static final int WINDOW_INSETS_TOP = 31;
private static final int WINDOW_INSETS_LEFT = 8;
private static final int WINDOW_INSETS_BOTTOM = 8;
private static final int WINDOW_INSETS_RIGHT = 8;
private Window window;
private final ChangeListener< Number > outputScaleXListener =
( observable, oldValue, newValue ) -> updateRendererScaleX( oldValue, newValue );
private final ChangeListener< Number > outputScaleYListener =
( observable, oldValue, newValue ) -> updateRendererScaleY( oldValue, newValue );
private FxWindowHiDpiWorkaround( final Window aWindow )
{
window = Objects.requireNonNull( aWindow );
}
private void uninstall()
{
window.outputScaleXProperty().removeListener( outputScaleXListener );
window.outputScaleYProperty().removeListener( outputScaleYListener );
window = null;
}
private void installListeners()
{
window.outputScaleXProperty().addListener( outputScaleXListener );
window.outputScaleYProperty().addListener( outputScaleYListener );
}
private void updateRendererScaleY( final Number aOldValue, final Number aNewValue )
{
if( aOldValue == null || aNewValue == null )
{
return;
}
if( window instanceof Stage && ( (Stage)window ).isMaximized() )
{
return;
}
final double height = window.getHeight();
if( isValidSize( height ) )
{
updateRendererScaleYImpl( aOldValue, aNewValue, height );
}
}
private void updateRendererScaleYImpl( final Number aOldValue, final Number aNewValue,
final double aHeight )
{
final int topBotInsets = WINDOW_INSETS_TOP + WINDOW_INSETS_BOTTOM;
final double previous = Math.ceil( topBotInsets * aOldValue.doubleValue() );
final double current = Math.ceil( topBotInsets * aNewValue.doubleValue() );
final double deltaY = current - previous;
window.setHeight( Math.ceil( aHeight ) + deltaY );
}
private void updateRendererScaleX( final Number aOldValue, final Number aNewValue )
{
if( aOldValue == null || aNewValue == null )
{
return;
}
if( window instanceof Stage && ( (Stage)window ).isMaximized() )
{
return;
}
final double width = window.getWidth();
if( isValidSize( width ) )
{
updateRendererScaleXImpl( aOldValue, aNewValue, width );
}
}
/**
* If window is showing directly on HiDPI panel, it <code>aSize</code> is {@link Double#NaN}.
*/
private boolean isValidSize( final double aSize )
{
return aSize > 0 && !Double.valueOf( aSize ).isNaN();
}
private void updateRendererScaleXImpl( final Number aOldValue, final Number aNewValue,
final double aWidth )
{
final int leftRightInsets = WINDOW_INSETS_LEFT + WINDOW_INSETS_RIGHT;
final double previous = Math.ceil( leftRightInsets * aOldValue.doubleValue() );
final double current = Math.ceil( leftRightInsets * aNewValue.doubleValue() );
final double deltaX = current - previous;
window.setWidth( Math.ceil( aWidth ) + deltaX );
}
public static void install()
{
Window.getWindows().addListener( (ListChangeListener< ? super Window >)change -> {
while( change.next() )
{
change.getAddedSubList().forEach( FxWindowHiDpiWorkaround::installWindow );
change.getRemoved().forEach( FxWindowHiDpiWorkaround::uninstallWindow );
}
} );
}
private static void uninstallWindow( final Window aWindow )
{
final FxWindowHiDpiWorkaround fxWindowHiDpiWorkaround =
(FxWindowHiDpiWorkaround)aWindow.getProperties().remove( FX_HIDPI_WORKAROUND_KEY );
if( fxWindowHiDpiWorkaround == null )
{
return;
}
fxWindowHiDpiWorkaround.uninstall();
}
private static void installWindow( final Window aWindow )
{
if( aWindow == null )
{
return;
}
if( getFxHiDpiWorkaround( aWindow ).isPresent() )
{
return;
}
final FxWindowHiDpiWorkaround fxWindowHiDpiWorkaround = new FxWindowHiDpiWorkaround( aWindow );
fxWindowHiDpiWorkaround.installListeners();
aWindow.getProperties().put( FX_HIDPI_WORKAROUND_KEY, fxWindowHiDpiWorkaround );
}
private static Optional< FxWindowHiDpiWorkaround > getFxHiDpiWorkaround( final Window aWindow )
{
if( aWindow == null )
{
return Optional.empty();
}
return Optional
.ofNullable( (FxWindowHiDpiWorkaround)aWindow.getProperties().get( FX_HIDPI_WORKAROUND_KEY ) );
}
}
FREQUENCY : always
- relates to
-
JDK-8228458 Window content is shrunk when run on 125% DPI
-
- Open
-
-
JDK-8261227 Window position stored in getX() or getY() is not actual on HiDPI screens
-
- Open
-