editable listview with custom object - java

So I have a listview filled with Objects of the class Tree, I want to be able to edit and add new items of the class tree to the listview and if possible when an item is selected and I press the delete key it will delete the tree object from the listview. the first item in the listview is the item that when overwriten it add the new item to the listview and spawns a new overwritable tree on the top of the listview, an exemple of this with strings and I want it with tree objects
public void start(Stage primaryStage) {
simpleList = new ListView<>(FXCollections.observableArrayList("add new Tree here","Item1", "Item2", "Item3", "Item4"));
simpleList.setEditable(true);
simpleList.setCellFactory(TextFieldListCell.forListView());
simpleList.setOnEditCommit(new EventHandler<ListView.EditEvent<String>>() {
#Override
public void handle(ListView.EditEvent<String> t) {
simpleList.getItems().set(t.getIndex(), t.getNewValue());
if (t.getIndex() == 0){
simpleList.getItems().add(0,"add new tree here");
}
}
});
simpleList.setOnEditCancel(new EventHandler<ListView.EditEvent<String>>() {
#Override
public void handle(ListView.EditEvent<String> t) {
System.out.println("setOnEditCancel");
}
});
BorderPane root = new BorderPane();
root.setCenter(simpleList);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public class Tree {
private int id;
public int getId() {
return id;
}
private String name;
public String getName() {
return name;
}
public Tree(int id, String name){
this.id = id;
this.name = name;
}
public String toString() {
return this.getName();
}
}
I know how to let it work with strings but don't know how I can make it work with custum objects, already searched and found I had to use a Callback object but can't manage to let it work, even after trying for serveral hours.
Thanks in advance!

You'll probably have to handle setting id of those objects somehow, but I hope this is what you were looking for
Start method:
public void start(Stage primaryStage) {
ListView<Tree> simpleList = new ListView<>(FXCollections.observableArrayList(new Tree(0, "add new tree here"), new Tree(1, "Tree one"), new Tree(2, "Tree two"), new Tree(1, "Tree three"), new Tree(1, "Tree four"), new Tree(1, "Tree five")));
simpleList.setEditable(true);
simpleList.setCellFactory(listView -> {
TextFieldListCell<Tree> cell = new TextFieldListCell<>();
cell.setConverter(new StringConverter<Tree>() {
#Override
public String toString(Tree tree) {
return tree.getName();
}
#Override
public Tree fromString(String string) {
Tree tree = cell.getItem();
tree.setName(string);
return tree;
}
});
return cell;
});
simpleList.setOnEditCommit(t -> {
simpleList.getItems().set(t.getIndex(), t.getNewValue());
if (t.getIndex() == 0) {
simpleList.getItems().add(0, new Tree(0, "add new tree here"));
}
});
// init delete item handler
simpleList.setOnKeyReleased(event -> {
if (event.getCode().equals(KeyCode.DELETE)) {
Tree selectedItem = simpleList.getSelectionModel().getSelectedItem();
simpleList.getItems().remove(selectedItem);
System.out.println(selectedItem + " deleted from list");
}
});
simpleList.setOnEditCancel(t -> System.out.println("setOnEditCancel"));
BorderPane root = new BorderPane();
root.setCenter(simpleList);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
Tree class:
public class Tree {
private int id;
private String name;
public Tree(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.getName();
}
}

Related

Create a listener logic for a custom combobox

I am trying to create a listener logic for a custom combo box that I have created that contains items with check boxes.
I was not able to proceed as I am not getting an idea on how to do it.
MainApplication.java
public class MainApplication extends Application{
#Override
public void start(Stage stage) {
Scene scene = new Scene(new VBox(), 450, 250);
ComboBox<ComboBoxItemWrap<Person>> cb = new ComboBox<>();
#SuppressWarnings("unchecked")
ObservableList<ComboBoxItemWrap<Person>> options = FXCollections.observableArrayList(
new ComboBoxItemWrap<>(new Person("A", "12-Aug-1994")),
new ComboBoxItemWrap<>(new Person("B", "13-Aug-1994")),
new ComboBoxItemWrap<>(new Person("C", "14-Aug-1994"))
);
cb.setCellFactory( c -> {
ListCell<ComboBoxItemWrap<Person>> cell = new ListCell<ComboBoxItemWrap<Person>>(){
#Override
protected void updateItem(ComboBoxItemWrap<Person> item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
final CheckBox cb = new CheckBox(item.toString());
cb.selectedProperty().bind(item.checkProperty());
setGraphic(cb);
}
}
};
cell.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
cell.getItem().checkProperty().set(!cell.getItem().checkProperty().get());
StringBuilder sb = new StringBuilder();
cb.getItems().filtered( f-> f!=null).filtered( f-> f.getCheck()).forEach( p -> {
sb.append("; "+p.getItem());
});
final String string = sb.toString();
cb.setPromptText(string.substring(Integer.min(2, string.length())));
});
return cell;
});
cb.setItems(options);
VBox root = (VBox) scene.getRoot();
Button bt = new Button("test");
bt.setOnAction(event -> {
cb.getItems().filtered( f -> f.getCheck()).forEach( item -> System.out.println(item.getItem()));
});
root.getChildren().addAll(cb, bt);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
ComboBoxItemWrap.java
public class ComboBoxItemWrap<T> {
private BooleanProperty check = new SimpleBooleanProperty(false);
private ObjectProperty<T> item = new SimpleObjectProperty<>();
ComboBoxItemWrap() {
}
ComboBoxItemWrap(T item) {
this.item.set(item);
}
ComboBoxItemWrap(T item, Boolean check) {
this.item.set(item);
this.check.set(check);
}
public BooleanProperty checkProperty() {
return check;
}
public Boolean getCheck() {
return check.getValue();
}
public void setCheck(Boolean value) {
check.set(value);
}
public ObjectProperty<T> itemProperty() {
return item;
}
public T getItem() {
return item.getValue();
}
public void setItem(T value) {
item.setValue(value);
}
#Override
public String toString() {
return item.getValue().toString();
}
}
Person.java
public class Person {
private StringProperty name = new SimpleStringProperty();
private StringProperty birthday = new SimpleStringProperty();
public Person() {
}
public Person(String name, String birthday) {
setNameValue(name);
setBirthdayValue(birthday);
}
public StringProperty getNameProperty() {
return name;
}
public String getNameValue() {
return name.getValue();
}
public void setNameValue(String value) {
name.setValue(value);
}
public StringProperty getBirthdayProperty() {
return birthday;
}
public String getBirthdayValue() {
return birthday.getValue();
}
public void setBirthdayValue(String value) {
birthday.setValue(value);
}
#Override
public String toString() {
return getNameValue()+" ("+getBirthdayValue()+")";
}
}
In the output application, a list of items with check boxes will get populated. On selection of any number of entries in the list, the entry name gets populated on the combo box itself separated by a ';'. Now I want my back end code to listen and identify the entries that have been selected in order to perform further operations.
You may not need to reinvent the wheel. Consider using ControlsFX CheckComboBox.
That being said there are several problems in the code:
You never update the property on a selection of the CheckBox. This can be easily fixed by using bidirectional bindings.
Since the ComboBox popup is closed, the CheckBox is no longer armed at the time the MOUSE_RELEASED event is triggered. this is a prerequesite for the selected state of the CheckBox changing though. Modifying the skin allows you to change this behaviour.
You use ObservableList.filtered to create FilteredLists that you throw away immediately afterwards. You also create a filtered list of a filtered list in the MOUSE_RELEASED event filter. This is not wrong per se, but you're creating an expensive object there without the need to do so: simply get a stream there. This is a much more lightweight way to filter a list, if the result is only needed once. Use filtered/FilteredList only if you need an ObservableList that contains elements from another ObservableList and that is automatically updated.
Also note that there is a way to make an ObservableList trigger update changes on a change of a property: Use the observableArrayList method taking an extractor as parameter.
This is how you could rewrite your code to make it work:
VBox root = new VBox();
Scene scene = new Scene(root, 450, 250);
ComboBox<ComboBoxItemWrap<Person>> cb = new ComboBox<>();
ObservableList<ComboBoxItemWrap<Person>> options = FXCollections.observableArrayList(item -> new Observable[] {item.checkProperty()});
options.addAll(
new ComboBoxItemWrap<>(new Person("A", "12-Aug-1994")),
new ComboBoxItemWrap<>(new Person("B", "13-Aug-1994")),
new ComboBoxItemWrap<>(new Person("C", "14-Aug-1994")));
cb.setCellFactory(c -> new ListCell<ComboBoxItemWrap<Person>>() {
private final CheckBox cb = new CheckBox();
#Override
protected void updateItem(ComboBoxItemWrap<Person> item, boolean empty) {
ComboBoxItemWrap<Person> oldItem = getItem();
if (oldItem != null) {
// remove old binding
cb.selectedProperty().unbindBidirectional(oldItem.checkProperty());
}
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
cb.selectedProperty().bindBidirectional(item.checkProperty());
cb.setText(item.toString());
setGraphic(cb);
}
}
});
// make sure popup remains open
ComboBoxListViewSkin<ComboBoxItemWrap<Person>> skin = new ComboBoxListViewSkin<>(cb);
skin.setHideOnClick(false);
cb.setSkin(skin);
cb.setItems(options);
cb.promptTextProperty().bind(Bindings.createStringBinding(() ->
options.stream().filter(ComboBoxItemWrap::getCheck).map(Object::toString).collect(Collectors.joining("; ")), options));
Note that if you want the popup to be closed after (de)selecting a checkbox, you could simply add a event filter for MOUSE_RELEASED for the checkbox that calls cb.arm() instead of modifying the skin.

JavaFx: show DatePicker

I have a ComboBox<MyItem> and I want to show a DatePicker when I select a special item from the ComboBox. I created a class that extends ComboBox, and I have a DatePicker in that class. I have added a listener to its selectedItemProperty:
public class CustomComboBox extends ComboBox<MyItem>{
private DatePicker datePicker;
public CustomComboBox(){
getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (MyItem.DATE.equals(newValue)) {
initDatePicker();
datePicker.show();
datePicker.requestFocus();
}
});
}
private void initDatePicker() {
if (datePicker == null) {
datePicker = new DatePicker();
datePicker.setFocusTraversable(false);
}
}
}
So if I select the DATE item the DatePicker should pop up and If I select a date I want to add as the value of the ComboBox
First of all why the datePicker not pops up? The second question is this posible to add the selected date to comboBox as value.
I assume you need something like this:
I did it by using a popup class from ControlsFX library.
Play with this demo app to understand the main idea.
import org.controlsfx.control.PopOver;
// here all other needed dependencies
public class Main extends Application {
private static final String DATE_TYPE = "DATE";
private class ComboBoxNode {
private Object value;
private String type;
private ComboBoxNode(final Object value, final String type) {
this.value = value;
this.type = type;
}
#Override
public String toString() {
return Objects.toString(value);
}
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final ObservableList<ComboBoxNode> items =
FXCollections.observableArrayList(
new ComboBoxNode(LocalDate.now(), DATE_TYPE),
new ComboBoxNode("11:35AM", "TIME"));
final PopOver datePopOver = new PopOver();
datePopOver.setTitle("Enter new date");
datePopOver.setCornerRadius(10);
datePopOver.setHeaderAlwaysVisible(true);
datePopOver.setCloseButtonEnabled(true);
datePopOver.setAutoHide(true);
final ComboBox<ComboBoxNode> customComboBox = new ComboBox<>(items);
customComboBox.getSelectionModel().selectedItemProperty().addListener((o, old, newNode) -> {
if (newNode != null) {
if (newNode.type.equals(DATE_TYPE)) {
final DatePicker datePicker = new DatePicker((LocalDate) newNode.value);
datePicker.valueProperty().addListener((obs, oldDate, newDate) -> {
items.set(customComboBox.getSelectionModel().getSelectedInde‌​x(), new ComboBoxNode(newDate, DATE_TYPE));
datePopOver.hide();
});
final StackPane stackPane = new StackPane(datePicker);
stackPane.setPadding(new Insets(10, 10, 10, 10));
datePopOver.setContentNode(stackPane);
datePopOver.show(customComboBox);
} else {
datePopOver.hide();
}
}
});
final FlowPane pane = new FlowPane(customComboBox);
pane.setPadding(new Insets(10, 10, 10, 10));
pane.setPrefWidth(400);
pane.setPrefHeight(300);
// Show Scene
final Scene scene = new Scene(pane);
primaryStage.setTitle("Popup calendar");
primaryStage.setScene(scene);
primaryStage.show();
}
}

JavaFX Switch Scene in a SplitPane?

At the Base I have an AnchorPane then a SplitPane. On the left pane I have a listView and depending on the list element selected, the right pane displays the appropriate content. The way I have done this is by overlapping AnchorPanes and setting them to .setVisible(false) initially and as they are selected I set them to .setVisible(true) like so :
public void listSelection() {
String selection = listView.getSelectionModel().getSelectedItem();
switch(selection) {
case "Speed of sound":
disableOld(); // disables old AnchorePane
response.setText("Speed of sound conversion");
AnchorPane1.setVisible(true);
break;
case "Temperature conversion":
disableOld();
response.setText("Temperature conversion");
AnchorPane2.setVisible(true);
break;
}
}
I would like to know how to produce the same effect visually but with different scenes as I would like for each new AnchorPane to have it's own FXML and ControllerClass.
You can implement something like this :
Your main class :
public void start(Stage primaryStage) throws IOException {
primaryStage.setTitle("Title");
primaryStage.setScene(createScene(loadMainPane("path_of_your_fxml")));
primaryStage.show();
}
private Pane loadMainPane(String path) throws IOException {
FXMLLoader loader = new FXMLLoader();
Pane mainPane = (Pane) loader.load(
getClass().getResourceAsStream(path));
return mainPane;
}
private Scene createScene(Pane mainPane) {
Scene scene = new Scene(mainPane);
return scene;
}
public static void main(String[] args) {launch(args); }
Then you can create a separate class call Navigator to store all your fxml paths:
public class Navigator {
private final String P1;
private final String P2;
//then you can implement getters...
public String getP1() {
return P1;
}
public String getP2() {
return p2;
}
private static FxmlController Controller;
public static void loadPane(String fxml) {
try {
FxmlController.setPane(
(Node) FXMLLoader.load(Navigator.class.getResource(fxml)));
} catch (IOException e) {
e.printStackTrace();
}
}
public Navigator() throws IOException {
this.P1 = "p1.fxml";
this.P2 = "p2.fxml";}
In your main FxmlController(which is the controller of the permanent layer of your application , rest of the stack-panes-{p1 and p2} will load on your permanent layer )
This is how you load layers on the main FxmlController :
#FXML
private StackPane stackPaneHolder;
...
public void setPane(Node node) {
if (stackPaneHolder.getChildren().isEmpty()) {
//if stackPaneHolder is empty
stackPaneHolder.getChildren().add(node);
} else {
if (stackPaneHolder.getClip() != node) {
//if stackPaneHolder is not empty then remove existing layer and add new layer
stackPaneHolder.getChildren().remove(0);
stackPaneHolder.getChildren().add(0, node);
}
}
}
Then you can load panes by pressing a button like below :
#FXML
private void btnAction(ActionEvent event) throws IOException {
Navigator.load(new Navigator().getP1());
..
This is how it works :

String array type variable's value doesn't get updated (returns empty when referred from another class). JavaFX

I am missing something fundamental here. I have a string array (String[] strArray) that is declared in a class (FirstController.java), and then initialized by a method. I am trying to print this array from another class (SecondController.java), but the array returned is empty. Please make me understand why this is happening. Thank you! Please see the following code (courtesy James_D):
FirstController.java (String[] strArray is declared and initialized here)
public class FirstController {
private Stage Stage2;
public String[] strArray; // this is the string array in question.
#FXML
private Button openStage2;
#FXML
private TextArea textArea1;
#FXML
private void openStage2Action(ActionEvent event) throws IOException {
Stage2 = new Stage();
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Second.fxml"));
Object root = fxmlLoader.load();
Scene scene = new Scene((Parent) root);
Stage2.setScene(scene);
SecondController secondController = (SecondController)fxmlLoader.getController();
secondController.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> obs, String oldValue, String newValue) {
textArea1.setText(newValue);
strArray = newValue.split("\n"); // here the string array is initialized.
// this string array gets printed by this
for(String s:strArray){
System.out.println(s);
}
}
});
Stage2.show();
}
//public String[] getStrArray(){
// return strArray;
//}
}
SecondController.java ("another class" from where the String[] strArray results as empty, when printed)
public class SecondController {
private StringProperty text = new SimpleStringProperty(this, "text", "");;
FirstController firstController = new FirstController();
#FXML
private TextArea textArea2 ;
public StringProperty textProperty() {
return text ;
}
public final String getText2() {
return text.get();
}
public final void setText(String text) {
this.text.set(text);
}
// ...
#FXML
private void showTextAction(ActionEvent event) {
text.set(textArea2.getText());
System.out.println(firstController.strArray); // this line prints empty array []
System.out.println(firstController.strArray.toString()); // this will result NullPointerException because the array is empty. But why it is empty?
}
}
First.java (Application class):
public class First extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("First.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {launch(args);}
}
So it looks like SecondController needs to access some kind of string array that is populated from elsewhere. Just define an ObservableList in SecondController and give it a get(...) method.
public class SecondController {
private StringProperty text = new SimpleStringProperty(this, "text", "");
// Remove any reference to FirstController
//FirstController firstController = new FirstController();
private ObservableList<String> strArray = FXCollections.observableArrayList();
#FXML
private TextArea textArea2 ;
public StringProperty textProperty() {
return text ;
}
public final String getText2() {
return text.get();
}
public final void setText(String text) {
this.text.set(text);
}
public ObservableList<String> getStrArray() {
return strArray ;
}
// ...
#FXML
private void showTextAction(ActionEvent event) {
text.set(textArea2.getText());
System.out.println(firstController.strArray); // this line prints empty array []
System.out.println(firstController.strArray.toString()); // this will result NullPointerException because the array is empty. But why it is empty?
}
}
and now in FirstController:
#FXML
private void openStage2Action(ActionEvent event) throws IOException {
Stage2 = new Stage();
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Second.fxml"));
Object root = fxmlLoader.load();
Scene scene = new Scene((Parent) root);
Stage2.setScene(scene);
SecondController secondController = (SecondController)fxmlLoader.getController();
secondController.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> obs, String oldValue, String newValue) {
textArea1.setText(newValue);
strArray = newValue.split("\n"); // here the string array is initialized.
// this string array gets printed by this
for(String s:strArray){
System.out.println(s);
}
}
});
secondController.getStrArray().setAll(...); // or addAll(...) as needed.
Stage2.show();
}

ArrayList displayed incorrectly

I'm testing to display Array List into JavaFX accordion:
public class Navigation {
// Object for storing conenctions
public static List<dataObj> list = new ArrayList<>();
private ObservableList<dataObj> data;
public class dataObj {
private String conenctionname;
public dataObj(String conenctionname) {
this.conenctionname = conenctionname;
}
public String getConenctionname() {
return conenctionname;
}
public void setConenctionname(String conenctionname) {
this.conenctionname = conenctionname;
}
}
public void initNavigation(Stage primaryStage, Group root, Scene scene) {
VBox stackedTitledPanes = createStackedTitledPanes();
ScrollPane scroll = makeScrollable(stackedTitledPanes);
scroll.getStyleClass().add("stacked-titled-panes-scroll-pane");
scroll.setPrefSize(395, 580);
scroll.setLayoutX(5);
scroll.setLayoutY(32);
root.getChildren().add(scroll);
}
private ScrollPane makeScrollable(final VBox node) {
final ScrollPane scroll = new ScrollPane();
scroll.setContent(node);
scroll.viewportBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> ov, Bounds oldBounds, Bounds bounds) {
node.setPrefWidth(bounds.getWidth());
}
});
return scroll;
}
/////////////////////////////////////////////////////////////////////////////////////
// Generate accordition with Connections, Tables and Description
private VBox createStackedTitledPanes() {
VBox stackedTitledPanes = new VBox();
stackedTitledPanes.getChildren().setAll(
createConnectionsList("Connections"));
((TitledPane) stackedTitledPanes.getChildren().get(0)).setExpanded(true);
stackedTitledPanes.getStyleClass().add("stacked-titled-panes");
return stackedTitledPanes;
}
//////////////////////////////////////////////////////////////////////////////
// Generate list with Connections
public TitledPane createConnectionsList(String title) {
initObject();
data = FXCollections.observableArrayList(list);
ListView<dataObj> lv = new ListView<>(data);
lv.setCellFactory(new Callback<ListView<dataObj>, ListCell<dataObj>>() {
#Override
public ListCell<dataObj> call(ListView<dataObj> p) {
return new ConnectionsCellFactory();
}
});
AnchorPane content = new AnchorPane();
content.getChildren().add(lv);
// add to TitelPane
TitledPane pane = new TitledPane(title, content);
return pane;
}
static class ConnectionsCellFactory extends ListCell<dataObj> {
#Override
public void updateItem(dataObj item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
for (int i = 0; i < list.size(); i++) {
setText(list.get(i).getConenctionname());
}
}
}
}
// Insert Some test data
public void initObject() {
dataObj test1 = new dataObj("test data 1");
dataObj test2 = new dataObj("test data 2");
list.add(test1);
list.add(test2);
}
}
But for some reason I cannot get proper list of Objects from the Array list and display them. I get this result:
The proper result should be test data 1 and test data 2. How I can fix this?
The problem is in the ConnectionsCellFactory, the method updateItem is called for every item in List. So in you code, for every cell you are setting the text for every item in the list
you should try:
static class ConnectionsCellFactory extends ListCell<dataObj> {
#Override
public void updateItem(dataObj item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getConenctionname());
}
}
}

Categories