I've a couple of copied elements in an observablelist which I use for copy/paste operations in a TableView. The name of the table is cpTable (copy and paste Table) for storing copied elements and to paste elements that are stored in the table. After each paste operation I want to clear the contents of cpTable before I copy other selected items with Ctrl+C.
But I always get the error:
JavaFX Application Thread" java.lang.UnsupportedOperationException:
Not supported.
at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.remove(ReadOnlyUnbackedObservableList.java:246)
Here is my pseudocode:
if (cpTable !=null) {
//first, get all copied items for removing all elements
ObservableList<String> copiedItems = cpTable.getItems();
int size = copiedItems.size();
// remove all elements
for(int i=0;i<size;i++) {
copiedItems.remove(i);
}
cpTable.setItems(copiedItems); //clear cpTable by setting an empty list
}
This is a method that copies the contents of selected items and puts it in a cpTable
public TableView<String> copySelectionToClipboard(TableView<String> table) {
ObservableList<String> data = table.getSelectionModel().getSelectedItems();
TableView<String> tmp = new TableView<>();
tmp.setItems(data);
return tmp;
}
When Ctrl+C is pressed the following line puts all copied items in a cpTable:
cpTable = copySelectionToClipboard( (TableView<String>) keyEvent.getSource());
As mentioned I want to clear all cpTable contents immediately after pasting
the items in a table.
Just clear your Observable List. It looks like you should use copiedItems.clear();
That should clear your table.
As James_D already mentioned, you haven't cleared exactly what's the point.
If you want to delete selected items from a table, you need to delete them from the table item list itself and not from the selection model.
A possible solution looks like this:
TableView<String> table = new TableView<>();
ObservableList<String> tableItems = table.getItems();
// needs multirowselection is set to true
ObservableList<String> readOnlyItems = table.getSelectionModel().getSelectedItems();
// removes all selected elements for the table
readOnlyItems.stream().forEach((item) -> {
tableItems.remove(item);
});
// clear the selection
table.getSelectionModel().clearSelection();
Update
This method get's an TableView, calls it's selection model to get all selected items. And then you add the data to a new TableView. And there is the problem! It's an unmodifiable read only list that you attached to your new table. First make it modifiable, like in the code below:
public TableView<String> copySelectionToClipboard(TableView<String> table) {
ObservableList<String> readOnlyData = table.getSelectionModel().getSelectedItems();
ObservableList<String> writableData = FXCollections.<String>observableArrayList(readOnlyData);
TableView<String> tmp = new TableView<>();
tmp.setItems(writableData);
return tmp;
The next problem is in your call to this method. You call it with a TableView<CsvData> and with a TableView<String> as your method needs. If CsvData is a subtype of String, than you have to change your method signature to TableView<? extends String>
If you are trying to clear all the items from your tableView and want just an empty tableView. You can use this:
myTableView.getItems().clear();
This basically gets all the items from your table view which is nothing but just the observable list now it performs clear operations to remove all the items in tableView.
Assuming you mean
table.getSelectionModel().getSelectedItems()
(since the selection model has no getItems() method), according to the Javadocs, this returns a read-only list. Thus attempting to modify the list will throw an UnsupportedOperationException.
To clear the selection, do
table.getSelectionModel().clearSelection();
(And similarly, if you want to manipulate the selection in any other way, you use methods on the selection model, rather than on the list.)
Related
I created a TableView via SceneBuilder so that it receives dynamic data, that is, I will always have distinct SQL scripts whose data I want to display in the TableView.
So the columns are created according to the amount of fields I detect in sql.
This is already being done. The problem is in the code for popular TableView rows.
For this, I get an ArrayList <String> with resultSet data, as it is dynamic information,
This ArrayList <String> receives each record with the fields separated by #. Then I get the
Number of fields I will have as columns and created TableView columns with similar titles To the field names. That works perfectly.
Then I created a loop to get the ArrayList <String> elements, called
Of aListString.At where:
Data [] [] has the number of rows according to the number of elements of aListString and the quantity Of columns according to the number of fields (qtdeCampos). Until now resolved.
Then the loop fills the array data [] [] without problem, each row with their respective fields.
Finally, I fill out the TableView with:
tableViewResultado.getItems().addAll(Arrays.asList(dados));
Since the TableView is defined as:
#FXML
private TableView<String[]> tableViewResultado;
However, the rows are not populated with the data data [] [], whose information is correctly Stored in this matrix. The code:
for(int j=0;j<qtdeCampos;j++)
{
TableColumn<String[],String> column = new TableColumn();
column.setText(arrayListCampos.get(j).getNomeCampo());
aTableColumn.add(column);
tableViewResultado.getColumns().add(column);
}
String dados[][] = new String[aListString.size()][qtdeCampos];
for(int i=0;i<aListString.size();i++)
{
String dadosX[]=aListString.get(i).split("#");
for(int k=0;k<dadosX.length;k++)
{
dados[i][k]=dadosX[k];
}
}
Where is the error?
Thank you for your attention.
i have the following method:
CreateStrip createStrip = new CreateStrip(input);
depBox.getChildren().add(createStrip.getStripGrid());
depBox is a VBox and .getStripGrid() return a GridPane.
CreateStrip has this method too:
public String getNameStrip() { return input.getNameStrip();}
during the life of the program depBox get many GridPane, each one with a different NameStrip.
sometimes i have the necessity to remove a specific GridPane from depBox that match with .getNameStrip().
i have tried:
for (Node node: depBox.getChildren()) {
//TODO REMOVE GRIDPANE CONTAIN THE NAME THAT MATCH WITH THE MESSAGE RECEIVED..
}
but i don't know how to set the matching control.
Step 1:
Attach data to the GridPane that allows you to identify the one to remove.
You could do this by using Node.setUserData or by using the properties Map (which I'll do in the following code snippets).
Creating the GridPane
GridPane gp = createStrip.getStripGrid();
gp.getProperties().put(NAME_KEY, createStrip);
depBox.getChildren().add(gp);
// use object not equal to any other object as key (i.e. equals uses reference equality)
static final Object NAME_KEY = new Object();
Step 2:
Use the information to remove the appropriate GridPane. searchName is the String that identifies the GridPane you want to remove (to check for equality with getNameStrip()):
depBox.getChildren().removeIf(c -> {
CreateStrip strip = (CreateStrip) c.getProperties().get(NAME_KEY);
return strip != null && searchName.equals(strip.getNameStrip());
});
Depending on your CreateStrip class it may not be necessary to add a instance of it as property. It may not even be the right thing to do, if it's a factory, but I think you get the idea nontheless.
Alternative
You can also assign a value to the id property of the Node and use those to identify the correct node using Node.lookup. However those need to be unique and be a valid css id, but you could use a Map to map from message to id.
It seems a simple question, but I can't find a solution myself.
I have a TableView (to be frank I have two TableViews and would like to put the items from one to the other) with a large number of rows (for my tests, I had a little more than 6600) in it.
When I move the items, I would like to modify the selection of the destination table to be exactly the ones freshly selected.
#FXML
private TableView<PositionWrapper> source;
#FXML
private TableView<PositionWrapper> target;
private ObservableList<PositionWrapper> sourceList = FXCollections.observableArrayList();
private ObservableList<PositionWrapper> targetList = FXCollections.observableArrayList();
private void moveSourceToTarget(MouseEvent event) {
// collecting selected items
ObservableList<PositionWrapper> selectedItems =
FXCollections.observableArrayList(
source.getSelectionModel().getSelectedItems());
// Adding selected items to the target
targetList.addAll(selectedItems);
// Removing selected items from source
sourceList.removeAll(selectedItems);
// Clearing the selection in both table (source has none of the
// selected items now, the target selection should be updated completely)
target.getSelectionModel().clearSelection();
source.getSelectionModel().clearSelection();
// My first try. Doesn't work (UnsupportedOperationException)
target.getSelectionModel().getSelectedItems().setAll(selectedItems);
// Second try: doing it one-by-one. Works, but extremely slow.
selectedItems.stream().forEach(p -> target.getSelectionModel().select(p));
}
The second alternative works, but extremely slow. (How slow I don't know, because I killed my program after 20 seconds.)
My question: how to select a large number of rows in a TableView quickly?
I made some deeper search and checked the implementation source code of the MultipleSelectionModelBase class. When selecting an item by row content (as I did in my second try), it iterates over all the items in the table to find the index of it, then calls the select(int) method with the index found.
It means that selecting several rows by content is an n*m algorithm, with a worst case of n^2 when selecting (almost) all items.
But this gave me the solution as well. As it is now proved to me that only the by-value selection suffers from the n^2 anomaly, the natural workaround is to use the index-based selection.
Here is my code snippet:
private void moveSourceToTarget(MouseEvent event) {
[...]
// Allocating an array to store indexes
int[] idx = new int[selectedItems.size()];
int p = 0;
// Performance tuning to move selected items into a set
// (faster contains calls)
Set<PositionWrapper> s = new HashSet<>(Arrays.asList(
selectedItems.toArray(new PositionWrapper[0])));
// Iterating over items in target list (but only once!)
for (int i = 0; i < target.getItems().size(); i++) {
if (s.contains(target.getItems().get(i))) {
// and adding to the list of indexes when selected
idx[p++] = i;
}
}
// Calling the more effective index-based selection setter
target.getSelectionModel().selectIndices(-1, idx);
}
Aftermath: the time required for selecting the 6600+ items reduced from 20+ sec to 0.1 sec.
BTW, I don't know why isn't selectAll(S...) is part of the JavaFX code...
because I don't really know the sollution.
Lets say i have a TableView that holds info about product: description, quantity and the price. Also have a label that shows a sum of prices (times quantity) of all products from a table. Now i want to be notified about any changes that took affect on this component like changes in existing rows or addition of a new one. Is there any listener or sth similar to achieve it?
Usually you construct a TableView by passing an ObservableList to it. Something like this:
TableView myTable = new TableView<>(myObservableList);
ObservableList<ClassOfTheObjectsInTheList> myObservableList = FXCollections.FXCollections.observableArrayList(anyNoneObservableCollection);
TableView<ClassOfTheObjectsInTheList> myTable = new TableView<>(myObservableList);
You can add a ListChangeListener to any ObservableList at will by doing:
myObservableList.addListener(new ListChangeListener<ClassOfObjectsInTheList>(){
#Override
public void onChanged(javafx.collections.ListChangeListener.Change<? extends ClassOfObjectsInTheList> pChange) {
while(pChange.next()) {
// Do your changes here
}
}
});
I have an application that I am building that has a table in it, I'm not using a tableview to build this table because I need each row to be able to expand similar to an accordion. I was able to achieve what I need by using a timeline and looping through the data and building each row (its kind of crude right now since I'm still working with dummy data eventually it will be a list iterator and not just a for loop) but I'm not happy with how its done. There are a lot of default values that will never change so I don't really need to set them in my worker class every time, I decided to just add them to the object class that I put together. So basically, at a high level it looks something like this:
for (int i = 0; i < 30; i++) {
RowBuilder builder = new RowBuilder(tableBox, i);
try {
builder.run();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
I'm passing it the parent which is a VBox - tableBox, then I'm passing the count for later use.
Inside the RowBuilder I'm getting a new instance of the object DashboardRow which has all the defaults set in it, then I'm setting the data for each row and returning the DashboardRow.
Here is an example of a getter setting values in the DashboardRow
public HBox getMainRow() {
mainRow.setAlignment(Pos.CENTER_LEFT);
mainRow.setPrefHeight(60);
mainRow.setMinHeight(60);
mainRow.setMaxHeight(60);
mainRow.setPrefWidth(-1);
mainRow.setStyle("-fx-background-color:#FFFFFF;");
return mainRow;
}
Inside the DashboardRow class I have a ton of new objects being created for every element I need in the row. There are 21 for each row, mostly VBox, HBox and StackPane to build the actual row, the rest are just labels and buttons.
This is what is looks like so far. Opened and closed states.
Is there a better way to dynamically build things like this in javafx? I'm basically pulling data from a database and looping through that data to populate a row.
I can't comment but it may be an answer anyway. Why can't you use the setGraphic method of a custom table cell and put the accordion node in a table. setGraphic takes any kind of node.
It sounds simpler than what you're doing.
I just tried it out with the Oracle sample and it works great.
I added
Callback<TableColumn<Person, String>, TableCell<Person, String>> accCellFactory
= new Callback<TableColumn<Person, String>, TableCell<Person, String>>() {
#Override
public TableCell call(TableColumn p) {
TitledPane t1 = new TitledPane("T1", new Button("B1"));
TitledPane t2 = new TitledPane("T2", new Button("B2"));
TitledPane t3 = new TitledPane("T3", new Button("B3"));
Accordion accordion = new Accordion();
accordion.getPanes().addAll(t1, t2, t3);
TableCell tc = new TableCell();
tc.setGraphic(accordion);
return tc;
}
};
and changed this line firstNameCol.setCellFactory(accCellFactory);
and I get
Of course you might want something other than buttons but I just copied the Accordion sample from the javadoc.