Slider bindBidirectional - javafx

Hi I'm stuck in situation where to use bidirectional binding for 2 sliders valueProperty
The task is: there's 2 sliders each of them has maximum value 100
But this value is divided between these 2 sliders, for example if you move first slider to value 20 then second slider value must be 80 and vice versa.
I know this should be done with bind bidirectional but how can I bind expression to the property?
slider1.valueProperty().bindBidirectional(slider2.getMax() - slider2.valueProperty()); ???
Any help will be highly appriciated.

You can't do this with bidirectional binding: you need to use two listeners:
slider1.valueProperty().addListener((obs, oldValue, newValue) ->
slider2.setValue(slider2.getMax() - newValue.doubleValue()));
slider2.valueProperty().addListener((obs, oldValue, newValue) ->
slider1.setValue(slider1.getMax() - newValue.doubleValue()));
SSCCE:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ConverselyBoundSliders extends Application {
#Override
public void start(Stage primaryStage) {
Slider slider1 = new Slider(0, 100, 50);
Slider slider2 = new Slider(0, 100, 50);
slider1.valueProperty().addListener((obs, oldValue, newValue) ->
slider2.setValue(slider2.getMax() - newValue.doubleValue()));
slider2.valueProperty().addListener((obs, oldValue, newValue) ->
slider1.setValue(slider1.getMax() - newValue.doubleValue()));
VBox root = new VBox(5, slider1, slider2);
root.setPadding(new Insets(12));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This seems to work just fine without any infinite recursion occurring; however since it involves floating-point arithmetic and comparisons for that to be true, you might want to make sure that rounding errors in the slider.getMax() - newValue.doubleValue() calculation don't end up with each listener calling the other without escaping from the recursion. The following is a bullet-proof way to do it:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ConverselyBoundSliders extends Application {
#Override
public void start(Stage primaryStage) {
Slider slider1 = new Slider(0, 100, 50);
Slider slider2 = new Slider(0, 100, 50);
new ConverseSliderBinding(slider1, slider2);
VBox root = new VBox(5, slider1, slider2);
root.setPadding(new Insets(12));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private static class ConverseSliderBinding {
private ChangeListener<Number> listener1 ;
private ChangeListener<Number> listener2 ;
private final Slider slider1 ;
private final Slider slider2 ;
private boolean adjusting ;
public ConverseSliderBinding(Slider slider1, Slider slider2) {
this.slider1 = slider1 ;
this.slider2 = slider2 ;
listener1 = (obs, oldValue, newValue) -> {
if (adjusting) return ;
adjusting = true ;
slider2.setValue(slider2.getMax() - newValue.doubleValue());
adjusting = false ;
};
listener2 = (obs, oldValue, newValue) -> {
if (adjusting) return ;
adjusting = true ;
slider1.setValue(slider1.getMax() - newValue.doubleValue());
adjusting = false ;
};
slider1.valueProperty().addListener(listener1);
slider2.valueProperty().addListener(listener2);
}
public void dispose() {
slider1.valueProperty().removeListener(listener1);
slider2.valueProperty().removeListener(listener2);
}
}
public static void main(String[] args) {
launch(args);
}
}

Related

How to stop transition when checkbox is unchecked javafx

So I've made a checkbox that applies a scale transition to a rectangle when checked. But the problem is that the transition keeps going even after I uncheck the checkbox. Any ideas on how to make it stop after un-checking?
checkbox.setOnAction(e -> {
ScaleTransition scaleT = new ScaleTransition(Duration.seconds(5), rectangle);
scaleT.setAutoReverse(true);
scaleT.setCycleCount(Timeline.INDEFINITE);
scaleT.setToX(2);
scaleT.setToY(2);
scaleT.play();
});
To control the animation, you need to define the transistion(with INDEFINITE cycle count) outside the CheckBox listener/action. Then you can just play/pause the animation as you required.
Below is the quick demo:
import javafx.animation.ScaleTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ScaleTransitionDemo extends Application {
#Override
public void start(Stage stage) {
Shape rectangle = new Rectangle(50, 50, Color.BLUE);
ScaleTransition transition = new ScaleTransition(Duration.seconds(1), rectangle);
transition.setDuration(Duration.seconds(1));
transition.setAutoReverse(true);
transition.setCycleCount(Timeline.INDEFINITE);
transition.setToX(3);
transition.setToY(3);
CheckBox checkBox = new CheckBox("Animate");
checkBox.selectedProperty().addListener((obs, old, selected) -> {
if (selected) {
transition.play();
} else {
transition.pause();
}
});
StackPane pane = new StackPane(rectangle);
VBox.setVgrow(pane, Priority.ALWAYS);
VBox root = new VBox(20, checkBox, pane);
root.setPadding(new Insets(10));
Scene scene = new Scene(root, 300, 300);
stage.setScene(scene);
stage.setTitle("Scale transition");
stage.show();
}
public static void main(String[] args) {
launch();
}
}
checking whether checkbox is selected or not with .isSelected() method . In this approach , scaled node will back to xy = 1 scale if checkbox is unchecked , but it will be disabled until transition ends .You can adjust setDuration . I've changed it just for gif recording. This is a single class javafx app you can try .
App.java
public class App extends Application {
#Override
public void start(Stage stage) {
Shape rectangle = new Rectangle(50, 50, Color.BLUE);
ScaleTransition scaleT = new ScaleTransition(Duration.seconds(1), rectangle);
CheckBox checkBox = new CheckBox("scale");
checkBox.setOnAction(e -> {
if (checkBox.isSelected()) {
scaleT.setDuration(Duration.seconds(1));
scaleT.setAutoReverse(true);
scaleT.setCycleCount(Timeline.INDEFINITE);
scaleT.setToX(2);
scaleT.setToY(2);
scaleT.play();
} else {
scaleT.setDuration(scaleT.getCurrentTime());
scaleT.stop();
scaleT.setCycleCount(1);
scaleT.setToX(1);
scaleT.setToY(1);
scaleT.play();
checkBox.setDisable(true);
scaleT.setOnFinished((t) -> {
checkBox.setDisable(false);
});
}
});
var scene = new Scene(new HBox(50, rectangle, checkBox), 640, 480);
stage.setScene(scene);
stage.setTitle("scale transition");
stage.show();
}
public static void main(String[] args) {
launch();
}
}

JavaFX TextField listener gives java.lang.IllegalArgumentException: The start must be <= the end

So I am writing a javafx program to manipulate the individual bits in a byte. I have a textfield for each bit. I want to implement a changelistener on the textfields so one cannot enter anything but a 0 or a 1. It works fine if the field is empty and the user tries to enter a letter, but if there is already a 0 or 1 in it it throws an exception and I dont understand why.
Here is my code:
public class Task03Controller implements Initializable {
#FXML private TextField zeroTextField, oneTextField, twoTextField, threeTextField,
fourTextField, fiveTextField, sixTextField, sevenTextField;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
zeroTextField.textProperty().addListener((observable, oldValue, newValue) -> {
if(!zeroTextField.getText().equals("0") && !zeroTextField.getText().equals("1"))
zeroTextField.clear();
else if(zeroTextField.getText().length() > 1)
zeroTextField.setText(zeroTextField.getText().substring(0, 0));
});
}
}
Using the same idea as the duplicate. You need to define a regular expression that matches binary numbers.
I am using "\\b[01]+\\b" to define binary numbers and "" to define an empty TextField.
MCVE
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class TestingGroundsTwo extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage)
{
UnaryOperator<Change> binaryFilter = change -> {
String newText = change.getControlNewText();
if (newText.matches("\\b[01]+\\b") || newText.matches("")) {
return change;
}
return null;
};
TextField textField = new TextField();
textField.setTextFormatter(new TextFormatter<>(binaryFilter));
stage.setTitle("Hello World!");
Scene scene = new Scene(new StackPane(textField), 750, 125);
scene.setFill(Color.GHOSTWHITE);
stage.setScene(scene);
stage.show();
}
}

Coordinates of Slider thumb in JavaFX

There is some method to know the position of a Slider thumb in JavaFX?
Use getBoundsInParent() and a lookup():
Bounds bounds = slider.lookup(".thumb").getBoundsInParent();
You may need to applyCss() to the slider before the lookup in order for it to work.
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SlipSlide extends Application {
#Override
public void start(Stage stage) {
Slider slider = new Slider(1, 10, 3);
slider.setMajorTickUnit(9);
slider.setMinorTickCount(8);
slider.setSnapToTicks(true);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setPrefWidth(250);
Label boundsLabel = new Label();
VBox layout = new VBox(10, slider, boundsLabel);
stage.setScene(new Scene(layout));
stage.show();
reportThumbBounds(slider, boundsLabel);
slider.valueProperty().addListener((observable, oldValue, newValue) -> {
reportThumbBounds(slider, boundsLabel);
});
slider.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
reportThumbBounds(slider, boundsLabel);
});
}
private void reportThumbBounds(Slider slider, Label boundsLabel) {
Bounds bounds = slider.lookup(".thumb").getBoundsInParent();
boundsLabel.setText(
String.format(
"(%.2f,%.2f) (%.2f,%.2f)",
bounds.getMinX(), bounds.getMinY(),
bounds.getMaxX(), bounds.getMaxY()
)
);
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX: how to hide the item selection after having selected an item

I made a WebEngine where i can use favorite Web URLS from a Choicebox (= favBox).
After having chosen an item, the item is shown and the website is loading. But my problem is: the item can still be seen for the rest of the session. How can I hide the item selection and just show the ChoiceBox without items?
Thanks a lot
#FXML
private void handleFavoritLoading(MouseEvent event) {
//favBox is a ChoiceBox
favBox.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println("OK");
browser.load(favBox.getItems().get(newValue.intValue()));
// how to make the favBox not showing the selected item???
}
});
}
You can clear the selection of a ChoiceBox, then nothing will be selected in it.
favBox.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
if (newValue != null) {
browser.load(newValue);
favBox.getSelectionModel().clearSelection();
}
}
);
Note that this behavior is a little bit strange as most of the time you probably want the selected choice to continue to be shown after selection. However, if you don't want the standard operation and want to immediately clear the choice after selection, you can always use the sample code provided here.
Sample app:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.*;
import javafx.stage.Stage;
import static javafx.collections.FXCollections.observableArrayList;
public class HiddenChoices extends Application {
#Override
public void start(Stage stage) throws Exception {
WebView webView = new WebView();
WebEngine browser = webView.getEngine();
VBox.setVgrow(webView, Priority.ALWAYS);
ChoiceBox<String> favBox = new ChoiceBox<>(
observableArrayList(
"http://www.google.com",
"http://andrew-hoyer.com/experiments/cloth/",
"http://www.effectgames.com/demos/canvascycle/",
"http://www.zynaps.com/site/experiments/environment.html?mesh=bart.wft"
)
);
favBox.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
if (newValue != null) {
browser.load(newValue);
favBox.getSelectionModel().clearSelection();
}
}
);
ProgressBar progress = new ProgressBar();
progress.progressProperty().bind(browser.getLoadWorker().progressProperty());
progress.visibleProperty().bind(browser.getLoadWorker().runningProperty());
HBox controls = new HBox(10, favBox, progress);
controls.setMinHeight(HBox.USE_PREF_SIZE);
controls.setAlignment(Pos.CENTER_LEFT);
stage.setScene(
new Scene(
new VBox(10, controls, webView)
)
);
stage.show();
favBox.getSelectionModel().select(0);
}
public static void main(String[] args) {
Application.launch();
}
}

javafx multiple buttons to same handler

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

Resources