您可以将变量传递给 FXML 中的 Insets 吗?
Can you pass through a variable to Insets in FXML?
我正在做一些工作,要求我根据标量值为元素设置不同的值。我的工作是在一个名为 Sizer 的 class 中使用一个名为 getScalar() 的方法得出标量值,其中 returns 是一个双精度值。下面是我现在拥有的部分 FXML。
<VBox id="mainWindow" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller">
<fx:define>
<Sizer fx:id="SCALAR" fx:factory="getScalar"/>
</fx:define>
<children>
<HBox alignment="CENTER_LEFT">
<children>
<Label fx:id="titleLabel" text="Label" HBox.hgrow="ALWAYS" />
<HBox fx:id="titleBar" HBox.hgrow="ALWAYS" />
</children>
<padding>
<!-- This part DOESN'T work at runtime -->
<Insets left="${SCALAR * 10.0}" right="${SCALAR * 10.0}" top="${SCALAR * 5.0}" />
</padding>
</HBox>
<!-- This part DOES work at runtime -->
<Separator prefWidth="${SCALAR * 200.0}" />
</children>
</Vbox>
在此代码中,如果我取出 Insets 引用并将这些值设置为 10.0、5.0 等,它会在运行时运行并出现我的对话框。使用 SCALAR 的 'prefWidth' 设置似乎工作正常,但是当我添加对 Insets 值的引用时,它会抛出错误并指向带有 Insets 的行。
javafx.fxml.LoadException: Cannot bind to untyped object.
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2621)
at javafx.fxml.FXMLLoader.access0(FXMLLoader.java:105)
at javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:306)
at javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:242)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:757)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2722)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2552)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2466)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2435)
我的理解
你可以通过定义块创建这些定义对象,然后通过表达式绑定和变量解析在其他属性中引用它们,但不知何故我不能用Insets做到这一点。我发现的唯一一件事是 'prefWidth' 有一个 setter 取双,而 'left' 或其他带有 Insets 的位置有吸气剂。我想它可能会在绑定到一个似乎没有 setter.
的对象时出现问题
任何帮助将不胜感激,因为我对 FXML 不是那么了解,而且我来自 JavaFX 世界,在那里这要容易得多。
编辑
下面是Sizer的内容class
public static Toolkit getToolKit() {
return Toolkit.getDefaultToolkit();
}
public static Dimension getScreenSize() {
return getToolKit().getScreenSize();
}
public static int getPercentOfHeight(double percent) {
return (int)(getScreenSize().getHeight() * percent / 100.0);
}
public static int getPercentOfWidth(double percent) {
return (int)(getScreenSize().getWidth() * percent / 100.0);
}
public static double getScaleFactor() {
if ((int) getScreenSize().getHeight() > 1800) return 2.0; //4K
else if ((int)getScreenSize().getHeight() > 1260) return 1.33; //1440p
else if ((int)getScreenSize().getHeight() > 900) return 1.0; //1080p
else return 0.667; //720p
}
${}
语法用于创建 expression binding,后者又用于将 Property
绑定到生成的 ObservableValue
。您的代码的问题是 Insets
不会将其状态公开为 Property
实例。最重要的是,Insets
class 是不可变的,这意味着状态是通过构造函数之一设置的,两个构造函数只接受 double
参数。您不能将表达式绑定的结果作为 double
传递,并且 FXMLLoader
不会 "cleverly" 在创建 Insets
实例时提取当前值。
据我了解,您想将 Insets
的每一边乘以一个 double
值,这只需要发生一次(即您不需要更新值随着时间的推移)。在这种情况下,一种解决方案是将 subclass Insets
和 "add a parameter" 分配给标量参数的每个构造函数。
package com.example;
import javafx.beans.NamedArg;
import javafx.geometry.Insets;
public class ScaledInsets extends Insets {
public ScaledInsets(@NamedArg(value = "scale", defaultValue = "1") double scale,
@NamedArg("top") double top, @NamedArg("right") right,
@NamedArg("bottom") double bottom, @NamedArg("left") double left) {
super(scale * top, scale * right, scale * bottom, scale * left);
}
public ScaledInsets(@NamedArg(value = "scale", defaultValue = "1") double scale,
@NamedArg("topRightBottomLeft") double topRightBottomLeft) {
super(scale * topRightBottomLeft);
}
}
这将允许您在 FXML 文件中使用以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<?import com.example.ScaledInsets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>
<StackPane xmlns="http://javafx.com/javafx/" xmlns:fx="http://javafx.com/fxml">
<fx:define>
<Sizer fx:id="SCALAR" fx:factory="getScaleFactor"/>
</fx:define>
<padding>
<ScaledInsets scale="$SCALAR" top="5" left="10" right="10"/>
</padding>
<Label text="Hello, World!"/>
</StackPane>
注意 FXML 也支持 scripting:
The <fx:script>
tag allows a caller to import scripting code into or embed script within a FXML file. Any JVM scripting language can be used, including JavaScript, Groovy, and Clojure, among others. Script code is often used to define event handlers directly in markup or in an associated source file, since event handlers can often be written more concisely in more loosely-typed scripting languages than they can in a statically-typed language such as Java.
我以前从未在 FXML 中使用过脚本,目前不知道如何在示例中使用它。可以这么说,你可以随意使用它,但我不知道脚本是否是一个可行的解决方案。
与您的问题无关,但您似乎在使用 AWT classes 来获取屏幕尺寸。 JavaFX 为此提供了自己的 API:javafx.stage.Screen
。您可以使用:
Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
获取主屏幕的尺寸。要获得整个屏幕的尺寸,而不仅仅是视觉区域,请使用 getBounds()
而不是 getVisualBounds()
。
我正在做一些工作,要求我根据标量值为元素设置不同的值。我的工作是在一个名为 Sizer 的 class 中使用一个名为 getScalar() 的方法得出标量值,其中 returns 是一个双精度值。下面是我现在拥有的部分 FXML。
<VBox id="mainWindow" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller">
<fx:define>
<Sizer fx:id="SCALAR" fx:factory="getScalar"/>
</fx:define>
<children>
<HBox alignment="CENTER_LEFT">
<children>
<Label fx:id="titleLabel" text="Label" HBox.hgrow="ALWAYS" />
<HBox fx:id="titleBar" HBox.hgrow="ALWAYS" />
</children>
<padding>
<!-- This part DOESN'T work at runtime -->
<Insets left="${SCALAR * 10.0}" right="${SCALAR * 10.0}" top="${SCALAR * 5.0}" />
</padding>
</HBox>
<!-- This part DOES work at runtime -->
<Separator prefWidth="${SCALAR * 200.0}" />
</children>
</Vbox>
在此代码中,如果我取出 Insets 引用并将这些值设置为 10.0、5.0 等,它会在运行时运行并出现我的对话框。使用 SCALAR 的 'prefWidth' 设置似乎工作正常,但是当我添加对 Insets 值的引用时,它会抛出错误并指向带有 Insets 的行。
javafx.fxml.LoadException: Cannot bind to untyped object.
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2621)
at javafx.fxml.FXMLLoader.access0(FXMLLoader.java:105)
at javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:306)
at javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:242)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:757)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2722)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2552)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2466)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2435)
我的理解
你可以通过定义块创建这些定义对象,然后通过表达式绑定和变量解析在其他属性中引用它们,但不知何故我不能用Insets做到这一点。我发现的唯一一件事是 'prefWidth' 有一个 setter 取双,而 'left' 或其他带有 Insets 的位置有吸气剂。我想它可能会在绑定到一个似乎没有 setter.
的对象时出现问题任何帮助将不胜感激,因为我对 FXML 不是那么了解,而且我来自 JavaFX 世界,在那里这要容易得多。
编辑 下面是Sizer的内容class
public static Toolkit getToolKit() {
return Toolkit.getDefaultToolkit();
}
public static Dimension getScreenSize() {
return getToolKit().getScreenSize();
}
public static int getPercentOfHeight(double percent) {
return (int)(getScreenSize().getHeight() * percent / 100.0);
}
public static int getPercentOfWidth(double percent) {
return (int)(getScreenSize().getWidth() * percent / 100.0);
}
public static double getScaleFactor() {
if ((int) getScreenSize().getHeight() > 1800) return 2.0; //4K
else if ((int)getScreenSize().getHeight() > 1260) return 1.33; //1440p
else if ((int)getScreenSize().getHeight() > 900) return 1.0; //1080p
else return 0.667; //720p
}
${}
语法用于创建 expression binding,后者又用于将 Property
绑定到生成的 ObservableValue
。您的代码的问题是 Insets
不会将其状态公开为 Property
实例。最重要的是,Insets
class 是不可变的,这意味着状态是通过构造函数之一设置的,两个构造函数只接受 double
参数。您不能将表达式绑定的结果作为 double
传递,并且 FXMLLoader
不会 "cleverly" 在创建 Insets
实例时提取当前值。
据我了解,您想将 Insets
的每一边乘以一个 double
值,这只需要发生一次(即您不需要更新值随着时间的推移)。在这种情况下,一种解决方案是将 subclass Insets
和 "add a parameter" 分配给标量参数的每个构造函数。
package com.example;
import javafx.beans.NamedArg;
import javafx.geometry.Insets;
public class ScaledInsets extends Insets {
public ScaledInsets(@NamedArg(value = "scale", defaultValue = "1") double scale,
@NamedArg("top") double top, @NamedArg("right") right,
@NamedArg("bottom") double bottom, @NamedArg("left") double left) {
super(scale * top, scale * right, scale * bottom, scale * left);
}
public ScaledInsets(@NamedArg(value = "scale", defaultValue = "1") double scale,
@NamedArg("topRightBottomLeft") double topRightBottomLeft) {
super(scale * topRightBottomLeft);
}
}
这将允许您在 FXML 文件中使用以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<?import com.example.ScaledInsets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>
<StackPane xmlns="http://javafx.com/javafx/" xmlns:fx="http://javafx.com/fxml">
<fx:define>
<Sizer fx:id="SCALAR" fx:factory="getScaleFactor"/>
</fx:define>
<padding>
<ScaledInsets scale="$SCALAR" top="5" left="10" right="10"/>
</padding>
<Label text="Hello, World!"/>
</StackPane>
注意 FXML 也支持 scripting:
The
<fx:script>
tag allows a caller to import scripting code into or embed script within a FXML file. Any JVM scripting language can be used, including JavaScript, Groovy, and Clojure, among others. Script code is often used to define event handlers directly in markup or in an associated source file, since event handlers can often be written more concisely in more loosely-typed scripting languages than they can in a statically-typed language such as Java.
我以前从未在 FXML 中使用过脚本,目前不知道如何在示例中使用它。可以这么说,你可以随意使用它,但我不知道脚本是否是一个可行的解决方案。
与您的问题无关,但您似乎在使用 AWT classes 来获取屏幕尺寸。 JavaFX 为此提供了自己的 API:javafx.stage.Screen
。您可以使用:
Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
获取主屏幕的尺寸。要获得整个屏幕的尺寸,而不仅仅是视觉区域,请使用 getBounds()
而不是 getVisualBounds()
。