java - 如何正确地将@DefaultProperty 注释添加到使用@NamedArg 的自定义类

标签 java javafx kotlin fxml fxmlloader

我创建了一个自定义类 ImageButton摆脱一些样板代码

open class ImageButton(@NamedArg("image") image: Image,
                       @NamedArg("tooltipText") tooltipText: String,
                       @NamedArg("width") width: Double, 
                       @NamedArg("height") height: Double) : Button() {
    init {
        prefWidth = width
        minWidth = NEGATIVE_INFINITY
        maxWidth = NEGATIVE_INFINITY

        prefHeight = height
        minHeight = NEGATIVE_INFINITY
        maxHeight = NEGATIVE_INFINITY

        cursor = ImageCursor.HAND
        effect = ImageInput(image)
        tooltip = Tooltip(tooltipText)
    }
}

现在,代替这个:

<Button fx:id="deleteButton" prefWidth="32.0" prefHeight="32.0" 
        minHeight="-Infinity" maxHeight="-Infinity"
        minWidth="-Infinity" maxWidth="-Infinity" 
        onMouseClicked="#deleteThePiece">
    <cursor>
        <ImageCursor fx:constant="HAND"/>
    </cursor>
    <tooltip>
        <Tooltip text="Delete Current Piece"/>
    </tooltip>
    <effect>
        <ImageInput>
            <source>
                <Image url="@/icons/delete.png"/>
            </source>
        </ImageInput>
    </effect>
</Button>

我可以这样写:

<ImageButton fx:id="deleteButton" width="32.0" height="32.0"
        onMouseClicked="#deleteThePiece" tooltipText="Delete Current Piece">
    <image>
        <Image url="@/icons/dice.png"/>
    </image>
</ImageButton>

但是,我希望能够进一步缩短它,像这样:

<ImageButton fx:id="deleteButton" width="32.0" height="32.0"
        onMouseClicked="#deleteThePiece" tooltipText="Delete Current Piece">
    <Image url="@/icons/dice.png"/>
</ImageButton>

我有很多这样的对象,所以能够使 fxml 标签尽可能短会很好。

我知道有一个注解@DefaultProperty可用于解包默认标签(例如,您可以省略 <children> 标签内的 <Pane> 标签,因为它具有 @DefaultProperty("children") 注释)所以我使用了它:

@DefaultProperty("image")
open class ImageButton(...) {...}

但随后在加载 fxml 文件时出现以下错误:

javafx.fxml.LoadException: Element does not define a default property.

我做了一些研究并发现了这个:

"Element does not define a default property" when @DefaultProperty is used

但是,它不包含解决方案。它只是说明了问题。

所以我的问题是:

是否可以使用 @DefaultProperty使用 @NamedArg 的自定义类上的注释注解?

如果是,我该如何实现?

如果不是,我应该尝试构建我的 ImageButton对象不同?例如使用 <fx:factory>

最佳答案

这里有几个选项,但@Slaw 在评论中指出了更简单的选项:

Use a String URL in the constructor instead of the Image

所以这应该可行:

public ImageButton(@NamedArg("url") String url, @NamedArg("text") String text) {
    setEffect(new ImageInput(new Image(url)));
    setText(text);
}

像这样的 FXML:

<ImageButton fx:id="imageButton" text="Click Me!" url="@icon.png"/>

现在让我们探讨一下 <image /> 的使用结合@DefaultProperty .

ImageButton 控件

首先,让我们定义我们的控件。为了简单起见(也因为这些不能被覆盖),我不会包括 widthheight :

public class ImageButton extends Button {

    public ImageButton(@NamedArg("image") Image image, @NamedArg("text") String text) {
        setImage(image);
        setText(text);
    }

    private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image") {
        @Override
        protected void invalidated() {
            // setGraphic(new ImageView(get()));
            setEffect(new ImageInput(get()));
        }
    };

    public final ObjectProperty<Image> imageProperty() {
       return image;
    }

    public final Image getImage() {
       return image.get();
    }

    public final void setImage(Image value) {
        image.set(value);
    }
}

和:

<ImageButton fx:id="imageButton" text="Click Me!">
     <image>
         <Image url="@icon.png"/>
     </image>
</ImageButton>

将工作得很好。但是,目的是删除 <image>标签。

默认属性

理论说你可以这样做:

@DefaultProperty("image")
public class ImageButton extends Button {
...
}

<ImageButton fx:id="imageButton" text="Click Me!">
     <Image url="@icon.png"/>
</ImageButton>

但是,抛出一个异常:

Caused by: javafx.fxml.LoadException: Element does not define a default property.

有关更多详细信息,为什么会发生此异常,请参阅链接 question .

基本上,正如评论中所讨论的那样,@DefaultProperty@NamedArg不要一起工作:为了扩展给定类的 FXML 属性,@NamedArg为此类提供新的构造函数,这需要使用 ProxyBuilder , 所以 FXMLLoader将使用 ProxyBuilder 的实例相反,这些不包括 @DefaultProperty注释。

build 者

虽然在 JavaFX 2.0 中使用了构建器设计模式,并且它在很久以前就被弃用了(在 Java 8 中,在 Java 9 中被删除,link),但仍然有一些构建器在当前的 JavaFX 代码。

事实上,FXMLLoader利用 JavaFXBuilderFactory ,作为默认的构建器工厂,它将调用此 ProxyBuilder如果NamedArg在类构造函数中可以找到注释,以及其他构建器,如 JavaFXImageBuilder .

有一些关于 builder 的描述here .

构建器实现

我们如何添加自己的构建器工厂? FXMLLoader有办法: setBuilderFactory .

我们可以延长JavaFXBuilderFactory吗? ?不,它是最终的,我们不能扩展它,我们必须从头开始创建一个。

ImageButtonBuilderFactory

让我们创建它:

import javafx.util.Builder;
import javafx.util.BuilderFactory;

public class ImageButtonBuilderFactory implements BuilderFactory {

    @Override
    public Builder<?> getBuilder(Class<?> type) {
        if (type == null) {
            throw new NullPointerException();
        }
        if (type == ImageButton.class) {
            return ImageButtonBuilder.create();
        }
        return null;
    }
}

现在让我们添加构建器:

ImageButtonBuilder

import javafx.scene.image.Image;
import javafx.util.Builder;

import java.util.AbstractMap;
import java.util.HashSet;
import java.util.Set;

public class ImageButtonBuilder extends AbstractMap<String, Object> implements Builder<ImageButton> {

    private String text = "";
    private Image image;

    private ImageButtonBuilder() {}

    @Override
    public Set<Entry<String, Object>> entrySet() {
        return new HashSet<>();
    }

    public static ImageButtonBuilder create() {
        return new ImageButtonBuilder();
    }

    @Override
    public Object put(String key, Object value) {
        if (value != null) {
            String str = value.toString();

            if ("image".equals(key)) {
                image = (Image) value;
            } else if ("text".equals(key)) {
                text = str;
            } else {
                throw new IllegalArgumentException("Unknown property: " + key);
            }
        }

        return null;
    }

    @Override
    public ImageButton build() {
        return new ImageButton(image, text);
    }

}

请注意 ImageButton与上面的类相同(没有 DefaultProperty 注释)。

使用自定义构建器

现在我们可以使用自定义构建器了:

FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("imagebutton.fxml"));
loader.setBuilderFactory(new ImageButtonBuilderFactory());
Parent root = loader.load();
Scene scene = new Scene(root);
...

FXML 在哪里:

<StackPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11" xmlns:fx="http://javafx.com/fxml/1">
      <ImageButton fx:id="imageButton" text="Click Me!">
            <image>
                  <Image url="@icon.png"/>
            </image>
      </ImageButton>
</StackPane>

如果我们现在运行它,它应该可以工作。我们已经验证了我们的新生成器可以正常工作。如果我们注释掉 setBuilderFactory打电话,它也会工作(使用 NamedArgProxyBuilder )。对于自定义生成器工厂,它不会使用 ProxyBuilder但是我们的自定义生成器。

最后一步

最后,我们可以利用DefaultProperty摆脱 <image>标签。

并且我们会将注释添加到构建器类,而不是控件!

现在我们有:


@DefaultProperty("image")
public class ImageButtonBuilder extends AbstractMap<String, Object> implements Builder<ImageButton> {
...
}

最后我们可以删除 <image> FXML 文件中的标记:

<StackPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11" xmlns:fx="http://javafx.com/fxml/1">
      <ImageButton fx:id="imageButton" text="Click Me!">
            <Image url="@icon.png"/>
      </ImageButton>
</StackPane>

它会起作用的!

关于java - 如何正确地将@DefaultProperty 注释添加到使用@NamedArg 的自定义类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57590068/

相关文章:

java - setOnPreferenceClickListener 帮助, Activity 启动时关闭

java - 尝试在模态对话框中更新 JLabel

java - Java FX 中的数据库连接最佳实践

java - 在数组中的字符串之间添加空格

kotlin - 为什么在Kotlin中不需要在Room中的查询功能添加suspended关键字?

java - 是否有实现 JCR API 的 ModeShape Java 客户端?

java - 如何使用 WindowBuilder 将选项卡添加到 JTabbedPane

android - 在 kotlin android 中显示 Unresolved 引用

macos - JavaFX native bundle 图标 OS X

android - RxJava toList() 不触发 subscribe() 方法(使用 Room)