【问题标题】:Override JComponent.getBaselineResizeBehavior() but keep Java5 compatibility覆盖 JComponent.getBaselineResizeBehavior() 但保持 Java5 兼容性
【发布时间】:2010-09-29 13:05:16
【问题描述】:

对于我的 Swing 项目,我需要同时支持 Java 5Java 6。 我定义了一个自定义JComponent(称为Picture),在将其嵌入JScrollPane 后,我将其放入使用DesignGridLayout 管理器的JPanel

DesignGridLayout 支持基线对齐,这要归功于 swing-layout 开源库(实现对 Java 5 的基线支持并提供与新的 Java 6 基线支持的兼容性)。

我的Picture覆盖 public int getBaseline(int width, int height) 以便我可以为它定义一个正确的基线。请注意,“override”并不完全正确:它覆盖了 Java6 上的方法,但在 Java5 中定义了它。

当我在 Java5 上运行示例应用程序时,一切都很好:我定义的 Picture 基线使用正确。

但是,当我使用 Java6 时,我的 Picture#getBaseline() 方法不会被调用!当然我的图片的基线对齐很糟糕(居中)。

在检查 Java6 源代码后,我看到,在 BasicScrollPaneUI 中,getBaseline() 首先在视口组件(我的 Picture 实例)上调用 getBaselineResizeBehavior()。 只有当getBaselineResizeBehavior()返回Component.BaselineResizeBehavior.CONSTANT_ASCENT时,它才会调用getBaseline()

现在我的问题是getBaselineResizeBehavior()JComponent 的Java6 方法,我无法在Java5 中实现它,因为它返回一个枚举Component.BaselineResizeBehavior,它在Java5 中不存在。

所以我的问题(最终)是:我如何实现(或模拟?)getBaselineResizeBehavior() 以便我的类仍然可以在 Java5 环境中编译和运行?

【问题讨论】:

  • 实际上,javac 中最好有“条件编译”(就像在 C/C++ 中一样)。
  • 条件编译不是很好——然后你需要两个发行版。反射对此效果更好(我已经做过很多次了) - 请参阅我的答案。
  • 如果条件编译是语言的一部分,那就太好了,但这是另一场争论......也许 AOP 可以帮助解决这个问题(但我想避免一个重度依赖的重度解决方案,只是为了这个“小”问题。

标签: java swing baseline


【解决方案1】:

我会创建一个 Picture 的子类,可能称为 PictureJava6,它实现了 getBaselineResizeBehaviour(),并且在创建 Picture 的实例时,这样做:

public Component pictureFactory() {
    if(javaVersion > "1.6") {
        return new PictureJava6();
    } else {
        return new Picture();
    }
}

【讨论】:

  • 好吧,虽然我不太喜欢这种方法,但这可能是获得我想要的东西的唯一方法,尽管它需要我用 JDK6(和 target=1.5)编译。我刚刚检查过它有效。谢谢!
  • 好吧,如果没有 java-6 库,PictureJava6 类显然无法编译。您需要 target=1.5 向后兼容是合理的(也许不是必需的?),所以我认为除了分别编译两个单独的版本之外,您无法通过其他任何方式解决这个问题。
【解决方案2】:

如何实现(或模拟?) getBaselineResizeBehavior() 以便我的 类仍然可以编译并在 Java5环境?

您无法使用 Java 5 库编译此方法声明,因为类型 Component.BaselineResizeBehaviour 不存在:

public Component.BaselineResizeBehavior getBaselineResizeBehavior()

您必须使用 Java 6 进行编译。如果您编译到 1.5 目标,您的类仍然可以在 Java 5 上运行,但您必须注意它们可以优雅地处理缺失的类型/方法。在遇到这些情况时为它们添加测试。确保开发人员在签入之前尝试在 Java 5 上运行他们的代码。

比如这个类……

public class MyPanel extends javax.swing.JPanel {

    public java.awt.Component.BaselineResizeBehavior getBaselineResizeBehavior() {
        return java.awt.Component.BaselineResizeBehavior.OTHER;
    }

    public static void main(String[] args) {
        new MyPanel();
        System.out.println("OK");
    }

}

...可以使用javacJDK编译器编译运行如下:

X:\fallback>javac -version
javac 1.6.0_05

X:\fallback>javac -target 1.5 MyPanel.java

X:\fallback>"C:\Program Files\Java\jre1.5.0_10\bin\java.exe" -cp . MyPanel
OK

所有流行的 IDE 都提供了生成旧类版本的选项。当您需要决定代码路径时,您可以使用反射在运行时测试方法/类型是否存在。

设置目标失败会导致如下错误:

Exception in thread "main" java.lang.UnsupportedClassVersionError: Bad version n
umber in .class file
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at java.security.SecureClassLoader.defineClass(Unknown Source)
    at java.net.URLClassLoader.defineClass(Unknown Source)
    at java.net.URLClassLoader.access$100(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClassInternal(Unknown Source)

【讨论】:

  • 很有趣,但它只是部分解决了问题:我不能用 Java5 编译它,我需​​要 Java6 和目标 1.5。
  • 其实在使用swing-layout.jar库的时候是行不通的:swing-layout使用反射来查找getBaseline()方法,但是这样做是通过调用Class循环遍历类的所有方法.getMethods() 由于 getBaselineResizeBehavior() 方法而引发 NoClassDefFoundException!
  • 不幸的是——将这种编译目标方法与工厂生产者结合起来可能是正确的方法;除非你想进入插件机制。
【解决方案3】:

您可以使用反射来尝试按名称获取 CONSTANT_ASCENT 返回值。如果不能反映,你是J5,否则是J6。这避开了显式依赖并允许编译到 J5。

跟随。是为对话模式执行此操作的示例:

try {
    Field  fld=Class.forName("java.awt.Dialog$ModalExclusionType").getField("TOOLKIT_EXCLUDE");
    Method mth=getClass().getMethod("setModalExclusionType",new Class[]{fld.getType()});
    mth.invoke(this,new Object[]{fld.get(null)});
    }
catch(Throwable thr) {
    log.errorln("Unable to configure window to be unaffected by modal dialogs - dialogs may need to be closed to operate help.");
    log.errorln("Use Java 6 or later to avoid modal dialogs conflicting with the help system.");
    log.errorln("Exception: "+thr);
    }

更新:我最初发布的代码已将 J5 代码注释掉;我已经改变了这一点,因为我意识到它暗示 J5 代码在 J6 中不起作用,从而混淆了问题 - 确实如此。

【讨论】:

    【解决方案4】:

    我认为在解析虚函数和重载时,返回类型不被视为方法签名的一部分;可能是您可以定义您的“覆盖”方法以返回 Object,并根据我的第一个答案反映返回 Enum。由于您在 J5 中进行编译,因此不会出现编译时冲突,但 JVM 仍应选择您的方法来覆盖......它可能 或者它可能会引发运行时异常。不过,还是值得一试的。

    例如:

    public Object getBaselineResizeBehavior() {
        Object ret;
        // reflect out the return value
        return ret;
        }  
    

    任何错误处理都可以是 System.out,纯粹用于调试,因为除非您是 J6,否则不会调用它,因此如果调用,正确编码的反射应该始终有效。

    当然,我会评论这种方法以明确发生了什么。

    【讨论】:

    • 非常感谢,我今晚试试,然后在这里报告
    • 不幸的是,这种方法不起作用,它编译得很好(使用 Java5)但是当在 Java6 下运行时,我的方法永远不会被调用:它看起来没有被识别为覆盖 Component#getBaselineResizeBehavior(); JVM 可能会检查返回类型,从而调用超类方法。
    • 啊,那太糟糕了。我没有想到更好的主意,很抱歉这没有任何帮助。
    猜你喜欢
    • 2011-08-04
    • 1970-01-01
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多