Javafx - изменить тему (CSS) в активном окне

Мне нужна кнопка, которая позволяет мне переключаться между темным/светлым режимом. Но у меня проблема Если я переключаюсь, активные окна не меняют свой стиль.

Первый из фрагментов кода, чтобы легко воссоздать проблему. Для стартового класса проектов Maven:

package testapp;

//This Class is Required in mavenproject to Start the Application

public class GUIStarter {
    public static void main(final String[] args) {
        try{
            TestApp.main(args);
        }catch (Exception e){
            System.out.println("GUIStarter Errror:\n"+e);
        }
    }
}

Основной класс окна:

public class TestApp extends Application {
    //stylepaths
    public static String mainLightModePath = ".\\style_lightmode.css";
    public static String mainDarkModePath = ".\\style-Darkmode.css";
    public static boolean isItDarkmode = true;

    public static void toggleMode(){
        if (isItDarkmode){
            isItDarkmode  = false;
        }else if (!isItDarkmode){
            isItDarkmode = true;
        }

    }

    //This is required to get the primary Stage in other Stages (Controllers)
    private static Stage pStage;

    public static Stage getPrimaryStage() {
        return pStage;
    }

    private void setPrimaryStage(Stage pStage) {
        TestApp.pStage = pStage;
    }

    public void setPrimaryWindow(Stage primaryStage){

        try{
            FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Mainframe.fxml"));
            
            Parent root = (Parent) fxmlLoader.load();
            Scene scene = new Scene(root);

            primaryStage.setTitle("TestApp");

            //scene.setMoveControl(titleBar);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch (Exception e){
            e.printStackTrace();
        }
        
    }

    //Start of Application
    @Override
    public void start(Stage primaryStage) {
        setPrimaryStage(primaryStage);
        pStage = primaryStage;
        setPrimaryWindow(primaryStage);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Класс основного контроллера:

public class Mainframe {
        public static Timeline time;

        @FXML
        public void options(ActionEvent event){
            try{ 
                TestApp.toggleMode();
                Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();
                Stage changer = TestApp.getPrimaryStage();
                stage.close();
                changer.hide();
                //My idea was to initialize again and load the styles in that way again but does not change anything
                FXMLLoader loader = new FXMLLoader(getClass().getResource("Mainframe.fxml"));
                loader.load();
                Mainframe ctrl = loader.getController();
                ctrl.initialize();
                changer.show();
                
                
            }catch (Exception e) {
                System.out.println("Error bei der App Klicken von ModeButton. \n error is: "+e);
            e.printStackTrace();
            }
        }

    @FXML
    public BorderPane primaryparent;

    //get and set for elements
    @FXML
    void initialize() {
        //Check Theme
        if (TestApp.isItDarkmode){
            primaryparent.getStylesheets().clear();
            primaryparent.getStylesheets().add(getClass().getResource(TestApp.mainDarkModePath).toString());
        }else if (!TestApp.isItDarkmode){
            primaryparent.getStylesheets().clear();
            primaryparent.getStylesheets().add(getClass().getResource(TestApp.mainLightModePath).toString());
        }
    }
    
}

Mainframe.fxml:

<?xml version = "1.0" encoding = "UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.text.Font?>

<BorderPane fx:id = "primaryparent" maxHeight = "-Infinity" maxWidth = "-Infinity" minHeight = "0.0" minWidth = "0.0" prefHeight = "224.0" prefWidth = "515.0" xmlns = "http://javafx.com/javafx/18" xmlns:fx = "http://javafx.com/fxml/1" fx:controller = "testapp.Mainframe">
   <center>
      <AnchorPane prefHeight = "200.0" prefWidth = "200.0" styleClass = "rootBackground" BorderPane.alignment = "CENTER">
         <children>
            <Button fx:id = "setDarkModeButton" layoutX = "175.0" layoutY = "99.0" mnemonicParsing = "false" onAction = "#options" styleClass = "settingsButton" text = "toggle Dark/LightMode">
               <font>
                  <Font name = "Arial" size = "13.0" />
               </font>
               <padding>
                  <Insets bottom = "5.0" left = "17.0" right = "17.0" top = "5.0" />
               </padding>
            </Button>
         </children>
      </AnchorPane>
   </center>
</BorderPane>


Раньше я просто overwrote использовал файл «style.css» и использовал .stop() и .show(). Это сработало нормально. Но это может усложниться, чем больше css-files у вас есть, и после того, как он упакован в .jar файл, он больше не работает.

Я надеюсь, что кто-нибудь может мне помочь. Потому что теперь я действительно понятия не имею...

Обновлено: Я хочу управлять им с другого контроллера:

package testapp;
public class Mainframe {
        public static Timeline time;

        @FXML Button setDarkModeButton;

        @FXML
        public void options(ActionEvent event){
            try{ 
                try{
                    Stage secSTAGE = new Stage();
                    FXMLLoader fxmlLoader = new FXMLLoader(TestApp.class.getResource("Mainframe2.fxml"));
                    Parent root = (Parent) fxmlLoader.load();
                    Scene scene = new Scene(root);
        
                    secSTAGE.setTitle("TestAppw2");
        
                    //scene.setMoveControl(titleBar);
                    secSTAGE.setScene(scene);
                    secSTAGE.show();
                } catch (Exception e){
                    e.printStackTrace();
                }
                
                
            }catch (Exception e) {
                System.out.println("Error bei der App Klicken von ModeButton. \n error is: "+e);
            e.printStackTrace();
            }
        }

    @FXML
    public BorderPane primaryparent;

    //get and set for elements
    @FXML
    void initialize() {
        //Check Theme
        if (TestApp.isItDarkmode){
            primaryparent.getStylesheets().clear();
            primaryparent.getStylesheets().add(getClass().getResource(TestApp.mainDarkModePath).toString());
            primaryparent.getStylesheets().add(getClass().getResource(TestApp.mainLightModePath).toString());
        }
    }
    
}

public class Mainframe2 {
        public static Timeline time;

        @FXML Button setDarkModeButton;

        @FXML
        public void options(ActionEvent event){
            try{ 
                TestApp.toggleMode();
                Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();

                FXMLLoader loader = new FXMLLoader(getClass().getResource("Mainframe.fxml"));
                Parent root = (Parent) loader.load();
                Mainframe ctrl = loader.getController();

                //setDarkModeButton.getStylesheets().set(0, "style_lightmode.css");
                if (TestApp.isItDarkmode){
                    root.getStylesheets().set(0, Mainframe.class.getResource(TestApp.mainDarkModePath).toString());
                }else{
                    root.getStylesheets().set(0, Mainframe.class.getResource(TestApp.mainLightModePath).toString());
                }            
            }catch (Exception e) {
                System.out.println("Error bei der App Klicken von ModeButton. \n error is: "+e);
            e.printStackTrace();
            }
        }

    @FXML
    public BorderPane primaryparent;

    //get and set for elements
    @FXML
    void initialize() {
        //Check Theme
        if (TestApp.isItDarkmode){
            primaryparent.getStylesheets().clear();
            primaryparent.getStylesheets().add(getClass().getResource(TestApp.mainDarkModePath).toString());

        }else if (!TestApp.isItDarkmode){
            primaryparent.getStylesheets().clear();
            primaryparent.getStylesheets().add(getClass().getResource(TestApp.mainLightModePath).toString());
        }
    }
    
}

минимальный воспроизводимый пример, пожалуйста... включая полную трассировку стека. И придерживайтесь соглашений об именах Java
kleopatra 06.05.2022 10:38

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

jewelsea 06.05.2022 11:13

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

jewelsea 06.05.2022 11:15

Вы также назвали класс Application (app.Application). Не делайте этого, это очень запутывает. JavaFX уже имеет Класс приложения. Прочтите Javadoc, чтобы понять, как работает жизненный цикл и каковы правила его использования.

jewelsea 06.05.2022 11:23

@jewelsea Я переименовал свои классы в stackoverlow, чтобы уменьшить их. Поэтому я сделал небольшие фрагменты всех файлов, которые необходимы для воссоздания проблемы так, как мне нужно. Надеюсь, теперь вы сможете мне помочь :)

Muddyblack k 06.05.2022 14:03

пожалуйста, прочитайте указанную страницу справки и действуйте соответственно - обратите внимание на М .. это выглядит как слишком много кода (alwaysontop не имеет отношения к стилю, и большинство правил стиля не нужны) и по-прежнему отсутствует трассировка стека .. не имеет отношения: не использовать статическую область видимости..

kleopatra 06.05.2022 14:45

.. или вы не получаете никаких ошибок? Если да, то это реально или просто потому, что вы их проглотили в ранлейтере?

kleopatra 06.05.2022 14:51

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

Muddyblack k 06.05.2022 19:34

Хорошо добавил одну из вещей, которые я пробовал: initialize() . Ошибки не выдает, но и не действует. Итак, но теперь я действительно надеюсь, что вы, ребята, сможете мне помочь...

Muddyblack k 06.05.2022 19:43

Я немного запутался с вашим Mainframe2 классом. В методе options вы загружаете файл FXML и добавляете таблицу стилей в корень Node. Но вы никогда не добавляете этот Node в граф сцены, поэтому он не отображается пользователю. Таким образом, никаких визуальных эффектов. Если вы хотите изменить стиль всех узлов, отображаемых в данный момент в сцене, либо измените список таблиц стилей Scene, либо rootScene.

Slaw 13.05.2022 00:25

БЛАГОДАРЮ ВАС!!! Я забыл changer.setScene(scene). Человек это была борьба ....

Muddyblack k 13.05.2022 17:44
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Введение в CSS
Введение в CSS
CSS является неотъемлемой частью трех основных составляющих front-end веб-разработки.
Как выровнять Div по центру?
Как выровнять Div по центру?
Чтобы выровнять элемент <div>по горизонтали и вертикали с помощью CSS, можно использовать комбинацию свойств и значений CSS. Вот несколько методов,...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
CSS: FlexBox
CSS: FlexBox
Ранее разработчики использовали макеты с помощью Position и Float. После появления flexbox сценарий полностью изменился.
1
12
110
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Видно, что вроде никого не интересует мой вопрос. Это не то, что я хотел, но пока ни у кого нет лучшего решения:

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

Таким образом, в TestApp.class метод setPrimaryWindow() должен быть установлен public и static. Таким образом, вы не можете использовать .getClass(), и pStage должен быть установлен на новую стадию в setPrimaryWindow():

public static void setPrimaryWindow(Stage primaryStage){

    try{
        pStage = primaryStage;
        FXMLLoader fxmlLoader = new FXMLLoader(TestApp.class.getResource("Mainframe.fxml"));
        
        Parent root = (Parent) fxmlLoader.load();
        Scene scene = new Scene(root);

        primaryStage.setTitle("TestApp");

        //scene.setMoveControl(titleBar);
        primaryStage.setScene(scene);
        primaryStage.show();
    } catch (Exception e){
        e.printStackTrace();
    }
    
}

@Override
public void start(Stage primaryStage) {
    setPrimaryStage(primaryStage);
    setPrimaryWindow(primaryStage);
}

В опциях maincontroller.class вы просто close() старое окно и вызываете setPrimaryWindow():

@FXML
public void options(ActionEvent event){
    try{ 
        TestApp.toggleMode();
        Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();
        Stage changer = TestApp.getPrimaryStage();
        stage.close();
        changer.close();

        //create new Window on Theme switch
        Stage nStage = new Stage();
        TestApp.setPrimaryWindow(nStage);
        
        
    }catch (Exception e) {
        System.out.println("Error bei der App Klicken von ModeButton. \n error is: "+e);
    e.printStackTrace();
    }
}
Ответ принят как подходящий

Css-файл можно изменить на ходу без перезагрузки или установки нового этапа

замена таблицы стилей методом set() для observableList

css file javafx

App.java

public class App extends Application {

    @Override
    public void start(Stage stage) {
        
        Button button = new Button("change");
        button.setOnAction(e ->button.getScene().getStylesheets().set(0, "1.css"));
        
        Button button1 = new Button("default");
        button1.setOnAction(e ->button.getScene().getStylesheets().set(0, "0.css"));
        HBox hBox = new HBox(button,button1);
        
        
        AnchorPane anchorPane =new AnchorPane(hBox);
        anchorPane.getStyleClass().add("pane");
        
        var scene = new Scene(anchorPane, 640, 480);
        scene.getStylesheets().add("0.css");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}

0.css

.pane{
    -fx-background-color:cyan;
    
}

1.css

.pane{
    -fx-background-color:orange;
    
    }

Хм, когда я добавляю это в свой код, я получаю эту ошибку: java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0 Вы знаете, как это сделать с файлами fxml?

Muddyblack k 12.05.2022 15:21

В моем Main Controller я использовал это на ActionEvent: setDarkModeButton.getScene().getStylesheets().set(0, ".\\style_lightmode.css");

Muddyblack k 12.05.2022 15:23

@Muddyblackk В списке должен быть хотя бы один элемент, чтобы set(0, element) работал. Вы можете видеть, что scene.getStylesheets().add("0.css") вызывается в приведенном выше примере перед любым из вызовов set(...). Кроме того, когда вы добавляете таблицу стилей без схемы URL, предполагается, что путь является именем ресурса; поиск ресурсов не поддерживает . и ...

Slaw 12.05.2022 21:40

Да, я добавил их в initialize(), но не понимаю, почему я добавил туда ````.getScene()``` ^^ Спасибо, это сработало! После того, как я правильно реализовал его в своем реальном приложении, я отмечу его как ответ :)

Muddyblack k 12.05.2022 23:02

Хорошо, это работает для самого нового открытого окна, но как я могу применить его к другому активному окну? Другие сказали, что у меня бесполезный код, так что это больше не мой вопрос... ctrl.primaryparent.getStylesheets().set(0, getClass().getResource(FirstController.mainDarkModePath).toS‌​tring()); Поэтому я хочу использовать его в другом контроллере. ctrl загружает другой контроллер, как в моем вопросе выше.

Muddyblack k 12.05.2022 23:29

Этот код выше ничего не делает. Даже без ошибок

Muddyblack k 12.05.2022 23:32

вам нужно знать, как передавать значения между этапами

Giovanni Contreras 13.05.2022 00:32

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

Giovanni Contreras 13.05.2022 00:55

Да ctrl.primaryparent — это значение из другого этапа, но оно не обновляет стиль.

Muddyblack k 13.05.2022 17:08

Хорошо, @slaw указал на мою проблему :) Спасибо, ребята, за ваше терпение. Я действительно боролся в этот раз...

Muddyblack k 13.05.2022 17:45

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

Giovanni Contreras 13.05.2022 17:53

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