JavaFX TextArea - Controlling the scroll bar sizes - javafx

Unpleasantly surprised by TextArea CSS font sizes having wacky effects on the sizes of the scroll bars, I'm trying to get control of the sizes myself. Please refer to the following SSCCE. I can easily control the vertical scroll bar, but the horizontal bar is simply ignoring the sizes I'm setting. Am I expecting something unreasonable here, or is this (yet another) bug in JavaFX? Thanks!
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
int lineCount = 100;
int wordCount = 70;
StringBuilder sb = new StringBuilder();
for (int lineNbr = 0; lineNbr < lineCount; lineNbr++) {
for (int wordNbr = 0; wordNbr < wordCount; wordNbr++) {
sb.append("Sample");
}
sb.append(System.getProperty("line.separator"));
}
textArea.setText(sb.toString());
StackPane root = new StackPane();
root.getChildren().add(textArea);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
double prefSize = 50;
ScrollBar vertScrollBar = (ScrollBar)textArea.lookup(".scroll-bar:vertical");
ScrollBar horizScrollBar = (ScrollBar)textArea.lookup(".scroll-bar:horizontal");
vertScrollBar.setPrefWidth(prefSize); // This works great!
horizScrollBar.setPrefHeight(prefSize); // This doesn't do anything!
horizScrollBar.setMinHeight(prefSize); // Nor does this
horizScrollBar.setPrefWidth(prefSize); // Nor this
horizScrollBar.setMinWidth(prefSize); // Nor this
}
}

ScrollBar vertScrollBar = (ScrollBar) textArea.lookup(".scroll-bar:vertical");
ScrollBar horizScrollBar = (ScrollBar) textArea.lookup(".scroll-bar:horizontal");
System.out.println(vertScrollBar + " " + horizScrollBar);
ScrollBar#35ef2d94[styleClass=scroll-bar]
ScrollBar#35ef2d94[styleClass=scroll-bar]
Same object (vertical ScrollBar).
ScrollPane sPane = (ScrollPane)textArea.getChildrenUnmodifiable().get(0);
ScrollBar horizScrollBar = (ScrollBar)sPane.getChildrenUnmodifiable().get(2);
horizScrollBar.setPrefHeight(prefSize); // This does something!
Update: Better way to receive the two ScrollBars.
ScrollBar[] bars = new ScrollBar[2];
textArea.lookupAll(".scroll-bar").toArray(bars);
bars[0].setPrefWidth(prefSize);
bars[1].setPrefHeight(prefSize);
Edit: Explanation for similar problem with lookupAll(...) and pseudo classes here and here.

Lookups are generally pretty fragile (and I don't think they're really intended to be robust); as noted in the links from the other answer they don't appear to support pseudoclasses.
You can of course just use CSS for this:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
int lineCount = 100;
int wordCount = 70;
StringBuilder sb = new StringBuilder();
for (int lineNbr = 0; lineNbr < lineCount; lineNbr++) {
for (int wordNbr = 0; wordNbr < wordCount; wordNbr++) {
sb.append("Sample");
}
sb.append(System.getProperty("line.separator"));
}
textArea.setText(sb.toString());
StackPane root = new StackPane();
root.getChildren().add(textArea);
Scene scene = new Scene(root, 300, 250) ;
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
with the following in style.css:
.scroll-pane .scroll-bar:horizontal {
-fx-pref-height: 50 ;
}
.scroll-pane .scroll-bar:vertical {
-fx-pref-width: 50 ;
}
This approach, of course, is far more convenient if you have multiple scroll panes in your application and want them all to have the same style of scroll bars.

Related

Is there a condition where a node in a javaFX scene will not be shown?

I decided to create a JavaFX calculator that includes Buttons and Labels and a Pane as my root Parent.
I'm still stuck with the layout, here is a rough sketch of the expected result:
Bear with my poor graphics.
The problem is that I see a blank screen when I run the following code:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.scene.text.*;
import javafx.scene.paint.Color;
import javafx.geometry.Pos;
/**
* This class is meant to serve as a simple calculator with basic funtions
* to try myself out with javafx
*/
public class CalculatorFX extends Application{
Label question; // This is the label that shows the question
Label answer; // This is the label that shows the answer
Button[] buttons = new Button[19]; // This is the buttons of the calculator
/**
* This is the root parent or container. It is defined out here because
* I hope to know its length and width are crucial for the layout.
*/
Pane root = new Pane();
/**
* This is the length of the Pane
*/
double rootHeight = root.getHeight();
/**
* This is the width of the Pane
*/
double rootWidth = root.getWidth();
public static void main(String[] args){
launch(args);
}
/**
* This start routine sets up the GUI
*/
public void start(Stage stage){
question = new Label("Type your question");
answer = new Label("Answer:");
question.setTextFill(Color.BLACK);
question.setFont( Font.font(null, FontWeight.BOLD, 18) );
question.setStyle("-fx-font: 15pt sans-serif; -fx-padding: 7px; -fx-border-color: darkred; -fx-border-width: 2px; -fx-text-fill: darkred; -fx-background-color: pink; ");
/* Initializing the numerical buttons first using a loop */
// for(int i = 0; i < 10; i++){
// buttons[i] = new Button(i + "");
// }
/* They are configured like this in the way, they would appear on
* the parent node */
buttons[0] = new Button("CLEAR");
buttons[1] = new Button("DEL");
buttons[2] = new Button("CONT");
buttons[3] = new Button("7");
buttons[4] = new Button("8");
buttons[5] = new Button("9");
buttons[6] = new Button("*");
buttons[7] = new Button("4");
buttons[8] = new Button("5");
buttons[9] = new Button("6");
buttons[10] = new Button("/");
buttons[11] = new Button("1");
buttons[12] = new Button("2");
buttons[13] = new Button("3");
buttons[14] = new Button("-");
buttons[15] = new Button("0");
buttons[16] = new Button(".");
buttons[17] = new Button("=");
buttons[18] = new Button("+");
/* Here we set the position of the children */
double unitX = rootWidth/4;
double unitY = rootHeight/9;
double nextX = rootWidth;
double nextY = rootHeight;
for(int lineNum = 6; lineNum >= 0; lineNum--){
nextY = nextY - unitY;
nextX = rootWidth - unitX;
for(int element = 4; element >=0 ; element--){
if(lineNum == 1){// Then we need to fill the lines
if( element == 1)
continue;
//buttons[lineNum + element + 8].relocate(nextX, nextY);
}
if(lineNum == 0){
answer.relocate(0, nextY - unitY);
System.out.println("Relocated answer label");
question.relocate(0,0);
System.out.println("Relocated answer label");
break; // This breaks out of two for loops because
// this is the last lineNum.
}
//buttons[lineNum + element + 8].relocate(nextX, nextY);
nextX = nextX - unitX;
}
}
/* There is also a need to resize the children */
question.setManaged(false);
question.resize(rootWidth, 2*unitY);
System.out.println("Resized label question");
answer.setManaged(false);
answer.resize(rootWidth, 2*unitY);
System.out.println("Resized label answer");
// for(int i = 0; i < buttons.length; i++){
// buttons[i].setManaged(false);
// buttons[i].resize(unitX, unitY);
// }
/* Time to configure them on our root */
root.setPrefWidth(400);
root.setPrefHeight(800);
root.getChildren().addAll(question, answer);
// for(int i = 0; i < buttons.length; i++){
// root.getChildren().add(buttons[i]);
// }
Scene scene = new Scene(root);
scene.getStylesheets().add("myStyle.css");
stage.setScene(scene);
stage.setTitle("CalculatorFX");
stage.show();
} // End of start
} // End of class CalculatorFX
Now, myStyle.css contains:
Button {
-fx-font: bold 16pt "Times New Roman";
-fx-text-fill: darkblue;
}
Label {
-fx-font: 15pt sans-serif;
-fx-padding: 7px;
-fx-border-color: darkred;
-fx-border-width: 2px;
-fx-text-fill: darkred;
-fx-background-color: pink;
}
Help me. Why do I see a blank screen?
To answer your main question:
Yes there is!
In JavaFX, Nodes have visible property:
Specifies whether this Node and any subnodes should be rendered as part of the scene graph.
You control it using the setVisible method.
To help you get started:
As Zephyr and Jamed_D pointed out, you are doing too much work.
I believe there are several correct ways to go about what you are trying to achieve.
A visual editor like SceneBuilder comes to mind. It could help you get a deeper understanding of JavaFX's components. Especially, if building the GUI programmatically is not a must for you.
Here is a simple example to help you get started with one of the ways:
public class App extends Application {
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage) {
var label1 = new Label("Type your question: ");
var label2 = new Label("Answer: ");
label1.setPrefHeight(40);
label2.setPrefHeight(40);
label1.setPadding(new Insets(0, 10, 0, 10));
label2.setPadding(new Insets(0, 10, 0, 10));
HBox label1HB = new HBox(label1);
HBox label2HB = new HBox(label2);
VBox vBox = new VBox();
vBox.setFillWidth(true);
vBox.setPrefWidth(Double.MAX_VALUE);
vBox.setPrefHeight(Double.MAX_VALUE);
vBox.getChildren().addAll(label1HB, label2HB);
List<HBox> rows = new ArrayList<>();
for (int row = 0; row < 6; row++) {
HBox hbox = new HBox();
for (int col = 0; col < 4; col++) {
Button button = new Button(row + " " + col);
button.setMaxWidth(Double.MAX_VALUE);
button.setMaxHeight(Double.MAX_VALUE);
HBox.setHgrow(button, Priority.ALWAYS);
hbox.getChildren().add(button);
}
VBox.setVgrow(hbox, Priority.ALWAYS);
rows.add(hbox);
}
vBox.getChildren().addAll(rows);
// Uncomment the following line to see the visible property in action.
// rows.get(2).setVisible(false);
var scene = new Scene(vBox, 400, 800);
stage.setScene(scene);
stage.show();
}
}
I personally like wrapping my nodes within a VBox or an Hbox because it allows me to change the scene's size without worrying about resizing everything again.
You can find many tutorials online explaining how to exploit different JavaFX layouts' properties to achieve better as well as cleaner GUI and code.
It should be mentioned that you are going to have to take care of:
A couple of issues, like what happens when the window is too small to fit your entire content?
Some specifics, like if you want some of your nodes to have a fixed width and height.
etc.

Blurry text appearance after some JavaFX TextArea manipulations

I see a strange appearance of the text contained in a TextArea aftrer doing some changes of TextArea content and style.
With the simplified code shown below I reproducibly see this when I click the button 4 times:
But this is what I expected to see:
Note: If I then click into the TextArea I see the expected result.
What can can be done to get the expected result?
Note that I need to set textarea min/max width and height to get a nice appearance of the content.
Of course I could set it to a bigger value, but that would destroy the look that is required.
I tried setCache as proposed here but that did not work.
I have JavaFX-8 on Windows 8.1. I would also be interested what results are seen in newer versions.
EDIT
With JavaFX-13 the result is:
The text seems to be moved to the right instead of centered as specified in the css (and also to the bottom). I had ecpected that the text is postioned the same as on initial start of the application.
CSS:
.text-area-centered *.text {
-fx-text-alignment: center ;
}
.text-area-centered .scroll-pane {
-fx-hbar-policy: NEVER;
-fx-vbar-policy: NEVER;
}
Java:
public class Main extends Application {
private static final BackgroundFill blackBGF = new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY);
private static final BackgroundFill whiteBGF = new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY);
private static double textareaXY = 50;
private TextArea textarea = new TextArea();
private int clickNo = 1;
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
VBox vb = new VBox();
root.setCenter(vb);
Button b = new Button("ClickMe");
b.addEventHandler(ActionEvent.ACTION, this::OnClickButton);
vb.getChildren().add(b);
vb.getChildren().add(textarea);
textarea.setEditable(false);
textarea.getStyleClass().add("text-area-centered");
textarea.setBackground(new Background(blackBGF));
textarea.setMinHeight(textareaXY);
textarea.setMaxHeight(textareaXY);
textarea.setMinWidth(textareaXY);
textarea.setMaxWidth(textareaXY);
textarea.setFont(new Font("Courier New",10));
textarea.setText("1 2 3\n4 5 6\n7 8 9");
primaryStage.show();
}
private void OnClickButton(ActionEvent event)
{
if(clickNo == 1)
{
textarea.setText("7");
textarea.setFont(new Font("Courier New Bold",24));
}
else if(clickNo == 2)
{
Region region = ( Region ) textarea.lookup( ".content" );
region.setBackground(new Background(blackBGF));
textarea.setStyle("-fx-text-inner-color: white;");
}
else if(clickNo == 3)
{
Region region = ( Region ) textarea.lookup( ".content" );
region.setBackground(new Background(whiteBGF));
textarea.setStyle("-fx-text-inner-color: black;");
}
else if(clickNo == 4)
{
textarea.setText("1 2 3\n4 5 6\n7 8 9");
textarea.setFont(new Font("Courier New",10));
}
clickNo++;
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX: How to scroll to a specific line in the textarea

package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;
import java.util.stream.IntStream;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
final StringBuilder sb = new StringBuilder();
IntStream.range(1, 100).forEach(i -> sb.append("Line " + i + "\n"));
TextArea ta = new TextArea();
ta.setText(sb.toString());
//how to I get line 30 at top of the visible textarea
double someValue = 0;
ta.setScrollTop(someValue);
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(ta, 300, 300));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
How do I get line 30 at top of the visible textarea?
I think 'someValue' should be relative to the total height which can be scrolled
But what is the total height that can be scrolled
This is the result I want to achieve:
This is a bit tricky. We could just determine each line height and call ta.setScrollTop((line - 1) * lineHeight);, but we do not know what line spacing TextArea uses.
But I found that TextAreaSkin contains public methods for determining bounds for any selected character, we just need to know its index.
#Override
public void start(Stage primaryStage) throws Exception{
final StringBuilder sb = new StringBuilder();
IntStream.range(1, 100).forEach(i -> sb.append("Line " + i + "\n"));
TextArea ta = new TextArea();
ta.setText(sb.toString());
// TextArea did not setup its skin yet, so we can't use it right now.
// We just append our task to the user tasks queue.
Platform.runLater(() -> {
// Define desired line
final int line = 30;
// Index of the first character in line that we look for.
int index = 0;
// for this example following line will work:
// int index = ta.getText().indexOf("Line " + line);
// for lines that do not contain its index we rely on "\n" count
int linesEncountered = 0;
boolean lineFound = false;
for (int i = 0; i < ta.getText().length(); i++) {
// count characters on our way to our desired line
index++;
if(ta.getText().charAt(i) == '\n') {
// next line char encountered
linesEncountered++;
if(linesEncountered == line-1) {
// next line is what we're looking for, stop now
lineFound = true;
break;
}
}
}
// scroll only if line found
if(lineFound) {
// Get bounds of the first character in the line using internal API (see comment below the code)
Rectangle2D lineBounds = ((com.sun.javafx.scene.control.skin.TextAreaSkin) ta.getSkin()).getCharacterBounds(index);
// Scroll to the top-Y of our line
ta.setScrollTop(lineBounds.getMinY());
}
});
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(ta, 300, 300));
primaryStage.show();
}
This solution works on Java 8, on 9+ TextAreaSkin was moved to the public package, so everything you need to make it work is to replace com.sun.javafx.scene.control.skin.TextAreaSkin with javafx.scene.control.skin.TextAreaSkin

JavaFX unable to display rectangle inside grid

So my teacher has just started teaching us how to use JavaFx and up until now, I had thought I had a pretty good grasp of the material. But, I am stuck and I don't quite know how to fix the issue or even what's causing the issue. I am trying to create a green rectangle that is three nodes high on a grid that is 64x64. The grid shows up just fine but for the life of me, I cannot understand why the rectangle is not showing up as well. Can someone please help me figure out why the rectangle won't show up? I am sure it's a small stupid issue but again I can't see it. Here is my code:
public class test2 extends Application {
private Cell[][] cell = new Cell[64][64];
public void start(Stage primaryStage) {
GridPane pane = new GridPane();
pane.setStyle("-fx-background-color: black");
for (int col = 1; col < 64; col++)
for (int row = 1; row < 64; row++)
pane.add(cell[col][row] = new Cell(), row, col);
Rectangle rectangle = new Rectangle(1,3);
rectangle.setFill(Color.GREEN);
VBox box2 = new VBox(rectangle);
box2.setAlignment(Pos.CENTER);
BorderPane bp = new BorderPane();
bp.setCenter(pane);
Scene scene = new Scene(bp, 1000, 700);
primaryStage.setScene(scene);
primaryStage.show();
}
public class Cell extends Pane{
public Cell(){
setStyle("-fx-border-color: white");
setPrefSize(1000, 1000);
return;
}
}
public static void main(String[] args) {
launch(args);
}

ProgressIndicator transparent background

So I have this code:
final StackPane g = new StackPane();
final ProgressIndicator p1 = new ProgressIndicator();
p1.setPrefSize(100, 100);
p1.progressProperty().addListener(new ChangeListener<Number>() {
#SuppressWarnings("rawtypes")
#Override
public void changed(ObservableValue ov, Number oldVal, Number newVal) {
if (p1.getProgress() < 0.3) {
p1.setStyle("-fx-progress-color: red;");
} else if (p1.getProgress() < 0.65) {
p1.setStyle("-fx-progress-color: orange;");
} else {
p1.setStyle("-fx-progress-color: green;");
}
}
});
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
final KeyValue kv = new KeyValue(p1.progressProperty(), 1);
final KeyFrame kf1 = new KeyFrame(Duration.millis(3000), kv);
timeline.getKeyFrames().add(kf1);
g.getChildren().addAll(p1); //veil
g.setAlignment(Pos.CENTER);
Then I add the stack pane to a scene and the scene to the stage.
I have two stages, the initial stage with all my things and this additional stage with the progress indicator. Here is the problem: the stage that contains the progress indicator has a white squared(box) as background that fits perfect to the other stage, because usually has the same white background. But when the initial background is not white I can see the white background of the progress indicator stage. So here is my question: How can I make this background transparent? I tried the BlendMode class, but this doesn't seem to work. Any ideas?
Thanks in advance!
Use an external style sheet with
.progress-indicator .indicator {
-fx-background-color: transparent ;
}
Here's a complete example (I used the external style sheet to manage the colors too, but this will work anyway):
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ProgressIndicatorTest extends Application {
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
ProgressIndicator pi = new ProgressIndicator();
Task<Void> counter = new Task<Void>() {
#Override
public Void call() throws Exception {
for (int i = 1; i <= 100; i++) {
Thread.sleep(50);
updateProgress(i, 100);
}
return null;
}
};
pi.progressProperty().bind(counter.progressProperty());
pi.progressProperty().addListener((obs, oldProgress, newProgress) -> {
PseudoClass warning = PseudoClass.getPseudoClass("warning");
PseudoClass critical = PseudoClass.getPseudoClass("critical");
if (newProgress.doubleValue() < 0.3) {
pi.pseudoClassStateChanged(warning, false);
pi.pseudoClassStateChanged(critical, true);
} else if (newProgress.doubleValue() < 0.65) {
pi.pseudoClassStateChanged(warning, true);
pi.pseudoClassStateChanged(critical, false);
} else {
pi.pseudoClassStateChanged(warning, false);
pi.pseudoClassStateChanged(critical, false);
}
});
pi.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
root.setStyle("-fx-background-color: antiqueWhite;");
root.getChildren().add(pi);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add("progress.css");
primaryStage.setScene(scene);
primaryStage.show();
new Thread(counter).start();
}
public static void main(String[] args) {
launch(args);
}
}
progress.css:
.progress-indicator .indicator {
-fx-background-color: transparent ;
}
.progress-indicator {
-fx-progress-color: green ;
}
.progress-indicator:critical {
-fx-progress-color: red ;
}
.progress-indicator:warning {
-fx-progress-color: orange ;
}
Try this for css (based on a bugfix for Java 8u40):
.progress-indicator:indeterminate > .spinner {
/** Applying to undo styling from .spinner, reported in RT-37965 */
-fx-background-color: transparent;
-fx-background-insets: 0;
-fx-background-radius: 0;
}
For me, none of the above answers worked.
I finally got it working by:
progressIndicator.getScene().getRoot().setStyle("-fx-background-color: transparent");
If you are using a scene, make the scene transparent by also doing:
scene.setFill(null);

Resources