Согласно https://openjfx.io/javadoc/22/javafx.graphics/javafx/scene/doc-files/cssref.html JavaFX 22 поддерживает псевдокласс focus-within
. Я использую JavaFX 23-ea+3, и это мой код:
public class JavaFxTest7 extends Application {
private static class Student {
private int id;
private int mark;
public Student(int id, int mark) {
this.id = id;
this.mark = mark;
}
public int getId() {
return id;
}
public int getMark() {
return mark;
}
}
private static class MyTab extends Tab {
private final TableView<Student> table = new TableView<>(FXCollections.observableList(
List.of(new Student(1, 3), new Student(2, 4), new Student(3, 5))));
private final MenuItem menuItem = new MenuItem("New Tab");
private final ContextMenu contextMenu = new ContextMenu(menuItem);
public MyTab() {
var idColumn = new TableColumn<Student, Integer>();
idColumn.setCellValueFactory((data) -> new ReadOnlyObjectWrapper<>(data.getValue().getId()));
var markColumn = new TableColumn<Student, Integer>();
markColumn.setCellValueFactory((data) -> new ReadOnlyObjectWrapper<>(data.getValue().getMark()));
table.getColumns().addAll(idColumn, markColumn);
menuItem.setOnAction((e) -> {
var newTab = new MyTab();
this.getTabPane().getTabs().add(newTab);
});
table.setContextMenu(contextMenu);
this.setContent(table);
}
}
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
TabPane tabPane = new TabPane();
tabPane.getTabs().add(new MyTab());
var scene = new Scene(tabPane, 400, 300);
var css= this.getClass().getResource("test7.css").toExternalForm();
scene.getStylesheets().add(css);
stage.setScene(scene);
stage.show();
}
}
и test7.css:
.tab-pane > .tab-header-area .tab .focus-indicator {
-fx-border-width: 2 0 0 0;
-fx-border-color: green;
-fx-border-insets: -3 -5 0 -5;
-fx-border-radius: 2 2 0 0;
}
.tab-pane > .tab-header-area .tab:focus-within .focus-indicator {
-fx-border-width: 2 0 0 0;
-fx-border-color: red;
-fx-border-insets: -3 -5 0 -5;
-fx-border-radius: 2 2 0 0;
}
И вот результат:
Как вы видите, таблица на первой вкладке имеет фокус, но вкладка имеет зеленую рамку, а не красную. Может ли кто-нибудь сказать, как это исправить? Или я неправильно понимаю смысл этого псевдокласса?
Редактировать:
@James_D объяснил правильное использование focus-widthin
с TabPane
. Однако, когда я изменил свой код на:
...
@Override
public void start(Stage stage) {
TabPane tabPane0 = new TabPane();
tabPane0.getTabs().add(new MyTab());
TabPane tabPane1 = new TabPane();
tabPane1.getTabs().add(new MyTab());
var scene = new Scene(new VBox(tabPane0, tabPane1), 400, 300);
var css= this.getClass().getResource("test7.css").toExternalForm();
scene.getStylesheets().add(css);
stage.setScene(scene);
stage.show();
}
...
с этими настройками CSS:
.tab-pane > .tab-header-area .tab .focus-indicator {
-fx-border-width: 2 0 0 0;
-my-tab-border-color: green;
-fx-border-color: -my-tab-border-color;
-fx-border-insets: -3 -5 0 -5;
-fx-border-radius: 2 2 0 0;
}
.tab-pane:focus-within > .tab-header-area .tab:selected .focus-indicator {
-my-tab-border-color: red;
}
Я получил такой результат:
Это не то, чего я ожидаю, потому что только один TabPane
может иметь Tab
с красной рамкой - тот, у которого есть сфокусированная таблица.
В документации псевдокласса focus-within
говорится:
применяется, когда переменная
focusWithin
имеет значение true
и обратите внимание, что focusWithin — это свойство Node , но Tab не является подклассом Node, поэтому у него нет такого свойства. (Tab
на самом деле является частью модели данных для TabPane
; на самом деле это не класс UI/View в смысле MVC.)
У Tab
есть свойство selected
, которое распространяется на псевдокласс CSS (хотя я не могу сразу найти документацию по последней части этого свойства). А TabPane
— это Node
, поэтому вы можете использовать псевдокласс focus-within
на панели вкладок. Следующее делает то, что вам нужно:
.tab-pane > .tab-header-area .tab .focus-indicator {
-fx-border-width: 2 0 0 0;
-fx-border-color: green;
-fx-border-insets: -3 -5 0 -5;
-fx-border-radius: 2 2 0 0;
}
.tab-pane:focus-within > .tab-header-area .tab:selected .focus-indicator {
-fx-border-width: 2 0 0 0;
-fx-border-color: red;
-fx-border-insets: -3 -5 0 -5;
-fx-border-radius: 2 2 0 0;
}
Кроме того, немного более элегантное решение (возможно) — использовать искомый цвет:
.tab-pane > .tab-header-area .tab .focus-indicator {
-fx-border-width: 2 0 0 0;
-my-tab-border-color: green;
-fx-border-color: -my-tab-border-color;
-fx-border-insets: -3 -5 0 -5;
-fx-border-radius: 2 2 0 0;
}
.tab-pane:focus-within > .tab-header-area .tab:selected .focus-indicator {
-my-tab-border-color: red;
}
@Pavel_K Я не думаю, что тебе нужно это делать. Если вы используете псевдокласс focus-within
для TabPane
(обратите внимание, что я делаю это в опубликованном мной CSS), то красная рамка будет применяться только к выбранной вкладке в TabPane, которая имеет фокус. Если вы переместите фокус на другую панель вкладок (или куда-либо еще), выбранная вкладка должна снова стать зеленой.
Я понял вашу идею и теперь тестирую ваше решение с помощью .tab-pane:focus-within ...
. У меня есть два TabPanes
и var scene = new Scene(new VBox(tabPane0, tabPane1), 400, 300);
. Проблема в том, что обе TabPane имеют вкладку с красной рамкой. Может быть, мне стоит открыть новый вопрос?
@Pavel_K Нет, я думаю, это достаточно тесно связано, что вы можете просто отредактировать свой текущий вопрос. Я отредактирую ответ, чтобы решить эту проблему (при условии, что смогу найти решение...).
@Pavel_K Я думаю, вы упускаете из виду, что селектор с примененным псевдоклассом похож на совершенно новый селектор, который переопределяет базовый селектор. В этом случае фокус внутри применяется к TabPane, а затем с помощью tab-pane: focus-within вы можете делать все, что вы делаете с tab-pane, включая настройку его компонентов. Поэтому, если вы настроите «tab.focus-indicator», он будет применяться только тогда, когда к панели вкладок применен «фокус внутри».
@DaveB Если вы знаете, как исправить код, указанный в разделе Edit
вопроса, не могли бы вы дать полный ответ? Я до сих пор не могу решить проблему.
Я поместил прослушиватель/подписку на focusWithinProperty() одной из панелей вкладок. Я не вижу, чтобы это изменилось предсказуемо. Я вижу, как он иногда переключается на ложь, а затем сразу же возвращается к истине. Так что либо мы неправильно интерпретируем реализацию focusWithin, либо есть ошибка. Следующий шаг для меня — выяснить, не являются ли причиной проблемы TableViews, поэтому мне любопытно, будет ли просто наличие пары VBoxes с TextFields внутри работать так, как мы ожидаем.
Но знание того, что вы можете увидеть это непосредственно через свойство, означает, что это не проблема структуры/подхода CSS. Это нечто.
Он работает так, как и следовало ожидать, с двумя VBox, каждый из которых содержит TextField. Когда TextFields получают фокус, свойства focusWithin следуют правильно. Если я добавлю эти TableViews в VBoxes, то он перестанет работать, поскольку TableView, по-видимому, означает, что для каждого из обоих VBoxes значение focusWithin почти всегда установлено в true.
@DaveB Большое спасибо. Тогда я открою ошибку.
@Pavel_K Не делай этого. Я собираюсь дать ответ, который это объясняет. Вот увидишь.
Оказывается, подход @James_D иногда верен, но не тогда, когда TableView
или ListView
находятся в Tab
содержимом. Основное предположение, лежащее в основе этого подхода, — что только один Node
может иметь фокус в любой момент времени — не всегда верно. Из https://bugs.openjdk.org/browse/JDK-8317426?jql=text%20~%20%22focusWithin%22 Проблема JDK:
Михаэль Штраус добавил комментарий - 2023-10-04 05:58 Это не ошибка.
focusWithin
устанавливается, когда любой из дочерних узлов равенfocused
, что всегда справедливо для TableView с выбранными ячейками.Вот источник путаницы: «фокус внутри всегда должен немедленно становиться ложным, когда фокус сцены перемещается в другое место». (из отчета отправителя)
focus-within
указано не так. Отправитель ищетfocus-owner-within
, которого не существует.Возможное улучшение описано здесь: https://mail.openjdk.org/pipermail/openjfx-dev/2023-August/042005.html
Дальнейшее объяснение содержится в связанном комментарии:
Узлы графа сцены имеют три свойства, связанных с фокусом:
- сосредоточенный
- фокус внутри
- видимый в фокусе
Что может быть немного удивительным, так это то, что можно сосредоточить несколько узлов. одновременно, но только один из этих сфокусированных узлов может быть владелец фокуса сцены. Свойство
Scene.focusOwner
указывает, какой из сфокусированных узлов является текущим владельцем фокуса.Еще более удивительно то, что узел может быть не сфокусирован и находиться в фокусе. владельцем одновременно (это может произойти, когда окно деактивирован).
С точки зрения
Node
, в настоящее время нет API, позволяющего легко определить, является ли он владельцем фокуса, и нет CSS. псевдокласс, соответствующий узлу, который является владельцем фокуса.Для поддержки этого мы могли бы добавить еще два свойства в класс
Node
. вариант использования: 4. владелец фокуса 5. фокус-владелец внутриОбратите внимание, что
focus-visible
соответствует только узлам, которые также находятся в фокусе. собственники, поэтому нет необходимости иметьfocus-owner-visible
недвижимость.Двух дополнительных свойств будет достаточно, чтобы соответствовать любому возможному состояние фокуса, но оно также может быть недостаточно полезным, чтобы гарантировать дополнительная поверхность API. Что вы думаете?
Итог: более одного Node
в Scene
могут иметь фокус в любой момент времени, и это типично для TableView
и ListView
, у которых выбраны строки. Любой дизайн, основанный на предположении, что только один Node
может иметь фокус одновременно, ошибочен (по крайней мере, когда задействованы TableView
или ListView
).
В настоящее время не существует Properties
, который можно было бы использовать, чтобы обойти эту проблему. В этом примере оба TableViews
имеют фокус просто потому, что они выбрали строки, и focusWithinProperty()
обоих Tabs
будет истинным, и это не ошибка.
Предположительно, вы могли бы использовать Scene.focusOwnerProperty()
для рекурсивной проверки всех дочерних элементов содержимого Tab
, чтобы найти совпадение, а затем связать его с StyleableProperty
из Tab
, но это выходит за рамки исходного вопроса.
«Что может быть немного удивительным, так это то, что несколько узлов могут быть сфокусированы одновременно, но только один из этих сфокусированных узлов может быть владельцем фокуса сцены». Обратите внимание, что это противоречит спецификации API (поэтому технически ошибка есть, даже если она есть только в документации). В документации говорится: «Чтобы иметь фокус ввода, Node
должен быть владельцем фокуса сцены»
Большое спасибо за ваше объяснение. Причина, по которой мне нужно привязать вкладку к
focus-within
, а не кselected
, заключается в том, что в реальном приложении у меня есть несколькоTabPane
в одной сцене. Поэтому разметка нескольких вкладок разнымиTabPane
выглядит странно и запутанно. Но из вашего объяснения кажется, что единственный способ сделать то, что я хочу, это что-то вроде -tab.getContent().focusWithinProperty().addListener(add/remove CSS classes to tab manually)
. Это верно?