JavaFX stopwatch timer - javafx

This is a class for a simple stopwatch for JavaFX, style the Label object as desired
package aaa;
import java.text.SimpleDateFormat;
import java.util.Date;
import javafx.beans.property.SimpleStringProperty;
/**
*
* #author D07114915
*/
public class KTimer extends Thread {
private Thread thread = null;
private SimpleDateFormat sdf = new SimpleDateFormat("mm:ss:S");
private String[] split;
private SimpleStringProperty min, sec, millis, sspTime;
private long time;
public static void main(String[] args) {
KTimer t = new KTimer();
t.startTimer(00);
}
public KTimer() {
min = new SimpleStringProperty("00");
sec = new SimpleStringProperty("00");
millis = new SimpleStringProperty("00");
sspTime = new SimpleStringProperty("00:00:00");
}
public void startTimer(long time) {
this.time = time;
thread = new Thread(this);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
public void stopTimer(long time) {
if (thread != null) {
thread.interrupt();
}
this.time = time;
setTime(time);
}
public void setTime(long time) {
this.time = time;
split = sdf.format(new Date(time)).split(":");
min.set(split[0]);
sec.set(split[1]);
if (split[2].length() == 1) {
split[2] = "0" + split[2];
}
millis.set(split[2].substring(0, 2));
sspTime.set(min.get() + ":" + sec.get() + ":" + millis.get());
}
public long getTime() {
return time;
}
public SimpleStringProperty getSspTime() {
return sspTime;
}
#Override
public void run() {
try {
while (!thread.isInterrupted()) {
setTime(time);
sleep(10);
time = time + 10;
}
} catch (Exception e) {
}
}
}//end of class
Now just get a listener on the property for your GUI
Add vars
KTimer ktimer;
Label timeLabel;
in your class initialize the vars
//Clock
ktimer = new KTimer();
timeLabel = new Label(ktimer.getSspTime().get());
ktimer.getSspTime().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
timeLabel.setText(ktimer.getSspTime().get());
}
});
then call the method to start and stop wherever you need to
Stop and reset is
ktimer.stopTimer(0);
Start and Pause timer is
ktimer.startTimer(ktimer.getTime());
Any improvements appreciated as the class is a bit CPU hungry..., but you can adjust the run thread and setTime(time) functions to suit the application

Here's a slightly different version (maybe better) and I'm not sure the synchronized methods are really necessary
package aaa;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import javafx.beans.property.SimpleStringProperty;
/**
*
* #author D07114915
*/
public class KTimer {
private SimpleDateFormat sdf = new SimpleDateFormat("mm:ss:S");
private String[] split;
private SimpleStringProperty sspTime;
private long time;
private Timer t = new Timer("Metronome", true);
private TimerTask tt;
boolean timing = false;
public KTimer() {
sspTime = new SimpleStringProperty("00:00:00");
}
public void startTimer(final long time) {
this.time = time;
timing = true;
tt = new TimerTask() {
#Override
public void run() {
if (!timing) {
try {
tt.cancel();
} catch (Exception e) {
e.printStackTrace();
}
} else {
updateTime();
}
}
};
t.scheduleAtFixedRate(tt, 10, 10);
}
public synchronized void stopTimer() {
timing = false;
}
public synchronized void updateTime() {
this.time = this.time + 10;
split = sdf.format(new Date(this.time)).split(":");
sspTime.set(split[0] + ":" + split[1] + ":" + (split[2].length() == 1 ? "0" + split[2] : split[2].substring(0, 2)));
}
public synchronized void moveToTime(long time) {
stopTimer();
this.time = time;
split = sdf.format(new Date(time)).split(":");
sspTime.set(split[0] + ":" + split[1] + ":" + (split[2].length() == 1 ? "0" + split[2] : split[2].substring(0, 2)));
}
public synchronized long getTime() {
return time;
}
public synchronized SimpleStringProperty getSspTime() {
return sspTime;
}
}

Building on KEV's answer and various demos I found across the internet, here's a simple "count up" timer with 100ms precision:
package fxtimer;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FXTimer extends Application {
private Timeline timeline;
private Label timerLabel = new Label(), splitTimerLabel = new Label();
private DoubleProperty timeSeconds = new SimpleDoubleProperty(),
splitTimeSeconds = new SimpleDoubleProperty();
private Duration time = Duration.ZERO, splitTime = Duration.ZERO;
#Override
public void start(Stage primaryStage) {
// Configure the Label
// Bind the timerLabel text property to the timeSeconds property
timerLabel.textProperty().bind(timeSeconds.asString());
timerLabel.setTextFill(Color.RED);
timerLabel.setStyle("-fx-font-size: 4em;");
splitTimerLabel.textProperty().bind(splitTimeSeconds.asString());
splitTimerLabel.setTextFill(Color.BLUE);
splitTimerLabel.setStyle("-fx-font-size: 4em;");
// Create and configure the Button
Button button = new Button();
button.setText("Start / Split");
button.setOnAction(new EventHandler() {
#Override
public void handle(Event event) {
if (timeline != null) {
splitTime = Duration.ZERO;
splitTimeSeconds.set(splitTime.toSeconds());
} else {
timeline = new Timeline(
new KeyFrame(Duration.millis(100),
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
Duration duration = ((KeyFrame)t.getSource()).getTime();
time = time.add(duration);
splitTime = splitTime.add(duration);
timeSeconds.set(time.toSeconds());
splitTimeSeconds.set(splitTime.toSeconds());
}
})
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
}
});
// Setup the Stage and the Scene (the scene graph)
StackPane root = new StackPane();
Scene scene = new Scene(root, 300, 250);
// Create and configure VBox
// gap between components is 20
VBox vb = new VBox(20);
// center the components within VBox
vb.setAlignment(Pos.CENTER);
// Make it as wide as the application frame (scene)
vb.setPrefWidth(scene.getWidth());
// Move the VBox down a bit
vb.setLayoutY(30);
// Add the button and timerLabel to the VBox
vb.getChildren().addAll(button, timerLabel, splitTimerLabel);
// Add the VBox to the root component
root.getChildren().add(vb);
primaryStage.setTitle("FX Timer");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

Related

javaFX: MediaPlayer's seek method hangs the player without error message oder Status change

I have a programm with 8 mediaplayer, which are controlled like one big video with a single set of controls.
I have one Slider to control the time, aka I call all MediaPlayer's seek methods in onMouseReleased of the slider. My Problem is, the mediaplayer hang all the time, without changing their status or calling onError . When I put every seek in a new Thread, these problems disappear msot of the time, but not always and I'm gettign a lot of concurrency issues.
Does anybody know the reason why the player hangs?
EDIT:
Ok, here a minimal reproducible example:
Class MediaControlMinimal:
package org.example;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Window;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;
public class MediaControlMinimal extends BorderPane {
private final List<MediaPlayer> mpList;
private Duration duration;
private boolean shouldPlay;
private int setupComplete;
private final Slider timeSlider;
private final HBox mediaBar;
private final Button playButton;
private final MediaPlayer controlMediaPlayer;
public MediaControlMinimal(List<MediaPlayer> mpList, int videoRows) {
this.mpList = mpList;
List<MediaView> mvList = new ArrayList<>(mpList.size());
Pane mvPane = new Pane() {
};
for (MediaPlayer mp : mpList) {
MediaView mediaView = new MediaView(mp);
mvList.add(mediaView);
mvPane.getChildren().add(mediaView);
}
mvPane.setStyle("-fx-background-color: black;");
setCenter(mvPane);
mediaBar = new HBox(); // 5 als param für spacing = 5, sieh zeile 247
mediaBar.setAlignment(Pos.CENTER);
mediaBar.setPadding(new Insets(5, 10, 5, 10));
BorderPane.setAlignment(mediaBar, Pos.CENTER);
playButton = new Button();
playButton.setOnAction(e -> {
shouldPlay = !shouldPlay;
if (shouldPlay) {
playAll();
} else {
pauseAll();
}
});
// Add time slider
timeSlider = new Slider();
HBox.setHgrow(timeSlider, Priority.ALWAYS);
timeSlider.setMinWidth(50);
timeSlider.setMaxWidth(Double.MAX_VALUE);
timeSlider.setOnMouseReleased(event -> {
for (MediaPlayer mp : mpList) {
mp.seek(Duration.millis((event.getX() / timeSlider.getWidth()) * timeSlider.getMax());
}
});
controlMediaPlayer = mpList.get(1);
controlMediaPlayer.currentTimeProperty().addListener(observable -> {
updateValues(controlMediaPlayer);
});
for (MediaPlayer mp : mpList) {
mp.setOnReady(() -> {
int videosPerRow = mpList.size() / videoRows;
if (setupComplete == 0) {
duration = mp.getMedia().getDuration();
timeSlider.setMax(duration.toMillis());
updateValues(mp);
final Window window = mvPane.getScene().getWindow();
final double titleHeight = window.getHeight() - mvPane.getScene().getHeight();
double windowHeight = videoRows * mp.getMedia().getHeight() + titleHeight;
if (!Main.isTransDesign) {
windowHeight += mediaBar.getHeight();
}
window.setHeight(windowHeight);
window.setWidth(videosPerRow * mp.getMedia().getWidth());
}
if (setupComplete < mpList.size()) {
final Node mpNode = mvPane.getChildren().get(mpList.indexOf(mp));
if (mpList.indexOf(mp) != 0 && mpNode.getLayoutX() == 0 && mpNode.getLayoutY() == 0) {
//fenster höhe
double xRelocate = mp.getMedia().getWidth() * (mpList.indexOf(mp) % videosPerRow);
double yRelocate = mp.getMedia().getHeight() * Math.floorDiv(mpList.indexOf(mp), videosPerRow);
mpNode.relocate(xRelocate, yRelocate);
}
++setupComplete;
}
});
mp.setCycleCount(MediaPlayer.INDEFINITE);
}
mediaBar.getChildren().add(playButton);
mediaBar.getChildren().add(timeSlider);
setBottom(mediaBar);
}
private void playAll() {
for (MediaPlayer mp : mpList) {
mp.play();
}
}
private void pauseAll() {
for (MediaPlayer mp : mpList) {
mp.pause();
}
}
protected void updateValues(MediaPlayer mp) {
if (timeSlider != null) {
Platform.runLater(() -> {
Duration currentTime = mp.getCurrentTime();
timeSlider.setDisable(duration.isUnknown());
if (!timeSlider.isDisabled() && duration.greaterThan(Duration.ZERO) && !timeSlider.isValueChanging()) {
timeSlider.setValue(currentTime.toMillis());
}
});
}
}
}
Class EmbeddedMediaPlayer
package org.example;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class EmbeddedMediaPlayer extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle(Main.WINDOW_TITLE);
Group root = new Group();
Scene scene = new Scene(root, 1586, 900);
List<MediaPlayer> mediaPlayerList = new ArrayList<>();
// create media player
for(String s : Main.MEDIA_URL){
MediaPlayer mediaPlayer = new MediaPlayer(new Media(s));
mediaPlayer.setAutoPlay(false);
mediaPlayerList.add(mediaPlayer);
}
MediaControlMinimal mediaControl = new MediaControlMinimal(mediaPlayerList, Main.VIDEO_ROWS);
scene.setRoot(mediaControl);
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop(){
System.out.println("Stage is closing");
System.exit(0);
}
/**
* The main() method is ignored in correctly deployed JavaFX application.
* main() serves only as fallback in case the application can not be
* launched through deployment artifacts, e.g., in IDEs with limited FX
* support. NetBeans ignores main().
*
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Class Main
package org.example;
public class Main {
public static final String[] MEDIA_URL = {
"https://video.fogodosamba.de/media/SambaReggae_Sticks.mp4",
"https://video.fogodosamba.de/media/SambaReggae_Fundo1.mp4",
"https://video.fogodosamba.de/media/SambaReggae_Dobra.mp4",
"https://video.fogodosamba.de/media/SambaReggae_Fundo2.mp4",
"https://video.fogodosamba.de/media/SambaReggae_Ansage.mp4",
"https://video.fogodosamba.de/media/SambaReggae_Timbal.mp4",
"https://video.fogodosamba.de/media/SambaReggae_Caixa.mp4",
"https://video.fogodosamba.de/media/SambaReggae_Repi.mp4"
};
public static final int VIDEO_ROWS = 2;
public static final String WINDOW_TITLE = "";
public static void main(String[] args) {
EmbeddedMediaPlayer.main(args);
}
}
EDIT 2:
Sometimes a video hsngs for a while, then starts up again, but plays in slow motion. No error message in onerror, no state change and getCurrentRate = 1.

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 Increasing the number failed

In my Example I am trying to make a counter;
It starts with 0 and everytime i click the button it must be increase one more.
I add a PREFIX and than my increaseNumber() method have to function but it doesn't work.
Did I create my EventHandler false?
Here are my classes:
import java.util.Observable;
public class Number extends Observable {
int number = 0;
public int getZahl() {
return number;
}
public void setZahl(int number) {
this.number = number;
}
public void increaseTheNumber() {
int oldNumber = number;
number++;
setChanged();
notifyObservers(oldNumber);
}
public String toString() {
return number + " ";
}
}
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class NumberGUI extends Application {
private Button btn;
private Label lbl;
private Label lbl2;
private Number num;
private static String PREFIX = "The new number is: ";
public static void main(String[] args) {
launch(args);
}
#Override
public void init() throws Exception {
num = new Number();
initButton();
initLabels();
}
private void initButton() {
btn = new Button();
btn.setText("Click to Increase");
btn.setPrefHeight(50);
btn.setPrefWidth(200);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
num.increaseTheNumber();
}
});
}
private void initLabels() {
lbl2=new Label(PREFIX+num.getZahl());
}
private Parent createSceneGraph() {
BorderPane root = new BorderPane();
root.setCenter(lbl);
root.setBottom(lbl2);
GridPane grid = new GridPane();
grid.addColumn(0,btn);
root.setCenter(grid);
return root;
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Counter!");
primaryStage.setScene(new Scene(createSceneGraph(),300,250));
primaryStage.show();
}
}
public class Main {
public static void main(String[] args) {
Number num = new Number();
ObserveNumber on = new ObserveNumber();
num.addObserver(on);
num.increaseTheNumber();
}
}
If you print your stored number every time you invoke increaseTheNumber method you will see that the number is indeed being increased.
public void increaseTheNumber() {
int oldNumber = number;
number++;
setChanged();
notifyObservers(oldNumber);
System.out.println(number);
}
Your label doesn't change because you set its content only once, when the number is still zero.
lbl2=new Label(PREFIX+num.getZahl());
Since your Number is an observable, you can add an Observer to it that will update the label every time it's been notified.
num.addObserver((o, arg) -> {
lbl2.setText(PREFIX+num.getZahl());
});
This becomes much easier, if you use JavaFX properties, like IntegerProperty, since this allows you to simply bind the text property:
#Override
public void start(Stage primaryStage) {
Label label = new Label();
IntegerProperty property = new SimpleIntegerProperty(1);
Button btn = new Button("Increment");
btn.setOnAction((ActionEvent event) -> {
// increment the property
property.set(property.get()+1);
});
// format the property by prepending the prefix string
label.textProperty().bind(property.asString("The new number is: %d"));
Scene scene = new Scene(new VBox(label, btn));
primaryStage.setScene(scene);
primaryStage.show();
}

Custom ListCell implements InvalidationListener, repaint components

I have a custom ListCell implemented that contains a BorderPane layout with some components.
The cell registers itself to the item. So when the duration of the item changes the invalidated method is called.
In this method I set the text of the duration label. My problem is now the method is called but the label is not repainted.
I think if setText is called the cell should repaint. It is possible to manually repaint the cell or the Label.?
public static class ListItemCell extends ListCell<MusicListItem> implements InvalidationListener{
private AnchorPane listItem;
private Label artist;
private Label title;
private Label duration;
private BorderPane borderPane;
private FlowPane flowPane;
public ListItemCell() {
initCellLayout();
}
public ListItemCell(final LogicInterfaceFX logic) {
...
}
public void initCellLayout() {
try {
this.listItem = (AnchorPane) FXMLLoader.load(getClass().getResource("/de/roth/jsona/view/themes/" + Config.getInstance().THEME + "/" + "layout_list_item.fxml"));
} catch (Exception e) {
e.printStackTrace();
}
this.borderPane = (BorderPane) listItem.getChildren().get(0);
this.flowPane = (FlowPane) borderPane.getLeft();
this.artist = (Label) flowPane.getChildren().get(0);
this.artist.getStyleClass().add(defaultTextClass);
this.title = (Label) flowPane.getChildren().get(1);
this.title.getStyleClass().add(defaultTextClass);
this.duration = (Label) borderPane.getRight();
this.duration.getStyleClass().add(defaultTextClass);
this.setGraphic(listItem);
}
#Override
public void updateItem(MusicListItem item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
item.addListener(this);
item.durationProperty().addListener(this);
// Duration
this.duration.setText(item.getDuration());
// Artist / Title
if (item.getArtist() != null) {
this.artist.setText(item.getArtist());
this.title.setText(" - " + item.getTitle());
} else {
this.artist.setText("");
this.title.setText(item.getFile().getName());
}
} else {
this.artist.setText("");
this.title.setText("");
this.duration.setText("");
}
}
#Override
public void invalidated(Observable observable) {
System.out.println("INVALIDATE!!!" + getItem().getFile().getAbsolutePath());
this.duration.setText(getItem().getDuration());
}
}
You have a bug in there: you need to make sure you remove listeners from old items when the item is updated. Remember that ListCells are reused, so updateItem(...) is called multiple times during the lifespan of your ListView.
I don't know if that's what is causing it to fail to update. This works for me:
import java.util.Random;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewUpdatableProperties extends Application {
#Override
public void start(Stage primaryStage) {
final ListView<Item> listView = new ListView<>();
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
final Random rng = new Random();
for (int i=1; i<=20; i++) {
listView.getItems().add(new Item("Item "+i, rng.nextInt(100)));
}
BorderPane root = new BorderPane();
root.setCenter(listView);
listView.setCellFactory(new Callback<ListView<Item>, ListCell<Item>>() {
#Override
public ListCell<Item> call(ListView<Item> param) {
return new ItemListCell();
}
});
HBox controls = new HBox();
controls.setPadding(new Insets(5));
Button incButton = new Button("Increment selected");
incButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (Item item : listView.getSelectionModel().getSelectedItems()) {
item.increment();
}
}
});
controls.getChildren().add(incButton);
root.setBottom(controls);
Scene scene = new Scene(root, 250, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class ItemListCell extends ListCell<Item> implements InvalidationListener {
private final HBox hbox ;
private final Label nameLabel ;
private final Label valueLabel ;
public ItemListCell() {
hbox = new HBox(5);
nameLabel = new Label();
valueLabel = new Label();
hbox.getChildren().addAll(nameLabel, valueLabel);
setGraphic(hbox);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
public void updateItem(Item item, boolean empty) {
Item oldItem = getItem();
if (oldItem != null) {
oldItem.valueProperty().removeListener(this);
}
super.updateItem(item, empty);
if (item != null) {
nameLabel.setText(item.getName());
valueLabel.setText(String.valueOf(item.getValue()));
item.valueProperty().addListener(this);
} else {
nameLabel.setText("");
valueLabel.setText("");
}
}
#Override
public void invalidated(Observable observable) {
final int value = getItem().getValue();
System.out.println("Invalidated: item is "+getItem().getName() + " with value "+value);
valueLabel.setText(String.valueOf(value));
}
}
public static class Item {
public Item(String name, int value) {
setName(name);
setValue(value);
}
private final StringProperty name = new SimpleStringProperty(this, "name");
public StringProperty nameProperty() {
return name ;
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
private final IntegerProperty value = new SimpleIntegerProperty(this, "value");
public IntegerProperty valueProperty() {
return value ;
}
public int getValue() {
return value.get();
}
public void setValue(int value) {
this.value.set(value);
}
public void increment() {
value.set(value.get()+1);
}
}
public static void main(String[] args) {
launch(args);
}
}
As stated in the other answer, there is no repaint() method in JavaFX. If you wire things up correctly, when the properties are invalidated, it will know to repaint.
JavaFX uses a "retained mode" rendering model whereas Swing uses an "immediate mode".
See: Retained Mode Versus Immediate Mode (Windows)
So, you don't have a direct repaint() method.

JavaFX: How to show an animated ProgressIndicator while adding columns to a TableView?

Adding the TableColumn objects to my TableView is a lengthy operation in my application---it causes everything to freeze for 3-4 seconds. I would like to keep the UI responsive while this is happening, but this is exactly the kind of thing that must be done on the JavaFX application thread. Can anything be done?
package tableviewpausetest;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.WorkerStateEvent;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author drmc
*/
public class TableViewPauseTest extends Application {
public static final int ROW_COUNT = 100;
public static final int COLUMN_COUNT = 80;
public static final ExecutorService executor = Executors.newSingleThreadExecutor();
private TableView<String> tableView = new TableView<>();
private Button button = new Button("Toggle Columns Visibility");
private ProgressIndicator progressIndicator = new ProgressIndicator();
private HBox buttonBox = new HBox(8);
private BorderPane borderPane = new BorderPane();
private Task task = null;
public void start(Stage primaryStage) {
this.tableView.setColumnResizePolicy(
TableView.CONSTRAINED_RESIZE_POLICY);
for (int i = 0; i < ROW_COUNT; ++i) {
this.tableView.getItems().add(":)");
}
this.button.setOnAction(new ToggleHandler(this));
this.buttonBox.getChildren().setAll(this.button);
this.borderPane.setCenter(this.tableView);
this.borderPane.setBottom(this.buttonBox);
Scene scene = new Scene(this.borderPane, 1024, 768);
primaryStage.setTitle("tableviewpausetest");
primaryStage.setScene(scene);
primaryStage.show();
}
private class ToggleHandler implements EventHandler<ActionEvent> {
private TableViewPauseTest app;
public ToggleHandler(TableViewPauseTest app) {
this.app = app;
}
#Override
public void handle(ActionEvent event) {
// Show the progress indicator.
this.app.buttonBox.getChildren().add(this.app.progressIndicator);
this.app.progressIndicator.setPrefHeight(this.app.button.getHeight());
// Ensure the columns exist.
if (this.app.tableView.getColumns().isEmpty()) {
for (int i = 0; i < COLUMN_COUNT; ++i) {
TableColumn<String, String> tableColumn = new TableColumn<>(
String.format("%s", i));
tableColumn.setVisible(false);
this.app.tableView.getColumns().add(tableColumn);
}
}
// Create and submit a concurrent task to toggle column visibility.
this.app.task = new ToggleTask(this.app);
this.app.task.setOnSucceeded(new ToggleSucceededHandler(this.app));
executor.submit(this.app.task);
}
}
private class ToggleSucceededHandler implements EventHandler<WorkerStateEvent> {
private TableViewPauseTest app;
public ToggleSucceededHandler(TableViewPauseTest app) {
this.app = app;
}
#Override
public void handle(WorkerStateEvent event) {
// Hide the progress indicator.
this.app.buttonBox.getChildren().remove(this.app.progressIndicator);
}
}
private class ToggleTask extends Task<String> {
private TableViewPauseTest app;
public ToggleTask(TableViewPauseTest app) {
this.app = app;
}
#Override
public String call() {
boolean newState = false;
String action = "hide";
if (this.app.tableView.getVisibleLeafColumns().isEmpty()) {
newState = true;
action = "show";
}
// This action must be performed on the JavaFX Application Thread,
// and it causes an extremely uncomfortable pause in my application.
Platform.runLater(new ToggleRunnable(this.app.tableView, newState));
return action;
}
}
private class ToggleRunnable implements Runnable {
private TableView<?> tableView;
private boolean newState;
public ToggleRunnable(TableView<?> tableView, boolean newState) {
this.tableView = tableView;
this.newState = newState;
}
#Override
public void run() {
for (TableColumn<?, ?> tableColumn : this.tableView.getColumns()) {
tableColumn.setVisible(this.newState);
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
I think this example code useful for you
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package progressbartablecelltest;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
*
* #author reegan
*/
public class ProgressBarTableCellTest extends Application {
public void start(Stage primaryStage) {
TableView<TestTask> table = new TableView<>();
Random rng = new Random();
for (int i = 0; i < 20; i++) {
table.getItems().add(
new TestTask(rng.nextInt(3000) + 2000, rng.nextInt(30) + 20));
}
TableColumn<TestTask, String> statusCol = new TableColumn("Status");
statusCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>(
"message"));
statusCol.setPrefWidth(75);
TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>(
"progress"));
progressCol
.setCellFactory(ProgressIndicatorTableCell.<TestTask>forTableColumn());
table.getColumns().addAll(statusCol, progressCol);
BorderPane root = new BorderPane();
root.setCenter(table);
primaryStage.setScene(new Scene(root));
primaryStage.show();
ExecutorService executor = Executors.newFixedThreadPool(table.getItems().size(), new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
for (TestTask task : table.getItems()) {
executor.execute(task);
}
}
public static void main(String[] args) { launch(args); }
static class TestTask extends Task<Void> {
private final int waitTime; // milliseconds
private final int pauseTime; // milliseconds
public static final int NUM_ITERATIONS = 100;
TestTask(int waitTime, int pauseTime) {
this.waitTime = waitTime;
this.pauseTime = pauseTime;
}
#Override
protected Void call() throws Exception {
this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
this.updateMessage("Waiting...");
Thread.sleep(waitTime);
this.updateMessage("Running...");
for (int i = 0; i < NUM_ITERATIONS; i++) {
updateProgress((1.0 * i) / NUM_ITERATIONS, 1);
Thread.sleep(pauseTime);
}
this.updateMessage("Done");
this.updateProgress(1, 1);
return null;
}
}
}
class ProgressIndicatorTableCell<S> extends TableCell<S, Double> {
public static <S> Callback<TableColumn<S, Double>, TableCell<S, Double>> forTableColumn() {
return new Callback<TableColumn<S, Double>, TableCell<S, Double>>() {
#Override
public TableCell<S, Double> call(TableColumn<S, Double> param) {
return new ProgressIndicatorTableCell<>();
}
};
}
private final ProgressIndicator progressIndicator;
private ObservableValue observable;
public ProgressIndicatorTableCell() {
this.getStyleClass().add("progress-indicator-table-cell");
this.progressIndicator = new ProgressIndicator();
setGraphic(progressIndicator);
}
#Override public void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
progressIndicator.progressProperty().unbind();
observable = getTableColumn().getCellObservableValue(getIndex());
if (observable != null) {
progressIndicator.progressProperty().bind(observable);
} else {
progressIndicator.setProgress(item);
}
setGraphic(progressIndicator);
}
}
}
Table Column Add with Progress Indicator

Resources