Хабрахабр

[Перевод] Учебник по JavaFX: CSS стилизация

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

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

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

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

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

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

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

.shiny-label:shiny { -fx-background-color: gold;
}

Теперь создадим сам класс.

public class ShinyLabel extends Label ); } 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.jar\com\sun\javafx\scene\control\skin\modena\modena.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.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть