javafx change css at runtime - css

Is it possible to change css for a JavaFX application while it is running?
The effect I am looking for is changing skins or themes at the click of a button.
The UI is in an FXML file if that makes any difference.
I have tried
Scene.getStylesheets()
.add(getClass().getResource(skinFileName).toExternalForm());
which has no effect.
thanks

It should have the effect. Try this full demo code:
public class CssThemeDemo extends Application {
private String theme1Url = getClass().getResource("theme1.css").toExternalForm();
private String theme2Url = getClass().getResource("theme2.css").toExternalForm();
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
final Scene scene = new Scene(root, 300, 250);
System.out.println("scene stylesheets: " + scene.getStylesheets());
scene.getStylesheets().add(theme1Url);
System.out.println("scene stylesheets: " + scene.getStylesheets());
final Button btn = new Button("Load Theme 1");
btn.getStyleClass().add("buttonStyle");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
scene.getStylesheets().remove(theme2Url);
System.out.println("scene stylesheets on button 1 click: " + scene.getStylesheets());
if(!scene.getStylesheets().contains(theme1Url)) scene.getStylesheets().add(theme1Url);
System.out.println("scene stylesheets on button 1 click: " + scene.getStylesheets());
}
});
final Button btn2 = new Button("Load Theme 2");
btn2.getStyleClass().add("buttonStyle");
btn2.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
scene.getStylesheets().remove(theme1Url);
System.out.println("scene stylesheets on button 2 click: " + scene.getStylesheets());
if(!scene.getStylesheets().contains(theme2Url)) scene.getStylesheets().add(theme2Url);
System.out.println("scene stylesheets on button 2 click: " + scene.getStylesheets());
}
});
ComboBox<String> comboBox = new ComboBox<String>(FXCollections.observableArrayList("Just", "another", "control"));
root.getChildren().add(VBoxBuilder.create().spacing(10).children(btn, btn2, comboBox).build());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
theme1 css:
.root{
-fx-font-size: 14pt;
-fx-font-family: "Tahoma";
-fx-base: #DFB951;
-fx-background: #A78732;
-fx-focus-color: #B6A678;
}
.buttonStyle {
-fx-text-fill: #006464;
-fx-background-color: #DFB951;
-fx-border-radius: 20;
-fx-background-radius: 20;
-fx-padding: 5;
}
theme2 css:
.root{
-fx-font-size: 16pt;
-fx-font-family: "Courier New";
-fx-base: rgb(132, 145, 47);
-fx-background: rgb(225, 228, 203);
}
.buttonStyle {
-fx-text-fill: red;
-fx-background-color: lightcyan;
-fx-border-color: green;
-fx-border-radius: 5;
-fx-padding: 3 6 6 6;
}
Note the same named CSS selectors in both theme1 and theme2 css files.

You can also try this piece of code(simple and truly illustrative) :
A Container for it: I chose BorderPane.
Add a main Scene for your application.
A Menu Bar with a set of items depending on the look of your application.
And item on the Menu bar.
BorderPane rootPane = new BorderPane();
Parent content = FXMLLoader.load(getClass().getResource("sample.fxml"));
rootPane.setCenter(content);
Scene scene = new Scene(root, 650, 550, Color.WHITE);
// Menu bar
MenuBar menuBar = new MenuBar();
// file menu
Menu fileMenu = new Menu("_File");
MenuItem exitItem = new MenuItem("Exit");
exitItem.setAccelerator(new KeyCodeCombination(KeyCode.X, KeyCombination.SHORTCUT_DOWN));
exitItem.setOnAction(ae -> Platform.exit());
fileMenu.getItems().add(exitItem);
menuBar.getMenus().add(fileMenu);
// Look and feel menu
Menu themeMenu = new Menu("_Theme");
themeMenu.setMnemonicParsing(true);
menuBar.getMenus().add(themeMenu);
rootPane.setTop(menuBar);
MenuItem theme1 = new MenuItem("Theme 1");
theme1.setOnAction(ae -> {
scene.getStylesheets().clear();
setUserAgentStylesheet(null);
scene.getStylesheets()
.add(getClass()
.getResource("theme1.css")
.toExternalForm());
});
MenuItem theme2 = new MenuItem("Theme 2");
theme2.setOnAction(ae -> {
scene.getStylesheets().clear();
setUserAgentStylesheet(null);
scene.getStylesheets()
.add(getClass()
.getResource("theme2.css")
.toExternalForm());
});
themeMenu.getItems()
.addAll(theme1,
theme2);
primaryStage.setScene(scene);
primaryStage.show();
Supposed that you have your two CSS files in the folder of the class
where you will call this code with the corresponding name theme1.css
and theme2.css.
Now you can switch between two themes while your application is running.

Related

How to corectly obtain the outer bounds of a node in JavaFX

I am trying to manually align nodes on a JavaFX Pane, but can't seem to be able to get the actual outer bounds of the nodes.
I don't know how better to explain the problem than by showing the following example I created to demonstrate it:
This window is generated by the following code:
public class JavaFXMWE extends Application {
private Pane root;
#Override
public void start(Stage primaryStage) {
root = new Pane();
normal();
bold();
inStackPane();
inStackPaneWithInsets();
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("MWE");
primaryStage.setScene(scene);
primaryStage.show();
}
private void normal() {
Text text1 = new Text("Some Text");
Text text2 = new Text("Some other Text");
text1.relocate(20, 20);
text2.relocate(text1.getBoundsInParent().getMaxX(), 20);
root.getChildren().addAll(text1, text2);
}
private void bold() {
Text text1 = new Text("Some Text");
Text text2 = new Text("Some other Text");
text1.setStyle("-fx-font-weight: bold");
text2.setStyle("-fx-font-weight: bold");
text1.relocate(20, 40);
text2.relocate(text1.getBoundsInParent().getMaxX(), 40);
root.getChildren().addAll(text1, text2);
}
private void inStackPane() {
Text text1 = new Text("Some Text");
Text text2 = new Text("Some other Text");
StackPane pane1 = new StackPane(text1);
StackPane pane2 = new StackPane(text2);
setBorder(pane1);
setBorder(pane2);
pane1.relocate(20, 60);
pane2.relocate(pane1.getBoundsInParent().getMaxX(), 60);
root.getChildren().addAll(pane1, pane2);
}
private void inStackPaneWithInsets() {
Text text1 = new Text("Some Text");
Text text2 = new Text("Some other Text");
StackPane pane1 = new StackPane(text1);
StackPane pane2 = new StackPane(text2);
setBorderAndInsets(pane1);
setBorderAndInsets(pane2);
pane1.relocate(20, 85);
pane2.relocate(pane1.getBoundsInParent().getMaxX(), 85);
root.getChildren().addAll(pane1, pane2);
}
private static void setBorder(Region node) {
node.setBorder(new Border(new BorderStroke(Color.BLACK,
BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT)));
}
private static void setBorderAndInsets(Region node) {
setBorder(node);
node.setPadding(new Insets(5));
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
How do I obtain the actual outer bounds of the nodes?
The CSS style is applied just before the first layout pass. The style property has no effect on the node until this happens. For this reason positioning children yourself is best done by overriding the layoutChildren method; this method is invoked during the layout pass. Alternatively you could add a listener to the boundsInParent property.
#Override
public void start(Stage primaryStage) throws Exception {
Text text1 = new Text("Some Text");
Text text2 = new Text("Some other Text");
text1.setStyle("-fx-font-weight: bold");
text2.setStyle("-fx-font-weight: bold");
Pane root = new Pane(text1, text2) {
#Override
protected void layoutChildren() {
text1.relocate(20, 40);
text2.relocate(text1.getLayoutX() + text1.prefWidth(-1), 40); // using text1.boundsInParent would work too, usually the size constraints returned by the minWidth, prefWidth and maxWidth methods should be used
}
};
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
}

How to disable right click on a menu in javafx

In javaFX code, a menu can popup by left click or right click. How to disable right click?
public void start(Stage primaryStage)
{
BorderPane root = new BorderPane();
MenuBar menuBar = new MenuBar();
Menu hello = new Menu("hello");
menuBar.getMenus().addAll(hello);
Menu world = new Menu("world");
menuBar.getMenus().addAll(world);
root.setCenter(menuBar);
MenuItem item = new MenuItem("laugh");
hello.getItems().add(item);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
When I right click the "hello" menu, it will popup menuitem "laugh".
The basic approach is to register a eventFilter on the MenuBar that consumes the events that should not be delivered to the children.
Doing so manually in your application code:
public class DisableRightClickOpenMenu extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
MenuBar menuBar = new MenuBar();
menuBar.addEventFilter(MouseEvent.MOUSE_PRESSED, ev -> {
if (ev.getButton() == MouseButton.SECONDARY) {
ev.consume();
}
});
Menu hello = new Menu("hello");
menuBar.getMenus().addAll(hello);
Menu world = new Menu("world");
menuBar.getMenus().addAll(world);
root.setCenter(menuBar);
MenuItem item = new MenuItem("laugh");
hello.getItems().add(item);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you want this behaviour across all your applications, you can implement a custom menuBarSkin that registers the filter and install the custom skin via a stylesheet.
The skin:
public class ExMenuBarSkin extends MenuBarSkin {
/**
* Instantiates a skin for the given MenuBar. Registers an
* event filter that consumes right mouse press.
*
* #param menuBar
*/
public ExMenuBarSkin(MenuBar menuBar) {
super(menuBar);
menuBar.addEventFilter(MouseEvent.MOUSE_PRESSED, ev -> {
if (ev.getButton() == MouseButton.SECONDARY) {
ev.consume();
}
});
}
}
In your stylesheet (replace with your fully qualified class name):
.menu-bar {
-fx-skin: "de.swingempire.fx.event.ExMenuBarSkin";
}
Its usage (replace the name with your stylesheet file name):
URL uri = getClass().getResource("contextskin.css");
primaryStage.getScene().getStylesheets().add(uri.toExternalForm());
This is usual behavior of menu in many programs. I don't think you can change it. However, you can use some other controls and simulate menu. (Like HBox and Labels).
I agree as far as I know there's no a standard way to do this, but you may want to consider the following workaround.
It is replacing the Menu node with a Menu object composed by an HBox and a Label: an EventHandler is added to the HBox and by checking the mouse button pressed we add/remove on the fly the MenuItem to its parent.
#Override
public void start(final Stage primaryStage) {
final BorderPane root = new BorderPane();
final MenuBar menuBar = new MenuBar();
final Menu menuHello = new Menu();
final Menu menuWorld = new Menu("world");
final MenuItem menuitem = new MenuItem("laugh");
final HBox hbox = new HBox();
menuBar.getMenus().addAll(menuHello, menuWorld);
root.setCenter(menuBar);
hbox.setPrefWidth(30);
hbox.getChildren().add(new Label("hello"));
menuHello.setGraphic(hbox);
menuHello.getItems().add(menuitem);
hbox.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(final MouseEvent e) {
if (e.getButton() == MouseButton.SECONDARY) {
System.out.println("Right click");
menuHello.getItems().remove(menuitem);
} else {
System.out.println("Left click");
if (!menuHello.getItems().contains(menuitem)) {
menuHello.getItems().add(menuitem);
menuHello.show(); // The .show method prevent 'losing' the current click }
}
}
});
final Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
This will produce the following result - preview
Note that I've used an HBox just for habit, there's no a particular reason.
While using a workaround like this, my suggestion would be to fill all the Menus with the same 'pattern', such as the HBox + Label combo in my example, and stylize them via css/code (width/height, background/fill/hover... colors etc.) in order to have them as uniform as possible and avoid creating graphic inconsistencies due to have different nodes types in the same menubar.

Style for floating TabPane does not appear

Trying to customize a floating TabPane by adding a CSS file just like in the example below. The new style does not appear for floating tabpane only - for a regular pane the style appears as expected.
public class FloatingTabPaneTest extends Application
{
public static void main(String[] args)
{
Application.launch(args);
}
#Override
public void start(Stage stage)
{
Parent root = createContentPane();
root.getStylesheets().add(getClass().getResource("/floatingTabPane.css").toExternalForm());
Scene scene = new Scene(root, 1000, 800);
stage.setScene(scene);
stage.setTitle(getClass().getSimpleName());
stage.show();
}
private Parent createContentPane()
{
TabPane tabPane = new TabPane();
tabPane.getStyleClass().add(TabPane.STYLE_CLASS_FLOATING);
addTab(tabPane, "Tab 1", new StackPane());
addTab(tabPane, "Tab 2", new StackPane());
addTab(tabPane, "Tab 3", new StackPane());
tabPane.getSelectionModel().selectLast();
return new BorderPane(tabPane);
}
private void addTab(TabPane tabPane, String name, Node content)
{
Tab tab = new Tab();
tab.setText(name);
tab.setContent(content);
tabPane.getTabs().add(tab);
}
}
FloatingTabPane.css:
.tab-pane.floating > .tab-header-area > .tab-header-background {
-fx-border-color: red;
-fx-border-width: 2;
-fx-background-color: cyan;
}
This is pretty interesting. The issue is because the headerBackground visibility is set to false, if you are on FLOATING style class.
If you search inside TabPaneSkin, you will find :
if (isFloatingStyleClass()) {
headerBackground.setVisible(false); // <---- Imp part
} else {
headerBackground.resize(snapSize(getWidth()), snapSize(getHeight()));
headerBackground.setVisible(true);
}
Since its visibility is set to false, your best shot is to do your changes on the tab-header-area instead of tab-header-background
.tab-pane.floating > .tab-header-area {
-fx-border-color: red;
-fx-border-width: 2;
-fx-background-color: cyan;
}
This will leave a thin red line on the tabs, but this is better than having no style at all ;)

JavaFX. How to make the border of imageview, when I click the imageview?

In javaFX, I want make a imageview that can change border when I click.
When click once, then imageview has a border.
When click again, then imageview doesn't have a border.
How can I make that?
Thanks in advance!
Well you will need:
a PseudoClass for toggling the CSS state
a wrapping Region, because the ImageView itself does neither support a background nor a border.
A simple working example (the toggling of the PseudoClass is done with the help of a BooleanProperty, which is common practice and makes the management of its state easier):
#Override
public void start(Stage primaryStage) {
PseudoClass imageViewBorder = PseudoClass.getPseudoClass("border");
ImageView imageview = new ImageView(
new Image("http://upload.wikimedia.org/wikipedia/commons/1/16/Appearance_of_sky_for_weather_forecast,_Dhaka,_Bangladesh.JPG"));
BorderPane imageViewWrapper = new BorderPane(imageview);
imageViewWrapper.getStyleClass().add("image-view-wrapper");
BooleanProperty imageViewBorderActive = new SimpleBooleanProperty() {
#Override
protected void invalidated() {
imageViewWrapper.pseudoClassStateChanged(imageViewBorder, get());
}
};
imageview.setOnMouseClicked(ev -> imageViewBorderActive
.set(!imageViewBorderActive.get()));
BorderPane root = new BorderPane(imageViewWrapper);
root.setPadding(new Insets(15));
Scene scene = new Scene(root, 700, 400);
scene.getStylesheets().add(
getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
and the necessary CSS:
.image-view-wrapper:border {
-fx-border-color: black;
-fx-border-style: solid;
-fx-border-width: 5;
}

Show drop down menu on mouse over

I want to create drop down menu like this:
I want when I place the mouse over the text to see combo box which I can use to select a value. When I remove the mouse I want to see simple Label. How I can do this?
Unhovered:
On Hover:
On Click and Choose:
On Choice Complete:
import javafx.application.Application;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class Hoverboard extends Application {
public class TextChooser extends StackPane {
private Label label = new Label();
private ComboBox<String> combo = new ComboBox<>();
public TextChooser(String... options) {
StackPane.setAlignment(label, Pos.CENTER_LEFT);
StackPane.setAlignment(combo, Pos.CENTER_LEFT);
label.textProperty().bind(
combo.getSelectionModel().selectedItemProperty()
);
label.visibleProperty().bind(
combo.visibleProperty().not()
);
label.setPadding(new Insets(0, 0, 0, 9));
combo.getItems().setAll(options);
combo.getSelectionModel().select(0);
combo.setVisible(false);
label.setOnMouseEntered(event -> combo.setVisible(true));
combo.showingProperty().addListener(observable -> {
if (!combo.isShowing()) {
combo.setVisible(false);
}
});
combo.setOnMouseExited(event -> {
if (!combo.isShowing()) {
combo.setVisible(false);
}
});
getChildren().setAll(label, combo);
}
}
#Override
public void start(Stage stage) throws Exception {
TextChooser textChooser = new TextChooser(
"xyzzy", "frobozz", "foobar"
);
VBox layout = new VBox(textChooser);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(Hoverboard.class);
}
}
Here is also css style version: https://github.com/varren/JavaFX-CSS-Styled-ComboBox-Demo
A little bit different from the default one, but you can play with css to get what you want. Default styles can be found in jxrt.jar!/com/sun/javafx/scene/control/skin/caspian/caspian.css
CSS
#changed{
-fx-background-color: transparent;
}
#changed .arrow,
#changed .arrow-button{
-fx-background-color: transparent;
}
/* this part is from default stiles fxrt.jar!/com/sun/javafx/scene/control/skin/caspian/caspian.css */
#changed:hover{
-fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;
-fx-background-radius: 5, 5, 4, 3;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-padding: 0;
}
#changed:showing > .arrow-button {
-fx-color: -fx-pressed-base;
}
#changed:hover > .arrow-button > .arrow{
-fx-background-insets: 1 0 -1 0, 0;
-fx-background-color: -fx-mark-highlight-color, -fx-mark-color;
-fx-padding: 0.166667em 0.333333em 0.166667em 0.333333em; /* 2 4 2 4 */
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
}
JAVA
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
HBox root = new HBox();
primaryStage.setTitle("Combo Box Style From Css");
ComboBox combobox = new ComboBox<String>(FXCollections.observableArrayList("One", "Two", "Three"));
combobox.getSelectionModel().select(0);
combobox.setId("changed");
ComboBox normalCombobox = new ComboBox<String>(FXCollections.observableArrayList("One", "Two", "Three"));
normalCombobox.getSelectionModel().select(0);
root.getChildren().addAll(combobox, normalCombobox);
Scene scene = new Scene(root, 300, 275);
scene.setFill(Color.WHITE);
String css = Main.class.getResource("styles.css").toExternalForm();
scene.getStylesheets().clear();
scene.getStylesheets().add(css);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
DEMO
Two potential approaches:
Try to use CSS to modify the look of the ComboBox so it looks like a normal text field; hide the arrow and border, and re-show them on :hover. You'll want to lookup the CSS reference for ComboBox: http://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#combobox
Use a normal TextField, and display a border (and arrow) on :hover. Attach a mouse-listener to the TextField to display a PopupControl on mouse click. Put a ListView inside the PopupControl so it behaves like a ComboBox. You'll need to create a class that implements Skin for your popup control. You should be able to find some examples on the web.

Resources