I want arrow keys in a ScrollPane to work in a way other than the default. Consuming arrow keys in a KeyPressed handler on the ScrollPane does not prevent them from being processed by he ScrollPane. Is there something that works?
In the example program,
type h or left-arrow to move the rect to the left on the grid as expected.
type l or right-arrow to move the rect to the right on the grid as expected.
Fig 1. Start
Fig 2. Start, then h key (rect moves left on the grid)
Fig. 3. Start, then left-arrow key (rect moves left on the grid, and the ScrollPane slider makes an unwanted move to the left)
Notes:
KeyClicked and KeyReleased are apparently irrelevant.
A workaround could be to request focus on the rect, but that has unwanted complications.
Another workaround could be to focus on the contained Pane.
I like that the OS highlights the border of the ScrollPane, indicating keyboard focus.
Focusing on the contained Pane prevents the highlighting for keyboard focus.
Overriding the default ScrollPane behavior of the arrow keys is not a problem for my App.
Related questions on StackOverflow are not quite the same situation.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ScrollPaneArrowKeys extends Application {
public static void main(String[] args) { launch(args); }
static Rectangle rect;
static ScrollPane sp;
#Override
public void start(Stage stage) {
rect = new Rectangle (100, 50);
var pane = new Pane (rect);
sp = new ScrollPane(pane);
var scene = new Scene (sp);
rect.setFill(Color.PALEGREEN);
pane.setMinWidth (600);
pane.setMinHeight(150);
pane.setStyle(grid);
sp.setMinWidth (300);
sp.setMinHeight(170);
sp.setHvalue(0.5);
sp.setOnKeyPressed(ScrollPaneArrowKeys::onKeyPressed);
sp.requestFocus();
reset();
stage.setScene(scene);
stage.show();
}
private static void onKeyPressed(KeyEvent e) {
switch (e.getCode()) {
case H:
case LEFT: rect.setX(rect.getX() - 10); break;
case L:
case RIGHT: rect.setX(rect.getX() + 10); break;
case ESCAPE: reset(); break;
default: break;
}
e.consume();
}
private static void reset() {
rect.setX(250);
rect.setY( 50);
sp.setHvalue(0.5);
}
String grid = """
-fx-background-color: white,
linear-gradient(from 0px 0px to 10px 0px, repeat, #d8f0f8 6.25%, transparent 6.25%),
linear-gradient(from 0px 0px to 50px 0px, repeat, #b0e0e8 1.25%, transparent 1.25%),
linear-gradient(from 0px 0px to 0px 10px, repeat, #d8f0f8 6.25%, transparent 6.25%),
linear-gradient(from 0px 0px to 0px 50px, repeat, #b0e0e8 1.25%, transparent 1.25%);
""";
}
If you want to override the default key handling of the Scroll Pane, you can achieve it with registering an Event Filter (instead of adding another Event Handler). You can read about this in e. g. this posting: JavaFX: What is the difference between EventHandler and EventFilter?
Considering the User Experience you may want to add another possibility for scrolling (e. g. CTRL + LEFT etc.). Here is a working example (the most part is taken from your code):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ScrollPaneArrowKeys extends Application {
private final Rectangle rect = new Rectangle(100, 50);
private final Pane pane = new Pane(rect);
private final ScrollPane sp = new ScrollPane(pane);
private final Scene scene = new Scene(sp);
#Override
public void start(Stage stage) {
// Register an Event Filter to override the default behaviour:
sp.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
e.consume(); // prevent default key handling
// Allow the user to scroll horizontally with the control key:
if (e.isControlDown())
switch (e.getCode()) {
case LEFT -> sp.setHvalue(sp.getHvalue() - .1);
case RIGHT -> sp.setHvalue(sp.getHvalue() + .1);
}
else
// Move they rectangle only:
switch (e.getCode()) {
case H, LEFT -> rect.setX(rect.getX() - 10);
case L, RIGHT -> rect.setX(rect.getX() + 10);
case ESCAPE -> resetRectPosition();
}
});
initStyling();
resetRectPosition();
stage.setScene(scene);
stage.show();
sp.requestFocus();
}
private void initStyling() {
rect.setFill(Color.PALEGREEN);
pane.setMinWidth(600);
pane.setMinHeight(150);
pane.setStyle("""
-fx-background-color: white,
linear-gradient(from 0px 0px to 10px 0px, repeat, #d8f0f8 6.25%, transparent 6.25%),
linear-gradient(from 0px 0px to 50px 0px, repeat, #b0e0e8 1.25%, transparent 1.25%),
linear-gradient(from 0px 0px to 0px 10px, repeat, #d8f0f8 6.25%, transparent 6.25%),
linear-gradient(from 0px 0px to 0px 50px, repeat, #b0e0e8 1.25%, transparent 1.25%);
""");
sp.setMinWidth(300);
sp.setMinHeight(170);
sp.setHvalue(0.5);
}
private void resetRectPosition() {
rect.setX(250);
rect.setY(50);
sp.setHvalue(0.5);
}
public static void main(String[] args) {
launch(args);
}
}
Related
Javafx linear-gradient repeat seems to reflect the colours rather than repeat.
I wrote a simple application to show what I see when using linear-gradient with repeat to create a striped pattern in my application on a custom Node (a StackPane). In my application this are added as overlays to a XYChart and their height varies. Using a Rectangle wasn't working well which is why I use a Stackpane and set a style on it rather than creating the LinearGradient programmatically.
The colour list is dynamic and varies in size in the application.
The issue is the way linear-gradient flips the list and reflects the colours on each repeat rather than just repeat.
This link describes a similar issue but just adding in endless stops seemless like a messy solution for my issue, it would be much better to add the colours once and repeat.
linear gradient repeat on css for javafx
java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
List<Color> colors = Arrays.asList( Color.RED,Color.BLUE,Color.YELLOW,Color.GREEN);
StackPane stackPane = new StackPane();
stackPane.setStyle(getLinearGradientStyle(colors));
root.setCenter(stackPane);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
catch (Exception e) {
e.printStackTrace();
}
}
private String getLinearGradientStyle(List<Color> colors) {
StringBuilder stringBuilder = new StringBuilder("-fx-background-color: linear-gradient(from 0px 0px to 10px 10px, repeat,");
for (int i = 0; i < colors.size(); i++) {
stringBuilder.append("rgb(")
.append((int) (colors.get(i).getRed() * 255)).append(",")
.append((int) (colors.get(i).getGreen() * 255)).append(",")
.append((int) (colors.get(i).getBlue() * 255))
.append(")")
.append(" ").append(getPercentage(i+1, colors.size()+1) );
if (i < colors.size() - 1) {
stringBuilder.append(",");
}
}
stringBuilder.append(");");
System.out.println("Main.getLinearGradientStyle():"+stringBuilder);
return stringBuilder.toString();
}
private String getPercentage(float i, int size) {
return (((1.0f / size) * 100 )*i)+ "%";
}
public static void main(String[] args) {
launch(args);
}
Here's a CSS3 example using repeating-linear-gradient:
https://tympanus.net/codrops/css_reference/repeating-linear-gradient/
scroll down to the following text: will create a striped background, where each linear gradient is a three-stripe gradient, repeated infinitely (this is the example)
My example uses a diagonal pattern which is what I need but the above example shows what I'd like to see in terms of solid repeating colours with out reflection in normal css.
Thanks for any help
This looks like a bug. If you run the following example (moved the CSS into a file):
Main.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Region region = new Region();
region.backgroundProperty().addListener((obs, ov, nv) ->
System.out.println(nv.getFills().get(0).getFill()));
Scene scene = new Scene(region, 500, 300);
scene.getStylesheets().add("Main.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
Main.css
.root {
-fx-background-color: linear-gradient(from 0px 0px to 10px 10px, repeat, red 20%, blue 40%, yellow 60%, green 80%);
}
You'll see the following printed out:
linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, 0xff0000ff 0.0%, 0xff0000ff 20.0%, 0x0000ffff 40.0%, 0xffff00ff 60.0%, 0x008000ff 80.0%, 0x008000ff 100.0%)
As you can see, despite using "repeat" in the CSS the LinearGradient that is created uses "reflect".
There is likely nothing you can do about this bug yourself, but if you don't mind setting the background in code (or probably even FXML) then the following should do what you want:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
LinearGradient gradient = new LinearGradient(0, 0, 10, 10, false, CycleMethod.REPEAT,
new Stop(0.2, Color.RED),
new Stop(0.4, Color.BLUE),
new Stop(0.6, Color.YELLOW),
new Stop(0.8, Color.GREEN)
);
Region region = new Region();
region.setBackground(new Background(new BackgroundFill(gradient, null, null)));
Scene scene = new Scene(region, 500, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
}
You can move the creation of the LinearGradient into a method that takes an arbitrary number of Colors, just like you're currently doing.
If you're interested, I believe the bug is located in javafx.css.CssParser around line 1872 (in JavaFX 12):
CycleMethod cycleMethod = CycleMethod.NO_CYCLE;
if ("reflect".equalsIgnoreCase(arg.token.getText())) {
cycleMethod = CycleMethod.REFLECT;
prev = arg;
arg = arg.nextArg;
} else if ("repeat".equalsIgnoreCase(arg.token.getText())) {
cycleMethod = CycleMethod.REFLECT;
prev = arg;
arg = arg.nextArg;
}
As you can see, it erroneously sets the CycleMethod to REFLECT when the text is equal to "repeat".
A bug report has been filed: JDK-8222222 (GitHub #437). Fix version: openjfx13.
https://stackoverflow.com/a/10615258/529411
I would like to add a background color to my tabpane dynamically (depending on certain conditions). How can I achieve this from code? One option is to assign he tab a specific ID which has the associated CSS, but in my case the color can be dynamically chosen by the user.
Also, I'm curious how to apply the styles in code when dealing with a hierarchy of components.
You can assign the background color to be a looked-up color in the CSS file:
.tab-pane > .tab-header-area > .tab-header-background {
-fx-background-color: -fx-outer-border, -fx-text-box-border, my-tab-header-background ;
}
Now in code you can set the value of the looked-up color whenever you need to:
tabPane.setStyle("my-tab-header-background: blue ;");
SSCCE:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.Stage;
public class DynamicTabHeaderBackground extends Application {
private static final String TAB_HEADER_BACKGROUND_KEY = "my-tab-header-background" ;
#Override
public void start(Stage primaryStage) {
TabPane tabPane = new TabPane();
tabPane.setStyle(TAB_HEADER_BACKGROUND_KEY+": blue ;");
tabPane.getTabs().addAll(new Tab("Tab 1"), new Tab("Tab 2"));
tabPane.getSelectionModel().selectedIndexProperty().addListener((obs, oldIndex, newIndex) -> {
if (newIndex.intValue() == 0) {
tabPane.setStyle(TAB_HEADER_BACKGROUND_KEY+": blue ;");
} else {
tabPane.setStyle(TAB_HEADER_BACKGROUND_KEY+": green ;");
}
});
Scene scene = new Scene(tabPane, 400, 400);
scene.getStylesheets().add("dynamic-tab-header.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
with dynamic-tab-header.css containing the CSS code above.
Update
If you have multiple tab panes, you might want to consider the following variant of the CSS file:
.tab-pane {
my-tab-header-background: derive(-fx-text-box-border, 30%) ;
}
.tab-pane > .tab-header-area > .tab-header-background {
-fx-background-color: -fx-outer-border, -fx-text-box-border,
linear-gradient(from 0px 0px to 0px 5px, -fx-text-box-border, my-tab-header-background) ;
}
This basically emulates the default behavior, but allows you to modify the background on any particular tab pane by calling the tabPane.setStyle(...) code as before.
I have a square label with some text, and I was wondering is there was a way to e.g. color the first 3/4 of the label blue, and the last 1/4 red.
If that's not possible, is it then possible with a rectangle?
Label box = new Label();
box.setStroke(Color.GRAY);
box.setPrefWidth(50);
box.setPrefHeight(100);
Use a background color with a linear gradient. The best way to do this is in an external CSS file, using the rule
-fx-background-color: linear-gradient(to right, blue 75%, red 75%);
The format used by the CSS linear-gradient function is described in the JavaFX CSS documentation.
SSCCE:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ColoredLabel extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label("Some text");
label.getStyleClass().add("split");
StackPane root = new StackPane(label);
Scene scene = new Scene(root, 350, 120);
scene.getStylesheets().add("split-background-label.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
with the split-background-label.css file
.split {
-fx-background-color: linear-gradient(to right, blue 75%, red 75%);
-fx-text-fill: white ;
}
I want to change background and border color of TextField use JavaFX CSS. I don't understand why -fx-border-color reset border radius of TextField?
As you can see the second TextField doesn't have border radius.
sample/style.css:
.validation-error {
-fx-background-color: #FFF0F0;
-fx-border-color: #DBB1B1;
}
sample/Main.java
package sample;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
TextField txtWithoutStyle = new TextField();
txtWithoutStyle.setText("Without Style");
TextField txtWithStyle = new TextField();
txtWithStyle.setText("With Style");
txtWithStyle.getStyleClass().add("validation-error");
VBox root = new VBox();
root.setPadding(new Insets(14));
root.setSpacing(14);
root.getChildren().addAll(txtWithoutStyle, txtWithStyle);
root.getStylesheets().add("/sample/style.css");
Scene scene = new Scene(root, 300, 275);
primaryStage.setTitle("Hello World");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Update 1
Additional question: Why -fx-background-color remove TextField border (just remove -fx-border-color from style.css to reproduce it)?
The default stylesheet applies borders to text fields (and almost all other controls) by using "nested backgrounds" instead of borders.
Some of the settings for the TextInputControl from the default stylesheet are:
-fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background);
-fx-background-insets: 0, 1;
-fx-background-radius: 3, 2;
This basically sets two background colors (both defined by a linear gradient), one (the "outer" one) with a color based on -fx-text-box-border, and the other with a color based on -fx-control-inner-background. The "outer" background is outside the "inner" background because they have insets of 0 and 1, respectively; the curved edge to the resulting apparent border is created by having radii of 3 and 2 for each background, respectively.
This is, anecdotally at least, far more efficient than using actual borders in the CSS, so this choice of technique is for performance reasons.
So to preserve the radius for the border, you can use the same technique, and just override the two background colors:
.validation-error {
-fx-background-color: #DBB1B1, #FFF0F0 ;
}
Note that you can also just replace the "looked-up-colors", which would also preserve the subtle linear gradients being used:
.validation-error {
-fx-text-box-border: #DBB1B1 ;
-fx-control-inner-background: #FFF0F0 ;
}
For highlighting when focused, the default uses colors named -fx-focus-color and -fx-faint-focus-color: so in the latter version you would probably want to redefine those too:
.validation-error {
-fx-text-box-border: #DBB1B1 ;
-fx-control-inner-background: #FFF0F0 ;
-fx-focus-color: #FF2020 ;
-fx-faint-focus-color: #FF202020 ;
}
How can i implement a transparent panel with non-transparent children in JavaFX 2?
The effect i want to achieve is for example applied to menus in Blender:
The menu-panel / window is transparent, but the text items are not transparent which leads to a pretty effect.
Set the background of your pane to a color with an alpha component. You can use a stylesheet or an inline style for this.
For example, if your pane was named glass, then the following will give it a rounded, translucent cyan background:
glass.setStyle("-fx-background-color: rgba(0, 100, 100, 0.5); -fx-background-radius: 10;");
You could also accomplish similar effects using blends, stackpanes or groups of items with the opacity set for items at the back of the stackpane or group.
Here is an executable example using the css background method.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class TranslucentPane extends Application {
#Override public void start(final Stage stage) throws Exception {
final ImageView imageView = new ImageView(
new Image("https://upload.wikimedia.org/wikipedia/commons/b/b7/Idylls_of_the_King_3.jpg")
);
imageView.setFitHeight(300);
imageView.setFitWidth(228);
final Label label = new Label("The Once\nand\nFuture King");
label.setStyle("-fx-text-fill: goldenrod; -fx-font: italic 20 \"serif\"; -fx-padding: 0 0 20 0; -fx-text-alignment: center");
StackPane glass = new StackPane();
StackPane.setAlignment(label, Pos.BOTTOM_CENTER);
glass.getChildren().addAll(label);
glass.setStyle("-fx-background-color: rgba(0, 100, 100, 0.5); -fx-background-radius: 10;");
glass.setMaxWidth(imageView.getFitWidth() - 40);
glass.setMaxHeight(imageView.getFitHeight() - 40);
final StackPane layout = new StackPane();
layout.getChildren().addAll(imageView, glass);
layout.setStyle("-fx-background-color: silver; -fx-padding: 10;");
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Sample program output: