Java Swing中的HiDPI支持,具有多种外观和感觉

时间:2016-07-03 12:32:57

标签: java swing dpi high-resolution java-9

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

一些实验发现UIManager包含许多可以调整的指标,以帮助您在使应用程序Hi-DPI友好的良好开端。 (UIManager-Defaults实用程序对于探索这些实用程序非常宝贵!)我发现L& Fs似乎完全完全彼此不同:

  • Windows L&amp; F为您提供了一个很好的(不完美的)默认字体大小,并且内置图标(如复选框和窗口图标)的大小适当 - 但许多其他指标仍然没有出现问题。< / p>

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

  • 在Nimbus中你可以只更新一个默认字体,其他字体就位了......但是我不知道如何缩放内置图标并有组合框,单选按钮(等)成功渲染!

我从玩耍中获得的感觉是,应该可以独立地为每个L&amp; F专用创建特定调整的列表。这可能包括调整FontIconIntegerDimension的默认值。

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

任何人都可以分享一个明确的清单,其中UIDefaults需要调整标准L&amp; Fs吗?

我很满意一直支持Metal和Windows的解决方案。

我认为这样的解决方案应该是可以重复使用的。可以解决一系列Swing应用程序的相同问题。我很惊讶没有这样的效用似乎存在。 (如果不是,请赐教!)这种方法当然不能解决所有问题(例如,您仍然需要手动调整对setPreferredSize等的任何调用。然后再次支持多种L&amp; Fs的应用程序应该倾向于无论如何,为了避免打电话。)不过,我认为它可以让很多应用程序开始。

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

2 个答案:

答案 0 :(得分:1)

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

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

    1. 倾向于返回使用NullLayout,尽管事实上UIManager(我认为这仍然是真的)能够处理getPreferredSize 21k x 48k,但看起来像LayoutManager不知道要划分那些正确的值,你可以看到,你可以通过使用GridBagLayout看到这个问题,对于4k场景有缩放/ zoom_in的reall问题,我认为在2k监视器上渲染AWT / Swing GUI,

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

      • 必须创建自己的paintIcon(在AWT / Swing中使用简单的形状)
      • 覆盖边框
      • 覆盖鼠标事件
      • 覆盖焦点事件
    3. 这些步骤对于今天的应用中的“平滑GUI”也是部分需要的,从旧的4:3屏幕 - &gt;以为具有低分辨率(HD_screens)的small_screens的笔记本电脑 - &gt; fullhd screen - &gt; fullhs wide sceen,以2k屏幕结束,注意我从未尝试过使用Nimbus L&amp; F中的尺寸变量建筑物,专业应用程序必须包含测试,检查

      • 原生OS(字体,边框,大小调整之间存在差异)
      • 获得屏幕分辨率的主显示屏幕分辨率,例如特别是FullHd屏幕(16:9)和宽FullHd屏幕(21:9)
      • 在你拥有多个显示器并且像素分辨率不同的情况下缓存所有显示器(问题是有两个相同的显示器,但有不一致的设置 - 用户设置到GPU,或者显示器有活动的电视卡 - 设置可以通过电视芯片修改)

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

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

答案 1 :(得分:1)

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

我对某些部分不满意,例如“整数&#39;和&#39;字体&#39;修改是非常&#34;魔法&#34;。这是我希望听到其他人提供更多Swing / L&amp; 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&amp; Fs使用的功能放在基类中。这似乎处理金属没有进一步细化:

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并修改了宽度&amp;高度。

然后覆盖其他L&amp; Fs的BasicTweaker

视窗:

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;
  }
}