JavaFX教程:CSS样式

如何使用良好的旧CSS样式化JavaFX组件。

JavaFX系列中的所有文章:

  1. JavaFX教程:入门
  2. JavaFX教程:世界您好!
  3. JavaFX教程:FXML和SceneBuilder
  4. JavaFX教程:基本布局
  5. JavaFX教程:高级布局
  6. JavaFX教程:CSS样式
  7. 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,或使用下划线。

 <!-- This is not valid --> <Label fx:id="my-label">I am a simple label</Label> <!-- This is valid --> <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仅有状态(例如focushover)的基本伪类的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); } } 

这里有几个重要部分:

  1. 我们具有BooleanProperty 布尔属性,而不是通常的boolean 。 这意味着有光泽的对象是可观察到的,并且我们可以跟踪(监听)其值的变化。
  2. 我们注册一个侦听器,每当闪亮对象的值更改时,就会使用shiny.addListener()进行调用
  3. 闪亮值更改时,我们将根据pseudoClassStateChanged(PseudoClass.getPseudoClass(“ shiny”),shiny.get())的当前值添加/删除闪亮
  4. 我们为所有闪亮标签标签添加一个自定义类,而不是仅从父类继承标签类。 因此,我们只能选择闪亮的标签。

默认样式表


即使您自己不提供任何样式,每个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具有优先级设置,该设置确定应使用的样式。 优先级列表-从最高到最低:

  1. 内联样式
  2. 父样式
  3. 场景样式
  4. 默认样式

这意味着,如果您在内联和场景级都设置了某个标签的背景色,则JavaFX将使用内联样式中设置的值,因为它具有更高的优先级。

补充阅读


JavaFX具有许多CSS属性,其描述超出了本文的范围;有关详细列表,请参见JavaFX官方CSS参考指南

Source: https://habr.com/ru/post/zh-CN477924/


All Articles