Java Swing中的HiDPI支持多种外观

Java Swing中的HiDPI支持多种外观,java,swing,dpi,high-resolution,java-9,Java,Swing,Dpi,High Resolution,Java 9,我希望为一些Swing应用程序添加Hi-DPI支持,但我没有找到一个足以满足我需求的解决方案。我需要支持多个look&feel,因此情况似乎比我发现的其他帖子更复杂(这些帖子倾向于建议“调整UI大小以匹配字体大小”) 一些实验发现,UIManager包含许多指标,可以对这些指标进行调整,使您在应用程序的高DPI友好性方面有一个良好的开端。(该实用程序对于探索这些方面来说是非常宝贵的!)但我发现L&F之间的工作方式似乎完全不同: Windows L&F为您提供了一个良好(不完美)的默认字体大小,

我希望为一些Swing应用程序添加Hi-DPI支持,但我没有找到一个足以满足我需求的解决方案。我需要支持多个look&feel,因此情况似乎比我发现的其他帖子更复杂(这些帖子倾向于建议“调整UI大小以匹配字体大小”)

一些实验发现,
UIManager
包含许多指标,可以对这些指标进行调整,使您在应用程序的高DPI友好性方面有一个良好的开端。(该实用程序对于探索这些方面来说是非常宝贵的!)但我发现L&F之间的工作方式似乎完全不同:

  • Windows L&F为您提供了一个良好(不完美)的默认字体大小,并且内置图标(如复选框和窗口图标)的大小适当-但许多其他指标仍然不正常

  • 在Metal中,您可以单独更新
    UIManager
    中的字体。只需做一点工作,就可以缩放内置的
    IconUIResource
    s以匹配

  • 在Nimbus中,您只需更新一种默认字体,其他字体就会就位。。。但我无法理解如何缩放内置图标并成功渲染组合框、单选按钮等

我从玩游戏中得到的感觉是,应该可以为每个L&F特定的产品单独创建一个特定调整的列表。这可能包括调整
字体
图标
整数
维度
的默认值

有没有人想出一个好的解决办法

任何人都可以分享一个明确的列表,其中的
UIDefaults
需要调整标准L&F

我会很高兴有一个只支持金属和窗户的解决方案

我认为这样的解决方案应该是可重用的&可以为一系列Swing应用程序解决同样的问题。我感到惊讶的是,现在似乎还没有这样的实用工具。(如果不是,请告诉我!)这种方法当然不能解决所有问题(例如,您仍然需要手动将任何调用缩放到
setPreferredSize
等。然后,已经支持多个L&Fs的应用程序也应该避免调用它。)尽管如此,我认为它可以让许多应用程序有一个良好的开端

我知道承诺已满,但我不能等那么久——即使在2017年发布之后,我也可能有一段时间无法切换。

  • 不是答案,只是我对这个问题的见解,请不要删除这个答案,它可以为未来一、两年提供非常丰富的信息

  • 我的观点,(我只限于懒惰的Win用户,可能对li/(U)NIX或OSX的真正超级用户的体验很感兴趣)

  • 倾向于返回使用NullLayout,尽管UIManager(我认为这仍然是真的)能够处理getPreferredSize 21k x 48k,但似乎LayoutManager不知道如何正确划分这些值,您可以看到,这个问题可以通过使用GridBagLayout看到,4k屏幕的缩放/放大确实存在问题,我认为AWT/Swing GUI在2k监视器上呈现正确

  • 对于2k/4k屏幕,您必须覆盖UIManager中的所有键(例如,AFAIK for Nimbus L&F可以直接使用存储在xml文件中的结构)

    • 必须创建自己的paintIcon(在AWT/Swing中使用的是简单形状)
    • 覆盖边界
    • 覆盖鼠标事件
    • 覆盖焦点事件
  • 在今天的应用中,“平滑GUI”部分也需要这些步骤,从旧的4:3屏幕->低分辨率小屏幕的Think笔记本电脑(高清屏幕)->全高清屏幕->全屏宽屏幕,以2k屏幕结束,注意我从未尝试过使用Nimbus L&F中的尺寸varians,专业申请必须包含测试、检查

    • 本机操作系统(字体、边框和大小之间存在差异)
    • 通过屏幕缩放,从主显示器获得以像素为单位的屏幕分辨率,例如,特别是全高清屏幕(16:9)和宽全高清屏幕(21:9)
    • 如果您有多个显示器且分辨率(像素)不同,则要缓存所有显示器(问题在于有两个相同的显示器,但存在离散设置-GPU的用户设置,或显示器具有活动电视卡-设置可由电视芯片修改)

    • 然后可以使用硬编码矩阵为各种大小创建GUI,以符合所有屏幕标准,默认情况下,成功覆盖(父)容器的getPreferredSize,然后LayoutManager将接受getPreferredSize表单硬编码矩阵作为原型,并正确执行自己的作业

  • 如果我还记得另一个有趣的事实,虫子,等等。。。等等,我将编辑这篇较长的评论,并附上我的评论


    • 这是一个基于我的初始原型的解决方案

      我对某些部分不满意,例如修改“整数”和“字体”的规则非常“神奇”。这是一个我希望从其他有更多Swing/L&F经验的人那里听到的地方

      我已经把它做成了一个社区维基,所以如果人们更喜欢在上面迭代并添加他们的知识,而不是发布他们自己的解决方案,那么请放心

      我从一个可以修改某些ui默认值的界面开始:

      public interface Tweaker {
        void initialTweaks();
        Font modifyFont(Object key, Font original);
        Icon modifyIcon(Object key, Icon original);
        Integer modifyInteger(Object key, Integer original);
      }
      
      可以这样调用:

      public void scaleUiDefaults() {
        float dpiScale = Toolkit.getDefaultToolkit().getScreenResolution() / 96f;
        Tweaker delegate = createTweakerForCurrentLook(dpiScale);
        tweakUiDefaults(delegate, dpiScale);
      }
      
      private Tweaker createTweakerForCurrentLook(float dpiScaling) {
        String testString = UIManager.getLookAndFeel().getName().toLowerCase();
        if (testString.contains("windows")) return new WindowsTweaker(dpiScaling);
        if (testString.contains("nimbus")) return new NimbusTweaker(dpiScaling);
        return new BasicTweaker(dpiScaling);
      }
      
      private void tweakUiDefaults(Tweaker delegate, float multiplier) {
        UIDefaults defaults = UIManager.getLookAndFeelDefaults();
      
        delegate.initialTweaks();
      
        for (Object key: Collections.list(defaults.keys())) {
          Object original = defaults.get(key);
          Object newValue = getUpdatedValue(delegate, key, original);
          if (newValue != null && newValue != original) {
            defaults.put(key, newValue);
          }
        }
      }
      
      private Object getUpdatedValue(Tweaker delegate, Object key, Object original) {
        if (original instanceof Font)    return delegate.modifyFont(key, (Font) original);
        if (original instanceof Icon)    return delegate.modifyIcon(key, (Icon) original);
        if (original instanceof Integer) return delegate.modifyInteger(key, (Integer) original);
        return null;
      }
      
      我将大多数L&F使用的功能放在基类中。这似乎可以处理金属而无需进一步细化:

      public class BasicTweaker {
        protected final float scaleFactor;
        protected final UIDefaults uiDefaults = UIManager.getLookAndFeelDefaults();
      
        public BasicTweaker(float scaleFactor) {
          this.scaleFactor = scaleFactor;
        }
      
        public void initialTweaks() {}
      
        public Font modifyFont(Object key, Font original) {
      
          // Ignores title & accelerator fonts (for example)
          if (original instanceof FontUIResource && key.toString().endsWith(".font")) {
              return newScaledFontUIResource(original, scaleFactor);
          }
          return original;
        }
      
        protected static FontUIResource newScaledFontUIResource(Font original, float scale) {
          int newSize = Math.round(original.getSize() * scale);
          return new FontUIResource(original.getName(), original.getStyle(), newSize);
        }
      
        public Icon modifyIcon(Object key, Icon original) {
          return new IconUIResource(new ScaledIcon(original, scaleFactor));
        }
      
        public Integer modifyInteger(Object key, Integer original) {
          if (!endsWithOneOf(lower(key), LOWER_SUFFIXES_FOR_SCALED_INTEGERS)) {
            return original;
          }
          return (int) (original * scaleFactor);
        }
      
        private boolean endsWithOneOf(String text, String[] suffixes) {
          return Arrays.stream(suffixes).anyMatch(suffix -> text.endsWith(suffix));
        }
      
        private String lower(Object key) {
          return (key instanceof String) ? ((String) key).toLowerCase() : "";
        }
      
        private static final String[] LOWER_SUFFIXES_FOR_SCALED_INTEGERS = 
          new String[] { "width", "height", "indent", "size", "gap" };
      }
      
      上面使用的类
      ScaledIcon
      可能超出了范围,但它本质上只是调用
      ImageIcon(image).paintIcon
      ,并修改了宽度和高度

      然后为其他L&F覆盖
      basictLawer

      窗口:

      public class WindowsTweaker extends BasicTweaker {
      
        public WindowsTweaker(float scaleFactor) {
      
          // Windows already scales fonts, scrollbar sizes (etc) according to the system DPI settings.
          // This lets us adjust to the REQUESTED scale factor, relative to the CURRENT scale factor
          super(scaleFactor / getCurrentScaling());
        }
      
        private static float getCurrentScaling() {
          int dpi = Toolkit.getDefaultToolkit().getScreenResolution();
          return dpi / 96f;
        }
      
        public Font modifyFont(Object key, Font original) {
          return super.modifyFont(key, original);
        }
      
        public Icon modifyIcon(Object key, Icon original) {
          return original;
        }
      }
      
      灵气:

      public class NimbusTweaker extends BasicTweaker {
      
        public NimbusTweaker(float scaleFactor) {
          super(scaleFactor);
        }
      
        public void initialTweaks() {
          Font font = uiDefaults.getFont("defaultFont");
          if (font != null) {
            uiDefaults.put("defaultFont", new FontUIResource(
                font.getName(), font.getStyle(), Math.round(font.getSize() * scaleFactor)));
          }
        }
      
        // Setting "defaultFont" above is sufficient as this will be inherited by all others
        public Font modifyFont(Object key, Font original) {
          return original;
        }
      
        // Scaling Radio or CheckBox button icons leads to really weird artifacts in Nimbus? Disable
        public Icon modifyIcon(Object key, Icon original) {
          return original;
        }
      }
      

      你关于只有Windows用户的观点正是我