I've written the following code.
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Callback;
public class App extends Application {
private ListView<String> listView;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
List<String> friendList = new ArrayList<String>();
friendList.add("Alice");
friendList.add("Bob");
listView = new ListView<>(FXCollections.observableArrayList(friendList));
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> p) {
ListCell<String> cell = new ListCell<String>() {
#Override
protected void updateItem(String t, boolean empty) {
super.updateItem(t, empty);
if (t != null) {
Label usernameLabel = new Label(t);
usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
Button callButton = new Button("Call");
callButton.setOnAction(e -> System.out.println("action")); // not working
callButton.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> System.out.println("entered"));
callButton.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("clicked")); // not working
HBox usernameBox = new HBox(5);
usernameBox.setAlignment(Pos.CENTER_LEFT);
usernameBox.getChildren().addAll(usernameLabel);
BorderPane borderPane = new BorderPane();
borderPane.setLeft(usernameBox);
borderPane.setRight(callButton);
VBox vbox = new VBox(3);
vbox.getChildren().addAll(borderPane);
setGraphic(vbox);
}
}
};
return cell;
}
});
stage.setScene(new Scene(listView));
stage.show();
}
}
If you look at the callButton, you see that it gets three different handlers. However, only the MOUSE_ENTERED event handler is really triggered. The other ones are completely ignored. What can be the problem?
EDIT: Added and removed some code, in order to make it runnable.
This is a known bug in JavaFX 8, which is fixed in the latest ea release (1.8.0_20).
As a workaround, create the controls once and register handlers with them, then just update their state in the updateItem(...) method:
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> p) {
Label usernameLabel = new Label();
usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
Button callButton = new Button("Call");
HBox usernameBox = new HBox(5);
usernameBox.setAlignment(Pos.CENTER_LEFT);
usernameBox.getChildren().addAll(usernameLabel);
BorderPane borderPane = new BorderPane();
borderPane.setLeft(usernameBox);
borderPane.setRight(callButton);
VBox vbox = new VBox(3);
vbox.getChildren().addAll(borderPane);
ListCell<String> cell = new ListCell<String>() {
#Override
protected void updateItem(String t, boolean empty) {
super.updateItem(t, empty);
if (t != null) {
usernameLabel.setText(t);
setGraphic(vbox);
} else {
setGraphic(null); // you will have weird bugs without this: don't omit it
}
}
};
callButton.setOnAction(e -> System.out.println("action: "+cell.getItem()));
callButton.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> System.out.println("entered "+ cell.getItem()));
callButton.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("clicked "+ cell.getItem()));
return cell;
}
});
Note that this "workaround" is really the preferred approach anyway, and the one that was intended by the designers of the "virtualized" controls like ListView, TableView, etc. The point is that updateItem(...) is potentially called very frequently by the application, whereas cells are created very rarely. By creating new controls in the updateItem(...) method you potentially introduce performance issues. Create them once for the cell, and then just configure them in updateItem(...). Note also how I just registered the event handlers once, and had the handlers refer to cell.getItem() to see which item is currently represented by the cell.
One last thing: you have a bug in your code (which I fixed). Since cells can be reused, including for the case where a cell displaying an item is reused for an empty cell, it's important that you always handle the case where the item is null (typically by setting text and/or graphic to null).
Could you add the code of getIconAndResizeTo16( String s ). I guess the node you return there consumes mouse clicks.
Here is a runnable example that demonstrates the issue. It is just a guess though.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class App extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) throws Exception {
Button callButton = new Button("", getIconAndResizeTo16("Phone"));
callButton.setOnAction(e -> System.out.println("clicked1")); // not working
callButton.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> System.out.println("entered"));
callButton.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("clicked")); // not working
Button chatButton = new Button("", getIconAndResizeTo16("Chat") );
chatButton.setOnAction(e -> System.out.println("clicked2")); // not working
HBox callIconBox = new HBox(3);
callIconBox.setAlignment(Pos.CENTER_RIGHT);
callIconBox.getChildren().addAll(callButton, chatButton);
stage.setScene(new Scene(callIconBox));
stage.show();
}
private Node getIconAndResizeTo16(String s) {
Label l = new Label("Consumes " + s + " Events");
l.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> { e.consume(); });
l.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> { e.consume(); });
return l;
}
}
Related
For some time i have been trying to get my tableview work as kind of spreadsheet that is updated by background thread and when cell get updated, it for few seconds higlights ( changes style ) and then goes back to original style.
I already know, that i can't store and set styles directly in table cell and i need some kind of backing class, that will hold this data. But tableview with its "reusing" of cells (using same cells for different data) acts really weird. When all cells fits on screen it works flawlessly for me, but once i place around 100 cells and it becomes scrollable it starts being buggy, sometimes styles ( or setted graphic) disappears and after scrolling appears, if i disable some top cells of view, some other cells after scrolling get disabled as well and so on. Is there any right way to do this?
What i need basically is
Background data thread ---updates--> tableview
Another thread --after few seconds removes style--> tableview
As i have it now, i have model class that holds data, style and reference to table cell where it should be ( i disabled ordering, so it should be ok ) and background thread updates data in model class, and that model class changes style on referenced cell and register itself in "style remover" thread, that after while removes style.
I think posting my actual code won't be useful, because once i've discovered that cells are being reused my code has become too complicated and a little bit unreadable so i want to completely redo it right way.
Peformance is not that important for me, there wont be more than 100 cells, but this highlighting and having buttons in tableview must work flawlessly.
This is how my app looks like now - for idea of what i need.
EDIT: here is link to my another question related to this.
The collaborators:
on the data side, a (view) model which has a recentlyChanged property, that's updated whenever the value is changed
on the view side, a custom cell that listens to that recentlyChanged property and updates its style as appropriate
The tricky part is to clean up cell state when re-used or not-used: the method that's always (hopefully!) called is cell.updateIndex(int newIndex), so that's the place to un-/register the listener.
Below a runnable (though crude ;) example
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import de.swingempire.fx.util.FXUtils;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TableCoreRecentlyChanged extends Application {
public static class RecentChanged extends TableCell<Dummy, String> {
private ChangeListener<Boolean> recentListener = (src, ov, nv) -> updateRecentStyle(nv);
private Dummy lastDummy;
/*
* Just to see any effect.
*/
protected void updateRecentStyle(boolean highlight) {
if (highlight) {
setStyle("-fx-background-color: #99ff99");
} else {
setStyle("-fx-background-color: #009900");
}
}
#Override
public void updateIndex(int index) {
if (lastDummy != null) {
lastDummy.recentlyChangedProperty().removeListener(recentListener);
lastDummy = null;
}
updateRecentStyle(false);
super.updateIndex(index);
if (getTableRow() != null && getTableRow().getItem() != null) {
lastDummy = getTableRow().getItem();
updateRecentStyle(lastDummy.recentlyChangedProperty().get());
lastDummy.recentlyChangedProperty().addListener(recentListener);
}
}
#Override
protected void updateItem(String item, boolean empty) {
if (item == getItem()) return;
super.updateItem(item, empty);
if (item == null) {
super.setText(null);
super.setGraphic(null);
} else {
super.setText(item);
super.setGraphic(null);
}
}
}
private Parent getContent() {
TableView<Dummy> table = new TableView<>(createData(50));
table.setEditable(true);
TableColumn<Dummy, String> column = new TableColumn<>("Value");
column.setCellValueFactory(c -> c.getValue().valueProperty());
column.setCellFactory(e -> new RecentChanged());
column.setMinWidth(200);
table.getColumns().addAll(column);
int editIndex = 20;
Button changeValue = new Button("Edit");
changeValue.setOnAction(e -> {
Dummy dummy = table.getItems().get(editIndex);
dummy.setValue(dummy.getValue()+"x");
});
HBox buttons = new HBox(10, changeValue);
BorderPane content = new BorderPane(table);
content.setBottom(buttons);
return content;
}
private ObservableList<Dummy> createData(int size) {
return FXCollections.observableArrayList(
Stream.generate(Dummy::new)
.limit(size)
.collect(Collectors.toList()));
}
private static class Dummy {
private static int count;
ReadOnlyBooleanWrapper recentlyChanged = new ReadOnlyBooleanWrapper() {
Timeline recentTimer;
#Override
protected void invalidated() {
if (get()) {
if (recentTimer == null) {
recentTimer = new Timeline(new KeyFrame(
Duration.millis(2500),
ae -> set(false)));
}
recentTimer.playFromStart();
} else {
if (recentTimer != null) recentTimer.stop();
}
}
};
StringProperty value = new SimpleStringProperty(this, "value", "initial " + count++) {
#Override
protected void invalidated() {
recentlyChanged.set(true);
}
};
public StringProperty valueProperty() {return value;}
public String getValue() {return valueProperty().get(); }
public void setValue(String text) {valueProperty().set(text); }
public ReadOnlyBooleanProperty recentlyChangedProperty() { return recentlyChanged.getReadOnlyProperty(); }
public String toString() {return "[dummy: " + getValue() + "]";}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
// primaryStage.setTitle(FXUtils.version());
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableCoreRecentlyChanged.class.getName());
}
so I'm writing a javafx app and I need to be able to select the cells from the list view (for copy paste purposes) but I don't want to make it editable, I mean, the content cannot be changed unless I want to (allowing it through a button, for example).
So I have the following code:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
List<String> contacts = new ArrayList<>(Arrays.asList("968787522","3424234234","2343234324"));
ListView<String> contactsList = new ListView();
contactsList.setItems(FXCollections.observableArrayList(contacts));
//this gives me the ability to edit the row as text field but I want this text field to not be editable
contactsList.setCellFactory(TextFieldListCell.forListView());
StackPane pane = new StackPane();
pane.getChildren().add(contactsList);
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show(); }
public static void main(String[] args) {
launch(args);
}
}
and if I set 'contactsList' as not editable, I'm not able to edit, neither select.
As you can see (image bellow),I'm editing the cell, but I want to be able to select the text(not the item), but I don't want to be able to delete characters (text selectable but not editable).
so after breaking my head off, lots of research and API reading, I came up with a solution. This does EXACTLY what I wanted to do. Here is the demo if someone needs it ;)
So the idea is, each time we want to select the content of a row we need to select the row, get the textField and set the editing to true or false, (every time).
So in the demo that I made, I placed a button so you can toggle the editing to true or false to be sure that's is working, and how is working.
Cheers.
I commented some of the code for better understanding, if you have any questions about this just let me know.
package sample;
import com.sun.javafx.scene.control.skin.VirtualFlow;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main extends Application {
private boolean editable = false;
public static IndexedCell getCell(final Control control, final int index) {
return getVirtualFlow(control).getCell(index);
}
public static VirtualFlow<?> getVirtualFlow(Control control) {
Group group = new Group();
Scene scene = new Scene(group);
Stage stage = new Stage();
if(control.getScene() == null) {
group.getChildren().setAll(control);
stage.setScene(scene);
stage.show();
}
VirtualFlow<?>flow = (VirtualFlow<?>) control.lookup("#virtual-flow");
return flow;
}
public void setEditable(ListView contactsList){
//this needs to be done since we need to run our code after the text field was rendered
//so we need to invoke our code after this happens, if not it will throw a null pointer...
Platform.runLater(() -> {
//this is one of the most important guys because javafx api says that
//TextFieldListCell.forListView() allows editing of the cell content when the cell is double-clicked,
// or when {#link ListView#edit(int)} is called.
int rowIndex = contactsList.getSelectionModel().getSelectedIndex();
contactsList.edit(rowIndex);
ListCell rootCell = (ListCell) getCell(contactsList, rowIndex);
TextField textField = (TextField) rootCell.getGraphic();
textField.setEditable(editable);
});
}
#Override
public void start(Stage primaryStage) throws Exception{
List<String> contacts = new ArrayList<>(Arrays.asList("968787522","3424234234","2343234324"));
ListView<String> contactsList = new ListView();
contactsList.setItems(FXCollections.observableArrayList(contacts));
contactsList.setEditable(true);
//this gives me the ability to edit the row as text field but I want this text field to not be editable
contactsList.setCellFactory(TextFieldListCell.forListView());
contactsList.setOnEditStart(e -> {
setEditable(contactsList);
});
StackPane pane = new StackPane();
Button editBtn = new Button("Toggle edit");
editBtn.setOnAction(event -> {
editable = !editable;
editBtn.setText("Editing = " + editable);
//to cancel any editing that might be occuring
contactsList.getSelectionModel().clearSelection();
});
pane.getChildren().addAll(contactsList,editBtn);
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If I understand you correctly, it is not necessary to set the listview to 'not editable', as the default behaviour should suffice for your purpose. Take a look at this code, for example:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class NewFXMain extends Application {
#Override
public void start(Stage primaryStage) {
ListView listView = new ListView();
listView.getItems().addAll("one","two","three","four");
listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println(listView.getSelectionModel().getSelectedItem());
}
});
StackPane root = new StackPane();
root.getChildren().add(listView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("ListView Example");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I changed nothing about the editable-property of the ListView, but I can select every item, without being able to edit it (in the sense of changing its value). You can easily add an EventHandler to the ListView to perform whatever operation you want to perform. You could also add an EventHandler to every cell of the ListView by manipulating the CellFactory, as shown in this answer: How to handle ListView item clicked action?
Here's what works for me:
TableView<DataBean> table = new TableView<>();
table.setItems(...); // list of some DataBean objects with dataBeanField proprty
table.setEditable(true);
TableColumn<DataBean, String> column = new TableColumn<>("SomeData");
column.setCellValueFactory(new PropertyValueFactory<DataBean, String>("dataBeanField"));
column.setCellFactory(new Callback<TableColumn<DataBean, String>, TableCell<DataBean, String>>() {
#Override
public TableCell<DataBean, String> call(TableColumn<DataBean, String> param) {
return new TextFieldTableCell<>(new DefaultStringConverter() {
private String defaultValue = "";
#Override
public String fromString(String newValue) {
return super.fromString(defaultValue);
}
#Override
public String toString(String value) {
return defaultValue = super.toString(value);
}
});
}
});
Basically my code is like this:
fileOpener.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent e) {
myFileList.add(openMusicTracks.showOpenDialog(window));
System.out.println(myFileList.getName(0)); //prints file name so I know this works
}
});
I want the add method (that's inside of the EventHandler) to actually edit the arraylist for everywhere else so that later when I reference it in
ObservableList<String> playList = FXCollections.observableArrayList ();
for(int i = 0; i < myFileList.size(); i++) {
playList.add(i, myFileList.get(i).getName());
System.out.println(myFileList.getName(0)); //doesn't print the file name, so I know this doesn't work.
}
the arraylist won't be empty. How do I do this? I'm sorry if there's a more elegant way to word this, but I have honestly no idea how to research this, I've tried. Thanks.
A simple example which shows how can an ArrayList be shared between methods.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class Main extends Application {
private List<String> list = new ArrayList<>();
#Override
public void start(Stage primaryStage) {
Button add = new Button("Add");
Button display = new Button("Show");
// Add Items
add.setOnAction(event -> list.add("Item"));
// Display Items
display.setOnAction(e -> {
printAndClear();
});
VBox root = new VBox(10, add, display);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
private void printAndClear() {
list.forEach(System.out::println);
list.clear();
}
public static void main(String[] args) {
launch(args);
}
}
I have an application which uses JavaFX. It contains a ListView (which uses a ObservableList). I added a ChangeListener using
list.getSelectionModel().selectedItemProperty().addListener(new ChangeListener...
and it works fine. Every time I select an oher item, the listener is called.
But it is also called when I remove an element from the ObservableList.
After the element is removed, an other element of the list is automatically selected and the listener is called.
How can I prevent this behaviour?
Thanks!
In case my comment was too cryptic;
package listchange;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ListChange extends Application {
#Override
public void start(Stage primaryStage) {
ObservableList<String> data = FXCollections.observableArrayList();
data.addAll("one","two","three","four");
ChangeListener changeListener = new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
System.out.println("new val "+newValue);
}
};
ListView lv = new ListView(data);
lv.getSelectionModel().selectedItemProperty().addListener(changeListener);
data.addListener(new ListChangeListener<String>() {
#Override
public void onChanged(ListChangeListener.Change<? extends String> c) {
c.next();
if (c.wasRemoved()){
lv.getSelectionModel().selectedItemProperty().removeListener(changeListener);
}
}
});
Button b = new Button("delete");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//you can remove listener here or in data ListChangeListener
//lv.getSelectionModel().selectedItemProperty().removeListener(changeListener);
if (data.size() > 0) data.remove(0);
//you have to re-add the listener after removing
lv.getSelectionModel().selectedItemProperty().addListener(changeListener);
}
});
VBox root = new VBox();
root.getChildren().addAll(lv,b);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
}
This way you'll still get selection changed events when traversing using keys. If you know where the deletion takes place, it's easy to just remove and then re-add the listener.
Try this:
final ObservableList<String> fruits = FXCollections.observableArrayList("Apple", "Banana", "Pear", "Strawberry", "Peach", "Orange", "Plum", "Melon", "Cherry", "Blackberry", "Melon", "Cherry", "Blackberry");
final ComboBox fruit = new ComboBox(fruits);
fruit.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
public void changed(ObservableValue<? extends String> ov, String old_val, String new_val) {
//TODO: your remove method
}
});
I found a solution that works, but it is not a good solution.
Instead of using a listener for selection changes, i only handle mouse click events on the list. When I receive a click event, I request the selected element from the ListView.
This is not called when I remove or add an element. Just when clicking on the list.
(This is code from the book "JavaFX 2.0 by example" by Carl Dea - the code example is freely available at Apress so I'm sure they don't mind me using it here)
I have example code which works perfectly
package javafx2introbyexample.chapter1.recipe1_11;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
*
* #author cdea
*/
public class CreatingAndWorkingWithObservableLists extends Application {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 1-11 Creating and Working with ObservableLists");
Group root = new Group();
Scene scene = new Scene(root, 400, 250, Color.WHITE);
// create a grid pane
GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(5));
gridpane.setHgap(10);
gridpane.setVgap(10);
// candidates label
Label candidatesLbl = new Label("Candidates");
GridPane.setHalignment(candidatesLbl, HPos.CENTER);
gridpane.add(candidatesLbl, 0, 0);
Label heroesLbl = new Label("Heroes");
gridpane.add(heroesLbl, 2, 0);
GridPane.setHalignment(heroesLbl, HPos.CENTER);
// candidates
final ObservableList<String> candidates = FXCollections.observableArrayList("Superman",
"Spiderman",
"Wolverine",
"Police",
"Fire Rescue",
"Soldiers",
"Dad & Mom",
"Doctor",
"Politician",
"Pastor",
"Teacher");
final ListView<String> candidatesListView = new ListView<String>(candidates);
candidatesListView.setPrefWidth(150);
candidatesListView.setPrefHeight(150);
gridpane.add(candidatesListView, 0, 1);
// heros
final ObservableList<String> heroes = FXCollections.observableArrayList();
final ListView<String> heroListView = new ListView<String>(heroes);
heroListView.setPrefWidth(150);
heroListView.setPrefHeight(150);
gridpane.add(heroListView, 2, 1);
// select heroes
Button sendRightButton = new Button(">");
sendRightButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
String potential = candidatesListView.getSelectionModel().getSelectedItem();
if (potential != null) {
candidatesListView.getSelectionModel().clearSelection();
candidates.remove(potential);
heroes.add(potential);
}
}
});
// deselect heroes
Button sendLeftButton = new Button("<");
sendLeftButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
String notHero = heroListView.getSelectionModel().getSelectedItem();
if (notHero != null) {
heroListView.getSelectionModel().clearSelection();
heroes.remove(notHero);
candidates.add(notHero);
}
}
});
VBox vbox = new VBox(5);
vbox.getChildren().addAll(sendRightButton,sendLeftButton);
gridpane.add(vbox, 1, 1);
GridPane.setConstraints(vbox, 1, 1, 1, 2,HPos.CENTER, VPos.CENTER);
root.getChildren().add(gridpane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
It is code for moving persons back and forth between two listviews, one at a time. What I want to do is make it possible to select and move several persons in one click.
The relevant excerpts I want to change are:
final ListView<String> candidatesListView = new ListView<String>(candidates);
candidatesListView.setPrefWidth(150);
candidatesListView.setPrefHeight(150);
gridpane.add(candidatesListView, 0, 1);
// heros
final ObservableList<String> heroes = FXCollections.observableArrayList();
final ListView<String> heroListView = new ListView<String>(heroes);
...
// select heroes
Button sendRightButton = new Button(">");
sendRightButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
String potential = candidatesListView.getSelectionModel().getSelectedItem();
if (potential != null) {
candidatesListView.getSelectionModel().clearSelection();
candidates.remove(potential);
heroes.add(potential);
}
}
});
// deselect heroes
Button sendLeftButton = new Button("<");
sendLeftButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
String notHero = heroListView.getSelectionModel().getSelectedItem();
if (notHero != null) {
heroListView.getSelectionModel().clearSelection();
heroes.remove(notHero);
candidates.add(notHero);
}
}
});
What I have tried changing:
First I add the following import:
import javafx.scene.control.SelectionMode;
Then I add the lines
candidatesListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
heroListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
beneath the respective declarations of the two lists.
Lastly I change the code of the handling of the eventbutton to
public void handle(ActionEvent event) {
ObservableList<String> potential = candidatesListView.getSelectionModel().getSelectedItems();
if (potential != null) {
System.out.println(potential);
candidates.removeAll(potential);
heroes.addAll(potential);
candidatesListView.getSelectionModel().clearSelection();
}
}
That is- I change it to getSelectedItem_s_, then I addAll and removeAll instead of merely adding/removing one person. This just leaves the listView blank when I try to move several people over. What gives?
Ps. I also tried just adding/removing several people one at a time by iterating over the list "potential", but that also gave the wrong result.
Unfortunately you've met a bug: http://javafx-jira.kenai.com/browse/RT-24367
The original problem is next: ListView.getSelectionMode() returns part of it's observable list but not the copy. So removing from that list leads to various issues.
Use next code which copies list before removing items from it:
sendRightButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
ObservableList<String> potential =
FXCollections.observableArrayList( //copy
candidatesListView.getSelectionModel().getSelectedItems());
if (potential != null) {
heroes.addAll(potential);
candidates.removeAll(potential);
candidatesListView.getSelectionModel().clearSelection();
}
}
});