Textarea scrollpane hold in text append - javafx

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());
}
}
}

Related

High refreshing rate in JavaFX

I'm trying to write a program with an equalizer, a frequency analyzer and a sound level meter. The model part seems to work very well but I'm experimenting some bugs with the IHM.
My last bug is with the level meter. After a while (from few milliseconds to few seconds), it freezes and don't update anymore. So, here is a (simplified) version of it. I added the runnable part to test and reproduce the bug. Of course, this bug appears sooner when I add other graphical components which also need to refresh very frequently. For example, the frequency analyze is represented by a line-chart with something like 1000 points.
public class LevelMeter2 extends Parent implements Runnable {
private IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
private Rectangle led;
private IntegerProperty height = new SimpleIntegerProperty();
private IntegerProperty width = new SimpleIntegerProperty();
private DoubleProperty linearValue = new SimpleDoubleProperty();
private Color backgroundColor=Color.BLACK;
private double minLinearValue, maxLinearValue;
public LevelMeter2 (int height2, int width2) {
this.height.set(height2);
this.levelMeterHeight.bind(height.multiply(0.9));
this.width.set(width2);
linearValue.set(1.0);
minLinearValue = Math.pow(10, -60.0/100);
maxLinearValue = Math.pow(10, 3.0/100)-minLinearValue;
Rectangle levelMeterShape = new Rectangle();
levelMeterShape.widthProperty().bind(width);
levelMeterShape.heightProperty().bind(height);
levelMeterShape.setStroke(backgroundColor);
this.getChildren().add(levelMeterShape);
led = new Rectangle();
led.widthProperty().bind(width.multiply(0.8));
led.translateXProperty().bind(width.multiply(0.1));
led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
led.setFill(Color.AQUA);
Rotate rotate = new Rotate();
rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
rotate.pivotYProperty().bind(height.divide(2));
rotate.setAngle(180);
led.getTransforms().add(rotate);
this.getChildren().add(led);
}
public double convertdBToLinearValue (double dB) {
return ((double)Math.round(100 * ((Math.pow(10, dB/100)-minLinearValue)/maxLinearValue)) ) /100 ;
//return (Math.pow(10, dB/100)-minLinearValue)/maxLinearValue;
}
public double convertLinearValueTodB (double linearValue) {
return 100*Math.log10(linearValue*maxLinearValue+minLinearValue);
}
public void setValue (double dB) {
if (dB>3) {
dB=3;
}
linearValue.setValue(convertdBToLinearValue(dB));
}
#Override
public void run() {
int i = 0;
double value=-20;
while (i<1000) {
setValue(value);
value = (Math.random()-0.5)*10+value;
if (value>3) {
value=3;
}
if (value<-60) {
value=-60;
}
i++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("END OF WHILE");
}
}
And a "Main" to test it :
public class MainGraph extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
HBox pane = new HBox();
LevelMeter2 levelMeter = new LevelMeter2(300,30);
Thread t = new Thread(levelMeter);
pane.getChildren().add(levelMeter);
t.start();
Scene scene = new Scene(pane, 300, 300);
primaryStage.setScene(scene);
primaryStage.setTitle("Test IHM");
primaryStage.setOnCloseRequest( event -> {
System.out.println("FIN");
System.exit(0);
});
primaryStage.show();
}
}
What's wrong with my code ? How can I write a more robust code that will allow me high refresh rates of my IHM ? Or how can I prevent from freezing ?
Thank you for you help.
I would suggest you move away from Threads and use something from JavaFX Animation package. In this example Timeline is used. This code is set to run at a rate of about 60 fps. You can adjust that using Duration.millis().
Main
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication342 extends Application
{
#Override
public void start(Stage primaryStage)
{
LevelMeter2 levelMeter = new LevelMeter2(300, 30);
Button button = new Button("Start");
button.setOnAction((event) -> {
switch (button.getText()) {
case "Start":
levelMeter.startAnimation();
button.setText("Stop");
break;
case "Stop":
levelMeter.stopAnimation();
button.setText("Start");
break;
}
});
HBox pane = new HBox(levelMeter, button);
Scene scene = new Scene(pane, 300, 300);
primaryStage.setScene(scene);
primaryStage.setTitle("Test IHM");
primaryStage.setOnCloseRequest(event -> {
System.out.println("FIN");
System.exit(0);
});
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
LevelMeter2
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
public final class LevelMeter2 extends Parent
{
private final IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
Timeline timeline;
double value = -20;
private final Rectangle led;
private final IntegerProperty height = new SimpleIntegerProperty();
private final IntegerProperty width = new SimpleIntegerProperty();
private final DoubleProperty linearValue = new SimpleDoubleProperty();
private final Color backgroundColor = Color.BLACK;
private final double minLinearValue;
private final double maxLinearValue;
public LevelMeter2(int height2, int width2)
{
this.height.set(height2);
this.levelMeterHeight.bind(height.multiply(0.9));
this.width.set(width2);
linearValue.set(1.0);
minLinearValue = Math.pow(10, -60.0 / 100);
maxLinearValue = Math.pow(10, 3.0 / 100) - minLinearValue;
Rectangle levelMeterShape = new Rectangle();
levelMeterShape.widthProperty().bind(width);
levelMeterShape.heightProperty().bind(height);
levelMeterShape.setStroke(backgroundColor);
this.getChildren().add(levelMeterShape);
led = new Rectangle();
led.widthProperty().bind(width.multiply(0.8));
led.translateXProperty().bind(width.multiply(0.1));
led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
led.setFill(Color.AQUA);
Rotate rotate = new Rotate();
rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
rotate.pivotYProperty().bind(height.divide(2));
rotate.setAngle(180);
led.getTransforms().add(rotate);
getChildren().add(led);
timeline = new Timeline(new KeyFrame(Duration.millis(16), (event) -> {
setValue(value);
value = (Math.random() - 0.5) * 10 + value;
if (value > 3) {
value = 3;
}
if (value < -60) {
value = -60;
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
}
public double convertdBToLinearValue(double dB)
{
return ((double) Math.round(100 * ((Math.pow(10, dB / 100) - minLinearValue) / maxLinearValue))) / 100;
}
public double convertLinearValueTodB(double linearValue)
{
return 100 * Math.log10(linearValue * maxLinearValue + minLinearValue);
}
public void setValue(double dB)
{
if (dB > 3) {
dB = 3;
}
linearValue.setValue(convertdBToLinearValue(dB));
}
public void startAnimation()
{
timeline.play();
}
public void stopAnimation()
{
timeline.stop();
}
}
Multiple LevelMeters Example:
Main
import java.util.ArrayList;
import java.util.List;
import javafx.animation.ParallelTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication342 extends Application
{
#Override
public void start(Stage primaryStage)
{
List<LevelMeter2> levelMeter2s = new ArrayList();
List<Timeline> metersTimelines = new ArrayList();
for (int i = 0; i < 9; i++) {
LevelMeter2 levelMeter2 = new LevelMeter2(300, 30);
levelMeter2s.add(levelMeter2);
metersTimelines.add(levelMeter2.getTimeline());
}
ParallelTransition parallelTransition = new ParallelTransition();
parallelTransition.getChildren().addAll(metersTimelines);
Button button = new Button("Start");
button.setOnAction((event) -> {
switch (button.getText()) {
case "Start":
parallelTransition.play();
button.setText("Stop");
break;
case "Stop":
parallelTransition.stop();
button.setText("Start");
break;
}
});
HBox hBox = new HBox();
hBox.getChildren().addAll(levelMeter2s);
VBox vBox = new VBox(hBox, new StackPane(button));
Scene scene = new Scene(vBox, 300, 350);
primaryStage.setScene(scene);
primaryStage.setTitle("Test IHM");
primaryStage.setOnCloseRequest(event -> {
System.out.println("FIN");
System.exit(0);
});
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
LevelMeter2
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
public final class LevelMeter2 extends Parent
{
private final IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
Timeline timeline;
double value = -20;
private final Rectangle led;
private final IntegerProperty height = new SimpleIntegerProperty();
private final IntegerProperty width = new SimpleIntegerProperty();
private final DoubleProperty linearValue = new SimpleDoubleProperty();
private final Color backgroundColor = Color.BLACK;
private final double minLinearValue;
private final double maxLinearValue;
public LevelMeter2(int height2, int width2)
{
this.height.set(height2);
this.levelMeterHeight.bind(height.multiply(0.9));
this.width.set(width2);
linearValue.set(1.0);
minLinearValue = Math.pow(10, -60.0 / 100);
maxLinearValue = Math.pow(10, 3.0 / 100) - minLinearValue;
Rectangle levelMeterShape = new Rectangle();
levelMeterShape.widthProperty().bind(width);
levelMeterShape.heightProperty().bind(height);
levelMeterShape.setStroke(backgroundColor);
this.getChildren().add(levelMeterShape);
led = new Rectangle();
led.widthProperty().bind(width.multiply(0.8));
led.translateXProperty().bind(width.multiply(0.1));
led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
led.setFill(Color.AQUA);
Rotate rotate = new Rotate();
rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
rotate.pivotYProperty().bind(height.divide(2));
rotate.setAngle(180);
led.getTransforms().add(rotate);
getChildren().add(led);
timeline = new Timeline(new KeyFrame(Duration.millis(25), (event) -> {
setValue(value);
value = (Math.random() - 0.5) * 10 + value;
if (value > 3) {
value = 3;
}
if (value < -60) {
value = -60;
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
}
public double convertdBToLinearValue(double dB)
{
return ((double) Math.round(100 * ((Math.pow(10, dB / 100) - minLinearValue) / maxLinearValue))) / 100;
}
public double convertLinearValueTodB(double linearValue)
{
return 100 * Math.log10(linearValue * maxLinearValue + minLinearValue);
}
public void setValue(double dB)
{
if (dB > 3) {
dB = 3;
}
linearValue.setValue(convertdBToLinearValue(dB));
}
public void startAnimation()
{
timeline.play();
}
public void stopAnimation()
{
timeline.stop();
}
public Timeline getTimeline()
{
return timeline;
}
}
Your implementation of run() appears to be updating the scene graph from a background thread. As discussed in Concurrency in JavaFX:
The JavaFX scene graph…is not thread-safe and can only be accessed and modified from the UI thread also known as the JavaFX Application thread. Implementing long-running tasks on the JavaFX Application thread inevitably makes an application UI unresponsive."
Instead, use a Task, illustrated here and here. Your implementation of call() can collect data asynchronously and notify the GUI of the current state via updateValue(). Your valueProperty() listener can then invoke setValue() safely. Because "Updates are coalesced to prevent saturation of the FX event queue," your application will perform satisfactorily even on older hardware.
Alternatively, if your audio source is one of the supported Media types, AudioBarChartApp, also seen here, updates the data model of a BarChart in an AudioSpectrumListener registered with the corresponding MediaPlayer. The image below displays pink noise.
private XYChart.Data<String, Number>[] series1Data;
…
audioSpectrumListener = (double timestamp, double duration,
float[] magnitudes, float[] phases) -> {
for (int i = 0; i < series1Data.length; i++) {
series1Data[i].setYValue(magnitudes[i] + 60);
}
};

JavaFX : how to have my title and menu centered on top of each other [WITH MVCE]

I'm new to JavaFX and I'm trying to make a menu that can be any size.
I've tried every layout possible for hours but I can't get a simple design done.
My background is a black Rectangle. I want the title to be centered on top of the screen, and my menu to be centered below the title.
Plus I want the stage size to be fixed to the Rectangle size, so that we don't see white on the background.
Here's my mvce :
package mvce_poneymon_menu;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Mvce_poneymon_menu extends Application {
#Override
public void start(Stage stage) throws Exception {
MenuView menuView = new MenuView(600, 600);
Group root = new Group();
Scene scene = new Scene(root);
stage.setTitle("Poneymon");
stage.setScene(scene);
root.getChildren().add(menuView);
menuView.requestFocus();
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MenuView.java :
package mvce_poneymon_menu;
import javafx.animation.TranslateTransition;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.util.Duration;
public class MenuView extends StackPane {
static final Font FONT = Font.font("", FontWeight.BOLD, 50);
int width;
int height;
VBox menuBox;
int currentItem = 0;
public MenuView(int w, int h) {
width = w;
height = h;
createContent();
setOnKeyPressedEvent();
}
private void createContent() {
GridPane grid = new GridPane();
MenuItem exitItem = new MenuItem("Exit");
exitItem.setOnActivate(() -> System.exit(0));
menuBox = new VBox(10,
new MenuItem("Start a game"),
new MenuItem("Parameters"),
exitItem);
menuBox.setAlignment(Pos.CENTER);
menuBox.setTranslateX(360);
getMenuItem(0).setActive(true);
HBox title = (HBox)createTitle("Poneymon");
grid.add(title, 0, 0);
grid.add(menuBox, 0, 1);
Rectangle bg = new Rectangle(width, height);
grid.setTranslateY(25);
this.getChildren().addAll(bg, grid);
}
private Node createTitle(String title) {
HBox letters = new HBox(0);
letters.setAlignment(Pos.CENTER);
for (int i = 0; i < title.length(); i++) {
Text letter = new Text(title.charAt(i) + "");
letter.setFont(FONT);
letter.setFill(Color.WHITE);
letters.getChildren().add(letter);
TranslateTransition tt = new TranslateTransition(Duration.seconds(2), letter);
tt.setDelay(Duration.millis(i * 50));
tt.setToY(-25);
tt.setAutoReverse(true);
tt.setCycleCount(TranslateTransition.INDEFINITE);
tt.play();
}
return letters;
}
private MenuItem getMenuItem(int index) {
return (MenuItem)menuBox.getChildren().get(index);
}
private void setOnKeyPressedEvent() {
this.setOnKeyPressed(new EventHandler<KeyEvent>() {
public void handle(KeyEvent e) {
if (e.getCode() == KeyCode.UP) {
if (currentItem > 0) {
getMenuItem(currentItem).setActive(false);
getMenuItem(--currentItem).setActive(true);
}
}
if (e.getCode() == KeyCode.DOWN) {
if (currentItem < menuBox.getChildren().size() - 1) {
getMenuItem(currentItem).setActive(false);
getMenuItem(++currentItem).setActive(true);
}
}
if (e.getCode() == KeyCode.ENTER) {
getMenuItem(currentItem).activate();
}
}
});
}
}
MenuItem.java :
package mvce_poneymon_menu;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
public class MenuItem extends HBox {
static final Font FONT = Font.font("", FontWeight.BOLD, 30);
private TriCircle c1 = new TriCircle();
private TriCircle c2 = new TriCircle();
private Text text;
private Runnable script;
private static class TriCircle extends Parent {
public TriCircle() {
Shape shape1 = Shape.subtract(new Circle(5), new Circle(2));
shape1.setFill(Color.WHITE);
Shape shape2 = Shape.subtract(new Circle(5), new Circle(2));
shape2.setFill(Color.WHITE);
shape2. setTranslateX(5);
Shape shape3 = Shape.subtract(new Circle(5), new Circle(2));
shape3.setFill(Color.WHITE);
shape3.setTranslateX(2.5);
shape3.setTranslateY(-5);
getChildren().addAll(shape1, shape2, shape3);
setEffect(new GaussianBlur(2));
}
}
public MenuItem(String name) {
super(15);
setAlignment(Pos.CENTER);
text = new Text(name);
text.setFont(FONT);
text.setEffect(new GaussianBlur(2));
getChildren().addAll(c1, text, c2);
setActive(false);
setOnActivate(() -> System.out.println(name + " activated"));
}
public void setActive(boolean b) {
c1.setVisible(b);
c2.setVisible(b);
text.setFill(b ? Color.WHITE : Color.GREY);
}
public void setOnActivate(Runnable r) {
script = r;
}
public void activate() {
if (script != null) {
script.run();
}
}
}
I'm sure this is very simple but I can't figure it out :c
My background is a black Rectangle. [...] Plus I want the stage size to be fixed to the Rectangle size, so that we don't see white on the background.
It would be much simpler to simply assign a background to the StackPane. This would allow you to resize MenuView and keep the size of the background the same as the size of the MenuView without additional logic.
Preventing resizing of the window should be done for the Stage using setResizable.
I want the title to be centered on top of the screen, and my menu to be centered below the title.
You're using an "unhealthy" amount of transformation properties. (I'm refering to translateX and translateY in this case.) These properties are not taken into account by the parent layout; during the layout the nodes are positioned where the same node without any transformation would be positioned and the rendering algorithm considers those transformations though.
Imho the following structure would suit the desired outcome better:
MenuView (root)
|- VBox (place menu items below title)
|- HBox (title container)
|- ...
|- MenuItem
|- MenuItem
|- MenuItem
To get the correct size for the title container, I recommend using a padding around the content.
There are several other things that I'd change:
Shape shape1 = Shape.subtract(new Circle(5), new Circle(2));
shape1.setFill(Color.WHITE);
I'd recommend changing this to circles with a stroke instead oc intersecting shapes.
Instead of blurring every child in MenuItem seperatly I'd recommend applying a blur on the item itself.
The TriCircle class does not contain any logic other than setting up the nodes. It could (and should) be replaced by a method creating a Group containing the circles.
#Override
public void start(Stage stage) {
MenuView menuView = new MenuView(600, 600);
Scene scene = new Scene(menuView);
stage.setTitle("Poneymon");
stage.setScene(scene);
menuView.requestFocus();
stage.setResizable(false); // prevent resizing of stage
stage.show();
}
public class MenuView extends StackPane {
static final Font FONT = Font.font("", FontWeight.BOLD, 50);
int currentItem = 0;
public MenuView(int w, int h) {
setPrefSize(w, h);
createContent();
setOnKeyPressedEvent();
}
private List<MenuItem> menuItems;
private void createContent() {
MenuItem exitItem = new MenuItem("Exit");
exitItem.setOnActivate(() -> Platform.exit());
menuItems = Arrays.asList(
new MenuItem("Start a game"),
new MenuItem("Parameters"),
exitItem);
VBox container = new VBox(10, createTitle("Poneymon"));
container.getChildren().addAll(menuItems);
container.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
getMenuItem(0).setActive(true);
setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
getChildren().add(container);
}
private Node createTitle(String title) {
final double movement = 25;
HBox letters = new HBox();
letters.setAlignment(Pos.CENTER);
// add space on top equla to the upwards movement of the letters
letters.setPadding(new Insets(movement, 0, 0, 0));
for (int i = 0; i < title.length(); i++) {
Text letter = new Text(title.charAt(i) + "");
letter.setFont(FONT);
letter.setFill(Color.WHITE);
letters.getChildren().add(letter);
TranslateTransition tt = new TranslateTransition(Duration.seconds(2), letter);
tt.setDelay(Duration.millis(i * 50));
tt.setToY(-movement);
tt.setAutoReverse(true);
tt.setCycleCount(TranslateTransition.INDEFINITE);
tt.play();
}
return letters;
}
private MenuItem getMenuItem(int index) {
return menuItems.get(index);
}
private void setOnKeyPressedEvent() {
this.setOnKeyPressed(new EventHandler<KeyEvent>() {
public void handle(KeyEvent e) {
if (e.getCode() == KeyCode.UP) {
if (currentItem > 0) {
getMenuItem(currentItem).setActive(false);
getMenuItem(--currentItem).setActive(true);
}
}
if (e.getCode() == KeyCode.DOWN) {
if (currentItem < menuItems.size() - 1) {
getMenuItem(currentItem).setActive(false);
getMenuItem(++currentItem).setActive(true);
}
}
if (e.getCode() == KeyCode.ENTER) {
getMenuItem(currentItem).activate();
}
}
});
}
}
public class MenuItem extends HBox {
static final Font FONT = Font.font("", FontWeight.BOLD, 30);
private Group c1 = createTriCircle();
private Group c2 = createTriCircle();
private Text text;
private Runnable script;
private static Circle createCircle(double centerX, double centerY) {
final double innerRadius = 2;
final double outerRadius = 5;
Circle circle = new Circle(centerX, centerY, (innerRadius + outerRadius) / 2, null);
circle.setStroke(Color.WHITE);
circle.setStrokeWidth(outerRadius - innerRadius);
return circle;
}
private static Group createTriCircle() {
return new Group(
createCircle(0, 0),
createCircle(5, 0),
createCircle(2.5, -5));
}
public MenuItem(String name) {
super(15);
setAlignment(Pos.CENTER);
text = new Text(name);
text.setFont(FONT);
setEffect(new GaussianBlur(2));
getChildren().addAll(c1, text, c2);
setActive(false);
setOnActivate(() -> System.out.println(name + " activated"));
}
public void setActive(boolean b) {
c1.setVisible(b);
c2.setVisible(b);
text.setFill(b ? Color.WHITE : Color.GREY);
}
public void setOnActivate(Runnable r) {
script = r;
}
public void activate() {
if (script != null) {
script.run();
}
}
}
To adjust the distance between title and menu items, you could use VBox.setMargin.

JavaFX TextField Array max length of text value

I am working on a JavaFX project and I have a problem using the TextField control. I want to limit the characters that users will enter to each TextField to one. I found a solution if you use a single textfield with a Listener:
public static void addTextLimiter(final TextField tf, final int maxLength) {
tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(final ObservableValue<? extends String> ov, final String oldValue, final String newValue) {
if (tf.getText().length() > maxLength) {
String s = tf.getText().substring(0, maxLength);
tf.setText(s);
}
}
});
But the problem is that I have an Array of TextFields. Do you guys maybe know how I can rewrite this listener for a TextFieldArray?
Array list implementation:
static public TextField[] tfLetters = new TextField[37];
Initialisation of the array:
private void layoutNodes() {
int letternummer = 0;
for (int i = 1; i < 8; i++) {
for (int j = 0; j < i + 1; j++) {
this.tfLetters[letternummer] = new TextField("Letter " + i);
this.add(tfLetters[letternummer], j, i);
tfLetters[letternummer].setPadding(new Insets(5, 30, 5, 5));
tfLetters[letternummer].setAlignment(Pos.CENTER);
tfLetters[letternummer].setMinSize(10, 10);
letternummer++;
}
}
I used the given solution:
Arrays.asList(tfLetters).forEach(tfLetters -> GamePresenter.addTextLimiter(tfLetters,1));
GamePresenter is the presenter of the view where the Listener is written.
In the view "GameView" I have implemented the Array of textfields. But now when I run the given solution I go the following NullPointerException:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at be.kdg.letterpyramide.view.GameView.GamePresenter.addTextLimiter(GamePresenter.java:36)
at be.kdg.letterpyramide.view.GameView.GameView.lambda$layoutNodes$0(GameView.java:52)
at java.util.Arrays$ArrayList.forEach(Arrays.java:3880)
GameView line: 36
tf.textProperty().addListener(new ChangeListener<String>() {
GameView line: 52
Arrays.asList(tfLetters).forEach(tfLetters -> GamePresenter.addTextLimiter(tfLetters,1));
Sidenote: I made it public static so I can use it in my GamePresenter. I'm very new to Java.
Thanks in advance!
This is a solution without a GridPane, but this is an easy process of adding the Fields also to a GridPane. And now with a TextFormatter that is much better.
import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ChangeListenerDemo extends Application {
#Override
public void start(Stage primaryStage) {
List<TextField> fields = createLimitedTextFields(9, 1);
VBox box = new VBox();
box.getChildren().addAll(fields);
Scene scene = new Scene(box, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
private List<TextField> createLimitedTextFields(int num, int maxLength) {
final List<TextField> fields = new ArrayList<>();
final UnaryOperator<TextFormatter.Change> filter
= (TextFormatter.Change change) -> {
if (change.getControlNewText().length() > maxLength) {
return null;
}
return change;
};
for (int i = 0; i < num; i++) {
final TextField tf = new TextField();
tf.setTextFormatter(new TextFormatter(filter));
fields.add(tf);
}
return fields;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

Collapse menu at the bottom of the stage

Based on this example
https://gist.github.com/SaiPradeepDandem/95accfa4d8de8b9b3310
I created this simple example code:
import javafx.animation.Animation;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class MainApp extends Application
{
BorderPane bp = new BorderPane();
public static void main(String[] args) throws Exception
{
launch(args);
}
#Override
public void start(final Stage stage) throws Exception
{
stage.setTitle("Slide out YouTube demo");
// create a WebView to show to the right of the SideBar.
bp.setStyle("-fx-background-color: #2f4f4f;");
bp.setPrefSize(800, 600);
// create a sidebar with some content in it.
final Pane lyricPane = createSidebarContent();
SideBar sidebar = new SideBar(250, lyricPane);
VBox.setVgrow(lyricPane, Priority.ALWAYS);
// layout the scene.
final BorderPane layout = new BorderPane();
StackPane st = new StackPane();
st.getChildren().addAll(bp, sidebar.getControlButton());
st.setAlignment(Pos.TOP_LEFT);
VBox vb = new VBox(10);
vb.getChildren().addAll(st);
layout.setLeft(sidebar);
layout.setCenter(vb);
// show the scene.
Scene scene = new Scene(layout);
scene.getStylesheets().add(getClass().getResource("/styles/slideout.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
private BorderPane createSidebarContent()
{
// create some content to put in the sidebar.
final Button changeLyric = new Button("New Song");
changeLyric.getStyleClass().add("change-lyric");
changeLyric.setMaxWidth(Double.MAX_VALUE);
changeLyric.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent actionEvent)
{
System.out.println("Some action");
}
});
changeLyric.fire();
final BorderPane lyricPane = new BorderPane();
lyricPane.setTop(changeLyric);
return lyricPane;
}
/**
* Animates a node on and off screen to the left.
*/
class SideBar extends VBox
{
/**
* #return a control button to hide and show the sidebar
*/
public Button getControlButton()
{
return controlButton;
}
private final Button controlButton;
/**
* creates a sidebar containing a vertical alignment of the given nodes
*/
SideBar(final double expandedWidth, Node... nodes)
{
getStyleClass().add("sidebar");
this.setPrefWidth(expandedWidth);
this.setMinWidth(0);
// create a bar to hide and show.
setAlignment(Pos.CENTER);
getChildren().addAll(nodes);
// create a button to hide and show the sidebar.
controlButton = new Button("Collapse");
controlButton.getStyleClass().add("hide-left");
// apply the animations when the button is pressed.
controlButton.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent actionEvent)
{
// create an animation to hide sidebar.
final Animation hideSidebar = new Transition()
{
{
setCycleDuration(Duration.millis(250));
}
#Override
protected void interpolate(double frac)
{
final double curWidth = expandedWidth * (1.0 - frac);
setPrefWidth(curWidth);
setTranslateX(-expandedWidth + curWidth);
}
};
hideSidebar.onFinishedProperty().set(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent actionEvent)
{
setVisible(false);
controlButton.setText("Show");
controlButton.getStyleClass().remove("hide-left");
controlButton.getStyleClass().add("show-right");
}
});
// create an animation to show a sidebar.
final Animation showSidebar = new Transition()
{
{
setCycleDuration(Duration.millis(250));
}
#Override
protected void interpolate(double frac)
{
final double curWidth = expandedWidth * frac;
setPrefWidth(curWidth);
setTranslateX(-expandedWidth + curWidth);
}
};
showSidebar.onFinishedProperty().set(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent actionEvent)
{
controlButton.setText("Collapse");
controlButton.getStyleClass().add("hide-left");
controlButton.getStyleClass().remove("show-right");
}
});
if (showSidebar.statusProperty().get() == Animation.Status.STOPPED && hideSidebar.statusProperty().get() == Animation.Status.STOPPED)
{
if (isVisible())
{
hideSidebar.play();
}
else
{
setVisible(true);
showSidebar.play();
}
}
}
});
}
}
}
How I can display the collapse menu at the bottom of the stage?
Also is there a way to display the collapsed panel closed when the stage is loaded.
Using your code:
import javafx.animation.Animation;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class MainApp extends Application
{
BorderPane bp = new BorderPane();
public static void main(String[] args) throws Exception
{
launch(args);
}
#Override
public void start(final Stage stage) throws Exception
{
stage.setTitle("Slide out YouTube demo");
// create a WebView to show to the right of the SideBar.
bp.setStyle("-fx-background-color: #2f4f4f;");
bp.setPrefSize(800, 600);
// create a sidebar with some content in it.
final Pane lyricPane = createSidebarContent();
SideBar sidebar = new SideBar(250, lyricPane);
VBox.setVgrow(lyricPane, Priority.ALWAYS);
// layout the scene.
final BorderPane layout = new BorderPane();
StackPane st = new StackPane();
st.getChildren().addAll(bp, sidebar.getControlButton());
st.setAlignment(Pos.TOP_LEFT);
VBox vb = new VBox(10);
vb.getChildren().addAll(st);
layout.setBottom(sidebar);
layout.setCenter(vb);
// show the scene.
Scene scene = new Scene(layout);
//scene.getStylesheets().add(getClass().getResource("/styles/slideout.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
private BorderPane createSidebarContent()
{
// create some content to put in the sidebar.
final Button changeLyric = new Button("New Song");
changeLyric.getStyleClass().add("change-lyric");
changeLyric.setMaxWidth(Double.MAX_VALUE);
changeLyric.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent actionEvent)
{
System.out.println("Some action");
}
});
changeLyric.fire();
final BorderPane lyricPane = new BorderPane();
lyricPane.setTop(changeLyric);
return lyricPane;
}
/**
* Animates a node on and off screen to the left.
*/
class SideBar extends VBox
{
/**
* #return a control button to hide and show the sidebar
*/
public Button getControlButton()
{
return controlButton;
}
private final Button controlButton;
/**
* creates a sidebar containing a vertical alignment of the given nodes
*/
SideBar(final double expandedWidth, Node... nodes)
{
getStyleClass().add("sidebar");
this.setPrefWidth(expandedWidth);
this.setMinWidth(0);
// create a bar to hide and show.
setAlignment(Pos.CENTER);
getChildren().addAll(nodes);
// create a button to hide and show the sidebar.
controlButton = new Button("Collapse");
controlButton.getStyleClass().add("hide-left");
// apply the animations when the button is pressed.
controlButton.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent actionEvent)
{
// create an animation to hide sidebar.
final Animation hideSidebar = new Transition()
{
{
setCycleDuration(Duration.millis(250));
}
#Override
protected void interpolate(double frac)
{
final double curWidth = expandedWidth * (1.0 - frac);
setPrefHeight(curWidth);
setTranslateY(-expandedWidth + curWidth);
}
};
hideSidebar.onFinishedProperty().set(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent actionEvent)
{
setVisible(false);
controlButton.setText("Show");
controlButton.getStyleClass().remove("hide-left");
controlButton.getStyleClass().add("show-right");
}
});
// create an animation to show a sidebar.
final Animation showSidebar = new Transition()
{
{
setCycleDuration(Duration.millis(250));
}
#Override
protected void interpolate(double frac)
{
final double curWidth = expandedWidth * frac;
setPrefHeight(curWidth);
setTranslateY(-expandedWidth + curWidth);
}
};
showSidebar.onFinishedProperty().set(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent actionEvent)
{
controlButton.setText("Collapse");
controlButton.getStyleClass().add("hide-left");
controlButton.getStyleClass().remove("show-right");
}
});
if (showSidebar.statusProperty().get() == Animation.Status.STOPPED && hideSidebar.statusProperty().get() == Animation.Status.STOPPED)
{
if (isVisible())
{
hideSidebar.play();
}
else
{
setVisible(true);
showSidebar.play();
}
}
}
});
}
}
}
This makes it expand 'up' when the button is clicked. It's a little jumpy on the first click, I'm at work so I can't debug that bit of it very extensively, but essentially you just set the custom sidebar object at the bottom of the border pane, and then you switch all the .width and .X methods to be .height and .Y methods instead. With some tweaking I'm sure you could also get it to expand 'down' instead.

javafx multiple buttons to same handler

I try to make a simple calculator with 20 buttons and one handler. In java I can use 'if' statement with event.getSource() in ActionPerformed to check which button is pressed, but it doesn't work with handler in javafx. Is it possible in javafx that all buttons has one handler? (I don't want to use java 8 Lambdas.)
Last time I tried with setId/getId but it same not work (to me).
public class Calculator extends Application {
public Button b0, b1;
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
b0 = new Button("0");
b0.setId("0");
b0.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
grid.add(b0, 0, 1);
b0.setOnAction(myHandler);
b1 = new Button("1");
b1.setId("1");
b1.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
grid.add(b1, 0, 0);
b1.setOnAction(myHandler);
Scene scene = new Scene(grid, 365, 300);
scene.getStylesheets().add
(Calculator.class.getResource("calculator.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
final EventHandler<ActionEvent> myHandler = new EventHandler<ActionEvent>(){
#Override
public void handle(final ActionEvent event) {
Button x = (Button) event.getSource();
if (x.getId().equals(b0.getId()))
System.out.println("0");
else if(x.getId().equals(b1.getId()))
System.out.println("1");
}
};
public static void main(String[] args) {
launch(args);
}
}
I tested your code and it seems to work just fine.
There's no real reason to test the ids of the buttons, though. If you really want to use the same handler (which I don't advise), just test for equality between each button and the source of the event:
final EventHandler<ActionEvent> myHandler = new EventHandler<ActionEvent>(){
#Override
public void handle(final ActionEvent event) {
if (event.getSource() == b0)
System.out.println("0");
else if(event.getSource() == b1)
System.out.println("1");
}
};
But it's (almost?) always better to use a different handler for each action. It keeps the code free of all the if/else constructs, which both makes it cleaner and better in terms of performance. Here, since your buttons do almost the same thing, you can use a single implementation but multiple objects.
Here's a complete example:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Calculator extends Application {
private final IntegerProperty value = new SimpleIntegerProperty();
class NumberButtonHandler implements EventHandler<ActionEvent> {
private final int number ;
NumberButtonHandler(int number) {
this.number = number ;
}
#Override
public void handle(ActionEvent event) {
value.set(value.get() * 10 + number);
}
}
#Override
public void start(Stage primaryStage) {
GridPane grid = createGrid();
for (int n = 1; n<10; n++) {
Button button = createNumberButton(n);
int row = (n-1) / 3;
int col = (n-1) % 3 ;
grid.add(button, col, 2 - row);
}
Button zeroButton = createNumberButton(0);
grid.add(zeroButton, 1, 3);
Button clearButton = createButton("C");
// without lambdas:
// clearButton.setOnAction(
// new EventHandler<ActionEvent>() {
// #Override
// public void handle(ActionEvent event) {
// value.set(0);
// }
// }
// );
// with lambdas:
clearButton.setOnAction(event -> value.set(0));
grid.add(clearButton, 2, 3);
TextField displayField = createDisplayField();
BorderPane root = new BorderPane();
root.setPadding(new Insets(10));
root.setTop(displayField);
root.setCenter(grid);
Scene scene = new Scene(root, 365, 300);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
private Button createNumberButton(int number) {
Button button = createButton(Integer.toString(number));
button.setOnAction(new NumberButtonHandler(number));
return button ;
}
private Button createButton(String text) {
Button button = new Button(text);
button.setMaxSize(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
GridPane.setFillHeight(button, true);
GridPane.setFillWidth(button, true);
GridPane.setHgrow(button, Priority.ALWAYS);
GridPane.setVgrow(button, Priority.ALWAYS);
return button ;
}
private GridPane createGrid() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(5);
grid.setVgap(5);
grid.setPadding(new Insets(10));
return grid;
}
private TextField createDisplayField() {
TextField displayField = new TextField();
displayField.textProperty().bind(Bindings.format("%d", value));
displayField.setEditable(false);
displayField.setAlignment(Pos.CENTER_RIGHT);
return displayField;
}
public static void main(String[] args) {
launch(args);
}
}

Resources