JavaFX: Selecting a lot of rows in a large table - javafx

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...

Related

Can't clear all items (elements) in an ObservableList

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.)

javafx tableview detect any change upon any of rows including addition of new one

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
}
}
});

JavaFX architecture in building dynamic "rows"

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.

Treeview checked nodes not returned in order

I using TreeView with ShowCheckBoxes="All" in an ASP.NET 3.5 web application and for some reason the checked nodes are not returned in order. Lets say I have nodes A,B,C and I select B and C and hit the save button and when I check the CheckedNodes property of the Treeview the checked nodes are in order (B,C). But the next time when I go back to the page and select the node A the order is being returned B,C,A. What could be the reason for this behavior?
CheckedNodes is a TreeNodeCollection which just implements ICollection. When the checkChanged event fires, it probably just adds the tree nodes to the CheckedNodes collection.
Nothing I can see on MSDN implies that you should assume the nodes would be ordered. All it says is:
Each time the page is posted to the
server, the CheckedNodes collection is
automatically populated with the
selected nodes.
From your experiment, it seems safe to assume that on the second postback, it simply adds any new checked nodes to the collection, rather than clearing the collection and re-adding everything.
A simple routine to sort the CheckedNodes collection will suffice as a solution to this problem. This routine (http://urenjoy.blogspot.com/2009/06/sort-checkednodes-treenodecollection-in.html) which I ran across from a search works directly by calling prior to looping through the collection. It essentially checks the text in a simple comparison and returns a new collection in order.
From the link above:
private TreeNodeCollection SortTreeNode(TreeNodeCollection nodeList)
{
for (int i = 0; i < nodeList.Count-1; i++)
{
for (int j = i + 1; j < nodeList.Count; j++)
{
if (nodeList[i].Text.CompareTo(nodeList[j].Text)>0)
{
TreeNode temp = nodeList[i];
nodeList.RemoveAt(i);
nodeList.AddAt(i,nodeList[j-1]);
nodeList.RemoveAt(j);
nodeList.AddAt(j, temp);
}
}
}
return nodeList;
}
Example calling code:
var tncInOrder = SortTreeNode(this.MyTreeView.CheckedNodes);
foreach (TreeNode node in tncInOrder )
{
//Iterate through the nodes in order
}

How do I programmatically associate a RadioButton with a RadioButtonGroup in ActionScript3?

I have a UI component that, for various reasons, I have to construct programatically. The component is a table of radio buttons grouped by column.
Right now, I'm constructing the column groups like so:
private function createGroupsForItemList(items: XMLList): void {
for each (var item: XML in items) {
var rbGroup: RadioButtonGroup = new RadioButtonGroup();
groups[item.#level.toString()] = rbGroup;
}
}
I'm trying to associate the RadioButton instances with the column groups like so:
private function createValueControl(item: XML): UIComponent {
var control: RadioButton = new RadioButton();
control.label = "";
control.group = groups[item.#level.toString()];
control.addEventListener(Event.SELECT, updateSelection);
return control;
}
I can see in the debugger that the control has an association to the group:
control.group == groups[item.#level.toString()]
However, I can see equally that the group does not know anything about the control:
group.radioButtons.length == 0
I imagine that this is because the setter for group in RadioButton is a dumb setter; all it does is copy to the variable, which doesn't do the magic that groupName does. However, I can't seem to find the value I should use to set the RadioButton.groupName property correctly.
So, in short, I'm stumped on how to get these bits to talk to each other. How do I do this?
-- EDIT --
It turns out that I can have the groups created and associated simply by setting the groupName property, but I can't get at the group to set up a selection listener; the group is NULL immediately after the setting process, which means that the second line below throws the Flex equivalent of an NPE:
control.groupName = groupNameForLevel(item);
control.group.addEventListener(Event.SELECT, updateSelection);
First instinct is that this issue has to do with invalidateDisplayList and when and how that is called. Of course, since issues related to that function are behind a number of Flex's quirks, I may just be scapegoating.
This is not the answer to your question per se, but it seems like it might actually work as an alternate solution.
RadioButtonGroups will initialize based on a IFlexDisplayObject. This means that you can do something like:
var c:HBox = new HBox();
var rbg:RadioButtonGroup = new RadioButtonGroup( c );
// do stuff with rbg.
c.addChild( new RadioButton() );
The problem is that it may not be the most practical answer, but it has the decided benefit of being a workable solution.
Setting groupName should work.
All I can suggest is to step through the group() getter of the RadioButton component and see where exactly it is failing. Are you programmatically creating the group too? If that's the case, maybe it isn't initialized fully yet.

Resources