我创建了一个自定义类 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 控件
首先,让我们定义我们的控件。为了简单起见(也因为这些不能被覆盖),我不会包括 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.
有关更多详细信息,为什么会发生此异常,请参阅链接 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
打电话,它也会工作(使用 NamedArg
和 ProxyBuilder
)。对于自定义生成器工厂,它不会使用 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/