Фокус внутри псевдокласса не работает для табуляции в JavaFX

Согласно 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 с красной рамкой - тот, у которого есть сфокусированная таблица.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
0
88
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В документации псевдокласса 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;
}

Большое спасибо за ваше объяснение. Причина, по которой мне нужно привязать вкладку к focus-within, а не к selected, заключается в том, что в реальном приложении у меня есть несколько TabPane в одной сцене. Поэтому разметка нескольких вкладок разными TabPane выглядит странно и запутанно. Но из вашего объяснения кажется, что единственный способ сделать то, что я хочу, это что-то вроде - tab.getContent().focusWithinProperty().addListener(add/remov‌​e CSS classes to tab manually). Это верно?

user5182503 19.04.2024 15:38

@Pavel_K Я не думаю, что тебе нужно это делать. Если вы используете псевдокласс focus-within для TabPane (обратите внимание, что я делаю это в опубликованном мной CSS), то красная рамка будет применяться только к выбранной вкладке в TabPane, которая имеет фокус. Если вы переместите фокус на другую панель вкладок (или куда-либо еще), выбранная вкладка должна снова стать зеленой.

James_D 19.04.2024 15:43

Я понял вашу идею и теперь тестирую ваше решение с помощью .tab-pane:focus-within .... У меня есть два TabPanes и var scene = new Scene(new VBox(tabPane0, tabPane1), 400, 300);. Проблема в том, что обе TabPane имеют вкладку с красной рамкой. Может быть, мне стоит открыть новый вопрос?

user5182503 19.04.2024 15:59

@Pavel_K Нет, я думаю, это достаточно тесно связано, что вы можете просто отредактировать свой текущий вопрос. Я отредактирую ответ, чтобы решить эту проблему (при условии, что смогу найти решение...).

James_D 19.04.2024 16:00

@Pavel_K Я думаю, вы упускаете из виду, что селектор с примененным псевдоклассом похож на совершенно новый селектор, который переопределяет базовый селектор. В этом случае фокус внутри применяется к TabPane, а затем с помощью tab-pane: focus-within вы можете делать все, что вы делаете с tab-pane, включая настройку его компонентов. Поэтому, если вы настроите «tab.focus-indicator», он будет применяться только тогда, когда к панели вкладок применен «фокус внутри».

DaveB 19.04.2024 16:10

@DaveB Если вы знаете, как исправить код, указанный в разделе Edit вопроса, не могли бы вы дать полный ответ? Я до сих пор не могу решить проблему.

user5182503 19.04.2024 19:35

Я поместил прослушиватель/подписку на focusWithinProperty() одной из панелей вкладок. Я не вижу, чтобы это изменилось предсказуемо. Я вижу, как он иногда переключается на ложь, а затем сразу же возвращается к истине. Так что либо мы неправильно интерпретируем реализацию focusWithin, либо есть ошибка. Следующий шаг для меня — выяснить, не являются ли причиной проблемы TableViews, поэтому мне любопытно, будет ли просто наличие пары VBoxes с TextFields внутри работать так, как мы ожидаем.

DaveB 19.04.2024 19:39

Но знание того, что вы можете увидеть это непосредственно через свойство, означает, что это не проблема структуры/подхода CSS. Это нечто.

DaveB 19.04.2024 19:53

Он работает так, как и следовало ожидать, с двумя VBox, каждый из которых содержит TextField. Когда TextFields получают фокус, свойства focusWithin следуют правильно. Если я добавлю эти TableViews в VBoxes, то он перестанет работать, поскольку TableView, по-видимому, означает, что для каждого из обоих VBoxes значение focusWithin почти всегда установлено в true.

DaveB 19.04.2024 21:12

@DaveB Большое спасибо. Тогда я открою ошибку.

user5182503 19.04.2024 21:17

@Pavel_K Не делай этого. Я собираюсь дать ответ, который это объясняет. Вот увидишь.

DaveB 19.04.2024 21:31
Ответ принят как подходящий

Оказывается, подход @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

Дальнейшее объяснение содержится в связанном комментарии:

Узлы графа сцены имеют три свойства, связанных с фокусом:

  1. сосредоточенный
  2. фокус внутри
  3. видимый в фокусе

Что может быть немного удивительным, так это то, что можно сосредоточить несколько узлов. одновременно, но только один из этих сфокусированных узлов может быть владелец фокуса сцены. Свойство 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 должен быть владельцем фокуса сцены»

James_D 24.04.2024 17:04

Другие вопросы по теме