Given
I have a model class Model that contains the following fields:
StringProperty stringProperty; ListProperty<String> listOfStringsProperty
I have a view class View extends VBox that has the following:
TextField stringTextField; TextFieldList stringsTextFieldList;
TextFieldList extends VBox is a custom class that I created that handles multiple TextFields with nice + and - buttons to add and remove input text fields.
TextFieldList class contains the field ObservableList<Node> inputTextFields and I can get the data from these InputTextFields by a method call List<String> getData()
Question
I was able to do the following:
stringTextField.textProperty().bindBidirectional(model.getStringProperty());
in order to bind the result of the stringTextField in View to the stringProperty in Model
And I need to do something like
stringsTextFieldList.listProperty().bindBidirectional(model.getListOfStringsProperty());
How can I do that?
If this design would not work, then how do you suggest I fix it? Is there a built-in class that does the same as TextFieldList but instead extends Control?
If you decide to make your own control you should create the "binding manually", that means that in the input ObservableList you add a ListChangeListener then you process the Change like in the example: check whether a new item is added, removed or updated and maintain your TextFields accordingly. It is possible, but my answer is mainly about proposing an existing control to re-use rather than create your own one.
So if you don't want to re-invent the wheel:
I don't know your exact use-case, but maybe it is reasonable to reuse a control that actually support a data model, like a ListView.
In the example I have modified the Model class to have an ObservableList<StringProperty> rather than a ListProperty<String> (note: it is also possible to simply have String objects in the list, I just modified it to make the binding really clear). I have added a ListView and used setCellFactory to draw TextFields as elements in the list which are bidirectionally bounded to the corresponding StringProperty in the list. I have also added several buttons to add and remove elements and a button to print the current content of the model.
Example:
Model.java
public class Model {
public ObservableList<StringProperty> listOfStringsProperty;
public Model(){
listOfStringsProperty = FXCollections.observableArrayList();
}
}
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
Model m = new Model();
m.listOfStringsProperty.addAll(new SimpleStringProperty("First"),
new SimpleStringProperty("Second"),
new SimpleStringProperty("Third"));
ListView<StringProperty> lv = new ListView<StringProperty>();
lv.setCellFactory(new Callback<ListView<StringProperty>, ListCell<StringProperty>>() {
#Override
public ListCell<StringProperty> call(ListView<StringProperty> param) {
return new ListCell<StringProperty>(){
#Override
protected void updateItem(StringProperty item, boolean empty) {
super.updateItem(item, empty);
if(item == null){
setText(null);
setGraphic(null);
return;
}
TextField tf = new TextField();
tf.textProperty().bindBidirectional(item);
setGraphic(tf);
}
};
}
});
lv.setItems(m.listOfStringsProperty);
root.setCenter(lv);
// Control buttons
HBox hbox = new HBox();
Button buttonAdd = new Button("Add");
buttonAdd.setOnAction(e -> m.listOfStringsProperty.add(new SimpleStringProperty("")));
Button buttonRemove = new Button("Remove last");
buttonRemove.setOnAction(e -> m.listOfStringsProperty.remove(m.listOfStringsProperty.size()-1));
Button buttonPrintModel = new Button("Print model");
buttonPrintModel.setOnAction(e -> System.out.println(m.listOfStringsProperty.toString()));
hbox.getChildren().addAll(buttonAdd, buttonRemove, buttonPrintModel);
root.setBottom(hbox);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
This will produce the following window:
You can use
Bindings.bindContent(List list1, ObservableList list2).
This is a special binding that keeps the list in sync with the observable list. Keep in mind that this is not bidirectional though.
If bidirectorionality is what you want, you should use invalidation listeners or change listeners to detect changes and synchronize the lists manually. You might need some crude locking mechanism to prevent a stack overflow.
Related
I'm trying to populatw multiple MenuButton's in javaFX with an arrayList of checkMenuItems. To add the check menu items im doing:
myMenuButton.getItems().addAll(ChecKMenuItemList);
this is what my code looks like
class Scratch {
private void updateClassList(ArrayList<Class> classArrayList) {
ArrayList<String> classCodeList = new ArrayList<>();
ArrayList<CheckMenuItem> checkMenuItemList = new ArrayList<>();
ArrayList<CheckMenuItem> checkMenuItemList2 = new ArrayList<>();
ArrayList<String> classNameList = new ArrayList<>();
//Create Arrays of class elements
for(Class aClass : classArrayList){
checkMenuItemList.add(new CheckMenuItem(aClass.getClassCode()));
}
//Clear Class Lists
addStudentsToClassClassListView.getItems().clear();
assignClassesToTeachersClassListView.getItems().clear();
//Populate dropdown lists
addStudentSelectClassesMenuButton.getItems().setAll(checkMenuItemList);
addTeacherSelectClasses.getItems().setAll(checkMenuItemList);
}
}
This function is called from another function after the user inputs a json file that is parsed for data.
The problem im running into is when i try to use .getItems().addAll() it only works once, in my code if you comment one of the two lines the other one will work and vice versa, its strange since they work on their own but not together
Since both of them work on their own I'm not sure what the issue would be thats causing it not too update. There is no error or exception simply nothing happens. After both of the lines executes and before the function completes while debugging it says the both menubuttons have 6 items but when you click on the menu button nothing happens
The issue is NOT:
ObservableList.getItems().addAll() cannot be used multiple times
it definitely can be used multiple times.
ObservableList<Integer> list = FXCollections.observableArrayList(1, 2, 3);
list.addAll(4, 5, 6);
list.addAll(7, 8, 9);
System.out.println(list);
Will output as expected:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
However, you need to use APIs correctly in context.
Items in the scene graph can only be in one position at a time. A CheckMenuItem is not a node, but it is probably backed by nodes and thus acts like a node, so I wouldn't add a single instance to more than one menu at a time.
Instead, create another CheckMenuItem instance, with the same data, and add that. Bidirectional binding can be used to ensure that if one menu item is checked, the other menu item's state is updated to reflect that, and vice versa.
See the scene javadoc:
A node may occur at most once anywhere in the scene graph. Specifically, a node must appear no more than once in the children list of a Parent or as the clip of a Node. See the Node class for more details on these restrictions.
Also, the node javadoc:
If a program adds a child node to a Parent (including Group, Region, etc) and that node is already a child of a different Parent or the root of a Scene, the node is automatically (and silently) removed from its former parent.
It would appear that CheckMenuItem acts the same way. It would probably be better if the Menu documentation stated that items can only appear in one menu at a time.
Examples to demonstrate failure and fixes
In this example, two menus are created and the same items are added to both menus. On execution, only one of the menus (the last one to which the items were added) will contain the added items.
The execution warns you, in the system error console, that there is a problem.
Dec 13, 2022 4:00:27 PM javafx.scene.control.Menu$6 onChanged
WARNING: Adding MenuItem Check 1 that has already been added to Menu 1
Dec 13, 2022 4:00:27 PM javafx.scene.control.Menu$6 onChanged
WARNING: Adding MenuItem Check 2 that has already been added to Menu 1
Broken code
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class MenuItemApp extends Application {
#Override
public void start(Stage stage) throws Exception {
MenuItem[] menuItems = createCheckMenuItems();
Menu menu1 = new Menu("Menu 1");
menu1.getItems().addAll(menuItems);
Menu menu2 = new Menu("Menu 2");
menu2.getItems().addAll(menuItems);
MenuBar menuBar = new MenuBar(menu1, menu2);
Scene scene = new Scene(menuBar);
stage.setScene(scene);
stage.show();
}
private MenuItem[] createCheckMenuItems() {
return new MenuItem[] {
new CheckMenuItem("Check 1"),
new CheckMenuItem("Check 2")
};
}
public static void main(String[] args) {
Application.launch();
}
}
We can fix this by just creating new check menu items for each menu.
Menu menu1 = new Menu("Menu 1");
menu1.getItems().addAll(createCheckMenuItems());
Menu menu2 = new Menu("Menu 2");
menu2.getItems().addAll(createCheckMenuItems());
But now the check menu items aren't in sync, if you change one, the other one doesn't automatically change. If you also want that behavior, you can use an MVC approach with a shared binding.
Fixed code with bidirectional binding to model properties
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class MenuItemApp extends Application {
class Model {
private final BooleanProperty boolean1 = new SimpleBooleanProperty();
private final BooleanProperty boolean2 = new SimpleBooleanProperty();
public BooleanProperty boolean1Property() {
return boolean1;
}
public BooleanProperty boolean2Property() {
return boolean2;
}
}
#Override
public void start(Stage stage) throws Exception {
Model model = new Model();
Menu menu1 = new Menu("Menu 1");
menu1.getItems().addAll(createCheckMenuItems(model));
Menu menu2 = new Menu("Menu 2");
menu2.getItems().addAll(createCheckMenuItems(model));
MenuBar menuBar = new MenuBar(menu1, menu2);
Scene scene = new Scene(menuBar);
stage.setScene(scene);
stage.show();
}
private MenuItem[] createCheckMenuItems(Model model) {
return new MenuItem[] {
createCheckMenuItem(1, model.boolean1Property()),
createCheckMenuItem(2, model.boolean2Property()),
};
}
private CheckMenuItem createCheckMenuItem(int n, BooleanProperty modelProperty) {
CheckMenuItem checkMenuItem = new CheckMenuItem("Check " + n);
checkMenuItem.selectedProperty().bindBidirectional(modelProperty);
return checkMenuItem;
}
public static void main(String[] args) {
Application.launch();
}
}
I'm currently made an Form with JavaFX.
Always i press a Button, i call the "addAnswer()"-Method.
In that I create a RadioButton, a Label and a delete-Button, which i bundle in a HBox. All that HBoxes i pack in a vBox.
The Problem now is the delete-Button. I want to delte just THAT HBox in which the clicked Button is.
Here is my code:
public void addAnswer() {
this.rB = new RadioButton();
checkAnswer.getToggles().add(rB);
hBox = new HBox();
tF = new TextField();
delAnswer = new Button("Löschen");
delAnswer.setId(Integer.toString(counter));
hBox.getChildren().addAll(rB, tF, delAnswer);
hBox.setId(Integer.toString(counter));
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBox.getId())));
System.out.println(delAnswer.getId());
vBox.getChildren().addAll(hBox);
counter++;
}
public void delAnswer(int e){
vBox.getChildren().remove(delAnswer.getId());
}
i tried this one above but i realized, that all the delAnswers-Buttons have the same ID: the number of how often i pressed the add-Button.
Is there any solution where i can just select that one i pressed with that dynamic way? Cause i don't kow how often somebody will press or delete something.
Thanks
hbox is a field and this is why always the HBox last added is used. (hBox is evaluated, when lambda body is executed, not at the time of the lambda creation). This would be different, if you used a (effectively) final local variable:
final HBox hBoxLocal = hBox;
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBoxLocal.getId())));
However I'd like to present a different solution which would allow you to use the same EventHandler<ActionEvent> for all delete Buttons:
You can get the Node that triggered the event using getSource. From this Node you can get the parent, which is the HBox. You can remove this from the VBox using the remove(Object) method
delAnswer.setOnAction(e -> {
// get button
Node source = (Node) e.getSource();
// remove parent of button from VBox
vBox.getChildren().remove(source.getParent());
});
I think your problem is that you give the same event to all your button,Begin by creating a list that stores your buttons and then increments the value of the ID after affecting it to an item :
List<Button> buttons = new ArrayList<>();
/*
Create Button and call IDEvt method to create new event
for each button
*/
private void IDEvt(Button btn){
btn.setId(String.valueOf(IDRank));
btn.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println(btn.getId());
}
});
IDRank++;
}
In order for the end-user to constrain a search to some columns of the main TableView, I needed a treeview with checkboxes.
I decided to embed this TreeView in a popup, showing on click on a custom button.
I have created the following class, inspired from the question:
Java FX8 TreeView in a table cell
public class CustomTreeMenuButton extends MenuButton {
private PopupControl popup = new PopupControl();
private TreeView<? extends Object> tree;
private CustomTreeMenuButton me = this;
public void setTree(TreeView<? extends Object> tree) {
this.tree = tree;
}
public CustomTreeMenuButton() {
super();
this.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (!popup.isShowing()) {
Bounds b = me.localToScreen(me.getBoundsInLocal());
double x = b.getMinX();
double y = b.getMaxY();
popup.setAutoHide(true);
// popup.setAutoFix(true);
popup.setAnchorX(x);
popup.setAnchorY(y);
popup.setSkin(new Skin<Skinnable>() {
#Override
public void dispose() {
}
#Override
public Node getNode() {
return tree;
}
#Override
public Skinnable getSkinnable() {
return null;
}
});
popup.show(me.getScene().getWindow());
}
}
});
}
}
The tree I am working with contains CheckBoxTreeItem objects, and while the popup is working, there is some weird blur on all checkboxes, whenever the focus is not on a checkbox. (See GIF below)
First, I was thinking it was maybe an antialiasing problem, but popup.getScene().getAntiAliasing().toString() returns DISABLED
Then, I saw that non integer anchor points could cause problems. However popup.setAutoFix(true) did nothing, nor did the following:
popup.setAnchorX(new Double(x).intValue());
popup.setAnchorY(new Double(y).intValue());
It might be worth noting that I am working with FXML.
How can I get sharp checkboxes regardless of their focus ?
I would suggest a built-in control, CustomMenuItem, rather than reinventing the wheel:
A MenuItem that allows for arbitrary nodes to be embedded within it,
by assigning a Node to the content property.
An example
// Create the tree
CheckBoxTreeItem<String> rootItem = new CheckBoxTreeItem<String>("All stuff");
rootItem.setExpanded(true);
final TreeView<String> tree = new TreeView<String>(rootItem);
tree.setEditable(true);
tree.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
for (int i = 0; i < 8; i++) {
final CheckBoxTreeItem<String> checkBoxTreeItem =
new CheckBoxTreeItem<String>("Stuff" + (i+1));
rootItem.getChildren().add(checkBoxTreeItem);
}
tree.setRoot(rootItem);
tree.setShowRoot(true);
// Create a custom menu item
CustomMenuItem customMenuItem = new CustomMenuItem(tree);
customMenuItem.setHideOnClick(false);
// Create the menu button
MenuButton mb = new MenuButton("Stuffs");
mb.getItems().add(customMenuItem);
And the output
Note: It is important to set the hideOnClickProperty to true, to avoid closing when the user clicks in the tree, which can be even done in the contructor, so you can shorten the initialization to:
CustomMenuItem customMenuItem = new CustomMenuItem(tree, false);
If you want to remove the hover glow, you can add the following CSS class:
.menu-item {
-fx-padding: 0;
}
I am working on an application in java FX. I need a data structure similar to JList in java swings.I have to use it in my project for displaying data on a scroll pane. I have tried using observable arraylist, with listview and Vbox. Here is my code snippet of the controller class.
public class Controller_class
implements Initializable {
#FXML // fx:id="myButton"
private Button dfctsave;
#FXML
final TextField dfctname = new TextField();
#FXML
ScrollPane dfctscroll = new ScrollPane ();
static ArrayList<String> jlstDefects=new ArrayList<String>();
#Override // This method is called by the FXMLLoader when initialization is complete
public void initialize(URL fxmlFileLocation, ResourceBundle resources)
{
dfctsave.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
ObservableList ad;
VBox v;
String d=dfctname.getText();
jlstDefects.add(d);
System.out.println(jlstDefects);
ad = FXCollections.observableArrayList(jlstDefects);
System.out.println(ad);
ListView lv = new ListView();
lv.setItems(ad);
v=new VBox();
v.getChildren().addAll(lv);
dfctscroll.setContent(v);
}
});
}}
It worked well and I got entries on to the scrollpane, but I need the index of the selected data items for swapping and further processing on scrollpane. I heard about SwingList which would work like the same. Can anyone explain the best alternates for Jlist in FX or explain how to use SwingList in FX.
I tried working with List view. Its working fine.For selecting a particular value on the list view and getting its index, I used the following code snippet.
public ListView<String> jlstDefects ;
public TextField fldDefectName;
private void jltDefectsListItemSelected()
{
int ndx = jlstDefects.getSelectionModel().getSelectedIndex();
if (ndxJlstDefectSelector == ndx)
return;
ndxJlstDefectSelector = ndx;
String strSelectedDefectName = lstDefectList.getDefect(ndx);
fldDefectName.setText(strLocalDefectName);
}
I have assigned the item which I got from the list view to a text field in my application.
Thanks for the suggestions provided.
In the application above I have a tabpane (the one with "age", "gender" and "zipcode") where each tab contains a VBox. The VBox is split in two:
The upper part of the VBox lets you view a list of lists, the lower part of the VBox contains a menu that allows you to change the list of lists.
The button in the lower part of the VBox updates the aforementioned list of lists. I want the upper node in the VBox to be updated anew when the underlying lists are changed.
The relevant code snippet might be
Node createHierarchySplitMenu(HierarchiesFromFile hierarchies, String hierarchyName){
VBox vBox = new VBox();
vBox.getChildren().add(createHierarchyScrollPane(hierarchies, hierarchyName));
vBox.getChildren().add(createHierarchyMenu());
return vBox;
}
When the button in the node in the lower part of the VBox (created by createHierarchyMenu()) I want createHierarchyScrollPane() to be called again to show the new list of lists. How do I do that?
Is there a regular pattern/way of updating one node from another (when they are at the same level.)?
What have you tried? Nothing worth mentioning- I am stuck.
If you need more info, please ask. Didn't want to bog you down with code.
From what I understand the solution can be next:
Create class to handle createHierarchyScrollPane():
private class HierarchyScrollPane extends ScrollPane {
public void update(HierarchiesFromFile hierarchies, String hierarchyName) {
// code from createHierarchyScrollPane() which works with "this" instead of new Scroll Pane
}
}
Pass instance to createHierarchyMenu():
Node createHierarchySplitMenu(HierarchiesFromFile hierarchies, String hierarchyName){
VBox vBox = new VBox();
ScrollPaneUpdate hsp = new HierarchyScrollPane();
hsp.update(hierarchies, hierarchyName);
vBox.getChildren().add(hsp);
vBox.getChildren().add(createHierarchyMenu(hsp));
return vBox;
}
Somewhere in createHierarchyMenu():
public void createHierarchyMenu(final HierarchyScrollPane hsp) {
// ...
Button btnSetMin = new Button("Set Min");
btnSetMinsetOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent t) {
// your code for data update
hsp.update(hierarchies, hierarchyName);
}
});
}