Hello I have app with alert and after no response from user I want to refresh alert/show it again and for some reason I don't see second alert it's empty like:
I am closing alert if it's showing so I don't know why next alert is empty.
package application;
//imports
public class Main extends Application{
int number = 50;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
alertMethod();
}
private void alertMethod() {
number += number + 10;
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Title");
alert.setHeaderText("Number " + number);
alert.setContentText("Choose your option.");
ButtonType buttonTypeOne = new ButtonType("Yes");
ButtonType buttonTypeCancel = new ButtonType("No", ButtonBar.ButtonData.CANCEL_CLOSE);
alert.getButtonTypes().setAll(buttonTypeOne, buttonTypeCancel);
Thread newThread = new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
Platform.runLater(new Runnable() {
#Override
public void run() {
if(alert.isShowing()) {
alert.close();
}
alertMethod();
}
});
}
});
newThread.start();
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == buttonTypeOne){
System.out.println("Pressed Yes");
} else if (result.get() == buttonTypeCancel) {
System.out.println("Pressed No");
}
}
}
This is just a guess at what I think you want. Your problem is not very clear.
This app uses #Sergey Grinev code from here.
It updates the Alert every two seconds.
//imports
import java.util.Optional;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application
{
int number = 50;
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
alertMethod();
}
private void alertMethod()
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Title");
alert.setHeaderText("Number " + number);
alert.setContentText("Choose your option.");
ButtonType buttonTypeOne = new ButtonType("Yes");
ButtonType buttonTypeCancel = new ButtonType("No", ButtonBar.ButtonData.CANCEL_CLOSE);
alert.getButtonTypes().setAll(buttonTypeOne, buttonTypeCancel);
Timeline twoSecondsWonder = new Timeline(new KeyFrame(Duration.seconds(2), new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
number = number + 10;
alert.setAlertType(Alert.AlertType.CONFIRMATION);
alert.setTitle("Title -> " + number);
alert.setHeaderText("Number " + number);
alert.setContentText("Choose your option.");
}
}));
twoSecondsWonder.setCycleCount(Timeline.INDEFINITE);
twoSecondsWonder.play();
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == buttonTypeOne) {
System.out.println("Pressed Yes");
}
else if (result.get() == buttonTypeCancel) {
System.out.println("Pressed No");
}
}
}
Related
I am trying to implement busy indicator using ProgressIndicator. But when the heavy load starts the indicator freezes. A sample code is shown below.
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class BusyIcon {
private static Stage busyWindow;
public static void showBusyIcon(final Stage stage) {
busyWindow = new Stage(StageStyle.UNDECORATED);
//busyWindow.setOpacity(.3);
busyWindow.initOwner(stage);
busyWindow.initModality(Modality.WINDOW_MODAL);
StackPane stackPane = new StackPane();
final ProgressIndicator loadingIndicator = new ProgressIndicator();
loadingIndicator.setVisible(true);
stackPane.getChildren().add(loadingIndicator);
Scene scene = new Scene(stackPane, 100, 100);
scene.setFill(Color.TRANSPARENT);
busyWindow.setScene(scene);
ChangeListener<Number> widthListener = (observable, oldValue, newValue) -> {
double stageWidth = newValue.doubleValue();
busyWindow.setX(stage.getX() + stage.getWidth() / 2 - stageWidth / 2);
};
ChangeListener<Number> heightListener = (observable, oldValue, newValue) -> {
double stageHeight = newValue.doubleValue();
busyWindow.setY(stage.getY() + stage.getHeight() / 2 - stageHeight / 2);
};
busyWindow.widthProperty().addListener(widthListener);
busyWindow.heightProperty().addListener(heightListener);
busyWindow.setOnShown(e -> {
busyWindow.widthProperty().removeListener(widthListener);
busyWindow.heightProperty().removeListener(heightListener);
});
busyWindow.show();
}
public static void closeBusyIcon(final Stage stage) {
if (busyWindow != null) {
busyWindow.close();
busyWindow = null;
}
}
}
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import preloader.BusyIcon;
public class QuestionExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Task Progress Tester");
StackPane testPane = new StackPane();
Button b = new Button("Load");
b.setOnAction((event) -> {
BusyIcon.showBusyIcon(primaryStage);
Task t = new Task() {
#Override
protected Object call() throws Exception {
try {
addNewComponent(testPane);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
};
t.setOnSucceeded((ev) -> {
BusyIcon.closeBusyIcon(primaryStage);
});
new Thread(t).start();
});
testPane.getChildren().add(b);
primaryStage.setScene(new Scene(testPane, 300, 250));
primaryStage.show();
}
private void addNewComponent(Pane testPane) {
try {
/**
* Some heavy load work will run here
*/
Thread.sleep(2000);
Platform.runLater(() -> {
try {
/**
* We need to change the fx controls here
*/
Button b1 = new Button("New Component");
testPane.getChildren().add(b1);
/**
* This may take some time
*/
Thread.sleep(2000);
} catch (Exception ex) {
ex.printStackTrace();
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
BusyIcon is used for showing progress indicator. If we are not using the Platform.runLater then it will throw 'Not in FX thread' exception will be thrown.
I suggest you try ControlsFX MaskerPane. The key is to set the MaskerPane visible and move it to the front of an AnchorPane before the task runs. When the task finishes, set it invisible and move it to the back of the AnchorPane.
DEMO:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.MaskerPane;
/**
*
* #author blj0011
*/
public class MaskerPaneTest extends Application
{
#Override
public void start(Stage primaryStage)
{
MaskerPane mpDeterminate = new MaskerPane();
MaskerPane mpUndeterminate = new MaskerPane();
mpDeterminate.setVisible(false);
mpUndeterminate.setVisible(false);
Button btn = new Button();
btn.setText("Determinate");
btn.setOnAction((ActionEvent event) -> {
mpDeterminate.setVisible(true);
mpDeterminate.toFront();
Task<Void> task = new Task<Void>()
{
#Override
protected Void call() throws Exception
{
for (int i = 0; i < 40000000; i++) {
//Do something
updateProgress(i, 40000000);
}
return null;
}
};
mpDeterminate.progressProperty().bind(task.progressProperty());
task.setOnSucceeded((workerStateEvent) -> {
mpDeterminate.setVisible(false);
mpDeterminate.toBack();
});
new Thread(task).start();
});
Button btn2 = new Button();
btn2.setText("Undeterminate");
btn2.setOnAction((ActionEvent event) -> {
mpUndeterminate.setVisible(true);
mpUndeterminate.toFront();
Task<Void> task = new Task<Void>()
{
#Override
protected Void call() throws Exception
{
for (int i = 0; i < 100000; i++) {
//Do something
System.out.println("working!");
}
return null;
}
};
mpUndeterminate.progressProperty().bind(task.progressProperty());
task.setOnSucceeded((workerStateEvent) -> {
mpUndeterminate.setVisible(false);
mpUndeterminate.toBack();
});
new Thread(task).start();
});
StackPane root = new StackPane(mpDeterminate, mpUndeterminate, new VBox(btn, btn2));
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
I have this example of Pie chart data:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.PieChart.Data;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.effect.Glow;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class MainApp extends Application
{
Stage stage;
PieChart chart;
ObservableList<Data> pieChartData = FXCollections.observableArrayList();
Label caption;
#Override
public void start(Stage stage)
{
this.stage = stage;
setUserAgentStylesheet(STYLESHEET_CASPIAN);
Scene scene = new Scene(new Group());
stage.setTitle("Imported Fruits");
stage.setWidth(500);
stage.setHeight(500);
chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
// Add some data
addPieChartData("Grapefruit", 13);
addPieChartData("Oranges", 25);
addPieChartData("Plums", 10);
addPieChartData("Pears", 22);
addPieChartData("Apples", 30);
// Some task which updates the Pie Chart
final Task task;
task = new Task<Void>()
{
#Override
protected Void call() throws Exception
{
int max = 50;
int l = 0;
for (int i = 1; i <= max; i++)
{
updatePieChartData("Grapefruit", l++);
updatePieChartData("Oranges", l++);
Thread.sleep(600);
}
return null;
}
};
new Thread(task).start();
((Group) scene.getRoot()).getChildren().addAll(chart, caption);
stage.setScene(scene);
stage.show();
}
public void addPieChartData(String name, double value)
{
pieChartData.add(new Data(name, value));
caption = new Label();
caption.setTextFill(Color.DARKORANGE);
caption.setStyle("-fx-font: 24 arial;");
for (final Data data : chart.getData())
{
Node node = data.getNode();
node.addEventHandler(MouseEvent.MOUSE_MOVED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
caption.setTranslateX(e.getSceneX() + 15);
caption.setTranslateY(e.getSceneY());
caption.setText(String.valueOf(data.getPieValue()) + "%");
caption.setVisible(true);
node.setEffect(new Glow());
//String styleString = "-fx-border-color: white; -fx-border-width: 1; -fx-border-style: dashed;";
//node.setStyle(styleString);
}
});
node.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
caption.setVisible(false);
node.setEffect(null);
//node.setStyle("");
}
});
final MenuItem resizeItem = new MenuItem("Resize");
resizeItem.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
System.out.println("Resize requested");
}
});
final MenuItem aboutItem = new MenuItem("About");
aboutItem.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
System.out.println("About requested");
}
});
final MenuItem changeColorItem = new MenuItem("Change Color");
changeColorItem.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
System.out.println("change Color Item requested");
}
});
final ContextMenu menu = new ContextMenu(resizeItem, aboutItem, changeColorItem);
node.setOnMouseClicked(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
if (MouseButton.SECONDARY.equals(event.getButton()))
{
menu.show(stage, event.getScreenX(), event.getScreenY());
}
}
});
}
}
// updates existing Data-Object if name matches
public void updatePieChartData(String name, double value)
{
for (Data d : pieChartData)
{
if (d.getName().equals(name))
{
d.setPieValue(value);
return;
}
}
addPieChartData(name, value);
}
public static void main(String[] args)
{
launch(args);
}
}
Is there any way similar to tickLabelFormatter in Line chart to display additional values in Pie chart names?
I want to display each Pie slice with names and number.
Just bind the pie chart data name to the required value.
pieChartData.forEach(data ->
data.nameProperty().bind(
Bindings.concat(
data.getName(), " ", data.pieValueProperty(), " Tons"
)
)
);
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.chart.*;
import javafx.scene.Group;
public class PieChartSample extends Application {
#Override public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Imported Fruits");
stage.setWidth(500);
stage.setHeight(500);
ObservableList<PieChart.Data> pieChartData =
FXCollections.observableArrayList(
new PieChart.Data("Grapefruit", 13),
new PieChart.Data("Oranges", 25),
new PieChart.Data("Plums", 10),
new PieChart.Data("Pears", 22),
new PieChart.Data("Apples", 30));
final PieChart chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
pieChartData.forEach(data ->
data.nameProperty().bind(
Bindings.concat(
data.getName(), " ", data.pieValueProperty(), " Tons"
)
)
);
((Group) scene.getRoot()).getChildren().add(chart);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you have changing values (like in the example code) and/or want to avoid displaying the values in the chart legend, you could modify the text label instead of changing the data names.
Each data item has a text node that holds the string which is shown as the pie chart label. This string can be updated before the chart children are drawn by overriding PieChart#layoutChartChildren:
chart = new PieChart(pieChartData) {
#Override
protected void layoutChartChildren(double top, double left, double contentWidth, double contentHeight) {
if (getLabelsVisible()) {
getData().forEach(d -> {
Optional<Node> opTextNode = chart.lookupAll(".chart-pie-label").stream().filter(n -> n instanceof Text && ((Text) n).getText().contains(d.getName())).findAny();
if (opTextNode.isPresent()) {
((Text) opTextNode.get()).setText(d.getName() + " " + d.getPieValue() + " Tons");
}
});
}
super.layoutChartChildren(top, left, contentWidth, contentHeight);
}
};
I have two circles redCircle and greenCircle : -
Circle greenCircle = new Circle(250,150 ,100, Color.TRANSPARENT);
Circle redCircle = new Circle(250,450,100,Color.TRANSPARENT);
greenCircle.setStroke(Color.GREEN);
greenCircle.setStrokeWidth(4);
group.getChildren().add(greenCircle);
redCircle.setStroke(Color.RED);
redCircle.setStrokeWidth(4);
group.getChildren().add(redCircle);
Basically I want Circle to turn on and off twice in 2 seconds. So I am able to turn on Light then wait for 0.5 second , turn off and again wait for 0.5 second and turn on Light. I am not about to turn off after 0.5 second.
public class LightOn {
public Task<Void> runLightOn() throws InterruptedException {
return new Task<Void>(){
#Override
protected Void call() throws Exception {
greenCircle.setFill(Color.GREEN);
return null;
}
};
}
}
public class LightOff {
public void perform() throws InterruptedException {
LightOn onL = new LightOn();
Task<Void> runLinghtOnTask = onL.runLightOn();
runLinghtOnTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(MovementEventsDemo.class.getName()).log(Level.SEVERE, null, ex);
}
greenCircle.setFill(Color.TRANSPARENT);
nextFunction();
}
});
new Thread(runLinghtOnTask).start();
}
}
public void nextFunction(){
Task<Void> sleeper2 = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
return null;
}
};
sleeper2.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
if(greenCircle.getFill()==Color.GREEN) {
greenCircle.setFill(Color.TRANSPARENT);
}else {
greenCircle.setFill(Color.GREEN);
}
}
});
new Thread(sleeper2).start();
}
I use this to execute : -
LightOff lf = new LightOff();
lf.perform();
The reason your code is not working is that an exception is being thrown in the call() method of the Task returned by runLightOn(), because you are changing the UI from a background thread. If you catch the exception or register an onFailed handler with the Tasks you will be able to log the exception.
For functionality like this, where you have specific timepoints (KeyFrames) at which you want values to change, use a Timeline instead of messing with multithreading:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FlashingLight extends Application {
#Override
public void start(Stage primaryStage) {
Circle circle = new Circle(250, 150, 100, Color.TRANSPARENT);
circle.setStroke(Color.GREEN);
circle.setStrokeWidth(4);
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), e -> circle.setFill(Color.GREEN)),
new KeyFrame(Duration.seconds(1.0), e -> circle.setFill(Color.TRANSPARENT))
);
timeline.setCycleCount(2);
Pane pane = new Pane(circle);
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
As a slight alternative, you can replace
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), e -> circle.setFill(Color.GREEN)),
new KeyFrame(Duration.seconds(1.0), e -> circle.setFill(Color.TRANSPARENT))
);
with
BooleanProperty on = new SimpleBooleanProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0.5), new KeyValue(on, true)),
new KeyFrame(Duration.seconds(1.0), new KeyValue(on, false))
);
circle.fillProperty().bind(
Bindings.when(on)
.then(Color.GREEN)
.otherwise(Color.TRANSPARENT));
The added benefits here are that you create a boolean value representing whether or not the light is on, which may be useful elsewhere in your logic, and that you separate the logic (on/off, and the timing) from the display (color, etc).
I have a ListView with my own ListCell<MyObject> implementation. Via a network signal, I receive an index of my ListCell that should be changed.
Over listView.getItems().get(index); there is no problem to access the model, but I want to make a layout change to the listCell with the received index and a layout change to the ListCell with the index+1;
How can I access the ListCell via the ListView?
I search for a method like this:
listView.getListCell(index);
Unfortunately right now there is no API to get List Cell by index or to get All children's(listcells) for ListView. One solution can be, define a new StringProperty specialIndicator in your MyObject class.
class MyObject {
....//u r properties
private StringProperty specialIndicator;
When ever you get index from network signal set this specialIndicator property of object and do forcerefresh of ListView
public void onReceivedNetWorkSignalIndex() {
listView.getItems().get(indexFromService).setSpecialIndicator("selected");
listView.getItems().get(indexFromService+1).setSpecialIndicator("selectedplusone");
//force refresh listview (it will trigger cellFactory again so that you can manipulate layout)
listView.setItems(null);
listView.setItems(allObjects);
}
As you already have custom Object ListView , i am assuming you already have custom cellFactory (if not you have to create one ) ,Modify your custom cell factory to handle this special Indicators
listView.setCellFactory(new Callback<ListView<MyObject>, ListCell<MyObject>>() {
#Override
public ListCell<MyObject> call(ListView<MyObject> myObjectListView) {
ListCell<MyObject> cell = new ListCell<MyObject>(){
#Override
protected void updateItem(MyObject myObject, boolean b) {
super.updateItem(myObject, b);
if(myObject != null) {
setText(myObject.getName());
if("selected".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/graphics for index retun from service." + myObject.getName());
} else if("selectedplusone".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/Graphics for index+1 returned from service" + myObject.getName());
}
myObject.setSpecialIndicator(""); // reset it back to empty
}
}
};
return cell;
}
});
Here is the whole sample Application ,you can look into it (in case the above explanation is not clear).
public class ListViewTest extends Application {
#Override
public void start(Stage stage) throws Exception {
VBox root = new VBox();
final ObservableList<MyObject> allObjects = FXCollections.observableArrayList(new MyObject("object0"), new MyObject("object1"),new MyObject("object2"),new MyObject("object3"),new MyObject("object4"));
final ListView<MyObject> listView = new ListView<>(allObjects);
listView.setCellFactory(new Callback<ListView<MyObject>, ListCell<MyObject>>() {
#Override
public ListCell<MyObject> call(ListView<MyObject> myObjectListView) {
ListCell<MyObject> cell = new ListCell<MyObject>(){
#Override
protected void updateItem(MyObject myObject, boolean b) {
super.updateItem(myObject, b);
if(myObject != null) {
setText(myObject.getName());
if("selected".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/graphics for index retun from service." + myObject.getName());
setText("I am selected Index from Service");
} else if("selectedplusone".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/Graphics for index+1 returned from service" + myObject.getName());
setText("I am selected Index +1 from Service");
}
myObject.setSpecialIndicator(""); // reset it back to empty
}
}
};
return cell;
}
});
Button serviceIndex2 = new Button("ServiceIndex2");
serviceIndex2.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
int indexFromService =2;
listView.getItems().get(indexFromService).setSpecialIndicator("selected");
listView.getItems().get(indexFromService+1).setSpecialIndicator("selectedplusone");
listView.setItems(null);
listView.setItems(allObjects);
}
});
root.getChildren().addAll(listView,serviceIndex2);
Scene scene = new Scene(root,500,500);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyObject {
private StringProperty name;
private StringProperty specialIndicator;
MyObject(String name) {
this.name = new SimpleStringProperty(name);
this.specialIndicator = new SimpleStringProperty();
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getSpecialIndicator() {
return specialIndicator.get();
}
public StringProperty specialIndicatorProperty() {
return specialIndicator;
}
public void setSpecialIndicator(String specialIndicator) {
this.specialIndicator.set(specialIndicator);
}
}
}
Here's a relatively simple approach, where there is just one "selected" index. Here I create a property to hold the index that is selected, and the cell factory just observes it, along with the cell's item property and index property, and sets the text via a binding. You could do something similar to set the graphic, if needed.
import java.util.concurrent.Callable;
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.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewStyleAroundSelection extends Application {
#Override
public void start(Stage primaryStage) {
final ListView<String> listView = new ListView<>();
for (int i=1; i<=20; i++) {
listView.getItems().add("Item "+i);
}
final HBox controls = new HBox(5);
final Button button = new Button("Set selection");
final TextField indexField = new TextField();
final IntegerProperty selectionIndex = new SimpleIntegerProperty();
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
selectionIndex.set(Integer.parseInt(indexField.getText()));
} catch (NumberFormatException nfe) {
indexField.setText("");
}
}
});
controls.getChildren().addAll(new Label("Enter selection index:"), indexField, button);
final BorderPane root = new BorderPane();
root.setCenter(listView);
root.setBottom(controls);
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> lv) {
final ListCell<String> cell = new ListCell<>();
cell.textProperty().bind(Bindings.createStringBinding(new Callable<String>() {
#Override
public String call() throws Exception {
if (cell.getItem() == null) {
return null ;
} else {
switch (cell.getIndex() - selectionIndex.get()) {
case -1: return cell.getItem() + " (selected item below)";
case 0: return cell.getItem() + " (selected)";
case 1: return cell.getItem() + " (selected item above)";
default: return cell.getItem();
}
}
}
}, cell.itemProperty(), cell.indexProperty(), selectionIndex));
return cell;
}
});
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
And here's a slightly more complex version. Here I have a custom data type which includes a boolean property. The update sets the boolean property of the specified item to true. The cell factory creates a cell, and observes the selected property both of the current item and of the previous item. Then, as before, it uses a binding to update the text of the cell.
import java.util.concurrent.Callable;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
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.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewStyleAroundSelection extends Application {
#Override
public void start(Stage primaryStage) {
final ListView<MyDataType> listView = new ListView<>();
for (int i=0; i<=20; i++) {
listView.getItems().add(new MyDataType("Item "+i, false));
}
final HBox controls = new HBox(5);
controls.setPadding(new Insets(5));
final Button button = new Button("Set selection");
final TextField indexField = new TextField();
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
int index = Integer.parseInt(indexField.getText());
if (index >= 0 && index < listView.getItems().size()) {
final MyDataType item = listView.getItems().get(index);
item.setSelected( ! item.isSelected() );
}
} catch (NumberFormatException nfe) {
indexField.setText("");
}
}
});
controls.getChildren().addAll(new Label("Enter selection index:"), indexField, button);
final BorderPane root = new BorderPane();
root.setCenter(listView);
root.setBottom(controls);
listView.setCellFactory(new Callback<ListView<MyDataType>, ListCell<MyDataType>>() {
#Override
public ListCell<MyDataType> call(ListView<MyDataType> lv) {
final ListCell<MyDataType> cell = new ListCell<>();
final IntegerBinding previousIndex = cell.indexProperty().subtract(1);
final ObjectBinding<MyDataType> previousItem = Bindings.valueAt(listView.getItems(), previousIndex);
final BooleanBinding previousItemSelected = Bindings.selectBoolean(previousItem, "selected");
final StringBinding thisItemName = Bindings.selectString(cell.itemProperty(), "name");
final BooleanBinding thisItemSelected = Bindings.selectBoolean(cell.itemProperty(), "selected");
cell.textProperty().bind(Bindings.createStringBinding(new Callable<String>() {
#Override
public String call() throws Exception {
if (cell.getItem() == null) {
return null ;
} else {
String value = cell.getItem().getName();
if (thisItemSelected.get()) {
value = value + " (selected) " ;
} else if (previousItemSelected.get()) {
value = value + " (selected item is above)";
}
return value ;
}
}
}, thisItemName, thisItemSelected, previousItemSelected));
return cell;
}
});
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class MyDataType {
private final BooleanProperty selected ;
private final StringProperty name ;
public MyDataType(String name, boolean selected) {
this.name = new SimpleStringProperty(this, "name", name);
this.selected = new SimpleBooleanProperty(this, "selected", selected);
}
public final String getName() {
return name.get();
}
public final void setName(String name) {
this.name.set(name);
}
public final StringProperty nameProperty() {
return name ;
}
public final boolean isSelected() {
return selected.get();
}
public final void setSelected(boolean selected) {
this.selected.set(selected);
}
public final BooleanProperty selectedProperty() {
return selected;
}
}
public static void main(String[] args) {
launch(args);
}
}
Cell has a style class called ".cell"
public Cell getListCell(ListView list, int index){
Object[]cells = list.lookupAll(".cell").toArray();
return (Cell)cells[index];
}
This is the method I used to solve the same problem. Please note that getting the cell view is considered bad practice, and shouldn't be done in a normal context, updating cells should only be done through the model, my special case was that I wanted to fire an event manually as part of a workaround.
private ListCell<?> getListCell(ListView<?> listView, int cellIndex) {
if (cellIndex == -1) {
return null;
}
//Virtual Flow is the container of all list cells
//Each ListView has exactly one VirtualFlow which we are searching for
Optional<VirtualFlow> virtualFlowOptional = listView.getChildrenUnmodifiable()
.stream()
.filter(node -> node instanceof VirtualFlow)
.map(n -> (VirtualFlow) n)
.findFirst();
if (virtualFlowOptional.isEmpty()) {
return null;
}
VirtualFlow<ListCell<?>> virtualFlow = virtualFlowOptional.get();
return virtualFlow.getCell(cellIndex);
}
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);
}
}