Учебник по JavaFX: CSS-стилизация

в 18:54, , рубрики: java, javafx

Как стилизовать компоненты JavaFX, используя старый добрый CSS.

Все посты в серии о JavaFX:

  1. Учебник по JavaFX: начало работы
  2. Учебник по JavaFX: Hello world!
  3. Учебник по JavaFX: FXML и SceneBuilder
  4. Учебник по JavaFX: основные макеты
  5. Учебник по JavaFX: расширенные макеты
  6. Учебник по JavaFX: CSS-стилизация
  7. JavaFX Weaver: интеграция JavaFX и Spring Boot приложения

Разделение визуальных элементов

В предыдущей статье о FXML мы узнали, как JavaFX обеспечивает четкое разделение задач путем разделения кода пользовательского интерфейса на две части. Компоненты и их свойства объявлены в файле FXML, а логика взаимодействия четко выделена в контроллер.

Кроме этого, есть и третья часть, язык FXML, управляющий только компонентами вашего приложения, их свойствами и тем, как они вложены друг в друга. Он не определяет визуальные элементы компонента, а именно: шрифты, цвета, фоны, отступы. В целом вы можете достичь этого в FXML, но не стоит этого делать. Вместо этого визуальные элементы должны быть четко определены в таблицах стилей CSS.

Таким образом, ваш дизайн становится независимым и может быть легко заменен или изменен без ущерба для остальной части приложения. Вы можете даже просто реализовать несколько тем, которые можно переключать по желанию пользователя.

CSS

Вы, вероятно, знакомы с CSS (Cascading Style Sheets — Каскадные таблицы стилей), используемыми для стилизации HTML-страниц в Интернете. Похожий подход реализован в JavaFX, несмотря на то, что JavaFX использует набор своих собственных пользовательских свойств.

Давайте рассмотрим пример:

.button {
    -fx-font-size: 15px;
}

Здесь использованы две основные концепции. Первая — это селектор — .button. Он определяет, к каким компонентам должен применяться стиль. В этом примере стиль применяется для всех кнопок.

Вторая часть — это фактические свойства стиля, которые будут применены ко всем компонентам, которые соответствуют нашему селектору. Свойства — это все, что расположено внутри фигурных скобок.

Каждое свойство имеет определенное значение. В нашем примере есть свойство -fx-font-size, которое определяет, насколько большим будет текст. В примере указано значение 15px, но это значение может быть любым другим.

Подводя итог — мы создали правило, которое гласит — все кнопки везде должны иметь текст размером 15 пикселей.

Selectors (Селекторы)

Теперь давайте рассмотрим подробнее, как работают селекторы в JavaFX. Это происходит почти так же, как в обычном CSS.

Class (Класс)

Класс в CSS представляет несколько похожих элементов. Например, кнопки или флажки. Селектор, который должен применяться ко всем элементам одного класса, начинается с точки ".", сопровождаемый непосредственно именем класса. Соглашение об именовании классов состоит в том, чтобы разделять отдельные слова с помощью символа "-". Следующий селектор применяется ко всем элементам с классом label.

.label {
    // Some properties
}

Built-in classes (Встроенные классы)

Хорошая новость заключается в том, что все встроенные компоненты JavaFX (такие как Label или Button) уже имеют предопределенный класс. Если вы хотите настроить стиль всех меток в своем приложении, вам не нужно добавлять какие-либо пользовательские классы для каждой из ваших меток. Каждая метка по-умолчанию имеет класс label.

Легко определить имя класса из компонента:

  • Возьмите имя Java класса компонента — например. Label
  • Сделайте имя строчным
  • Если он состоит из нескольких слов, разделите их с помощью символа "-"

Некоторые примеры:

  • Метка → метка
  • CheckBox → check-box

При использовании таких классов, как селекторы, не забудьте добавить ".". Это означает, что селектором для класса label является .label.

Custom classes (Пользовательские классы)

Если встроенных классов недостаточно, вы можете добавить свои собственные классы к своим компонентам. Вы можете использовать несколько классов, разделенных запятой:

<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. Он является корневым компонентом вашей сцены. Вы можете использовать его, чтобы стилизовать все внутри вашей сцены (например, установить глобальный шрифт). Это похоже на использование селектора тегов body в HTML.

ID

Другим способом выбора компонентов в 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");

Properties (Свойства)

Хотя CSS, используемый в JavaFX, очень похож на оригинальный веб-CSS, есть одно большое отличие. Имена свойств различны, и есть много новых свойств, специфичных для JavaFX. Они имеют префикс -fx-.

Вот некоторые примеры:

  • -fx-background-color: Цвет фона
  • -fx-text-fill: Цвет текста
  • -fx-font-size: Размер текста

Вы можете найти список всех свойств в официальном руководстве по дизайну.

Псевдоклассы

В дополнение к обычным классам, которые отмечают определенные компоненты, существуют так называемые псевдоклассы, которые обозначают состояние компонента. Это может быть, например, класс для маркировки того, что компонент имеет фокус или на нем находится курсор мыши.

Есть множество встроенных псевдоклассов. Давайте посмотрим на кнопку. Существует несколько псевдоклассов, которые вы можете использовать, например:

  • hover: мышь над кнопкой
  • focused: кнопка имеет фокус
  • disabled: кнопка отключена
  • pressed: кнопка нажата

Псевдоклассы начинаются с символа ":" (например, :hover) в селекторах CSS. Вам, конечно, нужно указать, к какому компоненту относится ваш псевдокласс — например, button:hover. В следующем примере показан селектор, который применяется ко всем кнопкам, имеющим фокус:

.button:focused {
    -fx-background-color: red;
}

В отличие от CSS, который имеет только базовые псевдоклассы для состояний, таких как focus и hover, JavaFX имеет специфичные для компонента псевдоклассы, которые относятся к различным состояниям или свойствам компонентов.

Например:

  • Полосы прокрутки (Scrollbars) имеют псевдоклассы horizontal и vertical
  • Элементы (Cells) имеют псевдоклассы odd и even
  • TitledPane имеет псевдоклассы expanded и collapsed

Пользовательские псевдоклассы

В дополнение ко встроенным псевдоклассам, вы можете определять и использовать свои собственные псевдоклассы.

Давайте создадим нашу собственную метку (наследуя от класса Label). У него будет новое логическое свойство, называемое shiny. В таком случае, мы хотим, чтобы у нашей метки был псевдокласс shiny.

Поскольку у метки есть псевдокласс shiny, мы можем установить фон метки gold:

.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. Это означает, что объект shiny является observable (наблюдаемым), и мы можем отслеживать (слушать) изменения в его значении.
  2. Мы регистрируем listener (слушатель), который будет вызываться каждый раз, когда значение объекта shiny изменяется с использованием shiny.addListener().
  3. Когда значение shiny изменяется, мы добавляем/удаляем псевдокласс shiny в зависимости от текущего значения pseudoClassStateChanged(PseudoClass.getPseudoClass(«shiny»), shiny.get()).
  4. Мы добавляем пользовательский класс для всех меток shiny-label, вместо того чтобы иметь только класс label, унаследованный от родителя. Таким образом, мы можем выбирать только метки shiny.

Таблица стилей по умолчанию

Даже если вы сами не предоставляете никаких стилей, каждое приложение JavaFX уже имеет некоторые визуальные стили. Существует таблица стилей по умолчанию, которая применяется к каждому приложению. Она называется modena (начиная с JavaFX 8, ранее она называлась caspian).

Эту таблицу стилей можно найти в файле:

jfxrt.jarcomsunjavafxscenecontrolskinmodenamodena.css

Или вы можете найти файл здесь. В том же каталоге есть множество изображений, используемых таблицей стилей.

Эта таблица стилей предоставляет стили по умолчанию, но имеет самый низкий приоритет по сравнению с другими типами таблиц стилей, поэтому вы можете легко ее переопределить.

Scene stylesheet (Таблица стилей сцены)

В дополнение к таблице стилей по умолчанию, упомянутой выше, вы, конечно, можете предоставить свою собственную. Самый высокий уровень, на котором вы можете применить стилизацию — это вся сцена. Вы можете реализовать это в вашем 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);

Обратите внимание на вызов toExternalForm(). Scene ожидает получить содержимое таблицы стилей в виде строки, а не файла, поэтому нам нужно предоставить содержимое нашей таблицы стилей в виде строки.

Parent stylesheet (Родительская таблица стилей)

В дополнение к таблице стилей для всей сцены, иногда бывает полезно иметь стили на уровне макета. То есть — для отдельного контейнера, такого как VBox, HBox или GridPane. Общим родителем всех макетов является родительский класс, который определяет методы для обработки таблиц стилей на уровне макета. Эти стили применяются только для компонентов в данном макете, а не для всей сцены. Стиль на уровне макета имеет приоритет над стилем на уровне сцены.

<HBox stylesheets="styles.css">
    ...
</HBox>

В Java вам нужно загрузить содержимое таблицы стилей самостоятельно, так же как и ранее для сцены:

HBox box = new HBox();
String stylesheet = getClass().getResource("/styles.css").toExternalForm();
box.getStylesheets().add(stylesheet);

Inline styles (Встроенные стили)

Пока что мы рассмотрели только случаи назначения внешней таблицы стилей для всей сцены или макета. Но можно задать индивидуальные свойства стиля на уровне компонента.

Здесь вам не нужно беспокоиться о селекторе, так как все свойства установлены для определенного компонента.

Вы можете указать несколько свойств, разделенных точкой с запятой:

<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, которое заключается в отделении стилей от компонентов. Теперь вы жестко привязываете свои визуальные элементы непосредственно к компонентам. Вы больше не сможете легко переключать свои таблицы стилей, когда это необходимо, вы не можете менять темы.

Более того, у вас больше нет единого центрального места, где определяется ваш стиль. Когда вам нужно что-то изменить в наборе похожих компонентов, вам нужно изменить каждый из компонентов по отдельности, а не редактировать только одно место во внешней таблице стилей. Поэтому следует избегать встроенных стилей компонентов.

Stylesheet priorities (Приоритеты таблиц стилей)

Вы можете обеспечить стилизацию на нескольких уровнях — сцена, родительский, встроенные стили, и также есть таблица стилей модены по умолчанию. Если вы изменяете одно и то же свойство одного и того же компонента на нескольких уровнях, JavaFX имеет настройку приоритета, которая определяет, какие стили следует использовать. Список приоритетов — от высшего к низшему:

  1. Inline styles (Встроенные стили)
  2. Parent styles (Родительские стили)
  3. Scene styles (Стили сцены)
  4. Default styles (Стили по умолчанию)

Это означает, что если вы установите цвет фона определенной метки как на встроенном, так и на уровне сцены, JavaFX будет использовать значение, установленное во встроенных стилях, поскольку оно имеет более высокий приоритет.

Дополнительное чтение

В JavaFX имеется множество свойств CSS, и их описание выходит за рамки этого поста, подробный список см. в официальном справочном руководстве по CSS для JavaFX.

Автор: val6852

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js