JavaFX change scene (view) in MVC - javafx

I have to do a game (Connect 4) as a project for my university, based on an MVC(Model, View, Controller) model. Now I am done with the two views (one which asks the user for input, the other that should print out the grid for the game, based on the user's input, but I have a problem, I cannot merge these two views and the two CSS documents together. Does anybody know how to do this?
Thank you very much for your help!
import java.util.InputMismatchException;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Labeled;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
public class View2 extends Application implements EventHandler<ActionEvent>{
Stage window;
//Boolean variable to store the answer
static char number;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
window = primaryStage;
//GridPane
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
grid.setVgap(8);
grid.setHgap(10);
//Row Label - Constraints use (child, column, row)
Label rowLabel = new Label("Number of Rows:");
GridPane.setConstraints(rowLabel, 0, 0);
//Column Label
Label colLabel = new Label("Number of Columns:");
GridPane.setConstraints(colLabel, 0, 1);
//Connections Label
Label conLabel = new Label("Needed Connections:");
GridPane.setConstraints(conLabel, 0, 2);
//Rows Input
TextField rowInput = new TextField();
rowInput.setPromptText("Number Rows");
GridPane.setConstraints(rowInput, 1, 0);
//Column Input
TextField colInput = new TextField();
colInput.setPromptText("Number Columns");
GridPane.setConstraints(colInput, 1, 1);
//Needed Connections
TextField conInput = new TextField();
conInput.setPromptText("Needed Connections");
GridPane.setConstraints(conInput, 1, 2);
//Display customized grid
Button displayButton = new Button("Display Grid");
GridPane.setConstraints (displayButton, 1, 4);
//Checking Input
displayButton.setOnAction(e-> {
isInt(rowInput, rowInput.getText());
isInt(colInput, colInput.getText());
isInt(conInput, conInput.getText());
});
//Add everything to grid
grid.getChildren().addAll(rowLabel, colLabel, conLabel, rowInput, colInput, conInput, displayButton);
Scene scene = new Scene(grid, 400, 200);
scene.getStylesheets().add(getClass().getResource("styleView2.css").toExternalForm());
window.setScene(scene);
window.show();
}
//Check input
private boolean isInt(TextField input, String message) {
try {
int number=Integer.parseInt(input.getText());
input.setText("The input is valid");
return true;
} catch(NumberFormatException e) {
input.setText("The input is not valid");
}
return false;
}
}
//CSS for View
.root{
-fx-background-color: #383838;
-fx-font-size: 40pt;
}
.button{
-fx-background-color: linear-gradient(#86c1b9, #7cafc2);
-fx-background-radius: 15;
-fx-min-width: 120;
-fx-min-height: 120;
-fx-pref-width: 120;
-fx-pref-height: 120;
}
//CSS for View2
.root{
-fx-background-color: #F8F8F8;
-fx-font-size: 10pt;
}
.label{
-fx-text-fill: #181818;
}
.button{
-fx-background-color: #AB4642;
-fx-text-fill: #FFFFFF;
-fx-background-radius: 5;
}
import java.util.ArrayList;
import javafx.event.ActionEvent;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
//View for creating the grid
public class View {
private Stage stage;
private Model model;
public int hRows = 6;
public int vRows = 7;
ArrayList[] hArray = new ArrayList[hRows];
public View(Stage stage, Model model) {
this.stage = stage;
this.model = model;
GridPane root = new GridPane();
stage.setTitle("Connect 4");
for (int i = 0; i < hArray.length; i ++) {
hArray[i] = new ArrayList<Button>();
}
for (int i = 0; i < hArray.length; i ++) {
for(int j = 0; j < vRows; j ++ ) {
Button b = new Button();
hArray[i].add(b);
b.setOnAction(this::gameButtons);
}
}
for (int i = 0; i < hArray.length; i ++) {
for(int j = 0; j < vRows; j ++ ) {
root.add((Node) (hArray[i].get(j)), i, j);
}
}
// Standard stuff for Scene and Stage
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public void start() {
stage.show();
}
private void gameButtons(ActionEvent e) {
}
}

When you need to learn something new, it is often much easier to do it on a smaller scale, rather than on you application.
This goes in line with SO requirement of posting mre when asking or answering.
Your question is essentially how to change scene in a javafx app, and the following code demonstrates just that, and nothing else. To test it you may copy the entire code into one file (View2.java) and run:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class View2 extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
Button displayButton = new Button("Display Grid");
//Change scene
displayButton.setOnAction(e-> {
Model model = new Model(6,12);
View view = new View(model);
primaryStage.setScene(new Scene(view.getRoot()));
});
grid.getChildren().add(displayButton);
Scene scene = new Scene(grid, 400, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class View{
private final GridPane root;
public View(Model model) {
root = new GridPane();
for (int i = 0; i < model.gethRows(); i ++) {
for(int j = 0; j < model.getvRows(); j ++ ) {
Button b = new Button(i+"-"+j);
root.add(b, j, i);
}
}
}
GridPane getRoot() {
return root;
}
}
class Model {
private final int hRows, vRows;
Model(int hRows, int vRows) {
this.hRows = hRows;
this.vRows = vRows;
}
int gethRows() {
return hRows;
}
int getvRows() {
return vRows;
}
}

Related

button doesn't change colour after scene building in JavaFX

I would like to change the colour of a button after I build the scene.
I have to do a battleship. When I place a boat many buttons have to change their colours.
So, I have a function do to it and I test that. I filter the boxes contained in "oceanBoxes" by their coordinates and when I get true I call function putBoatInside.
private void placeGuiBoat(int x, int y, int direction, int size) {
List<Integer> listBoxesBoatNeed;
if(direction == HORIZONTAL) {
listBoxesBoatNeed = IntStream.range(y,y+size).boxed().collect(Collectors.toList());
try {
listBoxesBoatNeed.forEach(e -> oceanBoxes.stream().filter(p->p.getX() == x && p.getY() == e).findFirst().orElse(null).putBoatInside());
} catch(Exception e) {}
}
}
And in Boxe class, I have this
public void putBoatInside() {
this.setStyle("-fx-background-color:#000;");
}
EDIT
Here the minimal reproducible example I made to solve it and see that the problem wasn't there.
Main
package application;
import javafx.scene.layout.GridPane;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import GUI.BtnOcean;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
GridPane root = new GridPane();
BtnOcean btn = new BtnOcean(1,1);
BtnOcean btn1 = new BtnOcean(1,2);
BtnOcean btn2 = new BtnOcean(1,3);
BtnOcean btn3 = new BtnOcean(1,4);
BtnOcean btn4 = new BtnOcean(1,5);
ArrayList<BtnOcean> ocean = new ArrayList<BtnOcean>(Arrays.asList(btn,btn1,btn2,btn3,btn4));
Button btn5 = new Button("change");
List<Integer> list = IntStream.range(1,5).boxed().collect(Collectors.toList());
btn5.setOnAction(j -> {
list.stream().forEach(e -> ocean.stream().filter( p-> p.getX() == 1 && p.getY() == e ).findFirst().orElse(null).putBoatInside());
});
root.add(btn, 0, 0);
root.add(btn1, 1, 0);
root.add(btn2, 2, 0);
root.add(btn3, 3, 0);
root.add(btn4, 4, 0);
root.add(btn5, 5, 0);
Scene scene = new Scene(root,1200,800);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
BtnOcean class
package GUI;
import javax.swing.ImageIcon;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class BtnOcean extends Button{
private int x;
private int y;
private String cssProps = "-fx-background-color:#fff;" +
"-fx-border-color:#000000;"+
"-fx-border-width: 1px;";
public BtnOcean(int x, int y,String text){
this.x = x;
this.y = y;
setMinHeight(60);
setMinWidth(60);
setStyle(cssProps);
setText(text);
}
public BtnOcean(int x, int y){
this.x = x;
this.y = y;
setMinHeight(60);
setMinWidth(60);
setStyle(cssProps);
}
public void putBoatInside() {
System.out.println(this);
this.setStyle("-fx-background-color:#000;");
}

How can I de-select/ Unfocus everything when presing ESC

I have the following mini-app:
public class TestApp extends Application {
public static void main(final String[] args) {
launch(args);
}
private final AtomicLong counter = new AtomicLong();
#Override
public void start(final Stage primaryStage) {
final VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
final TableView<String> tableView = new TableView<>();
final TableColumn<String, String> column = new TableColumn<>("Text");
column.setCellValueFactory(f -> new SimpleStringProperty(f.getValue()));
tableView.getColumns().add(column);
// Add some sample items to our TableView
for (int i = 0; i < 100; i++) {
tableView.getItems().add("Item #" + counter.incrementAndGet());
}
final Button button = new Button("Add items");
final TextArea t1 = new TextArea();
button.setOnAction(e -> {
final long oldElement = counter.get();
// Add more elements
for (int i = 0; i < 10; i++) {
tableView.getItems().add("Item #" + counter.incrementAndGet());
}
tableView.scrollTo("Item #" + oldElement);
});
root.getChildren().add(button);
root.getChildren().add(t1);
root.getChildren().add(tableView);
// Show the Stage
primaryStage.setWidth(300);
primaryStage.setHeight(300);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
When it starts up, the button has focus. If I click in the TextArea, it gets focus.
There is no way now to "unfocus" the TextArea again, besides pressing the button (which will trigger an action, which is unwanted if I only want to get rid of the focus).
How can I release all focus and deselect everything, for example when I pres ESC?
I don't know if this fully meets your requirements, but you can create a boolean variable to keep up with the state of ESCAPE. You can also use a ChangeListener that allows the root node to regain focus anytime it loses it.
import java.util.concurrent.atomic.AtomicLong;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestApp extends Application
{
boolean escActive = false;
final VBox root = new VBox(5);
ChangeListener<Boolean> changeListener1 = new ChangeListener()
{
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
System.out.println(newValue);
root.requestFocus();
}
};
public static void main(final String[] args)
{
launch(args);
}
private final AtomicLong counter = new AtomicLong();
#Override
public void start(final Stage primaryStage)
{
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
final TableView<String> tableView = new TableView<>();
final TableColumn<String, String> column = new TableColumn<>("Text");
column.setCellValueFactory(f -> new SimpleStringProperty(f.getValue()));
tableView.getColumns().add(column);
// Add some sample items to our TableView
for (int i = 0; i < 100; i++) {
tableView.getItems().add("Item #" + counter.incrementAndGet());
}
final Button button = new Button("Add items");
final TextArea t1 = new TextArea();
button.setOnAction(e -> {
if (!escActive) {
final long oldElement = counter.get();
// Add more elements
for (int i = 0; i < 10; i++) {
tableView.getItems().add("Item #" + counter.incrementAndGet());
}
tableView.scrollTo("Item #" + oldElement);
}
});
root.getChildren().add(button);
root.getChildren().add(t1);
root.getChildren().add(tableView);
root.setOnKeyReleased((event) -> {
System.out.println(event.getCode());
if (event.getCode() == KeyCode.ESCAPE) {
escActive = !escActive;
if (escActive) {
root.requestFocus();
root.focusedProperty().addListener(changeListener1);
}
else {
root.focusedProperty().removeListener(changeListener1);
}
}
});
// Show the Stage
primaryStage.setWidth(300);
primaryStage.setHeight(300);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}

Javafx: get a Scrollbar from a Treeview at a Button Event

I want to create a button event to create a list. On this list, the Scrollbar
should be moved. But I become an error because the lookup method is only usable after rendering.
HBox root = new HBox();
Button b = new Button("initList");
b.setOnAction(e->
{
ListView<String> list = new ListView<String>();
for (int i = 0; i < 20; i++)
{
list.getItems().add(i+"");
}
ScrollBar bar = (ScrollBar) list.lookup(".scroll-bar");
bar.setValue(0.5);
root.getChildren().add(list);
});
root.getChildren().add(b);
Scene scene = new Scene(root,400,400);
primaryStage.setScene(scene);
primaryStage.show();
To ensure to be called after the layout, you can queue the task using runLater:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class LookupTest extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
Button b = new Button("initList");
b.setOnAction(e ->
{
ListView<String> list = new ListView<String>();
for (int i = 0; i < 20; i++) {
list.getItems().add(i + "");
}
Platform.runLater(() -> {
ScrollBar bar = (ScrollBar) list.lookup(".scroll-bar");
if (bar != null) bar.setValue(0.5);
});
root.getChildren().add(list);
});
root.getChildren().add(b);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Fill out area in JavaFx application

I got a horizontal fill problem.
I got a app javafx app with a BorderPane.
In the setBottom I would like to add a info-component.
How ever this info-component extends Canvas.
But when I add it it does not fill or resize correct.
I also tried to use a Label and that too did not fill correct but in an other way.
I also tied with a TextField and that filled out and resized the area correct.
Do you guys got any ideas how to add a Canvas that fill out the area correct?
Canvas added
http://snag.gy/seswj.jpg
Label added:
http://snag.gy/6wSy8.jpg
TextField added:
http://snag.gy/7ajUc.jpg
Best regards
Fredrik
Okey here is some code.
My main class:
package a.app;
import java.net.URL;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class BannerTest extends Application
{
private Pane mainPanel;
Stage primaryStage;
#Override
public void start(Stage ps) throws Exception
{
primaryStage = ps;
primaryStage.setTitle( "BannerTest" );
mainPanel = createBorderPaneGui();
Scene scene = new Scene(mainPanel, 700, 700);
URL resource = BannerTest.class.getResource("/BannerTest.css");
String externalForm = resource.toExternalForm();
scene.getStylesheets().add( externalForm );
primaryStage.setScene( scene );
primaryStage.setTitle("BannerTest");
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
private Pane createBorderPaneGui()
{
mainPanel = new BorderPane();
GridPane applicationPanel = getApplicationPanel();
BorderedTitledPane borderedTitledPane = new BorderedTitledPane("Hubba", applicationPanel);
((BorderPane)mainPanel).setCenter( borderedTitledPane );
BorderedTitledPane banner = getBanner( 20, mainPanel );
((BorderPane)mainPanel).setBottom( banner );
return mainPanel;
}
private GridPane getApplicationPanel()
{
GridPane applicationPanel = new GridPane();
Label direcoryLabel = new Label("Directory:");
applicationPanel.add(direcoryLabel, 0, 0);
final TextField fileTextField = new TextField();
fileTextField.setEditable(false);
applicationPanel.add(fileTextField, 1, 0);
Button button = new Button("Select more then all you can select, Select more then all you can select");
HBox hBox = new HBox(10);
hBox.setAlignment(Pos.BOTTOM_RIGHT);
hBox.getChildren().add(button);
applicationPanel.add(hBox, 2, 0);
Label infoLabel = new Label("?");
applicationPanel.add(infoLabel, 3, 0);
infoLabel.setTooltip( new Tooltip("Select directory as search root."));
applicationPanel.setGridLinesVisible(true);
return applicationPanel;
}
private BorderedTitledPane getBanner(int height, Pane panel )
{
HBox bannerBox = new HBox(10);
Banner banner = new Banner(height, "", "C:/a/image/image.png", bannerBox);
//Canvas banner = new Canvas();
//Label banner = new Label("Hola");
//TextField banner = new TextField();
bannerBox.setHgrow(banner, Priority.ALWAYS);
bannerBox.getChildren().addAll( banner );
BorderedTitledPane borderedTitledPane = new BorderedTitledPane("Info", bannerBox);
return borderedTitledPane;
}
}
This class uses a style-css:
.mainPanel {
-fx-background-color: #8fbc8f;
}
.applicationPanel {
-fx-padding: 20 20 20 20;
-fx-alignment: top-center;
-fx-background-color: #00FFFF;
-fx-border-color: #FF0000;
-fx-border-width: 20px;
}
.bannerBox {
-fx-alignment: bottom-center;
-fx-background-color: #FFFFFF;
-fx-border-color: #0000FF;
-fx-border-width: 20px;
}
.banner{
-fx-alignment: bottom-center;
-fx-background-color: #00FF00;
}
.label {
-fx-font: 14px Helvetica;
}
.bordered-titled-title {
-fx-background-color: white;
-fx-translate-y: -15;
-fx-translate-x: 10;
}
.bordered-titled-border {
-fx-content-display: top;
-fx-border-insets: 40 40 15 15;
-fx-background-color: white;
-fx-border-color: black;
-fx-border-width: 2;
}
.bordered-titled-content {
-fx-padding: 26 26 26 26;
}
As you can see my main purpose is to display a banner, this banner is a Canvas that extends this class:
package a.app;
import javafx.beans.InvalidationListener;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
public class ResizableWidthCanvas extends Canvas
{
private Double endValue;
public ResizableWidthCanvas(int height)
{
setHeight( height );
InvalidationListener listener = new InvalidationListener(){
public void invalidated(javafx.beans.Observable arg0) {
setEndValue( getWidth() );
draw();
}
};
widthProperty().addListener(listener);
}
private void draw() {
double width = getWidth()-4;
double height = getHeight()-4;
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(2, 2, width, height);
}
#Override
public boolean isResizable() {
return true;
}
/*
#Override
public double prefWidth(double width) {
return getWidth();
}
*/
#Override
public double prefHeight(double height) {
return getHeight();
}
public Double getEndValue()
{
return endValue;
}
public void setEndValue(Double endValue)
{
this.endValue = endValue;
}
}
The Banner class it self:
package a.app;
import java.io.File;
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
public class Banner extends /*Canvas*/ ResizableWidthCanvas
{
String imageUrl;
String imagePath;
DoubleProperty doublePropertyX = new SimpleDoubleProperty();
DoubleProperty doublePropertyY = new SimpleDoubleProperty();
public Banner(int height, String imageUrl, String imagePath, Pane pane)
{
super(height);
setHeight( height );
widthProperty().bind( pane.widthProperty() );
File file = new File(imagePath);
final Image image = new Image(file.toURI().toString());
Double endValue = new Double( pane.getWidth()-image.getWidth() );
KeyValue keyValue = new KeyValue( doublePropertyX, endValue );
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0),
new KeyValue(doublePropertyX, 0)
),
new KeyFrame(Duration.seconds(3),
keyValue
)
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
AnimationTimer timer = new AnimationTimer()
{
int r = 0;
int g = 0;
int b = 0;
#Override
public void handle(long now)
{
GraphicsContext gc = getGraphicsContext2D();
if(r < 255)
{
r++;
}
else if(g < 255)
{
g++;
}
else if(b < 255)
{
b++;
}
else
{
r = 0;
g = 0;
b = 0;
}
gc.setFill( Color.rgb(r,g,b) );
gc.fillRect(0, 0, getWidth(), getHeight());
gc.drawImage(image,
doublePropertyX.doubleValue(),
doublePropertyY.doubleValue()+1
);
}
};
timer.start();
timeline.play();
}
}
I also use a BorderTitelPane that looks like:
package a.app;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
public class BorderedTitledPane extends StackPane
{
public BorderedTitledPane(String titleString, Node content)
{
Label title = new Label(" " + titleString + " ");
title.getStyleClass().add("bordered-titled-title");
StackPane.setAlignment(title, Pos.TOP_LEFT);
StackPane contentPane = new StackPane();
content.getStyleClass().add("bordered-titled-content");
contentPane.getChildren().add(content);
getStyleClass().add("bordered-titled-border");
getChildren().addAll(title, contentPane);
}
}
Hope that was all.
The width of your canvas is exactly equal to its container. Its coming out of box because it's starting x position is different. This is happening because in the class Banner, inside the constructor you have mentioned
widthProperty().bind(pane.widthProperty());
This means whatever is the width of the pane that will be set as the width of the canvas.
Instead of this, write the following
pane.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
setWidth((double)t1 - 70);//You can change the number according to difference in the x position
}
});

Textarea scrollpane hold in text append

It is my test code of textarea append text,
public class TextAreaScrollHold extends Application {
TextArea area = new TextArea();
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.getChildren().add(area);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
addTextInTextArea();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public void addTextInTextArea() {
for (int i = 0; i < 15; i++) {
area.appendText("Hello World " + i + "\n");
}
Task<Void> task = new Task() {
#Override
protected Void call() throws Exception {
for (int i = 15; i < 100; i++) {
area.appendText("Hello World " + i + "\n");
Thread.sleep(1000);
}
return null;
}
};
new Thread(task).start();
}
}
It my code data will update in thread. i need how to hold in scroll bar when data update in textarea. I have ref JavaFX TextArea and autoscroll and Access to TextArea's Scroll Pane or Scroll Bars but how solve this problems.
I need
When data update in textarea, i will scroll the text area scrollbar the bar will hold.
textArea.scrollTopProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
textArea.setScrollTop(100);
}
});
I have used this code but scroll bar in not moved bar will fixed in pixel 100 positions
You can use getCaretPostion and postionCaret (yes, that setter's method name is awkward for Java).
I quickly drafted up some code for you, use the scroll lock button to enable/disable scrolling:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ConsoleDemo extends Application {
Console console = new Console();
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.getChildren().add(console);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Console Demo");
primaryStage.setScene(scene);
primaryStage.show();
addTextInTextArea();
}
/**
* #param args
* the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public void addTextInTextArea() {
for (int i = 0; i < 15; i++) {
console.log("Hello World " + i);
}
Task<Void> task = new Task() {
#Override
protected Void call() throws Exception {
for (int i = 15; i < 100; i++) {
console.log("Hello World " + i);
Thread.sleep(1000);
}
return null;
}
};
new Thread(task).start();
}
public class Console extends BorderPane {
TextArea textArea = new TextArea();
int scrollLockPos = -1;
public Console() {
HBox toolbar = new HBox();
ToggleButton scrollLockButton = new ToggleButton("Scroll Lock");
scrollLockButton.setOnAction(e -> {
if (scrollLockButton.isSelected()) {
scrollLockPos = textArea.getCaretPosition();
} else {
scrollLockPos = -1;
}
});
HBox.setMargin(scrollLockButton, new Insets(5, 5, 5, 5));
toolbar.getChildren().add(scrollLockButton);
setCenter(textArea);
setTop(toolbar);
}
public void log(String text) {
textArea.appendText(text + "\n");
if (scrollLockPos != -1) {
textArea.positionCaret(scrollLockPos);
}
}
}
}
Not the nicest solution, but unless you want to use selection in the textarea while it's scrolling is locked, this one works. For a proper solution you'd need access to the skin / scrollpane / scrollbars and with the upcoming Java 9 version and its modularization you don't know what you will have access to since access to them is currently flagged as "restricted".
Edit:
Here's an alternate solution which uses the Range, console component only. With this version you can select text and keep the selection while the Scroll Lock button is down:
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.IndexRange;
import javafx.scene.control.TextArea;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
/**
* Console which provides a mechanism to lock scrolling. Selecting text and copying it works while scrolling is locked.
*/
public class Console extends BorderPane {
TextArea textArea = new TextArea();
ToggleButton scrollLockButton;
IndexRange range;
public Console() {
initComponents();
}
private void initComponents() {
// toolbar
HBox toolbar = new HBox();
toolbar.setAlignment(Pos.CENTER_RIGHT);
// clear
Button clearButton = new Button("Clear");
clearButton.setOnAction(e -> {
textArea.clear();
});
// scroll lock
scrollLockButton = new ToggleButton("Scroll Lock");
// button positions & layout
Insets insets = new Insets(5, 5, 5, 5);
HBox.setMargin(clearButton, insets);
HBox.setMargin(scrollLockButton, insets);
toolbar.getChildren().addAll(clearButton,scrollLockButton);
// component layout
setCenter(textArea);
setTop(toolbar);
}
public void log(String text) {
if (scrollLockButton.isSelected()) {
range = textArea.getSelection();
}
textArea.appendText(text + "\n");
if (scrollLockButton.isSelected()) {
textArea.selectRange(range.getStart(), range.getEnd());
}
}
}

Resources