窗口缩放

Windows scaling

本文关键字:缩放 窗口      更新时间:2023-10-16

Windows 8/10已经开始包含一个滑块,用于显示GUI元素应该缩放多少,右键单击桌面->显示。一个同事用4k屏幕的笔记本电脑是250%,而另一个同事用同样分辨率的4k 28英寸屏幕是150%。

如何以编程方式读取该值?我需要调整一些图形,让它在所有屏幕上看起来都一样。

我在Eclipse RCP应用程序上使用Java,但是通过JNI使用C或c++的方法也可以工作。

java.awt.Toolkit.getDefaultToolkit().getScreenResolution()参见API

以每英寸点为单位返回屏幕分辨率。

假设你的100%是96像素,你可以计算你的缩放系数

以下内容适合我。你需要获得窗口实际设备(多显示器环境),并计算它的绑定尺寸与显示模式尺寸。

public static double getWindowScale(Window window) {
    GraphicsDevice device = getWindowDevice(window);
    return device.getDisplayMode().getWidth() / (double) device.getDefaultConfiguration().getBounds().width;
}
public static GraphicsDevice getWindowDevice(Window window) {
    Rectangle bounds = window.getBounds();
    return asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()).stream()
            // pick devices where window located
            .filter(d -> d.getDefaultConfiguration().getBounds().intersects(bounds))
            // sort by biggest intersection square
            .sorted((f, s) -> Long.compare(//
                    square(f.getDefaultConfiguration().getBounds().intersection(bounds)),
                    square(s.getDefaultConfiguration().getBounds().intersection(bounds))))
            // use one with the biggest part of the window
            .reduce((f, s) -> s) //
            // fallback to default device
            .orElse(window.getGraphicsConfiguration().getDevice());
}
public static long square(Rectangle rec) {
    return Math.abs(rec.width * rec.height);
}

也许这里的答案可以帮助你:

[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
public enum DeviceCap
{
    VERTRES = 10,
    DESKTOPVERTRES = 117,
    // http://pinvoke.net/default.aspx/gdi32/GetDeviceCaps.html
}  

private float getScalingFactor()
{
    Graphics g = Graphics.FromHwnd(IntPtr.Zero);
    IntPtr desktop = g.GetHdc();
    int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES);
    int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES); 
    float ScreenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight;
    return ScreenScalingFactor; // 1.25 = 125%
}

虽然这个问题问的是Windows的伸缩,但被接受的解决方案在运行JDK11的Linux上不能正常工作。

下面的技术使用JNA来检测Linux上的屏幕比例因子,这与公开Gtk (Gimp Toolkit)库的Linux桌面兼容。

该代码片段包含几个Gtk版本相关的解决方案,因为Gtk API暴露的缩放信息已经更改了几次。此外,对于特定的用例(同一桌面上的混合标准和HiDPI监视器),信息可能是不准确的,因为缩放是特定于监视器的。

警告:如果Java在将来的版本中修复了此行为,或者Gtk更改了它们的API,则需要更新此解决方案。此外,此解决方案需要仅linux组件:交叉编译成功需要(包括)反射。

用法:GtkUtilities.getScaleFactor()

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import java.lang.reflect.Method;
public class GtkUtilities {
    /**
    * Initializes Gtk2/3 and returns the desktop scaling factor, usually 1.0 or 2.0
    */
    public static double getScaleFactor() {
        GTK gtkHandle = getGtkInstance();
        if (gtkHandle != null && gtkHandle.gtk_init_check(0, null)) {
            System.out.println("Initialized Gtk");
            if (gtkHandle instanceof GTK2) {
                return getGtk2ScaleFactor((GTK2)gtkHandle);
            } else {
                return getGtk3ScaleFactor((GTK3)gtkHandle);
            }
        } else {
            System.err.println("An error occurred initializing the Gtk library");
        }
        return 0;
    }
    private static GTK getGtkInstance() {
        System.out.println("Finding preferred Gtk version...");
        switch(getGtkMajorVersion()) {
            case 2:
                return GTK2.INSTANCE;
            case 3:
                return GTK3.INSTANCE;
            default:
                System.err.println("Not a compatible Gtk version");
        }
        return null;
    }
    /**
    * Get the major version of Gtk (e.g. 2, 3)
    * UNIXToolkit is unavailable on Windows or Mac; reflection is required.
    * @return Major version if found, zero if not.
    */
    private static int getGtkMajorVersion() {
        try {
            Class toolkitClass = Class.forName("sun.awt.UNIXToolkit");
            Method versionMethod = toolkitClass.getDeclaredMethod("getGtkVersion");
            Enum versionInfo = (Enum)versionMethod.invoke(toolkitClass);
            Method numberMethod = versionInfo.getClass().getDeclaredMethod("getNumber");
            int version = ((Integer)numberMethod.invoke(versionInfo)).intValue();
            System.out.println("Found Gtk " + version);
            return version;
        } catch(Throwable t) {
            System.err.println("Could not obtain GtkVersion information from UNIXToolkit: {}", t.getMessage());
        }
        return 0;
    }
    private static double getGtk2ScaleFactor(GTK2 gtk2) {
        Pointer display = gtk2.gdk_display_get_default();
        System.out.println("Gtk 2.10+ detected, calling "gdk_screen_get_resolution"");
        Pointer screen = gtk2.gdk_display_get_default_screen(display);
        return gtk2.gdk_screen_get_resolution(screen) / 96.0d;
    }
    private static double getGtk3ScaleFactor(GTK3 gtk3) {
        Pointer display = gtk3.gdk_display_get_default();
        int gtkMinorVersion = gtk3.gtk_get_minor_version();
        if (gtkMinorVersion < 10) {
            System.err.println("Gtk 3.10+ is required to detect scaling factor, skipping.");
        } else if (gtkMinorVersion >= 22) {
            System.out.println("Gtk 3.22+ detected, calling "gdk_monitor_get_scale_factor"");
            Pointer monitor = gtk3.gdk_display_get_primary_monitor(display);
            return gtk3.gdk_monitor_get_scale_factor(monitor);
        } else if (gtkMinorVersion >= 10) {
            System.out.println("Gtk 3.10+ detected, calling "gdk_screen_get_monitor_scale_factor"");
            Pointer screen = gtk3.gdk_display_get_default_screen(display);
            return gtk3.gdk_screen_get_monitor_scale_factor(screen, 0);
        }
        return 0;
    }
    /**
    * Gtk2/Gtk3 wrapper
    */
    private interface GTK extends Library {
        // Gtk2.0+
        boolean gtk_init_check(int argc, String[] argv);
        Pointer gdk_display_get_default();
        Pointer gdk_display_get_default_screen (Pointer display);
    }
    private interface GTK3 extends GTK {
        GTK3 INSTANCE = Native.loadLibrary("gtk-3", GTK3.class);
        // Gtk 3.0+
        int gtk_get_minor_version ();
        // Gtk 3.10-3.21
        int gdk_screen_get_monitor_scale_factor (Pointer screen, int monitor_num);
        // Gtk 3.22+
        Pointer gdk_display_get_primary_monitor (Pointer display);
        int gdk_monitor_get_scale_factor (Pointer monitor);
    }
    private interface GTK2 extends GTK {
        GTK2 INSTANCE = Native.loadLibrary("gtk-x11-2.0", GTK2.class);
        // Gtk 2.1-3.0
        double gdk_screen_get_resolution(Pointer screen);
    }
}

采用@seth-kitchen使用JNA的示例,这是可能的,即使在Java 8等较旧的jdk上也是如此。

注意:该技术的JNA部分在JDK11上不能很好地工作。代码中的注释解释了它将如何回退到Toolkit技术。

public static double getScaleFactor() {
    WinDef.HDC hdc = GDI32.INSTANCE.CreateCompatibleDC(null);
    if (hdc != null) {
        int actual = GDI32.INSTANCE.GetDeviceCaps(hdc, 10 /* VERTRES */);
        int logical = GDI32.INSTANCE.GetDeviceCaps(hdc, 117 /* DESKTOPVERTRES */);
        GDI32.INSTANCE.DeleteDC(hdc);
        // JDK11 seems to always return 1, use fallback below
        if (logical != 0 && logical/actual > 1) {
            return (double)logical/actual;
        }
    }
    return Toolkit.getDefaultToolkit().getScreenResolution() / 96.0d;
}

为了简单起见,上面的解决方案获取默认显示。您可以通过查找当前窗口句柄(通过Native.getComponentPointer(Component)或通过User32.INSTANCE.FindWindow(...)的标题),然后使用CreateaCompatibleDC(GetDC(window))来增强它以获得当前窗口的显示。

这里有一个" one liner ";这应该会给你比例因子:

double scale = java.awt.GraphicsEnvironment
    .getLocalGraphicsEnvironment()
    .getDefaultScreenDevice() // or cycle your getScreenDevices()
    .getDefaultConfiguration()
    .getDefaultTransform()
    .getScaleX();

如果您已经有一个java.awt。组件,你可以用这个代替:

double scale = myComponent.getGraphicsConfiguration().getDefaultTransform().getScaleX();

(基于本文)

这适用于macOS和Windows与Java 17

public static Window getWindow(Component component) {
    return component instanceof Window ? (Window) component : getWindow(component.getParent());
}
@Override
public synchronized void paintComponent(Graphics g) {
    super.paintComponent(g);
    float displayScaling = (float) getWindow(this).getGraphicsConfiguration().getDefaultTransform().getScaleX();
}