How to Create a Custom Control in JavaFX - javafx

Here I created my own control, with a canvas and ScrollBar. I didn't manage to correctly intercept the keyboard shortcuts or key pressed. When adding another component to the scene (here a TextField, commented), no keyboard events are received. Maybe I missed something about the focus?
Also, I had to add some --export to Gradle, to avoid some errors with the module.
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
repositories {
mavenCentral()
}
javafx {
version = "15.0.1"
modules = [ 'javafx.controls', 'javafx.graphics' ]
}
run {
jvmArgs = ['--add-exports=javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED',
'--add-exports=javafx.controls/com.sun.javafx.scene.control.inputmap=ALL-UNNAMED']
}
mainClassName = 'com.wisecoders.textpane.CustomControlApp'
package com.wisecoders.textpane;
import com.sun.javafx.scene.control.behavior.BehaviorBase;
import com.sun.javafx.scene.control.inputmap.InputMap;
import com.sun.javafx.scene.control.inputmap.KeyBinding;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class CustomControlApp extends Application {
private final BorderPane root = new BorderPane();
private final Scene scene = new Scene(root, 300, 250);
#Override
public void start(Stage stage) {
stage.setTitle("Sample Canvas");
root.setTop( new TextField());
MyControl control = new MyControl();
root.setCenter(control);
stage.setScene(scene);
stage.sizeToScene();
Platform.runLater( control::requestFocus );
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyControl extends Control {
private final StringBuilder buffer = new StringBuilder();
public MyControl(){
setPrefSize(400, 400 );
setFocusTraversable(true);
setOnMouseClicked(ev-> requestFocus());
requestFocus();
setOnKeyTyped(ev-> { if ( !ev.isShortcutDown() ){
addTextToBuffer( ev.getCharacter() );
}});
}
public void addTextToBuffer( String str ){
buffer.append( str );
((MyControlSkin)getSkin()).paintCanvas();
}
public String getText(){ return buffer.toString(); }
#Override protected Skin<?> createDefaultSkin() {
return new MyControlSkin(this);
}
}
class MyControlSkin extends SkinBase<MyControl> {
final BorderPane borderPane = new BorderPane();
final ScrollBar rightScroll = new ScrollBar();
final Canvas canvas = new Canvas();
final MyControlBehavior behavior;
public MyControlSkin(MyControl control) {
super(control);
behavior = new MyControlBehavior( control );
rightScroll.setOrientation(Orientation.VERTICAL);
borderPane.setRight( rightScroll );
borderPane.setCenter(canvas);
canvas.setWidth( 150);
canvas.setHeight( 150 );
getChildren().add( borderPane );
paintCanvas();
}
public void paintCanvas(){
GraphicsContext gr = canvas.getGraphicsContext2D();
gr.clearRect( 0,0, canvas.getWidth(), canvas.getHeight());
gr.setFill( Color.BLACK);
gr.fillText( "Buff:" + getSkinnable().getText(), 30, 20 );
}
#Override
public void dispose() {
super.dispose();
behavior.dispose();
getChildren().removeAll();
}
}
class MyControlBehavior extends BehaviorBase<MyControl> {
final InputMap<MyControl> inputMap;
public MyControlBehavior(MyControl control) {
super(control);
this.inputMap = createInputMap();
addDefaultMapping( inputMap, new InputMap.KeyMapping(new KeyBinding(KeyCode.C).shortcut().ctrl(), e-> copy() ) );
}
public void copy(){
final Clipboard clipboard = Clipboard.getSystemClipboard();
final ClipboardContent content = new ClipboardContent();
content.putString( getNode().getText() );
clipboard.setContent(content);
}
#Override
public InputMap<MyControl> getInputMap() {
return inputMap;
}
}
}

Related

Is JavaFX Animation slow?

I have a simple animation consisting of resizing the stage and fading in/out on scene change.
There are two scenes sample.fxml and sample2.fxml, which are being toggled between.
Both the scenes have a single <Button> centered inside a <BorderPane> and calling the loadScene method.
SampleController:
package sample;
import javafx.animation.*;
import javafx.beans.value.WritableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.util.Duration;
import java.io.IOException;
public class SampleController {
public BorderPane rootPane;
double DURATION = 1000;
#FXML
public void initialize() {
rootPane.setStyle("-fx-background-color: teal;");
// Add fade in animation
FadeTransition fadeTransitionIn = new FadeTransition(Duration.millis(DURATION), rootPane);
fadeTransitionIn.setFromValue(0);
fadeTransitionIn.setToValue(1);
fadeTransitionIn.play();
}
public void loadScene(ActionEvent actionEvent) {
Window screen = rootPane.getScene().getWindow();
// Add fade out animation
FadeTransition fadeTransitionOut = new FadeTransition(Duration.millis(DURATION), rootPane);
fadeTransitionOut.setFromValue(1);
fadeTransitionOut.setToValue(0);
WritableValue<Double> writableHeight = new WritableValue<>() {
#Override
public Double getValue() {
return screen.getHeight();
}
#Override
public void setValue(Double value) {
screen.setHeight(value);
}
};
WritableValue<Double> writableWidth = new WritableValue<>() {
#Override
public Double getValue() {
return screen.getWidth();
}
#Override
public void setValue(Double value) {
screen.setWidth(value);
}
};
Timeline timeline = new Timeline();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO,
new KeyValue(writableWidth, screen.getWidth()),
new KeyValue(writableHeight, screen.getHeight())
),
new KeyFrame(Duration.millis(DURATION),
new KeyValue(writableWidth, 1000.0),
new KeyValue(writableHeight, 300.0)
)
);
timeline.setOnFinished(new EventHandler<>() {
#Override
public void handle(ActionEvent event) {
try {
screen.getScene().setRoot(FXMLLoader.load(getClass().getResource("sample2.fxml")));
} catch (IOException e) {
e.printStackTrace();
}
}
});
new ParallelTransition(timeline, fadeTransitionOut).play();
}
}
SampleController2:
package sample;
import javafx.animation.*;
import javafx.beans.value.WritableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.util.Duration;
import java.io.IOException;
public class SampleController2 {
public BorderPane rootPane;
double DURATION = 1000;
#FXML
public void initialize() {
rootPane.setStyle("-fx-background-color: blue;");
// Add fade in animation
FadeTransition fadeTransitionIn = new FadeTransition(Duration.millis(DURATION), rootPane);
fadeTransitionIn.setFromValue(0);
fadeTransitionIn.setToValue(1);
fadeTransitionIn.play();
}
public void loadScene(ActionEvent actionEvent) {
Window screen = rootPane.getScene().getWindow();
// Add fade out animation
FadeTransition fadeTransitionOut = new FadeTransition(Duration.millis(DURATION), rootPane);
fadeTransitionOut.setFromValue(1);
fadeTransitionOut.setToValue(0);
WritableValue<Double> writableHeight = new WritableValue<>() {
#Override
public Double getValue() {
return screen.getHeight();
}
#Override
public void setValue(Double value) {
screen.setHeight(value);
}
};
WritableValue<Double> writableWidth = new WritableValue<>() {
#Override
public Double getValue() {
return screen.getWidth();
}
#Override
public void setValue(Double value) {
screen.setWidth(value);
}
};
Timeline timeline = new Timeline();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO,
new KeyValue(writableWidth, screen.getWidth()),
new KeyValue(writableHeight, screen.getHeight())
),
new KeyFrame(Duration.millis(DURATION),
new KeyValue(writableWidth, 500.0),
new KeyValue(writableHeight, 500.0)
)
);
timeline.setOnFinished(new EventHandler<>() {
#Override
public void handle(ActionEvent event) {
try {
screen.getScene().setRoot(FXMLLoader.load(getClass().getResource("sample.fxml")));
} catch (IOException e) {
e.printStackTrace();
}
}
});
new ParallelTransition(timeline, fadeTransitionOut).play();
}
}
However, the animation is very choppy.
Is JavaFX really that slow?
Or am I doing something wrong?
Edit: I even removed (commented out) the fade in/out animation. It appears even just the resize animation is too slow and choppy.
Any suggestions to improve the animation performance are appreciated.

JavaFX progress indicator stop spinning when heavy load runs

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

How to add two different attributes to one line of a combobox in javafx? [duplicate]

I have a int value which I want to use for configuration. It can have 2 values - 0 for active and 1 for Blocked. I want to display this into friendly combo box:
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MainApp extends Application
{
#Override
public void start(Stage stage) throws Exception
{
int state = 0;
ObservableList<String> options = FXCollections.observableArrayList(
"Active",
"Blocked"
);
ComboBox comboBox = new ComboBox(options);
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
It's not clear for me how I have to implement this into JavaFX Combobox.
When I have 0 I want to display this as Active and when I have 1 I want to display Blocked and also when I change the ComboBox value to update also int state value.
There are different ways to solve this problem. I have listed three of the solutions below. You can use any one of the below solutions which you feel is apt for your scenario.
Using a custom class
Create a custom class KeyValuePair, for storing the string and its corresponding value. Exposed the getters for the required fields.
Later, I have used the setCellFactory() of the comboxbox to show the required data. Use StringConverter to show the key in place of the object.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception
{
KeyValuePair keyValuePair1 = new KeyValuePair("Active", 0);
KeyValuePair keyValuePair2 = new KeyValuePair("Blocked", 1);
ObservableList<KeyValuePair> options = FXCollections.observableArrayList();
options.addAll(keyValuePair1, keyValuePair2);
ComboBox<KeyValuePair> comboBox = new ComboBox<>(options);
// show the correct text
comboBox.setCellFactory((ListView<KeyValuePair> param) -> {
final ListCell<KeyValuePair> cell = new ListCell<KeyValuePair>(){
#Override
protected void updateItem(KeyValuePair t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(String.valueOf(t.getKey()));
}else{
setText(null);
}
}
};
return cell;
});
comboBox.setConverter(new StringConverter<KeyValuePair>() {
#Override
public String toString(KeyValuePair object) {
return object.getKey();
}
#Override
public KeyValuePair fromString(String string) {
return null; // No conversion fromString needed.
}
});
// print the value
comboBox.valueProperty().addListener((ov, oldVal, newVal) -> {
System.out.println(newVal.getKey() + " - " + newVal.getValue());
});
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class KeyValuePair {
private final String key;
private final int value;
public KeyValuePair(String key, int value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public int getValue() {
return value;
}
}
}
Without using an extra class
As suggested by #kleopatra, you can even do this without using an extra class.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
ObservableList<Integer> options = FXCollections.observableArrayList();
options.addAll(1, 0);
ComboBox<Integer> comboBox = new ComboBox<>(options);
// show the correct text
comboBox.setCellFactory((ListView<Integer> param) -> {
final ListCell<Integer> cell = new ListCell<Integer>(){
#Override
protected void updateItem(Integer t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(t == 1 ? "Active" : "Blocked");
}else{
setText(null);
}
}
};
return cell;
});
comboBox.setConverter(new StringConverter<Integer>() {
#Override
public String toString(Integer object) {
return object == 1 ? "Active" : "Blocked";
}
#Override
public Integer fromString(String string) {
return null;
}
});
// print the value
comboBox.valueProperty().addListener((ov, oldVal, newVal) -> {
System.out.println("Changed from " + oldVal + " to " + newVal);
});
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Using Bindings
You can also use Bindings if you don't want to take the pain of creating a new class and you will always have two elements i.e. Active and Blocked.
Just bind the valueProperty() of your combobox to the state, which is supposed to store the value i.e. 0 or 1.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
IntegerProperty state = new SimpleIntegerProperty();
ObservableList options = FXCollections.observableArrayList("Active", "Blocked");
ComboBox<String> comboBox = new ComboBox<>(options);
state.bind(Bindings.when(comboBox.valueProperty().isEqualTo("Active")).then(0).otherwise(1));
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Here is another solution:
declare state as BooleanProperty:
private BooleanProperty state = new SimpleBooleanProperty(false);
bind state property to the valueProperty of comboBox:
comboBox.valueProperty().bind(new When(state).then("Active").otherwise("Blocked"));
complete example:
public class ComboboxTest extends Application {
private BooleanProperty state = new SimpleBooleanProperty(false);
private Button button;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
ObservableList<String> options = FXCollections.observableArrayList(
"Active",
"Blocked"
);
ComboBox comboBox = new ComboBox(options);
button = new Button("false");
button.setOnAction(e -> setSate());
button.textProperty().bind(state.asString());
BorderPane bp = new BorderPane(comboBox);
StackPane stackpane = new StackPane(button);
stackpane.setAlignment(Pos.CENTER);
bp.setTop(stackpane);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
comboBox.valueProperty().bind(new When(state).then("Active").otherwise("Blocked"));
}
public void setSate() {
if (state.get()) {
state.set(false);
} else {
state.set(true);
}
}
}

Display Combobox values from numbers

I have a int value which I want to use for configuration. It can have 2 values - 0 for active and 1 for Blocked. I want to display this into friendly combo box:
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MainApp extends Application
{
#Override
public void start(Stage stage) throws Exception
{
int state = 0;
ObservableList<String> options = FXCollections.observableArrayList(
"Active",
"Blocked"
);
ComboBox comboBox = new ComboBox(options);
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
It's not clear for me how I have to implement this into JavaFX Combobox.
When I have 0 I want to display this as Active and when I have 1 I want to display Blocked and also when I change the ComboBox value to update also int state value.
There are different ways to solve this problem. I have listed three of the solutions below. You can use any one of the below solutions which you feel is apt for your scenario.
Using a custom class
Create a custom class KeyValuePair, for storing the string and its corresponding value. Exposed the getters for the required fields.
Later, I have used the setCellFactory() of the comboxbox to show the required data. Use StringConverter to show the key in place of the object.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception
{
KeyValuePair keyValuePair1 = new KeyValuePair("Active", 0);
KeyValuePair keyValuePair2 = new KeyValuePair("Blocked", 1);
ObservableList<KeyValuePair> options = FXCollections.observableArrayList();
options.addAll(keyValuePair1, keyValuePair2);
ComboBox<KeyValuePair> comboBox = new ComboBox<>(options);
// show the correct text
comboBox.setCellFactory((ListView<KeyValuePair> param) -> {
final ListCell<KeyValuePair> cell = new ListCell<KeyValuePair>(){
#Override
protected void updateItem(KeyValuePair t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(String.valueOf(t.getKey()));
}else{
setText(null);
}
}
};
return cell;
});
comboBox.setConverter(new StringConverter<KeyValuePair>() {
#Override
public String toString(KeyValuePair object) {
return object.getKey();
}
#Override
public KeyValuePair fromString(String string) {
return null; // No conversion fromString needed.
}
});
// print the value
comboBox.valueProperty().addListener((ov, oldVal, newVal) -> {
System.out.println(newVal.getKey() + " - " + newVal.getValue());
});
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class KeyValuePair {
private final String key;
private final int value;
public KeyValuePair(String key, int value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public int getValue() {
return value;
}
}
}
Without using an extra class
As suggested by #kleopatra, you can even do this without using an extra class.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
ObservableList<Integer> options = FXCollections.observableArrayList();
options.addAll(1, 0);
ComboBox<Integer> comboBox = new ComboBox<>(options);
// show the correct text
comboBox.setCellFactory((ListView<Integer> param) -> {
final ListCell<Integer> cell = new ListCell<Integer>(){
#Override
protected void updateItem(Integer t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(t == 1 ? "Active" : "Blocked");
}else{
setText(null);
}
}
};
return cell;
});
comboBox.setConverter(new StringConverter<Integer>() {
#Override
public String toString(Integer object) {
return object == 1 ? "Active" : "Blocked";
}
#Override
public Integer fromString(String string) {
return null;
}
});
// print the value
comboBox.valueProperty().addListener((ov, oldVal, newVal) -> {
System.out.println("Changed from " + oldVal + " to " + newVal);
});
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Using Bindings
You can also use Bindings if you don't want to take the pain of creating a new class and you will always have two elements i.e. Active and Blocked.
Just bind the valueProperty() of your combobox to the state, which is supposed to store the value i.e. 0 or 1.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
IntegerProperty state = new SimpleIntegerProperty();
ObservableList options = FXCollections.observableArrayList("Active", "Blocked");
ComboBox<String> comboBox = new ComboBox<>(options);
state.bind(Bindings.when(comboBox.valueProperty().isEqualTo("Active")).then(0).otherwise(1));
BorderPane bp = new BorderPane(comboBox);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Here is another solution:
declare state as BooleanProperty:
private BooleanProperty state = new SimpleBooleanProperty(false);
bind state property to the valueProperty of comboBox:
comboBox.valueProperty().bind(new When(state).then("Active").otherwise("Blocked"));
complete example:
public class ComboboxTest extends Application {
private BooleanProperty state = new SimpleBooleanProperty(false);
private Button button;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
ObservableList<String> options = FXCollections.observableArrayList(
"Active",
"Blocked"
);
ComboBox comboBox = new ComboBox(options);
button = new Button("false");
button.setOnAction(e -> setSate());
button.textProperty().bind(state.asString());
BorderPane bp = new BorderPane(comboBox);
StackPane stackpane = new StackPane(button);
stackpane.setAlignment(Pos.CENTER);
bp.setTop(stackpane);
bp.setPrefSize(800, 800);
Scene scene = new Scene(bp);
stage.setScene(scene);
stage.show();
comboBox.valueProperty().bind(new When(state).then("Active").otherwise("Blocked"));
}
public void setSate() {
if (state.get()) {
state.set(false);
} else {
state.set(true);
}
}
}

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