如何正确地将 @DefaultProperty 注释添加到使用 @NamedArg 的自定义 class
How to properly add @DefaultProperty annotation to custom class that uses @NamedArg
我创建了一个自定义 class 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
可用于解包默认标签(例如,您可以省略 <Pane>
标签内的 <children>
标签,因为它具有@DefaultProperty("children")
注解)所以我用了:
@DefaultProperty("image")
open class ImageButton(...) {...}
但是当加载 fxml 文件时出现以下错误:
javafx.fxml.LoadException: Element does not define a default property.
我做了一些研究并发现了这个:
但是,它不包含解决方案。它只是说明了问题。
所以我的问题是:
是否可以在使用 @NamedArg
注释的自定义 class 上使用 @DefaultProperty
注释?
如果是,我该如何实现?
如果不是,我是否应该尝试以不同的方式构造我的 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 控件
首先,让我们定义我们的控件。为了简单起见(也因为这些不能被覆盖),我不会包括 width
和 height
:
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.
有关更多详细信息,为什么会发生此异常,请参阅链接 。
基本上,正如评论中所讨论的,@DefaultProperty
和 @NamedArg
不能一起工作:为了扩展给定 class、@NamedArg
的 FXML 属性为此 class 提供新的构造函数,这需要使用 ProxyBuilder
,因此 FXMLLoader
将使用 ProxyBuilder
的实例,而这些不包括 @DefaultProperty
注解.
建设者
尽管在 JavaFX 2.0 中使用了构建器设计模式,但很久以前就已弃用(在 Java 8 中,在 Java 9 中删除,link), 当前 JavaFX 代码中还有一些 builders.
事实上,FXMLLoader
使用 JavaFXBuilderFactory
作为默认的构建器工厂,如果在 [=151] 中找到 NamedArg
注释,它将调用此 ProxyBuilder
=] 构造函数,以及 JavaFXImageBuilder
.
等其他构建器
有一些关于建造者的描述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
与上面的 class 相同(没有 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
调用,它也会起作用(使用 NamedArg
和 ProxyBuilder
)。对于自定义构建器工厂,它不会使用 ProxyBuilder
,而是我们的自定义构建器。
最后一步
最后,我们可以利用 DefaultProperty
去掉 <image>
标签。
并且我们会将注释添加到构建器 class,而不是控件!
所以现在我们有:
@DefaultProperty("image")
public class ImageButtonBuilder extends AbstractMap<String, Object> implements Builder<ImageButton> {
...
}
最后我们可以从 FXML 文件中删除 <image>
标签:
<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>
它会起作用的!
我创建了一个自定义 class 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
可用于解包默认标签(例如,您可以省略 <Pane>
标签内的 <children>
标签,因为它具有@DefaultProperty("children")
注解)所以我用了:
@DefaultProperty("image")
open class ImageButton(...) {...}
但是当加载 fxml 文件时出现以下错误:
javafx.fxml.LoadException: Element does not define a default property.
我做了一些研究并发现了这个:
但是,它不包含解决方案。它只是说明了问题。
所以我的问题是:
是否可以在使用 @NamedArg
注释的自定义 class 上使用 @DefaultProperty
注释?
如果是,我该如何实现?
如果不是,我是否应该尝试以不同的方式构造我的 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 控件
首先,让我们定义我们的控件。为了简单起见(也因为这些不能被覆盖),我不会包括 width
和 height
:
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.
有关更多详细信息,为什么会发生此异常,请参阅链接
基本上,正如评论中所讨论的,@DefaultProperty
和 @NamedArg
不能一起工作:为了扩展给定 class、@NamedArg
的 FXML 属性为此 class 提供新的构造函数,这需要使用 ProxyBuilder
,因此 FXMLLoader
将使用 ProxyBuilder
的实例,而这些不包括 @DefaultProperty
注解.
建设者
尽管在 JavaFX 2.0 中使用了构建器设计模式,但很久以前就已弃用(在 Java 8 中,在 Java 9 中删除,link), 当前 JavaFX 代码中还有一些 builders.
事实上,FXMLLoader
使用 JavaFXBuilderFactory
作为默认的构建器工厂,如果在 [=151] 中找到 NamedArg
注释,它将调用此 ProxyBuilder
=] 构造函数,以及 JavaFXImageBuilder
.
有一些关于建造者的描述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
与上面的 class 相同(没有 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
调用,它也会起作用(使用 NamedArg
和 ProxyBuilder
)。对于自定义构建器工厂,它不会使用 ProxyBuilder
,而是我们的自定义构建器。
最后一步
最后,我们可以利用 DefaultProperty
去掉 <image>
标签。
并且我们会将注释添加到构建器 class,而不是控件!
所以现在我们有:
@DefaultProperty("image")
public class ImageButtonBuilder extends AbstractMap<String, Object> implements Builder<ImageButton> {
...
}
最后我们可以从 FXML 文件中删除 <image>
标签:
<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>
它会起作用的!