I'm totally new in Java and Im just trying out different little things. In the following code, I've set up a ListView into which I'm able to add new items via AddButton.
Now I'm wondering how to save those manually added items in my program so that next time Im running the code, not only the default items 1-5 within the code, but also the items I've manually added are shown in my table? I think I need to build some external library in which the manually added items are saved and be loaded every time I run the code again?
Also how can I enable marking and deleting multiple items with a single button click on delete?
And is there a another more detailed way to wright this code selectedProducts.forEach(allProducts:: remove); to see the background of whats happening here?
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
//BAUSTEINE - Eingabefeld:
TextField nameInput = new TextField();
nameInput.setMinWidth(100);
nameInput.setPromptText("Name");
TextField priceInput = new TextField();
priceInput.setMinWidth(100);
priceInput.setPromptText("Price");
TextField quantityInput = new TextField();
quantityInput.setMinWidth(100);
quantityInput.setPromptText("Quantity");
//BAUSTEINE - Tabelle:
TableColumn<Product, String> nameSpalte = new TableColumn<>();
nameSpalte.setText("Name");
nameSpalte.setMinWidth(200);
nameSpalte.setCellValueFactory(new PropertyValueFactory<>("Name"));
TableColumn<Product, Double> priceSpalte = new TableColumn<>();
priceSpalte.setText("Price");
priceSpalte.setMinWidth(200);
priceSpalte.setCellValueFactory(new PropertyValueFactory<>("price"));
TableColumn<Product, String> quantitySpalte = new TableColumn<>();
quantitySpalte.setText("Quantity");
quantitySpalte.setMinWidth(200);
quantitySpalte.setCellValueFactory(new PropertyValueFactory<>("quantity"));
TableView<Product> tabelle = new TableView<Product>();
tabelle.getColumns().addAll(nameSpalte, priceSpalte, quantitySpalte);
tabelle.setItems(getProduct());
//BAUSTEINE - Buttons:
Button addButton = new Button("Add");
addButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Product product = new Product();
product.setName(nameInput.getText());
product.setPrice(Double.parseDouble(priceInput.getText()));
product.setQuantity(Integer.parseInt(quantityInput.getText()));
tabelle.getItems().addAll(product);
nameInput.clear();
priceInput.clear();
quantityInput.clear();
}
});
Button deleteButton = new Button("Delete");
deleteButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
ObservableList<Product> allProducts = tabelle.getItems();
ObservableList<Product> selectedProducts = tabelle.getSelectionModel().getSelectedItems();
selectedProducts.forEach(allProducts:: remove);
}
});
//LAYOUT:
HBox hBox = new HBox();
hBox.setPadding(new Insets(10,10,10,10));
hBox.setSpacing(10);
hBox.getChildren().addAll(nameInput, priceInput, quantityInput, addButton, deleteButton);
VBox vBox = new VBox();
vBox.getChildren().addAll(tabelle, hBox);
//EIGENSCHAFTEN DER SCENE:
Scene scene = new Scene(vBox);
//EIGENSCHAFTEN DER STAGE:
stage.setScene(scene);
//PROGRAMMSTART:
stage.show();
}
public ObservableList<Product> getProduct() {
ObservableList<Product> products = FXCollections.observableArrayList();
products.add(new Product("Item 1", 859.00, 20));
products.add(new Product("Item 2", 2.49, 198));
products.add(new Product("Item 3", 99.00, 74));
products.add(new Product("Item 4", 19.99, 12));
products.add(new Product("Item 5", 1.49, 856));
return products;
}
}
Is probably too broad a question for this forum. You need to decide
on how you want the products represented in a persistent format. It
could be in a flat file, in a text format you define (in which case
you would just use standard java.io classes to read and write
them), you could use Java serialization (again using standard
java.io; note that this is not very popular these days and, while
it's easy to get it working, it can be tricky to get it to work
well). You could use a standard format such as JSON or XML, and use
a library (e.g. Jackson or Google GSON) to marshal/unmarshal your
products to this format. Or you could use a relational database and
JDBC or JPA to read/write to the database. (Once you've decided how you want them represented, try an implementation and post specific questions if you are stuck.)
Use tabelle.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Your current code is basically equivalent to
for (Product p : selectedProducts) {
allProducts.remove(p);
}
Note that this is a little fragile, because removing an item from the list may result in resetting the selected items (depending on the implementation of the selection model, etc), in which case bad things will happen when you try to iterate through a list that is changing. It's probably better to copy the selected items to a new list:
List<Product> selectedItems = new ArrayList<>(tabelle.getSelctionModel().getSelectedItems());
for (Product p : selectedItems) {
allProducts.remove(p);
}
and note that there's also this:
List<Product> selectedItems = new ArrayList<>(tabelle.getSelctionModel().getSelectedItems());
allProducts.removeAll(selectedItems);
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 need help creating a delete button on a stack pane in JavaFX.
It should remove the chosen stack pane from the group.
RinStackPane is extended javafx StackPane, gp is java fx Group.
Thank you in advance!
Here is my code:
private void setOnCreateMathOperation(Button createMathOperationBtn) {
createMathOperationBtn.setOnAction(event -> {
var mathOperation = createMathOperation();
RinStackPane stack1 = new RinStackPane();
stack1.setNodeType(NodesTypes.MathOperation);
stack1.setUnderlyingNode(mathOperation);
stack1.setName(mathOperation.getName());
stack1.getChildren().add(mathOperation);
TextField newtf1 = new TextField();
GridPane grid = new GridPane();
grid.addRow(1, newtf1);
stack1.getChildren().add(grid);
makeDraggable(stack1);
addToRectCollection(stack1);
gp.getChildren().add(stack1);
});
}
I am not sure if you want to keep the delete button inside or outside the StackPane.
Anyway you can easily bind an ObservableList to the Group's children. When an elemente will be deleted from the list, automatically this event will be reflected on Group's children:
//Create observable list of Node (you can constrain to RinStackPane if you prefer):
ObservableList<Node> list = FXCollections.observableArrayList();
//bind lists to gp's children:
Bindings.bindContentBidirectional(list, gp.getChildren());
Your code:
private void setOnCreateMathOperation(Button createMathOperationBtn) {
createMathOperationBtn.setOnAction(event -> {
RinStackPane stack1 = new RinStackPane();
//...
//setup the stack
//...
//Add the delete button to the stack:
Button deleteButton = new Button("Delete me!");
deleteButton.setOnMouseClicked(event -> {
list.remove(stack1); //<--- remove action
});
stack1.getChildren().add(deleteButton);
//add to the list instead of gp's children
list.add(stack1);
});
}
Note how the stack is not added nor removed to/from the gp's children, instead the list is used.
If you want the deleteButton outside the stack the code is the same, just be sure that both list and stack1 are somehow passed to the button's click handler.
Hope this helps.
I am trying to make a dynamically sized MenuButton.
I am designing a library program. Books go on Shelves. Over the course of the program, the number of shelves could increase or decrease.
I want to make a menu Button that can reflect the shelves in the library - the set of MenuItems should increase if the number of shelves increase and decrease if the number of shelves decrease.
This is my current code. However, it doesn't remove any MenuItems. Also, it duplicates all the MenuItems already included.
previous code omitted…
//the button “shelfBtn.getItems” is a MenuButton defined elsewhere
Button btn = new Button(“Refresh”);
btn.setTranslateX(-20);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for(String shelf : shelfs){
MenuItem newShelf = new MenuItem(shelf);
newShelf.setOnAction(new EventHandler<ActionEvent() {
#Override
public void handle(ActionEvent event) {
// ignore this
shelfField.setText(shelf);
}
});
shelfBtn.getItems().add(newShelf);
}
}
});
remaining code omitted…
I have also tried using iteration to limit extra menuItems from being created - to no avail.
Additionally:
1) Is there a way to just delete a menuItem?
2) Is there a way to clear a MenuButton?
Thanks
Simply modifiy the items ObservableList:
#Override
public void start(Stage primaryStage) {
ListView<String> listView = new ListView<>();
for (int i = 0; i < 26; i++) {
listView.getItems().add(Character.toString((char) ('a'+i)));
}
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
MenuButton menuButton = new MenuButton();
Button btn = new Button("Modify");
btn.setOnAction((ActionEvent event) -> {
// create menu items from selection
menuButton.getItems().clear();
for (String s : listView.getSelectionModel().getSelectedItems()) {
menuButton.getItems().add(new MenuItem(s));
}
});
Scene scene = new Scene(new VBox(listView, menuButton, btn));
primaryStage.setScene(scene);
primaryStage.show();
}
As with any List there are multiple ways to remove and add elements to the list, like add, remove, clear, ect.
Adding duplicates can be prevented by using a Set, e.g.
Set<String> items = new shelfBtn.getItems().stream()
.map(MenuItem::getText)
.collect(Collectors.toCollection(HashSet::new));
for(String shelf : shelfs){
if (items.add(shelf)) {
...
}
}
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.
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.