JavaFX Table view Cell Reuse - javafx

I have a javafx application with a TableView. Only the first column (dataTypeColumn) is editable and it contains a ComboBox for editing. Now the TableView does what it is supposed to do, however there is a combination of weird bugs when i start editing a cell and then scroll it out of view without commiting the edit. I am pretty sure it is because of the way TableView reuses the cells. However I haven't found any way to intervene with it, for example to forbid reuse of cells that are currently editing. Can you help me with that.
Even though I am pretty sure it is because of cell reuse, I will write the whole problem below, in case the source of the problem is a different.
The column in question contains Values of the enum DataType
I have a cell Factory for that column looking like this:
dataTypeColumn.setCellFactory(param -> new ComboBoxTableCell<>(DataType.values()));
The values for the column get read like this:
dataTypeColumn.setCellValueFactory(param -> Bindings.valueAt(configuration, param.getValue()));
In case this is confusing, the items that I give my TableView are Integers (from 0 to n-1), and in the different column CellValueFactories the actual values will be loaded depending on the Integer assoziated with the current column.
So when editing it, it shows a ComboBox with all the Values that DataType can have and let's the user select one. I have a callback on the column that reacts to the Edit commited event and looks like this:
public void editCommited(TableColumn.CellEditEvent<Integer, DataType> event) {
//configuration is an ObservableList containing DataType elements
configuration.set(event.getRowValue(), event.getNewValue());
}
So about the problems that occur. At the beginning all cells in that column have the same value: "nothing selected". It is a special value of the enum reserved for this case, as I didn't find a setPlaceholder function on the ComboBoxTableCell class. When I know start editing one of the cells and then scroll it out of view, one of the next rows will suddendly be in the editing state as well, even though that row has not been touched before. If I scroll further an cell in the editing state will appear every time the previous one scrolls out of view. I can also go back and will find the same cells in the editing state. If I edit the cell that should not be in the editing state, it will not change, but instead the cell that I originally tried to edit will change it's value. This might be due to the underlying ObservableList, that automatically updates the value in the column.
If I start editing a cell that has a different value (from a previous edit), something even more weird happens. When scrolling it out of view again a new row will have a cell in the editing page, however with the default value "nothing selected". If I scroll backwards the cell that i tried to edit originally will no longer be in the editing state however it's value has changed to "nothing selected". As if the new cell that got the editing state somehow commited it's own value.
Please some help with this :)

This indeed seems to be a bug. Rather surprising, if you move the scroll bar with the mouse, the edit is properly canceled, but not, if you use the mouse wheel to scroll.
You can easily create a workaround for this buf however, since you simply have to cancel the edit, when the item is replaced. You could e.g. use this method to create the cellFactory on your own:
public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>> comboBoxCellFactory(ObservableList<T> items) {
if (items == null) {
throw new IllegalArgumentException();
}
return column -> new ComboBoxTableCell<S, T>(items) {
#Override
public void updateIndex(int i) {
cancelEdit();
super.updateIndex(i);
}
};
}

Related

Hide column in QAbstractTableModel

I have a self created Qt-Model derieved by QAbstractTableModel. The data behind the model contains multiple QUuid-columns, whose cell-data I need to pass around the application. Due to design-reasons I don't want to show the QUuid-columns to the user, but keep them in the background to always guarantee access to the needed id-columns.
The data is bound to a Qtitan TableView Grid, where I can hide the column, but not totally remove it from the view. I can always reenable the visability which is not what I want.
So my question is if there are any options from the Qt-Model-side to hide a column or to avoid binding it to the view and just keep the data in the background.
You can subtract those columns from the visible columns by returning the column respectively in columnCount.
This would require to either move them to the end, or map the user visible column count to the underlying columns in your data() implementation.
It is probably a bit simpler to move those invisible columns to the end to avoid the mapping, but you can also do the mapping if you like.
int MyModel::columnCount(const QModelIndex& parent) const
{
return allColumns - columnsToHide;
}

How to implement a JavaFX table with multiple TableViews and Cell Factories?

I have a table whose list of visible items are filtered by radio buttons at the top of the table. So let's say the radio buttons are A, B, C - if A if chosen the table will only show items of type A, if B is chosen it will show only type B, etc.
I create a separate TableView instance for each selection because the users will be interacting with these filtered values in the table and editing values etc. I have a custom cell factory for each column in my table as well because rows need to be highlighted/frozen according to user input.
So:
for (all values A.. C) {
createNewTableView();
}
private void createNewTableView() {
TableView tableView = new TableView();
...
MyCustomCellFactory customCF = new MyCustomCellFactory();
customCF.setTableView(tableView);
clmn1.setCellFactory(customCF);
...
}
My problem is that when my custom cell factory fires, even through I specifically tell it which instance of table view it belongs to, the table view instance is not the correct one. It's always just the last one I created. I've verified this by checking the instance id's.
So the question is how can I have multiple table view instances with custom cell factories and ensure that I'm always acting on the right one in my custom cell factory code?
Thanks!
The problem actually had to do with not properly instantiating the table columns for each instance of TableView. I posted a simpler example here - JavaFX tabbed pane with a table view on each tab? - and got the answer I needed.

reload table in WatchKit

We have a hierarchical watch app.
The root controller is a table of menu items. That list of items is controlled by a server. The data is retrieved and stored in core data. The menu is populated the first time going into the app.
But I want this table to stay current. My thought was to add code to willActivate to check if there was changes, and reload the table. In my reload logic I call the same function I called the first time, which sets the menuTable.setNumberOfRows and creates each row. Looking at what I'm putting in the logs, it is going through this logic with a different count of rows and new labels. But the app on the watch shows the table with the old data.
How can I get this table to reload with the new information?
I've had this problem too and as rmp says, it still seems to be a bug in watchOS 1.0.1. The problem appears when you try to reload your tableView after run willActivate() and nothing will happen.
In my case, I reload the tableView after receive a reply from a delegate and then I reload all the content just if it's necessary. To achieve this, I remove all rows from a NSIndexSet and load again.
if isNecessary {
self.table.removeRowsAtIndexes(NSIndexSet(indexesInRange: NSMakeRange(0, maxItems)))
isNecessary = false
}
I've tried a lot of tricks but none has worked for me:
Force to reload rows by table.setNumberOfRows(0, withRowType: "data")
Setting parameters to empty text before assign new values
One thing you could do is to hide tableView before removing rows, and avoid the remove animation.
It is a bug in WatchKit. Seems like Apple doesn't handle the repetitive interface object correctly.
The general principle here is: Only insert or remove necessary rows after a table is created. Do not reload the whole table like what we usually do in iOS. It just doesn't work (or trigger the bug).
So specifically, you have to:
Do this in willActivated method. This is correct.
If this is the first load, before the table is even created, do what you are now doing – load all table rows.
For all following times, don't reload the table, fetch the new data and check the desired number of rows.
Compare with the current number of rows in the table, insert to or remove from the bottom of the existing table.
Now simply re-assign the new data to all existing rows. Again, do not reload.
It should work if you follow the above steps.
I have found what works best given the current state of watchkit is to remove all rows then re-populate the table.
Try something like this:
- (void)loadTableData{
//clear the table
[mainTableView setNumberOfRows:0 withRowType:#"myTableRow"];
//set the row count again
[mainTableView setNumberOfRows:numberOfRows withRowType:#"myTableRow"];
//populate table
}
Its pretty simple. As Apple hasn't provided any method to reload the data.
You can still achieve that by simply populating the rows for the tableview.
Below is the sample code:
for index in 0..<tracksTableView.numberOfRows {
if let controller = tracksTableView.rowController(at: index) as? EditPlaylistRowController {
controller.playingTrackId = self.playingTrackID
controller.sharedTrack = trackItems[index]
}
}
Use it whenever you want to refresh the data.
NOTE:
You can still make some conditional statements inside the row controller class.
Happy to help :)

JavaFX TableView Insert in ObservableList when column sorting is active

I have a JavaFX TableView defined as :
TableView<MyMessage> messages;
It is supported by an ObservableList :
private ObservableList<MyMessage> messagesList;
// Allocate the message list
messagesList = FXCollections.observableArrayList();
// Set the list into place
messages.setItems( messagesList );
The MyMessage object has 10 fields all of type String. There is a background operation that inserts MyMessage objects into the ObservableList.
messages.add( new MyMessage( String ... colData ) );
Everything works as advertised. The table rows of the TableView are updated as objects are inserted into the ObservableList.
What doesn't seem to work as expected, is if I click on a column header to sort the table, the subsequent items inserted by the background thread are appended to the end of the table. Not to the proper sorted row position. If I click to resort the column, the table is resorted properly.
Do I need to some how force the re-sorting of the table each time I add something to the ObservableList? Or should the TableView take care of that for me.
Thanks.
Java FX 8 now has a .sort() method on tableView that can be called to re-trigger sorting. Some operations will trigger a re-sort automatically but external events adding/deleting entries in the observable collection do not.
should the TableView take care of that for me
No.
There was a request that TableView maintain sort order as the underlying list changes but it was closed as "not an issue". You can review the comments on the issue for more detailed information. In short, keeping the list sorted is the responsibility of the underlying ObservableList or the application maintaining the list.
Do I need to some how force the re-sorting of the table each time I add something to the ObservableList?
In essence, yes.
JavaFX was supposed to ship with a SortedObservableList implementation which effectively cooperated with the TableView to make all of the sorting handling pretty much automatic for the application. But, for JavaFX 2.2, the functionality did not make the cut for inclusion. Currently, this functionality is scheduled for delivery as part of JDK/JRE8 and can be tracked via this umbrella issue for improved sorting support in TableView.

UITableViewController indexPathForSelectedRow returns incorrect value

I'm not sure what is going on but I have a list that has 3 items and I am selecting row 1.
I am using a viewcontroller that is a UITableViewController and that has a property tableView where I am accessing the indexPathForSelectedRow during the didSelectRowAtIndexPath.
For some reason this value is incorrect sometimes...
What could cause this?
The UITableView * param on didSelectRowAtIndexPath is also called tableView.
If your table has more than one section that can confuse things (it's caught me before). indexpath.row will return the row number in the section of the table, not the row number of the overall table. If that's not what you're expecting it'll appear wrong.

Resources