4

我希望为一些 Swing 应用程序添加 Hi-DPI 支持,但我未能找到足以满足我需求的解决方案。我需要支持多种外观和感觉,所以情况似乎比我发现的其他帖子要复杂得多(这些帖子倾向于建议“调整你的 UI 大小以匹配你的字体大小”)。

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

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

  • 在 Metal 中,您可以UIManager单独更新字体。通过一些工作,您可以扩展内置的IconUIResources 以匹配。

  • 在 Nimbus 中,您可以只更新一个默认字体,其他字体就位......但我无法缩放内置图标并成功呈现组合框、单选按钮(等)!

我从玩弄中得到的感觉是,应该可以为每个 L&F 特定的特定调整创建一个列表。这可能包括调整FontIcon和sInteger的默认值。Dimension

有没有人想出一个好的解决方案?

谁能分享一份UIDefaults需要对标准 L&F 进行调整的最终清单?

我会对一个始终支持 Metal 和 Windows 的解决方案感到满意。

我想这样的解决方案应该是可重复使用的,并且可以解决一系列 Swing 应用程序的相同问题。我很惊讶似乎还没有这样的实用程序存在。(如果不是,请赐教!)这种方法当然不能解决所有问题(例如,您仍然需要手动扩展对setPreferredSize等的任何调用。然后,已经支持多个 L&F 的应用程序应该避免调用它。)尽管如此,我认为它可以让许多应用程序有一个良好的开端。

我知道JDK-9承诺提供完整的 Hi-DPI 支持,但我不能等那么久——即使在 2017 年发布之后,我也可能无法切换一段时间。

4

2 回答 2

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

  • 我的观点,(我仅限于懒惰的 Win 用户,来自 LI/(U)NIX 或 OSX 的真正高级用户的体验可能会很有趣)

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

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

      • 必须创建自己的paintIcon(在 AWT/Swing 中使用简单的形状)
      • 覆盖边框
      • 覆盖鼠标事件
      • 覆盖焦点事件
    3. 在今天的应用程序中,“流畅的 GUI”也部分需要这些步骤,从旧的 4:3 屏幕 -> 认为具有低分辨率小屏幕(HD_screens)的笔记本电脑 -> 全高清屏幕 -> 全高清屏幕,以 2k 屏幕结束,注意 I从未尝试过使用 Nimbus L&F 中内置的尺寸变化,专业应用程序必须包含测试,检查

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

      • 然后可以使用硬编码矩阵创建用于各种尺寸的 GUI,以适应所有屏幕标准,默认情况下通过覆盖(父)容器的 getPreferredSize 成功,然后 LayoutManager 将接受 getPreferredSize 形式的硬编码矩阵作为原型并正确完成自己的工作,

    4. 如果我记得另一个有趣的事实,错误,等等,我会编辑这个我更长的评论,我的评论

于 2016-07-03T14:06:24.917 回答
1

这是基于我的初始原型设计的解决方案。

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

我已经把它变成了一个社区维基,所以如果人们更愿意对此进行迭代并添加他们的知识,而不是发布他们自己的解决方案,那么请随意。

我从一个可以修改某些 ui-defaults 的界面开始:

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 使用的功能放在一个基类中。这似乎无需进一步改进就可以处理 Metal:

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使用修改后的宽度和高度进行调用。

然后覆盖BasicTweaker其他 L&F ......

视窗:

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;
  }
}
于 2016-07-04T01:58:59.907 回答