跳到主要内容

JEP 253:为模块化准备 JavaFX UI 控件和 CSS API

QWen Max 中英对照

概述

为 JavaFX UI 控件和 CSS 功能定义公共 API,这些功能目前只能通过内部 API 获得,因此由于模块化的原因将变得不可访问。

目标

许多使用 JavaFX 的 UI 控件和 CSS 功能的开发者长期以来都忽略了避免使用内部 com.sun.* API 的警告。在许多情况下,为了实现预期的效果,开发者别无选择,只能使用这些内部 API。随着 Java 9 即将发布,尤其是 Project Jigsaw 中模块之间引入了强边界,开发者会发现他们的代码将无法再编译或运行,因为 com.sun.* 包将不再可访问。本 JEP 的目标是为目前由内部 API 提供的功能定义公共 API。

非目标

鉴于模块化的意义,即即将无法访问 com.sun.* 包,因此无法以保留任何程度的向后兼容性的方式来实现这一点。因此,本 JEP 的目标并非保留向后兼容性。这并不意味着我们可以随意破坏任何内容;我们的意图仅是引入因强制执行模块边界而直接破坏的新 API(并演进现有的私有 API)。所有其他未受模块化影响的现有 API 将保持不变。

成功指标

成功可以通过两种方式来衡量:

  • 依赖于 JavaFX 内部 API 的项目,特别是 Scene Builder、ControlsFX 和 JFXtras,在更新到新 API 后仍然可以正常工作,且功能没有任何损失。这三个项目均为开源项目,它们将提供一个极佳的测试平台,以确保所有必要的 API 都已提供。

  • 围绕新 API 的讨论结果,例如控制行为所需的输入映射工作,带来了开发者长期以来所要求的功能改进。最终,如果一切按计划进行,第三方控件应该能够在不依赖任何内部 API 的情况下构建。

动机

如果不开展这项工作,许多项目将需要大幅减少所提供的功能,而对某些项目来说,这可能是致命的。例如,如果没有 Scene Builder 当前拥有的内部 API 访问权限,它可能难以维持可行性——毫无疑问,它在提供 CSS 样式功能和控件属性操作方面的能力将受到严重削弱,而这两项功能正是 Scene Builder 的核心功能。同样的论点也适用于大多数其他具有一定程度自定义控件或 CSS 实现的基于 JavaFX 的项目。

描述

该 JEP 被分解为两个半相关的子项目,每个子项目在实现最终目标的过程中都非常重要。这些项目并没有特定的执行顺序。

项目一:将 UI 控件皮肤制作成公共 API

目前,所有的皮肤都位于 com.sun.javafx.scene.control.skin 中。这意味着,第三方如果扩展了某个皮肤(例如,TextFieldSkin)以添加额外功能、重写现有方法或修改控件皮肤的外观和行为,那么在 JDK 9 中他们的应用程序将无法正常运行。当然,这是依赖非公共 API 的用户的责任,但我们长期以来一直在讨论将此 API 公开,以便更好地支持第三方对 UI 控件的修改。

目的是将许多 JavaFX 控件皮肤移动到适当的公共包中,很可能是 javafx.scene.control.skin。并不打算同时移动相关的行为类。

这项工作的绝大部分是对每个现有的皮肤类进行审查,确保以下内容:

  • 皮肤之间存在 API 一致性,

  • 存在高质量的 Javadoc 和单元测试,并且

  • 每个类中的 API 通过将公共方法更改为私有方法保持在最低限度。

这项研究在单独的沙盒仓库中已经有了相当大的进展,尽管耗时,但只有几类值得关注的问题需要进一步分析,例如工具类、重复类和纯粹的实现类。除此之外,还有一些类可能有资格被引入到 javafx.scene.control 包中,或者至少需要进一步考虑,因为它们并非真正的皮肤。这些类包括 FXVK(虚拟键盘)、ColorPaletteCustomColorDialogDatePickerContentInputField。最后,有一些类,理想情况下方法应该设为私有的,但由于其他仅用于实现的类依赖于这些 API,因此无法这样做。针对所有这些问题,都会进行解决方案的研究,目前并不存在重大问题。

在 9 中将皮肤设为公共 API 的意图是为了确保它们的持续可用性。该 API 将有意保持在最低限度,并尽可能地简化,目的是在后续版本中根据开发人员的要求提供更有用的 API。众所周知,API(大部分)是永久性的,因此允许公共皮肤 API 在几个更新版本中逐步成熟似乎是最佳做法。

截至六月中旬,该项目已到达几乎所有的代码都已迁移并清理完毕的阶段。计划是在七月中旬到八月初之间的 JDK 9 构建版本中将其公开发布。以下是所有已迁移到 javafx.scene.control.skin 作为公共 API 的类的列表:

  • AccordionSkin
  • ButtonBarSkin
  • ButtonSkin
  • CellSkinBase
  • CheckBoxSkin
  • ChoiceBoxSkin
  • ColorPickerSkin
  • ComboBoxBaseSkin
  • ComboBoxListViewSkin
  • ComboBoxPopupControl
  • ContextMenuSkin
  • DateCellSkin
  • DatePickerSkin
  • HyperlinkSkin
  • LabelSkin
  • LabeledSkinBase
  • ListCellSkin
  • ListViewSkin
  • MenuBarSkin
  • MenuButtonSkin
  • MenuButtonSkinBase
  • NestedTableColumnHeader
  • PaginationSkin
  • ProgressBarSkin
  • ProgressIndicatorSkin
  • RadioButtonSkin
  • ScrollBarSkin
  • ScrollPaneSkin
  • SeparatorSkin
  • SliderSkin
  • SpinnerSkin
  • SplitMenuButtonSkin
  • SplitPaneSkin
  • TabPaneSkin
  • TableCellSkin
  • TableCellSkinBase
  • TableColumnHeader
  • TableHeaderRow
  • TableRowSkin
  • TableRowSkinBase
  • TableViewSkin
  • TableViewSkinBase
  • TextAreaSkin
  • TextFieldSkin
  • TextInputControlSkin
  • TitledPaneSkin
  • ToggleButtonSkin
  • ToolBarSkin
  • TooltipSkin
  • TreeCellSkin
  • TreeTableCellSkin
  • TreeTableRowSkin
  • TreeTableViewSkin
  • TreeViewSkin
  • VirtualContainerBase
  • VirtualFlow

以上列出的均为 JavaFX 中与用户界面组件皮肤(Skin)相关的类名,这些类用于定义和控制 JavaFX 控件的外观和行为。由于它们是专有名词,因此保留了原始英文名称。

这些类在六月中旬时,几乎剥离了所有未从 SkinBase 继承的 API。未来,计划根据早期访问版本的反馈逐步添加实用的 API。某些类,例如文本输入控件和虚拟化控件,已经具备额外的 API 以支持其功能。

项目二:审查并公开相关的 CSS API

与 Project One 一样,此项目旨在将目前位于 com.sun.* 包中的 API 公开发布。同样,这需要进行代码审查以尽量精简 API,同时还需要增加单元测试,并大幅完善文档。

这项工作的驱动因素将是继续允许 Scene Builder 在 JDK 9 中编译,并进行适当的修改。

截至六月中旬,该项目已进展到几乎所有代码都已迁移并清理完毕的阶段。计划是在七月中旬至八月初的 JDK 9 构建版本中将其公开发布。以下是所有已迁移到 javafx.css 的类的列表,它们作为公共 API:

CascadingStyle.java:public class CascadingStyle implements Comparable<CascadingStyle> {
CascadingStyle.java: public Style getStyle() {
CascadingStyle.java: public CascadingStyle(final Style style, Set<PseudoClass> pseudoClasses,
CascadingStyle.java: public String getProperty() {
CascadingStyle.java: public Selector getSelector() {
CascadingStyle.java: public Rule getRule() {
CascadingStyle.java: public StyleOrigin getOrigin() {
CascadingStyle.java: public ParsedValueImpl getParsedValueImpl() {

CompoundSelector.java:final public class CompoundSelector extends Selector {
CompoundSelector.java: public List<SimpleSelector> getSelectors() {
CompoundSelector.java: public CompoundSelector(List<SimpleSelector> selectors, List<Combinator> relationships)
CompoundSelector.java: public Match createMatch() {

CssError.java:public class CssError {
CssError.java: public static void setCurrentScene(Scene scene) {
CssError.java: public final String getMessage() {
CssError.java: public CssError(String message) {
CssError.java: public final static class PropertySetError extends CssError {
CssError.java: public PropertySetError(CssMetaData styleableProperty,

Declaration.java:final public class Declaration {
Declaration.java: public ParsedValue getParsedValue() {
Declaration.java: public String getProperty() {
Declaration.java: public Rule getRule() {

Rule.java:final public class Rule {
Rule.java: public final ObservableList<Declaration> getDeclarations() {
Rule.java: public final ObservableList<Selector> getSelectors() {
Rule.java: public Stylesheet getStylesheet() {
Rule.java: public StyleOrigin getOrigin() {

Selector.java:abstract public class Selector {
Selector.java: public Rule getRule() {
Selector.java: public void setOrdinal(int ordinal) {
Selector.java: public int getOrdinal() {
Selector.java: public abstract Match createMatch();
Selector.java: public abstract boolean applies(Styleable styleable);
Selector.java: public abstract boolean applies(Styleable styleable, Set<PseudoClass>[] triggerStates, int bit);
Selector.java: public abstract boolean stateMatches(Styleable styleable, Set<PseudoClass> state);
Selector.java: public static Selector createSelector(final String cssSelector) {
Selector.java: protected void writeBinary(DataOutputStream os, StringStore stringStore)

SimpleSelector.java:final public class SimpleSelector extends Selector {
SimpleSelector.java: public String getName() {
SimpleSelector.java: public List<String> getStyleClasses() {
SimpleSelector.java: public Set<StyleClass> getStyleClassSet() {
SimpleSelector.java: public String getId() {
SimpleSelector.java: public NodeOrientation getNodeOrientation() {

Size.java:final public class Size {
Size.java: public Size(double value, SizeUnits units) {
Size.java: public double getValue() {
Size.java: public SizeUnits getUnits() {
Size.java: public boolean isAbsolute() {
Size.java: public double pixels(double multiplier, Font font) {
Size.java: public double pixels(Font font) {
Size.java: public double pixels() {

Style.java:final public class Style {
Style.java: public Selector getSelector() {
Style.java: public Declaration getDeclaration() {
Style.java: public Style(Selector selector, Declaration declaration) {

Stylesheet.java:public class Stylesheet {
Stylesheet.java: public String getUrl() {
Stylesheet.java: public StyleOrigin getOrigin() {
Stylesheet.java: public void setOrigin(StyleOrigin origin) {
Stylesheet.java: public List<Rule> getRules() {
Stylesheet.java: public List<FontFace> getFontFaces() {
Stylesheet.java: public static Stylesheet loadBinary(URL url) throws IOException {
Stylesheet.java: public static void convertToBinary(File source, File destination) throws IOException {

CssParser.java:final public class CssParser {
CssParser.java: public CssParser() {
CssParser.java: public Stylesheet parse(final String stylesheetText) {
CssParser.java: public Stylesheet parse(final URL url) throws IOException {
CssParser.java: public Stylesheet parseInlineStyle(final Styleable node) {
CssParser.java: public ParsedValueImpl parseExpr(String property, String expr) {
CssParser.java: public static ObservableList<CssError> errorsProperty() {

概述

这两个项目的最终结果是创建了:

  • 一个新的用于 UI 控件外观的包 javafx.scene.control.skin,在经过审查、文档编写和测试后;

  • 必要的 CSS 相关类的迁移、审查、文档编写和测试。

测试

测试将仅限于那些没有特殊平台或硬件要求的附加单元测试。

风险与假设

主要风险是工作范围超出了预期。虽然已经做了一些研究来更好地理解需求,但不可否认的是,制作好的 API 将需要投入相当多的时间。