如何使用良好的旧CSS样式化JavaFX组件。
JavaFX系列中的所有文章:
- JavaFX教程:入门
- JavaFX教程:世界您好!
- JavaFX教程:FXML和SceneBuilder
- JavaFX教程:基本布局
- JavaFX教程:高级布局
- JavaFX教程:CSS样式
- JavaFX Weaver:集成JavaFX和Spring Boot应用程序
视觉元素的分离
在上
一篇有关FXML的文章中,我们了解了JavaFX如何通过将UI代码一分为二来提供清晰的任务分离。 组件及其属性在FXML文件中声明,并且交互逻辑已明确分配给控制器。
此外,还有第三部分,FXML语言,它仅控制应用程序的组件,它们的属性以及它们如何相互嵌入。 它没有定义组件的视觉元素,即:字体,颜色,背景,缩进。 通常,您可以在FXML中实现此目的,但您不应该这样做。 相反,视觉元素应在CSS样式表中明确定义。
因此,您的设计变得独立,可以轻松替换或更改而不会影响应用程序的其余部分。 您甚至可以简单地实现几个可以根据用户要求切换的主题。
的CSS
您可能熟悉用于在网络上设置HTML页面样式的CSS(级联样式表)。 尽管JavaFX使用一组自己的自定义属性,但在JavaFX中实现了类似的方法。
让我们看一个例子:
.button { -fx-font-size: 15px; }
这里使用两个主要概念。 第一个是
.button选择器。 它确定样式应应用于哪些组件。 在此示例中,样式应用于所有按钮。
第二部分是样式的实际属性,它将应用于与我们的选择器匹配的所有组件。 属性是花括号中的所有内容。
每个属性都有特定的含义。 在我们的示例中,有
-fx-font-size属性,该属性确定文本
的大小 。 在示例中,该值为
15px ,但是该值可以是其他任何值。
总而言之,我们创建了一条规则,规定所有按钮都应包含15个像素的文本。
选择器
现在,让我们仔细看看选择器在JavaFX中的工作方式。 这种情况几乎与常规CSS相同。
班级
CSS中的类表示几个相似的元素。 例如,按钮或复选框。 选择器应应用于同一个类的所有元素,选择器以点“。”开头,后跟类名。 类的命名约定是用“-”字符分隔各个单词。 以下选择器适用于带有
label类的所有元素。
.label { // Some properties }
内置类
好消息是,所有JavaFX内置组件(例如Label或Button)已经具有预定义的类。 如果要自定义应用程序中所有标签的样式,则无需为每个标签添加任何自定义类。 默认情况下,每个标签都有一个
标签类。
从组件确定类名很容易:
- 以Java组件类的名称为例。 标签
- 将名称改为小写
- 如果它由多个单词组成,请用“-”符号将它们分开
一些例子:
使用选择器之类的类时,请确保添加“。”。 这意味着
标签类的选择器是
.label 。
自订课程
如果内置的类还不够,则可以将自己的类添加到组件中。 您可以使用多个以逗号分隔的类:
<Label styleClass="my-label,other-class">I am a simple label</Label>
或在Java中:
Label label = new Label("I am a simple label"); label.getStyleClass().addAll("my-label", "other-class");
默认情况下,以这种方式添加类不会删除组件类(在这种情况下为
label )。
有一个叫做
root的特殊类。 它是场景的根源。 您可以使用它来设置场景中所有内容的样式(例如,设置全局字体)。 这类似于在HTML中使用body标签选择器。
编号
在CSS中选择组件的另一种方法是使用组件标识符(ID)。 它是组件的唯一标识符。 与可以分配给多个组件的类不同,标识符在场景中必须唯一。
而符号“。”用于指示类。 在其选择器名称的前面,标识符用符号“#”标记。
#my-component { ... }
在FXML中,可以使用
fx:id设置组件的CSS标识符。
<Label fx:id="foo">I am a simple label</Label>
但是,有一个警告。 相同的
标识符用于引用在控制器中声明的具有相同名称
的组件对象 。 由于控制器中的标识符和字段名称必须匹配,因此
fx:id必须考虑字段名称的Java命名限制。 尽管CSS命名约定定义了由“-”字符分隔的单个单词,但对于Java字段名称而言,它是无效字符。 因此,对于带有少量单词的
fx:id ,您需要使用其他命名约定,例如CamelCase,或使用下划线。
<Label fx:id="my-label">I am a simple label</Label> <Label fx:id="my_label">I am a simple label</Label> <Label fx:id="MyLabel">I am a simple label</Label>
在Java中,您可以简单地调用组件的
setId()方法。
Label label = new Label("I am a simple label"); label.setId("foo");
物产
尽管JavaFX中使用的CSS与原始的Web CSS非常相似,但还是有很大的不同。 属性名称不同,并且有许多新的JavaFX特定属性。 它们具有前缀
-fx- 。
以下是一些示例:
- -fx-background-color :背景色
- -fx-text-fill :文本颜色
- -fx-font-size :文字大小
您可以在
官方设计指南中找到所有属性的列表。
伪类
除了标记特定组件的常用类之外,还有指示组件状态的所谓伪类。 例如,这可以是用于标记组件具有焦点或鼠标光标位于其上的类。
有许多内置的伪类。 让我们看一下按钮。 您可以使用几种伪类,例如:
- 悬停 :将鼠标悬停在按钮上
- 有焦点 :按钮有焦点
- 禁用 :按钮已禁用
- 按下 :按下按钮
伪类以CSS选择器中的“:”字符(例如
:: hover )开头。 当然,您需要指定您的伪类属于哪个组件-例如,
button:hover 。 下面的示例显示一个选择器,该选择器适用于所有具有焦点的按钮:
.button:focused { -fx-background-color: red; }
与CSS仅有状态(例如
focus和
hover)的基本伪类的CSS不同,JavaFX具有与组件不同的状态或属性相关的特定于组件的伪类。
例如:
- 滚动条具有水平和垂直伪类
- 元素(单元)具有伪类奇数和偶数
- TitledPane已扩展和折叠伪类。
自定义伪类
除了内置的伪类,您还可以定义和使用自己的伪类。
让我们创建自己的标签(从Label类继承)。 它将具有一个称为
Shiny的新逻辑属性。 在这种情况下,我们希望我们的标签具有
闪亮的伪
类 。
由于标签具有
闪亮的伪
类 ,我们可以设置
金色标签的背景:
.shiny-label:shiny { -fx-background-color: gold; }
现在创建类本身。
public class ShinyLabel extends Label { private BooleanProperty shiny; public ShinyLabel() { getStyleClass().add("shiny-label"); shiny = new SimpleBooleanProperty(false); shiny.addListener(e -> { pseudoClassStateChanged(PseudoClass.getPseudoClass("shiny"), shiny.get()); }); } public boolean isShiny() { return shiny.get(); } public void setShiny(boolean shiny) { this.shiny.set(shiny); } }
这里有几个重要部分:
- 我们具有BooleanProperty 布尔属性,而不是通常的boolean 。 这意味着有光泽的对象是可观察到的,并且我们可以跟踪(监听)其值的变化。
- 我们注册一个侦听器,每当闪亮对象的值更改时,就会使用shiny.addListener()进行调用 。
- 当闪亮值更改时,我们将根据pseudoClassStateChanged(PseudoClass.getPseudoClass(“ shiny”),shiny.get())的当前值添加/删除闪亮伪类 。
- 我们为所有闪亮标签标签添加一个自定义类,而不是仅从父类继承标签类。 因此,我们只能选择闪亮的标签。
默认样式表
即使您自己不提供任何样式,每个JavaFX应用程序也已经具有一些视觉样式。 有一个默认样式表适用于每个应用程序。 它称为
Modena (从JavaFX 8开始,以前称为
caspian )。
可以在文件中找到此样式表:
jfxrt.jar \ com \ sun \ javafx \ scene \ control \ skin \ modena \ modena.css或者您可以在
这里找到文件。 在同一目录中,样式表使用了许多图像。
此样式表提供默认样式,但其优先级低于其他类型的样式表,因此您可以轻松地覆盖它。
场景样式表
除了上述默认样式表之外,您当然可以提供自己的样式表。 可以应用样式化的最高级别是整个场景。 您可以在FXML中实现此功能:
<BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" stylesheets="styles.css" ... > ... </BorderPane>
或在您的Java代码中:
String stylesheet = getClass().getResource("/styles.css").toExternalForm(); scene.getStylesheets().add(stylesheet);
注意对
externalForm()的调用。 Scene希望以字符串而不是文件的形式获取样式表的内容,因此我们需要以字符串的形式提供样式表的内容。
父样式表
除了整个场景的样式表之外,有时在布局级别上使用样式也很有用。 也就是说-对于单独的容器,例如VBox,HBox或GridPane。 所有布局的共同父对象是父类,该类定义用于在布局级别处理样式表的方法。 这些样式仅适用于此布局中的组件,不适用于整个场景。 布局级别的样式优先于场景级别的样式。
<HBox stylesheets="styles.css"> ... </HBox>
在Java中,您需要自己加载样式表的内容,就像场景之前一样:
HBox box = new HBox(); String stylesheet = getClass().getResource("/styles.css").toExternalForm(); box.getStylesheets().add(stylesheet);
内联样式
到目前为止,我们仅研究了将外部样式表分配给整个场景或布局的情况。 但是您可以在组件级别设置单个样式属性。
在这里,您不必担心选择器,因为所有属性都是为特定组件设置的。
您可以指定用分号分隔的几个属性:
<Label style="-fx-background-color: blue; -fx-text-fill: white"> I'm feeling blue. </Label>
在Java中,可以使用
setStyle()方法:
Label label = new Label("I'm feeling blue."); label.setStyle("-fx-background-color: blue; -fx-text-fill: white");
组件级别的样式优先于场景样式以及布局级别的父样式。
为什么需要避免它们
组件级样式可以很方便,但这是一种快速而肮脏的解决方案。 您将放弃CSS的主要优点,即将样式与组件分离。 现在,您将视觉元素直接绑定到组件。 您不再能在必要时轻松切换样式表,也无法更改主题。
而且,您不再需要在单个中心位置定义样式。 当需要在一组相似的组件中进行更改时,需要分别更改每个组件,而不是仅在外部样式表中编辑一个位置。 因此,应避免内联组件样式。
样式表优先级
您可以在多个级别上提供样式-场景,父样式,内联样式,并且还有默认的调制解调器样式表。 如果您在多个级别上更改同一组件的相同属性,则JavaFX具有优先级设置,该设置确定应使用的样式。 优先级列表-从最高到最低:
- 内联样式
- 父样式
- 场景样式
- 默认样式
这意味着,如果您在内联和场景级都设置了某个标签的背景色,则JavaFX将使用内联样式中设置的值,因为它具有更高的优先级。
补充阅读
JavaFX具有许多CSS属性,其描述超出了本文的范围;有关详细列表,请参见
JavaFX的
官方CSS参考指南 。