I have two buttons in two separate classes, and I want to change the onAction of the first button when the second button has been pressed to be the original action plus one additional method call. Once the first button has been pressed I want its onAction to revert to the original.
What I currently have working is essientially
Button b1 = new Button("b1");
b1.setOnAction((event)-> {
oldActionMethod();
});
public void oldActionMethod(){
//actual code
}
b2.setOnAction((event)-> {
//some stuff
Button b1 = getB1();
EventHandler<ActionEvent> temp = b1.getOnAction();
b1.setOnAction((event) -> {
b1class.oldActionMethod();
additionalMethod();
b1.setOnAction(temp);
});
});
In order to make this work I had to move the block of code that was originally in the setOnAction lambda expression to a helper function. Is there a cleaner way to do this? Something like this which would eliminate the need for the helper function?
b1.setOnAction((event)-> {
//actual code
});
b2.setOnAction((event) -> {
//stuff
Button b1 = getB1();
EventHandler<ActionEvent> temp = b1.getOnAction();
b1.setOnAction(b1.getOnAction() + methodCall());
b1.setOnAction(temp);
//stuff
});
The way I have it currently does work but it feels really hack-y so I am just interested to know if there is a better option where you could essentially concatenate an actionEvent with another method. Also if there is a way to not require storing the original event in a temp object and resetting it at the end. A possible solution would be if I could tell b2 to listen for the next time b1 is pressed, but I don't know if there is any way to do that when they are in two separate classes.
One solution is to have a shared model class between the two classes containing the buttos.
See the following mcve. For conviniense the entire code can be copy-pasted into one file (FaMain.java) and run:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FxMain extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Model model = new Model();
AClass aClass = new AClass(model);
BClass bClass = new BClass(model);
Label show = new Label();
show.textProperty().bind(model.getTextProperty());
VBox root = new VBox(10);
root.getChildren().addAll(aClass.getButton(),show, bClass.getButton());
primaryStage.setScene(new Scene(root, 400,100));
primaryStage.sizeToScene();
primaryStage.show();
}
public static void main(final String[] args) {
launch(args);
}
}
class Model {
private final BooleanProperty aButtonSelected;
private final SimpleStringProperty textProperty;
Model(){
aButtonSelected = new SimpleBooleanProperty();
textProperty= new SimpleStringProperty();
}
ObservableValue<? extends String> getTextProperty() {
return textProperty;
}
BooleanProperty aButtonSelectedProperty(){
return aButtonSelected;
}
void bButtonClicked() {
textProperty.set(aButtonSelected.get() ? "Button clicked. Toggle IS selected" :
"Button clicked. Toggle is NOT selected");
}
}
class AClass{
private final ToggleButton aButton;
AClass(Model model) {
aButton = new ToggleButton("Toogle");
model.aButtonSelectedProperty().bind(aButton.selectedProperty());
}
ToggleButton getButton(){
return aButton;
}
}
class BClass{
private final Button bButton;
BClass(Model model) {
bButton = new Button("Click");
bButton.setOnAction(e->model.bButtonClicked());
}
Button getButton(){
return bButton;
}
}
Related
I am trying to build a Combobox in JavaFX that should work as followed.
The user should only type numbers in, but the typed numbers have to be formattet.
ex. 111-111-1111.
So if the user types in three numbers a - should be added automatically.
I figured out how to do that.
I blocked everything but numbers with a TexFormatter.
Now the main Part of the problem comes the part after this.
I added a keyevent.key_released place the caret at the end when a key is released.
But if the user is typing too fast it won't work.
Most of the time it works just fine like that, but when is the user every doing something only the way I expect?
I could not find another way to get the actual value of the combobox, because it seems to refresh after hitting enter or so.
Adding the - in the Textformatter resultet in the programm listening to itself and I wasn't able to place the caret at the end position.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author
*/
public class tester extends Application{
ComboBox<String> combo = new ComboBox<String>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
System.out.println("test");
HBox root = new HBox();
//
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
#Override
public TextFormatter.Change apply(TextFormatter.Change t) {
System.out.println(t.getText());
if (t.isReplaced())
if(t.getText().matches("[^0-9]")) {
t.setText(t.getControlText().substring(t.getRangeStart(), t.getRangeEnd()));}
if (t.isAdded()) {
// Add in Formatter //
if (t.getControlText().length() == 2 || t.getControlText().length() == 6 && t.getText().matches("[0-9]")){
t.setText(t.getText() + "-");
combo.getEditor().end();
}
//
if (t.getText().matches("[^0-9]")) {
t.setText("");}
}
return t;
}
};
combo.setEditable(true);
combo.getEditor().setTextFormatter(new TextFormatter<>(filter));
combo.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, e -> {
combo.getEditor().end();
});
root.getChildren().add(combo);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
Update 3: solution
I finally got it: just save a reference of MyRoot in a property:
MainApp
package my.group.javafxtestbinding;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainApp extends Application {
private MyRoot root;
#Override
public void start(Stage primaryStage) {
root = new MyRoot();
HBox parent = root.myRoot;
Scene scene = new Scene(parent, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
Button gcButton = new Button("Collect Garbage");
gcButton.setOnMousePressed(event -> System.gc());
parent.getChildren().add(gcButton);
}
public static void main(String[] args) {
launch(args);
}
}
Update 3 solution end
Update 2:
So, I'm still having problems with losing my bindings and it's due to garbage collection; that I can reproduce. See the follwing Minimal Working Example.
The structure of the example is somewhat special, that is because I use the MVP framework from Adam Bien: afterburner.fx. And I wanted to simulate the same structure in my example.
I read a lot (i.e. here, here) and understand the basic problem but still can't really grasp it.
I'm somewhat new to JavaFX, hope somebody can give me a hint.
Reproduce:
When you click on an item in the table, the content of the label changes due to the binding between the selected model in the table (Select) and the label (Show). MyRoot manages both.
When you click on the button garbage collection is executed and the binding is gone.
MainApp
package my.group.javafxtestbinding;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainApp extends Application {
#Override
public void start(Stage primaryStage) {
MyRoot root = new MyRoot();
HBox parent = root.myRoot;
Scene scene = new Scene(parent, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
Button gcButton = new Button("Collect Garbage");
gcButton.setOnMousePressed(event -> System.gc());
parent.getChildren().add(gcButton);
}
public static void main(String[] args) {
launch(args);
}
}
MyRoot
package my.group.javafxtestbinding;
import javafx.scene.layout.HBox;
public class MyRoot {
public HBox myRoot;
private final Select select;
private final Show show;
public MyRoot() {
select = new Select();
show = new Show();
myRoot = new HBox();
myRoot.getChildren().addAll(select.myTable, show.myLabel);
show.selectedModelProperty().bind(select.selectedModelProperty());
}
}
Select
package my.group.javafxtestbinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
public class Select {
private ObjectProperty<MyModel> selectedModel;
public TableView<MyModel> myTable;
private final TableColumn<MyModel, String> myColumn;
public Select() {
this.selectedModel = new SimpleObjectProperty<>();
myTable = new TableView();
myColumn = new TableColumn("Name");
myTable.getColumns().add(myColumn);
myTable.setItems(getList());
myColumn.setCellValueFactory(cellData -> cellData.getValue().getNameProperty());
ChangeListener<MyModel> changeListener = new ChangeListener<MyModel>() {
#Override
public void changed(ObservableValue<? extends MyModel> observable, MyModel oldValue, MyModel newValue) {
selectedModel.set(newValue);
}
};
myTable.getSelectionModel().selectedItemProperty().addListener(changeListener);
}
public ObjectProperty<MyModel> selectedModelProperty() {
return selectedModel;
}
private ObservableList<MyModel> getList() {
MyModel model1 = new MyModel("Joe");
MyModel model2 = new MyModel("Jim");
MyModel model3 = new MyModel("Jack");
ObservableList<MyModel> list = FXCollections.observableArrayList(model1, model2, model3);
return list;
}
}
Show
package my.group.javafxtestbinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Label;
public class Show {
private ObjectProperty<MyModel> selectedModel;
public Label myLabel;
public Show() {
this.selectedModel = new SimpleObjectProperty<>();
myLabel = new Label("Init");
ChangeListener listener = new ChangeListener<MyModel>() {
#Override
public void changed(ObservableValue<? extends MyModel> observable, MyModel oldValue, MyModel newValue) {
if (newValue != null) {
myLabel.setText(newValue.getName());
}
}
};
this.selectedModel.addListener(listener);
}
public ObjectProperty<MyModel> selectedModelProperty() {
return selectedModel;
}
}
MyModel
package my.group.javafxtestbinding;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class MyModel {
private final StringProperty name;
public MyModel(String name) {
this.name = new SimpleStringProperty(name);
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty getNameProperty() {
return name;
}
}
Update 2 end
Update 1:
Ok, thank you for your comments.
I thought I was doing it wrong. But if you say that this is the correct way then the problem must be somewhere else. I will post it when I find the problem...
Update 1 end
Original Post:
I have problems binding many properties to one observable.
Let's say I have three (or more) properties ObjectProperty<Foo> firstProp in FirstObject, ObjectProperty<Foo> secondProp in SecondObject and ObjectProperty<Foo> thirdProp in ThirdObject.
How can I bind second and third property to the first which is the observable.
So when firstProp changes the ChangeListeners attached to secondProp and thirdProp will be fired.
What I tried:
SecondObject.secondProperty().bind(FirstObject.firstProperty());
ThirdObject.thirdProperty().bind(FirstObject.firstProperty());
The result is that it sometimes works and sometimes not. If I add more bindings in the same way they do not work at all.
Is this not working because of this:
Note that JavaFX has all the bind calls implemented through weak listeners. This means the bound property can be garbage collected and stopped from being updated.
Javadoc bind()
I find a lot of answers to how to bind many observables to one property, but not the other way.
Can somebody point me in the right direction?
I have a JavaFX application with several Stages open to provide floating windows. I want to iterate through these Stages from front to back. I have a list of all the Stages which I'd like to sort and am looking for a method that will let me compare two of them and determine which is in front. Is there a way to do this?
This is just one possibility.
Store each open Stage in a list that can be observed for changes.
Add a listener on each Stage's focusedProperty. When it changes to true,
Remove the stage from our List and readd it at index 0
Now, create a listener on the List and your "focused" Stage will always be at index 0.
You now have an ArrayList that stores the open stages, in order.
Here is a simple MCVE to demonstrate. There are certainly areas to be improved upon and I welcome suggestions, but this does provide some basic functionality.
OpenStages.java:
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
/**
* Implementation of a SimpleListProperty that will store our ObservableArrayList
*/
public class OpenStages<Stage> extends SimpleListProperty<Stage> {
/**
* Constructor that creates an ObservableArrayList
*/
public OpenStages() {
super(FXCollections.observableArrayList());
}
/**
* Removes this Stage from the list and re-adds it at index 0
*/
public void focusStage(Stage stage) {
this.remove(stage);
this.add(0, stage);
}
}
Main.java:
import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Custom SimpleListProperty that holds our ObservableArrayList of open stages
OpenStages<Stage> openStages = new OpenStages<>();
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
Button btnCreateStage = new Button("Create New Stage");
btnCreateStage.setOnAction(event -> {
// Create a new Stage
Stage newStage = new Stage();
// Add a listener to the focusedProperty of the Stage. When focus changes to true, we
// need to update our openStages list
newStage.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
openStages.focusStage(newStage);
}
});
// Add the stage to our OpenStages list.
openStages.add(newStage);
// Simple layout for the new Stage
VBox stageRoot = new VBox();
stageRoot.setPrefSize(300, 100);
stageRoot.setPadding(new Insets(10));
stageRoot.setAlignment(Pos.CENTER);
// Let's add a label and title to indicate which Stage this is
stageRoot.getChildren().add(new Label("Stage #" + openStages.size()));
newStage.setTitle("Stage #" + openStages.size());
newStage.setScene(new Scene(stageRoot));
// Finally, let's show the stage
newStage.show();
});
// Now, let's create a simple listener for our openStages list to print out the focused Stage
openStages.addListener(new ListChangeListener<Stage>() {
#Override
public void onChanged(Change<? extends Stage> c) {
// Only interested in a stage being added
if (c.next() && c.wasAdded()) {
System.out.println("Focused stage: " + openStages.get(0).getTitle());
}
}
});
// Add the Button to our main layout
root.getChildren().add(btnCreateStage);
// Show the Stage
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
I want to add textfield and combobox dynamically on button click but not able to do it. Below is the code which I tried. In this code I have added the field successfully but not able to remove the item one by one on button click. only last item is removing after adding the field multiple times. so please check my code where I did mistake.
package application;
import java.awt.Color;
import java.net.URL;
import java.util.ResourceBundle;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXTextField;
import com.sun.javafx.scene.layout.region.Margins;
import javafx.event.ActionEvent;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
public class CustomerController implements Initializable {
public JFXTextField
acc_no,ifsc_code,micr_code,acc_no1,ifsc_code1,micr_code1;
public JFXComboBox<String> bank_name,bank_name1;
public JFXButton add_row,rmv_row;
public GridPane grid_component;
public VBox vBox2_component, vbox1_component;
public AnchorPane anchor_pane;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
// TODO Auto-generated method stub
bank_name.getItems().removeAll(bank_name.getItems());
bank_name.getItems().addAll("Bank of India", "Dena Bank", "HDFC Bank");
new AutoCompleteComboBoxListener(bank_name);
}
// add button functionality
public void AddBankDetails(ActionEvent event) {
/*if(count == max_row-1){
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Maximum of 10 rows can be added\",\"Failed!!");
alert.showAndWait();
return;
}
count++;*/
bank_name1 = new JFXComboBox();
bank_name1.getStyleClass().add("textfield_margin");
bank_name1.setLabelFloat(true);
bank_name1.setFocusColor(javafx.scene.paint.Color.valueOf("#2196F3"));
bank_name1.setPromptText("Bank Name");
bank_name1.setUnFocusColor(javafx.scene.paint.Color.valueOf("939393"));
vbox1_component.setMargin(bank_name1, new Insets(20, 10, 10, 10));
ifsc_code1 = new JFXTextField();
ifsc_code1.getStyleClass().add("textfield_margin");
ifsc_code1.setLabelFloat(true);
ifsc_code1.setFocusColor(javafx.scene.paint.Color.valueOf("#2196F3"));
ifsc_code1.setPromptText("IFSC Code");
ifsc_code1.setUnFocusColor(javafx.scene.paint.Color.valueOf("939393"));
vbox1_component.setMargin(ifsc_code1, new Insets(20, 10, 10, 10));
acc_no1 = new JFXTextField();
acc_no1.getStyleClass().add("textfield_margin");
acc_no1.setLabelFloat(true);
acc_no1.setFocusColor(javafx.scene.paint.Color.valueOf("#2196F3"));
acc_no1.setPromptText("Account number");
acc_no1.setUnFocusColor(javafx.scene.paint.Color.valueOf("939393"));
vBox2_component.setMargin(acc_no1, new Insets(20, 10, 10, 10));
micr_code1 = new JFXTextField();
micr_code1.getStyleClass().add("textfield_margin");
micr_code1.setLabelFloat(true);
micr_code1.setFocusColor(javafx.scene.paint.Color.valueOf("#2196F3"));
micr_code1.setPromptText("MICR Code");
micr_code1.setUnFocusColor(javafx.scene.paint.Color.valueOf("939393"));
vBox2_component.setMargin(micr_code1, new Insets(20, 10, 10, 10));
vbox1_component.getChildren().addAll(bank_name1,ifsc_code1);
vBox2_component.getChildren().addAll(acc_no1,micr_code1);
}
//remove button functionality
public void rmvBankDetails(ActionEvent events) {
vbox1_component.getChildren().removeAll(bank_name1,ifsc_code1);
vBox2_component.getChildren().removeAll(acc_no1,micr_code1);
}
}
You only store a reference to the controls that are created for the last call of AddBankDetails. This means if you click the add button multiple times and then click the remove button multiple times. You only get a single successful remove operation for the VBoxes.
Assuming this is the only code modifying the VBoxes and the VBoxes don't contain other children, you could simply remove the last 2 children from each list:
private static void removeElements(List<?> list, int count) {
int size = list.size();
if (size >= count) {
list.subList(size - count, size).clear();
}
}
public void rmvBankDetails(ActionEvent events) {
removeElements(vbox1_component.getChildren(), 2);
removeElements(vBox2_component.getChildren(), 2);
}
Additional notes:
Depending on your layout using GridPane could be a option. TableView may also be worth a look.
You probably need access to all your controls, so keeping a list of the newly added controls in a list could be desireable. Using this data you could also retrieve the controls to remove.
private static class Container {
final JFXComboBox bank_name1;
final JFXTextField ifsc_code1;
final JFXTextField acc_no1;
final JFXTextField micr_code1;
Container(JFXComboBox bank_name1, JFXTextField ifsc_code1, JFXTextField acc_no1, JFXTextField micr_code1) {
this.bank_name1 = bank_name1;
this.ifsc_code1 = ifsc_code1;
this.acc_no1 = acc_no1;
this.micr_code1 = micr_code1;
}
}
private final List<Container> bankDetailsContainers = new ArrayList<>();
public void AddBankDetails(ActionEvent event) {
...
bankDetailsContainers.add(new Container(bank_name1, ifsc_code1, acc_no1, micr_code1));
}
public void rmvBankDetails(ActionEvent events) {
if (!bankDetailsContainers.isEmpty()) {
Container c = bankDetailsContainers.remove(bankDetailsContainers.size() - 1);
vbox1_component.getChildren().removeAll(c.bank_name1, c.ifsc_code1);
vBox2_component.getChildren().removeAll(c.acc_no1, c.micr_code1);
}
}
I want to set up a model for my project so my controllers can communicate with each other. I want it to have a setter and getter, to allow easy access to styling certain nodes from either class.
My question: is it possible to bind a style property (ex. "-fx-background-color: blue") to a node?
From my research, I see that this is definitely possible with text values for labels (explained by James_D here: JavaFX - How to use a method in a controller from another controller?), but I am having a hard time figuring out what the syntax for doing a similar thing with "setStyle" would be.
The model I have so far:
public class Model {
private final StringProperty shadow = new SimpleStringProperty("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.24), 10,0,0,0)");
public StringProperty shadowProperty() {
return shadow;
}
public final String getShadow() {
return shadowProperty().get();
}
public final void setShadow(String shadow) {
shadowProperty().set(shadow);
}
}
I understand how I would set the "shadow" value from a controller, but what I don't understand is how I can bind a node from another controller to listen to that change.
Let's say the node is something like:
#FXML AnchorPane appBar
I want "appBar" to take on any changes made to "shadow" in the model. What would that look like?
You need to add a listener to the shadowProperty to listen to its changes.
something.shadowProperty() .addListener( (observable, oldValue, newValue) -> {
//do something with appBar
}) ;
I'm not entirely sure what you want to achieve, but this should answer your question about how to listen to property changes.
PS: im on mobile, so no guarantees regarding typos
Edit: you can also bind the property of one object to the property of another. Use bind() for that.
EDIT: Here is an example:
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
Property<Background> backgroundProperty;
StringProperty styleProperty;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox(10);
backgroundProperty = new SimpleObjectProperty<>();
styleProperty = new SimpleStringProperty();
// Pane that changes background by listener
Pane pane1 = new Pane();
pane1.setMinHeight(40);
backgroundProperty.addListener( (observable, oldValue, newValue) -> {
pane1.setBackground(backgroundProperty.getValue());
});
// Pane that changes background by property binding
Pane pane2 = new Pane();
pane2.setMinHeight(40);
pane2.backgroundProperty().bind(backgroundProperty);
// Pane that binds the style property
Pane pane3 = new Pane();
pane3.setMinHeight(40);
pane3.styleProperty().bind(styleProperty);
backgroundProperty.setValue(new Background(new BackgroundFill(Color.RED, null, null)));
styleProperty.setValue("-fx-background-color: black");
root.getChildren().add(pane1);
root.getChildren().add(pane2);
root.getChildren().add(pane3);
Scene scene = new Scene(root, 200, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
}