Javafx – change Theme (CSS) on active window

I want a Button that allows me to switch between Dark/Light-mode. But I have the problem If I switch, the active windows will not change their Style.

First of the Code snippets to easy recreate the problem. For Maven projects starter class:

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);
        }
    }
}

The primary window class:

package testapp;

//IMPORT
//JAVAFX
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.stage.Stage;

//title
import javafx.scene.Scene;
//IMPORT END

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");
            primaryStage.setAlwaysOnTop(true);

            //scene.setMoveControl(titleBar);
            primaryStage.setScene(scene);
            primaryStage.show();

            primaryStage.setOnCloseRequest(event -> {
               System.out.println("EXIT APPLICATION");
                try{
                    Platform.exit();
                }catch(Exception e){
                    System.out.println("Error while exit of application n error is: "+e);
                }
            });
        } 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);
    }
}

The maincontroller class:

package testapp;

//IMPORT
//JAVAFX
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.event.ActionEvent;
import javafx.scene.Parent;
import javafx.scene.layout.BorderPane;
import javafx.scene.Scene;
//Countdown
import javafx.animation.Timeline;

public class Mainframe {
        public static Timeline time;


            //SettingButton
    @FXML
    public void options(ActionEvent event) {
        Stage primstageinfo = (Stage) ((Node)event.getSource()).getScene().getWindow();
            //Second Stage
            try{
                Stage secondStage = new Stage();

                FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Optionsframe.fxml"));
                Parent root = (Parent) fxmlLoader.load();
                Scene scene = new Scene(root);
    
                secondStage.setTitle("Settings");
                secondStage.setAlwaysOnTop(true);

                secondStage.setX(primstageinfo.getX());
                secondStage.setY(primstageinfo.getY() + primstageinfo.getHeight());

                secondStage.setScene(scene);
                secondStage.show();
    
                secondStage.setOnCloseRequest(eventB -> {
                   System.out.println("EXIT APPLICATION");
                    try{
                        secondStage.close();
                    }catch(Exception e){
                        System.out.println("Error while exit of Settings popup n error is: "+e);
                    }
                });
            } catch (Exception 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());
        }
    }
    
}

Second stage controller:

package testapp;

//IMMPORT
//JAVAFX
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.event.ActionEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.control.ToggleButton;

//extra
import java.net.URL;
import java.util.ResourceBundle;

//IMMPORT END

public class Optionsframe {

    //Get Elements from UI
    @FXML private ResourceBundle resources;

    @FXML private URL location;
    
    @FXML private ToggleButton setDarkModeButton;
    
    @FXML private ToggleButton setAlwaysonTopButton;

    //functions for actions on UI elements
    
    //Buttonfunctions
    @FXML
    public void toggleDarkmode(ActionEvent event){
        try{ 
            TestApp.toggleMode();
            Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();
            Stage changer = TestApp.getPrimaryStage();
            stage.close();
            changer.close();
            changer.show();
            
            
        }catch (Exception e) {
            System.out.println("Error bei der App Klicken von ModeButton. n error is: "+e);
        e.printStackTrace();
        }
    }

    @FXML private BorderPane settingsparent;
    //get and set for elements
    @FXML
    void initialize() {
        //Check Theme
        if(TestApp.isItDarkmode){
            settingsparent.getStylesheets().clear();
            settingsparent.getStylesheets().add(getClass().getResource(TestApp.mainDarkModePath).toString());
        }else if(!TestApp.isItDarkmode){
            settingsparent.getStylesheets().clear();
            settingsparent.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.layout.HBox?>
<?import javafx.scene.text.Font?>

<BorderPane fx:id="primaryparent" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="0.0" minWidth="0.0" prefHeight="450.0" prefWidth="500.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>
            <HBox layoutY="318.0" prefHeight="51.0" prefWidth="238.0" AnchorPane.bottomAnchor="0.4000000000000199" AnchorPane.leftAnchor="0.0">
               <children>
                  <Button mnemonicParsing="false" onAction="#options" styleClass="settingsButton" text="settings">
                     <font>
                        <Font name="Arial" size="13.0" />
                     </font>
                     <padding>
                        <Insets bottom="5.0" left="17.0" right="17.0" top="5.0" />
                     </padding>
                     <HBox.margin>
                        <Insets left="5.0" top="13.0" />
                     </HBox.margin>
                  </Button>
               </children>
            </HBox>
         </children>
      </AnchorPane>
   </center>
</BorderPane>

Optionsframe.fxml:

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>

<BorderPane fx:id="settingsparent" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="0.0" minWidth="0.0" prefHeight="200.0" prefWidth="382.0" stylesheets="@style-Darkmode.css" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="testapp.Optionsframe">
   <center>
      <AnchorPane prefHeight="200.0" prefWidth="200.0" styleClass="popupBackground" BorderPane.alignment="CENTER">
         <children>
            <VBox layoutX="281.0" layoutY="-1.0" prefHeight="168.0" prefWidth="117.0" AnchorPane.rightAnchor="-3.0" AnchorPane.topAnchor="-1.0">
               <children>
                  <Label styleClass="settingslabel" text="Dark-Theme:" textFill="WHITE">
                     <VBox.margin>
                        <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
                     </VBox.margin>
                  </Label>
                  <ToggleButton fx:id="setDarkModeButton" mnemonicParsing="false" onAction="#toggleDarkmode" text="ToggleButton" />
               </children>
            </VBox>
         </children>
      </AnchorPane>
   </center>
</BorderPane>

Lightmode css:

*{
    -rootBackgroundColor: #dcdcdc;
    -topWindowBackgroundColor: #bfbfbf;
    -mostFontColor: #1b1b1b;
    -settingsTextColor: #5f5f5f;
    -settingsButtonHoverColor: #aaaaaa;
    -PrompTextColor: #4e4e4e;
}

.rootBackground{
    -fx-background-color:  -rootBackgroundColor;
}
.popupBackground{
    -fx-background-color:  -topWindowBackgroundColor;
}

.settingsButton{
    -fx-background-color: -rootBackgroundColor;
    -fx-text-fill:  -settingsTextColor;
    -fx-background-radius: 5em;
}

.settingsButton:hover{
    -fx-cursor: hand;
    -fx-background-color: -settingsButtonHoverColor;
}

.settingslabel{
    -fx-text-fill:  -mostFontColor;
    -fx-font: 14 arial;
}
.settingstoggle{
    -fx-toggle-color: -toggleButtonColor;
    -fx-untoggle-color: -untoggleButtonColor;
    -fx-size: 9.0;
}
*{
    -rootBackgroundColor: #25292e;
    -topWindowBackgroundColor: #383838;
    -mostFontColor: #FFFFFF;
    -settingsTextColor: #757576;
    -settingsButtonHoverColor: #2d3238;
    -PrompTextColor: #EBEBEB;
}

.rootBackground{
    -fx-background-color:  -rootBackgroundColor;
}
.popupBackground{
    -fx-background-color:  -topWindowBackgroundColor;
}

.settingsButton{
    -fx-background-color: -rootBackgroundColor;
    -fx-text-fill:  -settingsTextColor;
    -fx-background-radius: 5em;
}

.settingsButton:hover{
    -fx-cursor: hand;
    -fx-background-color: -settingsButtonHoverColor;
}

.settingslabel{
    -fx-text-fill:  -mostFontColor;
    -fx-font: 14 arial;
}
.settingstoggle{
    -jfx-toggle-color: -toggleButtonColor;
    -jfx-untoggle-color: -untoggleButtonColor;
    -jfx-toggle-line-color: -togglelineButtonColor;
    -jfx-untoggle-line-color: -untogglelineButtonColor;
    -jfx-size: 9.0;
    -jfx-disable-visual-focus: false;
}

Before I just overwrote the “style.css” file and used .stop() & .show(). This worked fine. But it can get complicated the more css-files you have and after it is packed into a .jar file it doesn’t work anymore.

I hope anyone can help me. Because now I have really no Idea…

Leave a Comment