I'm making a playlist based mp3 player using javafx and I got everything working except how to pause/resume a song. I tried simply checking the player.Status()and using that but it didn't work so I stored the time of the song when pause()is clicked, in a Duration pausetime variable and it works that way but only once. What happens is: I click pause(), it works, click play(), it resumes the song but after that the pause button stops doing anything.
Btw I'm using two separate ToggleButton for pause and play because of the style I'm going for.
Here's the part of the code i'm talking about:
public void play(){
if (player != null){
player.stop();
}
if (pausebutton.isSelected()){
pausebutton.setSelected(false); //resume part
slider.setValue(pausetime.toSeconds());
play();
}
this.player = players.get(i);
player.setStartTime(pausetime);
player.play();
slide(i);
csong.setText(playlist.get(i).getName());
player.setOnEndOfMedia(new Runnable(){
#Override public void run(){
if (shuffle.isSelected()){
i = rand.nextInt(players.size() + 1);
}
else{
i++;
}
if(loop.isSelected()){
if (i == players.size()){
i = 0;
}}
list.getSelectionModel().select(i);
play();
}
});
}
public void pause(){
player.pause();
pausetime = player.getCurrentTime();
playbutton.setSelected(false);
}
I have created a very simple mp3 player, which has most (if not all) of the components said above.
It has play and pause toggle buttons, which do work ;)
It has labels updating the time elapsed for the song
It has a sliderbar, which can be used to move forward/backward. It automatically updates on song play.
Complete Code
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.text.DecimalFormat;
public class MediaPlayerSample extends Application {
private MediaPlayer player;
private final ToggleButton playButton = new ToggleButton("Play");
private final ToggleButton pauseButton = new ToggleButton("Pause");
private final ToggleGroup group = new ToggleGroup();
private final Label totalDuration = new Label();
private final Label currentDuration = new Label();
private final DecimalFormat formatter = new DecimalFormat("00.00");
private final SliderBar timeSlider = new SliderBar();
private Duration totalTime;
#Override
public void start(Stage primaryStage)
{
//Add a scene
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setStyle("-fx-background-color: ANTIQUEWHITE");
HBox playPause = new HBox(playButton, pauseButton);
HBox sliderBox = new HBox(timeSlider, currentDuration, totalDuration);
HBox.setHgrow(sliderBox, Priority.ALWAYS);
root.getChildren().addAll(sliderBox, playPause);
Scene scene = new Scene(root, 300, 100);
Media pick = new Media(getClass().getResource("/delete/abc/abc.mp3").toExternalForm());
player = new MediaPlayer(pick);
// Play the track and select the playButton
player.play();
playButton.setSelected(true);
player.currentTimeProperty().addListener(new ChangeListener<Duration>() {
#Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
timeSlider
.sliderValueProperty()
.setValue(newValue.divide(totalTime.toMillis()).toMillis() * 100.0);
currentDuration.setText(String.valueOf(formatter.format(newValue.toSeconds())));
}
});
player.setOnReady(() -> {
// Set the total duration
totalTime = player.getMedia().getDuration();
totalDuration.setText(" / " + String.valueOf(formatter.format(Math.floor(totalTime.toSeconds()))));
});
// Slider Binding
timeSlider.sliderValueProperty().addListener((ov) -> {
if (timeSlider.isTheValueChanging()) {
if (null != player)
// multiply duration by percentage calculated by
// slider position
player.seek(totalTime.multiply(timeSlider
.sliderValueProperty().getValue() / 100.0));
else
timeSlider.sliderValueProperty().setValue(0);
}
});
//Applying Toggle Group to Buttons
playButton.setToggleGroup(group);
pauseButton.setToggleGroup(group);
// Action for Buttons
playButton.setOnAction(e -> {
play();
});
pauseButton.setOnAction(e -> {
pause();
});
//show the stage
primaryStage.setTitle("Media Player");
primaryStage.setScene(scene);
primaryStage.show();
}
public void play(){
player.play();
playButton.setSelected(true);
}
public void pause(){
player.pause();
playButton.setSelected(false);
}
private class SliderBar extends StackPane {
private Slider slider = new Slider();
private ProgressBar progressBar = new ProgressBar();
public SliderBar() {
getChildren().addAll(progressBar, slider);
bindValues();
}
private void bindValues(){
progressBar.prefWidthProperty().bind(slider.widthProperty());
progressBar.progressProperty().bind(slider.valueProperty().divide(100));
}
public DoubleProperty sliderValueProperty() {
return slider.valueProperty();
}
public boolean isTheValueChanging() {
return slider.isValueChanging();
}
}
public static void main(String[] args) {
launch(args);
}
}
A simple look while it plays
You can style the mediaplayer using stylesheet. You can go through JavaFX CSS Reference for advanced topic on css.
For a mediaplayer with advanced features like:
Adding songs to a Playlist
Playing songs one after another
Drag and drop songs into mediaview
and much more, you can go visit this example.
Related
The following are the changes I made in fxml
Changes in the java file , here my code :
private ProgressIndicator pi;
void handlebuildButtonAction(ActionEvent event) throws IOException, GeneralSecurityException {
if ((entServer.isSelected()==true || compasServer.isSelected()==true)) {
if(!fileList.isEmpty()){
ProgressIndicator pi = new ProgressIndicator();
pi.setProgress(10);
}
}
The progress indicator is not updated when I run the application. I'm not sure how to sync the changes to UI. Assist me on this. Thanks in advance.
output
For example: if you set 0.1 - progress will be 10%, 0.2 - 20% and so on, so when you set the progress => 1 you will always have "done".
Here, this an example with a button, when you click the button, your progress indicator will be updated(one click + 10%):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
public class Test extends Application {
private ProgressIndicator pi;
private double counter = 0;
public void start(Stage stage)
{
ProgressIndicator pi = new ProgressIndicator();
Button button = new Button("Press");
TilePane root = new TilePane();
// action event
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
public void handle(ActionEvent e)
{
counter += 0.1;
pi.setProgress(counter);
}
};
button.setOnAction(event);
root.getChildren().add(button);
root.getChildren().add(pi);
// create a scene
Scene scene = new Scene(root, 200, 200);
// set the scene
stage.setScene(scene);
stage.show();
}
public static void main(String args[])
{
// launch the application
launch(args);
}
}
Just change this code for your case:
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
public void handle(ActionEvent e)
{
if ((entServer.isSelected()==true || compasServer.isSelected()==true)) {
if (!fileList.isEmpty()) {
counter += 0.1;
pi.setProgress(counter);
}
}
}
};
Hope that helps you!
I'm developing a simple image editing functionality as a part of a larger JavaFX application, but I'm having some trouble to work out the undo/zoom and draw requirements together.
My requirements are the following:
The user should be able to:
Draw freehand on the image
Zoom in and out the image
Undo the changes
If the canvas is bigger than the window, it should have scroll-bars.
How I implemented these requirements:
The Drawing is done by starting a line when the mouse is pressed on the canvas, stroking it when it is dragged and closing the path when the button is released.
The Zoom works by scaling the canvas to a higher or lower value.
The Undo method takes a snapshot of the current state of the canvas when the mouse is pressed (before any change is made) and push it to a Stack of Images. When I need to undo some change I pop the last image of the Stack and draw it on the canvas, replacing the current image by the last one.
To have scroll-bars I just place the Canvas inside a Group and a ScrollPane.
Everything works fine, except when I try to draw on a scaled canvas. Due to the way I implemented the Undo functionality, I have to scale it back to 1, take a snapshot of the Node then scale it back to the size it was before. When this happens and the user is dragging the mouse the image position changes below the mouse pointer, causing it to draw a line that shouldn't be there.
Normal (unscaled canvas):
Bug (scaled canvas)
I tried the following approaches to solve the problem:
Don't re-scale to take the snapshot - Doesn't cause the unwanted line, but I end up with different image sizes in the stack, if it's smaller (zoomed out) when the snapshot was taken I now have a lower resolution of the image that I can't scale up without losing quality.
Tweak the logic and put the pushUndo call to the mouseReleased event - It almost worked, but when the user scrolled to a place and it's drawing there, the re-scaling causes the image to scroll back to the top-left;
Tried to search an way to "clone" or serialize the canvas and store the object state in the Stack - Didn't found anything I was able to adapt, and JavaFX doesn't support serialization of its objects.
I think the problem can be solved either by reworking the undo functionality as it doesn't need to re-scale the canvas to copy its state or by changing the way I zoom the canvas without scaling it, but I'm out of ideas on how to implement either of those options.
Below is the functional code example to reproduce the problem:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Stack;
public class Main extends Application {
Stack<Image> undoStack;
Canvas canvas;
double canvasScale;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
canvasScale = 1.0;
undoStack = new Stack<>();
BorderPane borderPane = new BorderPane();
HBox hbox = new HBox(4);
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> undo());
Button btnIncreaseZoom = new Button("Increase Zoom");
btnIncreaseZoom.setOnAction(actionEvent -> increaseZoom());
Button btnDecreaseZoom = new Button("Decrease Zoom");
btnDecreaseZoom.setOnAction(actionEvent -> decreaseZoom());
hbox.getChildren().addAll(btnUndo, btnIncreaseZoom, btnDecreaseZoom);
ScrollPane scrollPane = new ScrollPane();
Group group = new Group();
canvas = new Canvas();
canvas.setWidth(400);
canvas.setHeight(300);
group.getChildren().add(canvas);
scrollPane.setContent(group);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(2.0);
gc.setStroke(Color.RED);
canvas.setOnMousePressed(mouseEvent -> {
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
});
canvas.setOnMouseDragged(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
});
canvas.setOnMouseReleased(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
gc.closePath();
});
borderPane.setTop(hbox);
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 800, 600);
stage.setScene(scene);
stage.show();
}
private void increaseZoom() {
canvasScale += 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void decreaseZoom () {
canvasScale -= 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void pushUndo() {
// Restore the canvas scale to 1 so I can get the original scale image
canvas.setScaleX(1);
canvas.setScaleY(1);
// Get the image with the snapshot method and store it on the undo stack
Image snapshot = canvas.snapshot(null, null);
undoStack.push(snapshot);
// Set the canvas scale to the value it was before the method
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void undo() {
if (!undoStack.empty()) {
Image undoImage = undoStack.pop();
canvas.getGraphicsContext2D().drawImage(undoImage, 0, 0);
}
}
}
Consider drawing Shape objects, in this case Path objects, and apply scale to them:
import java.util.Stack;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class Main extends Application {
private Path path;
private Stack<Path> undoStack;
private Group group;
private double scale = 1;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
undoStack = new Stack<>();
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> undo());
Button btnIncreaseZoom = new Button("Increase Zoom");
btnIncreaseZoom.setOnAction(actionEvent -> increaseZoom());
Button btnDecreaseZoom = new Button("Decrease Zoom");
btnDecreaseZoom.setOnAction(actionEvent -> decreaseZoom());
HBox hbox = new HBox(4, btnUndo, btnIncreaseZoom, btnDecreaseZoom);
group = new Group();
BorderPane root = new BorderPane(new Pane(group), hbox, null,null, null);
Scene scene = new Scene(root, 300, 400);
root.setOnMousePressed(mouseEvent -> newPath(mouseEvent.getX(), mouseEvent.getY()));
root.setOnMouseDragged(mouseEvent -> addToPath(mouseEvent.getX(), mouseEvent.getY()));
primaryStage.setScene(scene);
primaryStage.show();
}
private void newPath(double x, double y) {
path = new Path();
path.setStrokeWidth(1);
path.setStroke(Color.BLACK);
path.getElements().add(new MoveTo(x,y));
group.getChildren().add(path);
undoStack.add(path);
}
private void addToPath(double x, double y) {
path.getElements().add(new LineTo(x, y));
}
private void increaseZoom() {
scale += 0.1;
reScale();
}
private void decreaseZoom () {
scale -= 0.1;
reScale();
}
private void reScale(){
for(Path path : undoStack){
path.setScaleX(scale);
path.setScaleY(scale);
}
}
private void undo() {
if(! undoStack.isEmpty()){
Node node = undoStack.pop();
group.getChildren().remove(node);
}
}
}
I solved the problem by extending the Canvas component and adding a second canvas in the extended class to act as a copy of the main canvas.
Every time I made a change in the canvas I do the same change in this "carbon" canvas. When I need to re-scale the canvas to get the snapshot (the root of my problem) I just re-scale the "carbon" canvas back to 1 and get my snapshot from it. This doesn't cause the drag of the mouse in the main canvas, as it remains scaled during this process. Probably this isn't the optimal solution, but it works.
Below is the code for reference, to anyone who may have a similar problem in the future.
ExtendedCanvas.java
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import java.util.Stack;
public class ExtendedCanvas extends Canvas {
private final double ZOOM_SCALE = 0.1;
private final double MAX_ZOOM_SCALE = 3.0;
private final double MIN_ZOOM_SCALE = 0.2;
private double currentScale;
private final Stack<Image> undoStack;
private final Stack<Image> redoStack;
private final Canvas carbonCanvas;
private final GraphicsContext gc;
private final GraphicsContext carbonGc;
public ExtendedCanvas(double width, double height){
super(width, height);
carbonCanvas = new Canvas(width, height);
undoStack = new Stack<>();
redoStack = new Stack<>();
currentScale = 1.0;
gc = this.getGraphicsContext2D();
carbonGc = carbonCanvas.getGraphicsContext2D();
setEventHandlers();
}
private void setEventHandlers() {
this.setOnMousePressed(mouseEvent -> {
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.beginPath();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
});
this.setOnMouseDragged(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.stroke();
});
this.setOnMouseReleased(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
gc.closePath();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.stroke();
carbonGc.closePath();
});
}
public void zoomIn() {
if (currentScale < MAX_ZOOM_SCALE ) {
currentScale += ZOOM_SCALE;
setScale(currentScale);
}
}
public void zoomOut() {
if (currentScale > MIN_ZOOM_SCALE) {
currentScale -= ZOOM_SCALE;
setScale(currentScale);
}
}
public void zoomNormal() {
currentScale = 1.0;
setScale(currentScale);
}
private void setScale(double value) {
this.setScaleX(value);
this.setScaleY(value);
carbonCanvas.setScaleX(value);
carbonCanvas.setScaleY(value);
}
private void pushUndo() {
redoStack.clear();
undoStack.push(getSnapshot());
}
private Image getSnapshot(){
carbonCanvas.setScaleX(1);
carbonCanvas.setScaleY(1);
Image snapshot = carbonCanvas.snapshot(null, null);
carbonCanvas.setScaleX(currentScale);
carbonCanvas.setScaleY(currentScale);
return snapshot;
}
public void undo() {
if (hasUndo()) {
Image redo = getSnapshot();
redoStack.push(redo);
Image undoImage = undoStack.pop();
gc.drawImage(undoImage, 0, 0);
carbonGc.drawImage(undoImage, 0, 0);
}
}
public void redo() {
if (hasRedo()) {
Image undo = getSnapshot();
undoStack.push(undo);
Image redoImage = redoStack.pop();
gc.drawImage(redoImage, 0, 0);
carbonGc.drawImage(redoImage, 0, 0);
}
}
public boolean hasUndo() {
return !undoStack.isEmpty();
}
public boolean hasRedo() {
return !redoStack.isEmpty();
}
}
Main.java
package com.felipepaschoal;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
ExtendedCanvas extendedCanvas;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
BorderPane borderPane = new BorderPane();
HBox hbox = new HBox(4);
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> extendedCanvas.undo());
Button btnRedo = new Button("Redo");
btnRedo.setOnAction(actionEvent -> extendedCanvas.redo());
Button btnDecreaseZoom = new Button("-");
btnDecreaseZoom.setOnAction(actionEvent -> extendedCanvas.zoomOut());
Button btnResetZoom = new Button("Reset");
btnResetZoom.setOnAction(event -> extendedCanvas.zoomNormal());
Button btnIncreaseZoom = new Button("+");
btnIncreaseZoom.setOnAction(actionEvent -> extendedCanvas.zoomIn());
hbox.getChildren().addAll(
btnUndo,
btnRedo,
btnDecreaseZoom,
btnResetZoom,
btnIncreaseZoom
);
ScrollPane scrollPane = new ScrollPane();
Group group = new Group();
extendedCanvas = new ExtendedCanvas(300,200);
group.getChildren().add(extendedCanvas);
scrollPane.setContent(group);
borderPane.setTop(hbox);
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 600, 400);
stage.setScene(scene);
stage.show();
}
}
So im trying to stop a single thread when I have multiple threads running, here is the code im using to initialise the threads. Basically I have multiple textFields in javafx, and when a button is clicked on the screen, it fills the textFields, one by one, with an incrementing timer. Now I also have a button for each of the textfields to clear it, but the problem is when I clear it, because the thread is still running, the timer vanishes for a second and comes back because of the line 'orderTimes.get(boxNo).setText(minute + second);' in the code.
Now what I've tried is creating a list of threads and I've tried implementing it below but it doesn't work, this is so I can call each individual thread if its button to clear has been clicked.
Does anyone know how I can close/stop only one single thread out of multiple that are running? If more info is needed just let me know, thanks.
public static void createIncrementingTimer(int boxNo, List<TextField> orderTimes) {
minutesList.set(boxNo, 0);
secondsList.set(boxNo, 0);
state = true;
new Thread(threadList.get(boxNo)) {
int currentMinutes = 0;
int currentSeconds = 0;
public void run() {
for (;;) {
if (state = true) {
try {
sleep(1000);
if (secondsList.get(boxNo) > 59) {
secondsList.set(boxNo, 0);
currentSeconds = 0;
minutesList.set(boxNo, currentMinutes + 1);
currentMinutes++;
}
if (secondsList.get(boxNo) < 10) {
second = ":0" + Integer.toString(secondsList.get(boxNo));
} else {
second = ":" + Integer.toString(secondsList.get(boxNo));
}
secondsList.set(boxNo, currentSeconds + 1);
currentSeconds++;
if (minutesList.get(boxNo) < 10) {
minute = "0" + Integer.toString(minutesList.get(boxNo));
} else {
minute = Integer.toString(minutesList.get(boxNo));
}
orderTimes.get(boxNo).setText(minute + second);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
threadList.get(boxNo).start();
}
The code I'm using to clear the textfields is below, with orderTimes being the list of textFields that I'm trying to clear.
public static void eraseBox(int clickedButtonNumber, List<TextArea> orderContentsList, List<TextField> tableNumbers, List<TextField> orderNumbers, List<TextField> orderTimes) {
orderContentsList.get(clickedButtonNumber).setText(null);
tableNumbers.get(clickedButtonNumber).clear();
orderNumbers.get(clickedButtonNumber).clear();
orderTimes.get(clickedButtonNumber).clear();
}
I would suggest you try to avoid Threads. The Animation API is designed to make doing work that would normally be done in a Thread easier. In this example, the IncrementingTimer class consists of two Labels and three Buttons. The Labels are used to show the time. The Buttons are used to control the Timeline. The Timeline is used to increment the Labels value each second or every sixty seconds. I have added three IncrementingTimers to the app.
Main
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* JavaFX App
*/
public class App extends Application {
#Override
public void start(Stage stage) {
var scene = new Scene(new VBox(new IncrementingTimer(), new IncrementingTimer(), new IncrementingTimer()), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
IncrementingTimer
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.util.Duration;
/**
*
* #author blj0011
*/
final public class IncrementingTimer extends HBox
{
IntegerProperty secondsCounter = new SimpleIntegerProperty();//Keeps up with seconds
IntegerProperty minutesCounter = new SimpleIntegerProperty();//Keeps up with minutes
Label lblSeconds = new Label();//Displays the seconds
Label lblMinutes = new Label();//Displays the minutes
Label lblColon = new Label(":");//Display the colon between minutes and seconds
Button btnPlay = new Button("Play");//Plays the Timeline
Button btnStop = new Button("Stop");//Stops the Timeline
Button btnPause = new Button("Pause");//Pauses the Timeline
Timeline timeline;//Used to run code that changes the Labels. This Timeline runs every one second.
public IncrementingTimer()
{
lblSeconds.textProperty().bind(secondsCounter.asString("%02d"));//Binds the seconds label to the seconds counter. Sets the String to always show two digits. Exmaple 1 is shown as 01.
lblMinutes.textProperty().bind(minutesCounter.asString("%02d"));//Binds the minutes label to the minutes counter. Sets the String to always show two digits. Exmaple 1 is shown as 01.
getChildren().addAll(lblMinutes, lblColon, lblSeconds, btnPlay, btnStop, btnPause);
timeline = new Timeline(new KeyFrame(Duration.seconds(1), (event) -> {//Replace the one with .016 to speed this up for testing purposes.
secondsCounter.set(secondsCounter.get() + 1);
if (secondsCounter.get() == 60) {
secondsCounter.set(0);
minutesCounter.set(minutesCounter.get() + 1);
if (minutesCounter.get() == 60) {
minutesCounter.set(0);
}
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
btnPlay.setOnAction((event) -> {
timeline.play();
});
btnPause.setOnAction((event) -> {
timeline.pause();
});
btnStop.setOnAction((event) -> {
timeline.stop();
secondsCounter.set(0);
minutesCounter.set(0);
});
this.setAlignment(Pos.CENTER);
}
}
As recommended and demonstrated by Sedric, use JavaFx Animation tools for the counters.
The following one-file mre demonstrating implementation of counters using two different animation tools.
One uses PauseTransition and uses Timeline, each with its stop button.
(copy-paste the entire code into Timers.java and run)
import java.io.IOException;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.PauseTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Timers extends Application {
#Override public void start(final Stage stage) throws IOException {
VBox root = new VBox(new CounterPane(new TimeLineCounter()), new CounterPane(new PauseTransitionCounter()));
stage.setScene(new Scene(root));
stage.show();
}
public static void main(final String[] args) { launch(args); }
}
class CounterPane extends HBox{
private final Counter counter;
CounterPane(Counter counter) {
super(5);
this.counter = counter; //todo: check not null
Button stopBtn = new Button("Stop");
stopBtn.setOnAction(e->stop());
getChildren().addAll(stopBtn, counter);
}
void stop(){
counter.getAnimation().stop();
}
}
abstract class Counter extends Label {
protected int count = 0;
public Counter() {
setAlignment(Pos.CENTER); setPrefSize(25, 25);
count();
}
abstract void count();
abstract Animation getAnimation();
}
class TimeLineCounter extends Counter {
private Timeline timeline;
#Override
void count() {
timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
final KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> { setText(String.valueOf(count++) ); }
);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}
#Override
Animation getAnimation() {
return timeline;
}
}
class PauseTransitionCounter extends Counter {
private PauseTransition pauseTransition;
#Override
void count() {
pauseTransition = new PauseTransition(Duration.seconds(1));
pauseTransition.setOnFinished(event ->{
setText(String.valueOf(count++) );
pauseTransition.play();
});
pauseTransition.play();
}
#Override
Animation getAnimation() {
return pauseTransition;
}
}
The if(state=true) should rather be if(state==true) or just if(state), but in fact the for(;;) could do the entire thing as while(state), simply shutting down the thread when you set state=false.
Then, fully stopping the thread could happen as state=false;threadList.get(boxNo).join();, and you can clear the field only after that (since the thread will set it to something in the last step too).
With a simpler approach you could throw away the state, and revert to for(;;), with the twist of having the try-catch() around the loop, outside. This way you can use threadList.get(boxNo).interrupt();threadList.get(boxNo);.join(); to stop the thread, and on top of that it will be immediate, as the sleep() ends immediately when the thread is interrupted.
I'm using two WebViews to display two versions of HTML formatted text for comparison. The two display the same amount of text (same number of lines and corresponding lines have always the same length).
When the displayed text exceeds the size of the node, the WebView gets scroll bars. Of course I want these scroll bars to scroll synchronously so that always the corresponding text is displayed.
In order to supply a minimal, complete and verifiable example, I trimmed the code down to this:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.GridPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class SynchronizedWebViewsTest extends Application {
protected class DifferencePanel extends GridPane {
private WebView actualPane;
private WebView expectedPane;
public DifferencePanel() {
setPadding(new Insets(20, 20, 20, 20));
actualPane = new WebView();
expectedPane = new WebView();
setResultPanes();
addRow(0, actualPane, expectedPane);
}
public void setHtml(WebView webView) {
Platform.runLater(() -> {
webView.getEngine().loadContent(createHtml());
});
}
public void synchronizeScrolls() {
final ScrollBar actualScrollBarV = (ScrollBar)actualPane.lookup(".scroll-bar:vertical");
final ScrollBar expectedScrollBarV = (ScrollBar)expectedPane.lookup(".scroll-bar:vertical");
actualScrollBarV.valueProperty().bindBidirectional(expectedScrollBarV.valueProperty());
final ScrollBar actualScrollBarH = (ScrollBar)actualPane.lookup(".scroll-bar:horizontal");
final ScrollBar expectedScrollBarH = (ScrollBar)expectedPane.lookup(".scroll-bar:horizontal");
actualScrollBarH.valueProperty().bindBidirectional(expectedScrollBarH.valueProperty());
}
private String createHtml() {
StringBuilder sb = new StringBuilder(1000000);
for (int i = 0; i < 100; i++) {
sb.append(String.format("<nobr>%03d %2$s%2$s%2$s%2$s%2$s%2$s%2$s%2$s</nobr><br/>\n",
Integer.valueOf(i), "Lorem ipsum dolor sit amet "));
}
return sb.toString();
}
private void setResultPanes() {
setHtml(actualPane);
setHtml(expectedPane);
}
} // ---------------------------- end of DifferencePanel ----------------------------
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage dummy) throws Exception {
Stage stage = new Stage();
stage.setTitle(this.getClass().getSimpleName());
DifferencePanel differencePanel = new DifferencePanel();
Scene scene = new Scene(differencePanel);
stage.setScene(scene);
differencePanel.synchronizeScrolls();
stage.showAndWait();
}
}
I tried using adding a listener:
actualScrollBarV.onScrollFinishedProperty().addListener(event -> {
System.out.println(event);
});
But the listener is never invoked.
I'm using Java version 1.8.0_92, but with version 9.0.4 I get the same result.
Can anybody tell me, what I'm missing here?
I would post a comment, but sadly I did not have enough reputation.
Did you tried the following solution? Create listeners on value changed event, instead of binding. Synchronizing two scroll bars JavaFX
I could not get the ScrollBar approach working. It turned out that the listeners were actually invoked (breakpoints in lambdas are not always working?). Setting the scroll bar value of the other WebView did not get it inclined change the scroll bar or the view port. :-(
There is something strange going on with events in WebView; that might be because there is a native library involved...
However, the approach using the event handler of WebView works. The event handler of each WebView simply mirrors all events to the other WebView, using a synchronizing field Boolean scrolling to avoid recursion.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class SynchronizedWebViewsTest extends Application {
protected class DifferencePanel extends GridPane {
private Boolean scrolling = Boolean.FALSE;
private WebView actualPane;
private WebView expectedPane;
public DifferencePanel() {
setPadding(new Insets(20, 20, 20, 20));
actualPane = new WebView();
expectedPane = new WebView();
setResultPanes();
addRow(0, actualPane, expectedPane);
}
public void setHtml(WebView webView) {
Platform.runLater(() -> {
webView.getEngine().loadContent(createHtml());
});
}
public void synchronizeScrolls() {
wireViews(actualPane, expectedPane);
wireViews(expectedPane, actualPane);
}
private void wireViews(WebView webView, WebView otherWebView) {
webView.addEventHandler(Event.ANY, event -> {
if (!scrolling.booleanValue()) {
synchronized (scrolling) {
scrolling = Boolean.TRUE;
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
Point2D origin = webView.localToScreen(0, 0);
Point2D otherOrigin = otherWebView.localToScreen(0, 0);
double offsetX = otherOrigin.getX() - origin.getX();
double offsetY = otherOrigin.getY() - origin.getY();
double x = mouseEvent.getX();
double y = mouseEvent.getY();
double screenX = mouseEvent.getScreenX() + offsetX;
double screenY = mouseEvent.getScreenY() + offsetY;
MouseButton button = mouseEvent.getButton();
int clickCount = mouseEvent.getClickCount();
boolean shiftDown = mouseEvent.isShiftDown();
boolean controlDown = mouseEvent.isControlDown();
boolean altDown = mouseEvent.isAltDown();
boolean metaDown = mouseEvent.isMetaDown();
boolean primaryButtonDown = mouseEvent.isPrimaryButtonDown();
boolean middleButtonDown = mouseEvent.isMiddleButtonDown();
boolean secondaryButtonDown = mouseEvent.isSecondaryButtonDown();
boolean synthesized = mouseEvent.isSynthesized();
boolean popupTrigger = mouseEvent.isPopupTrigger();
boolean stillSincePress = mouseEvent.isStillSincePress();
MouseEvent otherMouseEvent =
new MouseEvent(otherWebView, otherWebView, mouseEvent.getEventType(), x, y, screenX,
screenY, button, clickCount, shiftDown, controlDown, altDown, metaDown,
primaryButtonDown, middleButtonDown, secondaryButtonDown, synthesized,
popupTrigger, stillSincePress, null);
otherWebView.fireEvent(otherMouseEvent);
}
else {
otherWebView.fireEvent(event.copyFor(otherWebView, otherWebView));
}
scrolling = Boolean.FALSE;
}
}
});
}
private String createHtml() {
StringBuilder sb = new StringBuilder(1000000);
for (int i = 0; i < 100; i++) {
sb.append(String.format("<nobr>%03d %2$s%2$s%2$s%2$s%2$s%2$s%2$s%2$s</nobr><br/>\n",
Integer.valueOf(i), "Lorem ipsum dolor sit amet "));
}
return sb.toString();
}
private void setResultPanes() {
setHtml(actualPane);
setHtml(expectedPane);
}
}
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage dummy) throws Exception {
Stage stage = new Stage();
stage.setTitle(this.getClass().getSimpleName());
DifferencePanel differencePanel = new DifferencePanel();
Scene scene = new Scene(differencePanel);
stage.setScene(scene);
differencePanel.synchronizeScrolls();
stage.showAndWait();
}
}
This works for all input methods I'm interested in:
Keyboard: PageUp, PageDown, all 4 arrow keys, "space bar" (same as PageDown) and shift-"space bar" (same as PageUp), Home and End
Mouse wheel: RollDown and RollUp as well as shift-RollUp (scroll left) and shift-RollDown (scroll right)
Using the mouse to click or to drag the scroll bar.
Using the mouse to select text outside of the current view port.
Mirroring the mouse events has the added benefit that text gets selected in both WebViews.
I'm writing music player and I don't know how to code slider dragging handler to set value after user frees mouse button. When I write simple MouseDragged method dragging brings non estetic "rewinding" sound because mediaplayer changes value every time slider moves. While playing slider automatic changes value by mediaplayer listener to synchronize with track duration. This is what I got so far.
ChangeListener<Duration> timeListener = new ChangeListener<Duration>() {
#Override
public void changed(
ObservableValue<? extends Duration> observableValue,
Duration duration,
Duration current) {
durSlider
.setValue(current
.toSeconds());
}
};
durSlider.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
mediaPlayer.seek(Duration.seconds(durSlider.getValue()));
}
});
The valueChanging property of the slider indicates if the slider is in the process of being changed. It is an observable property, so you can attach a listener directly to it, and respond when the value stops changing:
durSlider.valueChangingProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs, Boolean wasChanging, Boolean isNowChanging) {
if (! isNowChanging) {
mediaPlayer.seek(Duration.seconds(durSlider.getValue()));
}
}
});
This won't change the position of the player if the user clicks on the "track" on the slider, or uses the keyboard to move it. For that, you can register a listener with the value property. You need to be careful here, because the value is also going to change via your time listener. In theory, the time listener should set the value of the slider, and then that should cause an attempt to set the current time of the player to the exact value it already has (which would result in a no-op). However, rounding errors will likely result in a lot of small adjustments, causing the "static" you are observing. To fix this, only move the media player if the change is more than some small minimum amount:
private static double MIN_CHANGE = 0.5 ; //seconds
// ...
durSlider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> obs, Number oldValue, Number newValue) {
if (! durSlider.isValueChanging()) {
double currentTime = mediaPlayer.getCurrentTime().toSeconds();
double sliderTime = newValue.doubleValue();
if (Math.abs(currentTime - sliderTime) > 0.5) {
mediaPlayer.seek(newValue.doubleValue());
}
}
}
});
Finally, you don't want your time listener to move the slider if the user is trying to drag it:
ChangeListener<Duration> timeListener = new ChangeListener<Duration>() {
#Override
public void changed(
ObservableValue<? extends Duration> observableValue,
Duration duration,
Duration current) {
if (! durSlider.isValueChanging()) {
durSlider.setValue(current.toSeconds());
}
}
};
Here's a complete example (using lambdas for brevity):
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
import javafx.util.Duration;
public class VideoPlayerTest extends Application {
private static final String MEDIA_URL =
"http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv";
private static final double MIN_CHANGE = 0.5 ;
#Override
public void start(Stage primaryStage) {
MediaPlayer player = new MediaPlayer(new Media(MEDIA_URL));
MediaView mediaView = new MediaView(player);
Slider slider = new Slider();
player.totalDurationProperty().addListener((obs, oldDuration, newDuration) -> slider.setMax(newDuration.toSeconds()));
BorderPane root = new BorderPane(mediaView, null, null, slider, null);
slider.valueChangingProperty().addListener((obs, wasChanging, isChanging) -> {
if (! isChanging) {
player.seek(Duration.seconds(slider.getValue()));
}
});
slider.valueProperty().addListener((obs, oldValue, newValue) -> {
if (! slider.isValueChanging()) {
double currentTime = player.getCurrentTime().toSeconds();
if (Math.abs(currentTime - newValue.doubleValue()) > MIN_CHANGE) {
player.seek(Duration.seconds(newValue.doubleValue()));
}
}
});
player.currentTimeProperty().addListener((obs, oldTime, newTime) -> {
if (! slider.isValueChanging()) {
slider.setValue(newTime.toSeconds());
}
});
Scene scene = new Scene(root, 540, 280);
primaryStage.setScene(scene);
primaryStage.show();
player.play();
}
public static void main(String[] args) {
launch(args);
}
}