JavaFX set diffenrent styles to ProgressIndicator and Spinner - java

at the moment i'm working with a Progress-Indicator and a bunch of spinners.
I want to give them different styles in one CSS-File. My Problem is, that the Progress-Indicator always uses the style of I planned for the spinners.
I did an little application as example:
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Pane root = new Pane();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
ProgressIndicator value = new ProgressIndicator(-1.0);
value.getStyleClass().forEach(clazz -> System.out.println(clazz));
value.setPrefHeight(100);
value.setPrefWidth(100.0);
root.getChildren().add(value);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
I want to have my Progress-Indicator a red background and trying it todo like this:
.progress-indicator > .indicator {
-fx-background-color: red;
}
but without styling the spinner it won't work. so i append this:
.spinner {
-fx-background-color: red;
}
The problem is, that all my spinners now got red backgrounds. I could do it by using diffenrent CSS-Files. But It would be way better if I only need one CSS-File.
Thanks in advance for your time and help!

You faced this bug in modena.css which is fixed in 8u40. Now you can safely change the background color of progress indicator with:
.progress-indicator:indeterminate > .spinner {
/** Applying to undo styling from .spinner, reported in RT-37965 */
-fx-background-color: red;
/* originally was */
/*-fx-background-color: transparent;*/
-fx-background-insets: 0;
-fx-background-radius: 0;
}

I think you can create styles for each individual class?
Can't you add a custom style class for each spinner and apply that using spinner.getStyleClass().add("my-style")?
This will allow you to define may classes at a more specified level than just at the object level.

Related

How can I change the color of a JFXSlider's thumb according to the color of the slider at the current position of the thumb on the slider?

I'm using a JFXSlider in JavaFX and I've used a linear gradient for the color of the JFXSlider's track (with CSS). However, I'd also like to change the color of the thumb to that of the slider for that position. I've used the following CSS for the slider's linear gradient and for getting rid of the default green color of the JFXSlider:
.jfx-slider .track {
-fx-pref-height: 10;
-fx-background-color: linear-gradient(to right,red,orange);
}
.jfx-slider .colored-track {
-fx-background-color: transparent;
}
I tried the following CSS code to get the thumb color to be the same as that of the slider at the current position, but it didn't work.
.jfx-slider .thumb {
-fx-background-color: linear-gradient(to right,red,orange);
}
I guess it's probably that the code I tried only provides an internal linear gradient for the thumb's background color. Does anyone know how to solve this problem? P.S. I'm using JFoenix 9.0.10, JavaFX 15, and JDK 15.
One possible solution would be to add a global CSS variable and change it depending on the JFXSlider current value. For example :
.root {
-fx-custom-color : red;
}
And then use this variable on your jfx-slider css rules like :
/* Styling the slider thumb */
.jfx-slider>.thumb {
-fx-background-color: -fx-custom-color;
}
/* Styling the animated thumb */
.jfx-slider>.animated-thumb {
-fx-background-color: -fx-custom-color;
}
After that, you need to figure out how to update the "-fx-custom-color" variable and how to determine which color you need to set for the specific value of the Slider (or rather location).
First, you should add a listener to the value property to listen for value changes. Second, use the interpolate method of the Color class to determine the color, and finally, update the new value for the -fx-custom-color using inline CSS style to the JFXSlider.
Here is a complete example :
import com.jfoenix.controls.JFXSlider;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class SliderTesting extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
JFXSlider slider = new JFXSlider(0, 100, 0);
slider.valueProperty().addListener(e -> {
Color imageColor = Color.RED.interpolate(Color.ORANGE,
slider.getValue() / 100);
slider.setStyle("-fx-custom-color : " + colorToHex(imageColor) + ";");
});
VBox box = new VBox(slider);
box.setPadding(new Insets(20));
box.setPrefSize(400, 400);
box.setAlignment(Pos.CENTER);
Scene scene = new Scene(box);
scene.getStylesheets()
.add(this.getClass().getResource("custom-jfoenix.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
public static String colorToHex(Color color) {
return String.format("#%02X%02X%02X", (int) (color.getRed() * 255),
(int) (color.getGreen() * 255), (int) (color.getBlue() * 255));
}
}
And the "custom-jfoenix.css" file
.root {
-fx-custom-color : red;
}
/* Styling the slider track */
.jfx-slider>.track {
-fx-pref-height: 10;
}
/* Styling the slider thumb */
.jfx-slider>.thumb {
-fx-background-color: -fx-custom-color;
}
/* Styling the filled track */
.jfx-slider>.colored-track {
-fx-background-color: linear-gradient(to right, red, orange);
}
/* Styling the animated thumb */
.jfx-slider>.animated-thumb {
-fx-background-color: -fx-custom-color;
}
And the result :

How to force my custom skin to be used by all buttons in my JavaFX application?

Suppose I wrote a new skin for Button.
In JavaFX 8 it can look like:
import com.sun.javafx.scene.control.skin.ButtonSkin;
// This is quite dirty because of using non-stable api:
public class MyButtonSkin extends ButtonSkin {
...
}
Then I wish to use this skin in my application for every button created by FXMLLoader (via <Button ...> fxml-element)
and I don't want to change fxml-files.
My questions are:
What are possibilities to do this in JavaFX 8?
Can be some DI-framework used to do this?
And what about JavaFX 9?
Additional code example:
This skin introduces new underkeypress pseudoclass for buttons:
import com.sun.javafx.scene.control.skin.ButtonSkin;
import javafx.css.PseudoClass;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class MyButtonSkin extends ButtonSkin {
private static final PseudoClass PSEUDO_CLASS_UNDERKEYPRESS = PseudoClass.getPseudoClass("underkeypress");
public MyButtonSkin(Button button) {
super(button);
button.addEventFilter(KeyEvent.KEY_PRESSED,
(event -> {
if (event.getCode() == KeyCode.SPACE) {
pseudoClassStateChanged(PSEUDO_CLASS_UNDERKEYPRESS, true);
}
}));
button.addEventFilter(KeyEvent.KEY_RELEASED,
(event -> {
if (event.getCode() == KeyCode.SPACE) {
pseudoClassStateChanged(PSEUDO_CLASS_UNDERKEYPRESS, false);
}
}));
}
}
That pseudoclass should be used via CSS like:
.button:underkeypress {
-fx-base: red;
}
As #kleopatra noted the best way to apply the skin to all Buttons in your scene is using a CSS stylesheet.
.button {
-fx-skin: "my.package.MyButtonSkin";
}
apply this stylesheet to the root of the scene
Parent root = loader.load(); // load fxml
root.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
AFAIK there is no easier way in JavaFX 9 for this. The only difference is that ButtonSkin was moved to the public API, see javafx.scene.control.skin.ButtonSkin.

How to scale Label within a ListView in JavaFX

I have a ListView with some Labels in it. The labels' width property is bound to the width property of the ListView but they seem to be slightly larger meaning that a horizontal scrollbar is shown on the list view. What I want is to fit the labels in the list view without the scrollbar on the bottom. I have looked at various padding and insets values on both the label and the list view but none I have found are the culprit (most are zero).
Here is an example which demonstrates the problem.
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
public class ListViewScrollExample extends Application {
private ListView<Node> listView;
#Override
public void start(Stage stage) throws Exception {
listView = new ListView<>();
addItem("Some quite long string to demonstrate the problem");
Scene scene = new Scene(listView);
stage.setScene(scene);
stage.show();
}
public void addItem(String item) {
Label label = new Label(item);
label.setWrapText(true);
label.maxWidthProperty().bind(listView.widthProperty());
listView.getItems().add(label);
}
public static void main(String[] args){
Application.launch(args);
}
}
The default CSS file adds padding to a ListCell (line 2316 in the current release):
.list-cell {
-fx-padding: 0.25em 0.583em 0.25em 0.583em; /* 3 7 3 7 */
}
It generally a bad idea to use Node instances as the data backing a ListView: you should use String in this example, and use the cell factory to create a label displaying the string that is configured as you need. The following seems to work for your example:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
public class ListViewScrollExample extends Application {
private ListView<String> listView;
#Override
public void start(Stage stage) throws Exception {
listView = new ListView<>();
listView.getItems().add("Some quite long string to demonstrate the problem");
listView.setCellFactory(lv -> {
ListCell<String> cell = new ListCell<String>() {
private Label label = new Label();
{
label.setWrapText(true);
label.maxWidthProperty().bind(Bindings.createDoubleBinding(
() -> getWidth() - getPadding().getLeft() - getPadding().getRight() - 1,
widthProperty(), paddingProperty()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
label.setText(item);
setGraphic(label);
}
}
};
return cell ;
});
Scene scene = new Scene(listView);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args){
Application.launch(args);
}
}
Here I created a list cell that displays a label as its graphic, with the text of the label set to the string to be displayed. The constructor for the cell binds the label's max width to the width of the cell, less any space required for padding. The call to setContentDisplay(ContentDisplay.GRAPHIC_ONLY) appears necessary, so the cell doesn't try to allocate any space for text.
It may be possible to do this by setting the text directly on the list cell and calling setWrapText(true) on the cell (which is, after all, also a subclass of Labeled), but I couldn't get it to work this way.
I couldn't replicate the problem but you can try the following instead of label.maxWidthProperty().bind(listView.widthProperty());
double i = Double.parseDouble(listView.widthProperty().toString());
label.setMaxWidth((i-2.0));
You can change the 2.0 to any pixel count you need to alter the screen by.

JavaFX: Remove percentage from ProgressIndicator

I have been trying to remove the percentage text below the ProgressIndicator, but is unsuccessful. I have found several mentions of this and tried the accepted answer, but it does not work
Using Java 8 Update 121
package com.company.mytest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ProgressApp extends Application {
public static void main(String[] args) {
launch(args);
}
/* (non-Javadoc)
* #see javafx.application.Application#start(javafx.stage.Stage)
*/
#Override
public void start(Stage stage) throws Exception {
ProgressIndicator progress = new ProgressIndicator();
progress.setProgress(0.5f);
StackPane root = new StackPane();
root.getChildren().add(progress);
Scene scene = new Scene(root);
scene.getStylesheets()
.add(getClass().getResource("progress.css").toExternalForm());
stage.setScene(scene);
stage.setWidth(300);
stage.setHeight(300);
stage.setTitle("JavaFX 8 app");
stage.show();
}
}
I know the CSS is loaded, because I am able to change the percentage font size. This is the CSS content of the progress.css file.
.progress-indicator .percentage {
visibility: hidden;
}
Setting the -fx-fill attribute to null seems solving the problem:
.progress-indicator .percentage {
-fx-fill:null;
}
Update:
Setting only this attribute hides the percentage text, but still takes up the space.
A possible workaround is to set the -fx-padding attribute of the ProgressIndicator:
.progress-indicator .percentage {
-fx-fill:null;
}
.progress-indicator {
-fx-padding: 0 0 -16 0;
}
The only problem is the hardcoded value: if the CSS for .progress-indicator .percentage is changed (e.g. bigger font size) then this also has to be adapted.
Alternatively a programatic solution in this answer: How can I avoid the display of percentage values, when using the ProgressIndicator of JavaFX UI Controls

JavaFX 2 TextArea that changes height depending on its contents

In JavaFX 2.2, is there any way to make TextArea (with setWrapText(true) and constant maxWidth) change its height depending on contents?
The desired behaviour: while user is typing something inside the TextArea it resizes when another line is needed and decreases when the line is needed no more.
Or is there a better JavaFX control that could be used in this situation?
You can bind the prefHeight of the text area to the height of the text it contains. This is a bit of a hack, because you need a lookup to get the text contained in the text area, but it seems to work. You need to ensure that you lookup the text node after CSS has been applied. (Typically this means after it has appeared on the screen...)
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ResizingTextArea extends Application {
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
textArea.setWrapText(true);
textArea.sceneProperty().addListener(new ChangeListener<Scene>() {
#Override
public void changed(ObservableValue<? extends Scene> obs, Scene oldScene, Scene newScene) {
if (newScene != null) {
textArea.applyCSS();
Node text = textArea.lookup(".text");
textArea.prefHeightProperty().bind(Bindings.createDoubleBinding(new Callable<Double>() {
#Override
public Double call() {
return 2+text.getBoundsInLocal().getHeight();
}
}), text.boundsInLocalProperty()));
}
}
});
VBox root = new VBox(textArea);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Two things to add to James_D's answer (because I lack the rep to comment):
1) For big fonts like size 36+, the text area size was wrong at first but corrected itself when I clicked inside the text area. You can call textArea.layout() after applying CSS, but the text area still does not resize immediately after the window is maximized. To get around this, call textArea.requestLayout() asynchronously in a Change Listener after any change to the Text object's local bounds. See below.
2) The text area was still a few pixels short and the scroll bar still visible. If you replace the 2 with textArea.getFont().getSize() in the binding, the height fits perfectly to the text, no matter whether the font size is tiny or huge.
class CustomTextArea extends TextArea {
CustomTextArea() {
setWrapText(true);
setFont(Font.font("Arial Black", 72));
sceneProperty().addListener((observableNewScene, oldScene, newScene) -> {
if (newScene != null) {
applyCss();
Node text = lookup(".text");
// 2)
prefHeightProperty().bind(Bindings.createDoubleBinding(() -> {
return getFont().getSize() + text.getBoundsInLocal().getHeight();
}, text.boundsInLocalProperty()));
// 1)
text.boundsInLocalProperty().addListener((observableBoundsAfter, boundsBefore, boundsAfter) -> {
Platform.runLater(() -> requestLayout());
});
}
});
}
}
(The above compiles for Java 8. For Java 7, replace the listener lambdas with Change Listeners according to the JavaFX API, and replace the empty ()-> lambdas with Runnable.)

Categories

Resources