So, i need to fill a Table View using a JavaFX thread but the table is being filled only ~70% of time. I am looking at my code and i really can't find where the problem comes from, my guess is that the task is somehow being executed before the data is successfully retrieved/processed from db. Thanks is advance :)
private Executor exec;
private ObservableList<User> cellData = FXCollections.observableArrayList();
.
.
.
public void fillTable(HashMap<String,Object> whereClause){
Task<List<User>> task = new Task<List<User>>(){
#Override
public ObservableList<User> call(){
cellData.clear();
cellData.addAll(userRepository.getAll(whereClause));
userId.setCellValueFactory(new PropertyValueFactory<>("userID"));
userName.setCellValueFactory(new PropertyValueFactory<>("userName"));
userMail.setCellValueFactory(new PropertyValueFactory<>("userMail"));
userPhone.setCellValueFactory(new PropertyValueFactory<>("userPhone"));
isAdmin.setCellValueFactory(cellData -> {
String isAdminAsString = cellData.getValue().isAdmin() ? "Admin" : "Medic";
return new ReadOnlyStringWrapper(isAdminAsString);
});
isDeleted.setCellValueFactory(cellData -> {
String isActiveUser = cellData.getValue().isDeleted() ? "No" : "Yes";
return new ReadOnlyStringWrapper(isActiveUser);
});
logger.info("Cell values set");
return cellData;
}
};
exec.execute(task);
task.setOnFailed(e -> System.out.println(task.getException().getMessage()));
task.setOnSucceeded(e -> userTable.setItems((ObservableList<User>) task.getValue()));
logger.info("Fill user Table Task executed");
You don't give enough context for a proper, fully confident answer, but my guess is you're encountering issues relating to threads. JavaFX is not thread-safe; using the wrong thread to update the UI can lead to undefined behavior, such as the data only appearing ~70% of the time. There's an important rule in JavaFX that you must always follow:
Never read or write the state of objects that are connected—directly or indirectly—to a live scene graph on a thread other than the JavaFX Application Thread.
Your code does not follow this rule. Inside the call method of your Task you are structurally modifying cellData and setting the cellValueFactory of various TableColumns. This leads to said objects being modified by whatever thread is executing the Task. If the Executor is any hint, that thread is definitely not the JavaFX Application Thread.
I'm not sure why you're setting the cellValueFactory of your TableColumns inside the call method in the first place. The cell value factory is configuration that only needs to be done once—when you create the TableColumn (or shortly thereafter). In other words, configuring the cell value factory in the call method is wrong not just because it happens on a background thread but also because it happens each time you execute the Task. Remove the set-the-cell-value-factory code from the call method and move it, if needed, to where you're creating the TableColumns. If you're using FXML, and the TableColumns are created for you and injected, then the controller's initialize method is a good place for this sort of configuration.
Your cellData list is connected to your TableView, if not at first then definitely after the first successful execution of your Task. Modifying cellData on a background thread will notify the TableView of those changes on the same thread (listeners are invoked on the same thread that made the change). The easy solution is to have your Task return a new List and then update the TableView if successful.
Task<List<User>> task = new Task<List<User>>() {
#Override protected List<User> call() throws Exception {
return userRepository.getAll(whereClause);
}
});
task.setOnSucceeded(event -> userTable.getItems().setAll(task.getValue()));
task.setOnFailed(event -> task.getException().printStackTrace());
exec.execute(task);
The setAll method of ObservableList will first clear the list then add all the elements of the given collection (or array). This is somewhat more efficient than calling clear followed by addAll because it results in only one change event. Also, if you want to continue using cellData you can, assuming you've previously set it as your table's items; just use cellData.setAll(task.getValue()) instead.
Regarding the use of:
task.setOnSucceeded(e -> userTable.setItems((ObservableList<User>) task.getValue()));
Since you clearly expect an ObservableList<User> to be returned, you should be using a Task<ObservableList<User>> instead of a Task<List<User>>. This will mean getValue() returns ObservableList<User> and thus the cast becomes unneeded. However, if you follow the advice above, then this is irrelevant.
Related
I am developing a library program and when I make a given successive actions, I get an unexpected row added to my books' JTable.
The actions are this:
launching of the program
add a filter based on the commentaries (I have many filters, which are classes inheriting from RowFilter, they are all associated to the RowSorter of the JTable. This part of the program works fine).
In the initial state, there are 2 book in the JTable, which are "le livre de la jungle" and "Eloge des mathématiques". After the use of the filter, there only remains one, "Eloge des mathématiques". The other is not displayed but is still in the model.
select in the JTable the remaining book("le livre de la jungle"). With the debugger, I saw that during the actions triggered by this click, a book with an empty title and author is created. It is not visible but if I
deactivate all the filters, There are now 3 books in the JTable. (2 previous one plus the "ghost" with dummy values.) The dummy values come from a method, saveChanges(), which saves the current fields.
Here is a picture of the program, with the ribbon(north), the fields(center), and the JTable(bottom).
you can see the additional row, unexpected.
Now, let's see some code:
When I click on the JTable's row, I trigger this method:
table.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
if (table.getSelectedRow() > -1) {
Book book = ((TableATM) table.getModel()).getBook(table
.convertRowIndexToModel(table.getSelectedRow()));
getStatesManager().getState().selectBook(book);
}
}
});
This calls selectBook, from the UnboundState class. There are 2 states for the window: BoundState & UnboundState. UnboundState is used when the fields are empty. When the fields contain the datas of a book, (e.g. after a remote search on internet with the ISBN) it is immediately saved and the state becomes bound.
After the search I talked about above, the state remains the same as the initial one, I mean UnboundState : the search only changes the JTable, not the fields.
so, UnboundState's selectBook is triggered :
#Override
public void selectBook(Book book) {
statesManager.setBound(book);
}
the setBound method's purpose is to change the state :
public void setBound(Book book) {
state = new BoundState(book, this, bookWindow,
new ChoiceISBNDialog(bookWindow));
}
here is the constructor called by setBound:
public BoundState(Book book, StatesManager statesManager,
BookWindow bookWindow, ChoiceISBNDialog choice) {
this.statesManager = statesManager;
this.bookWindow = bookWindow;
this.choice = choice;
SwingUtilities.invokeLater(() -> {
statesManager.enableFields(true);
if (statesManager.getState().getName().equals("BOUND"))
saveChanges();
statesManager.displayBook(book);
statesManager.setCaretsToZero();
bookWindow.ribbon.btn_revertChanges.setEnabled(false);
bookWindow.ribbon.btn_storeInTable.setEnabled(false);
});
}
The important thing is that th emethod saveChanges() should not be called, because the current state when the invokeLater is called is "UNBOUND".
But when I debug the program, I get this: the methods are ran, following the odrer I gave to you, except that the debugger don't go into the invokeLater, it continues, and the methods finish, the last one is the JTable listener. but I put a breakpoint in the saveChanges() method, and I see that the saveChanges() method is triggered AFTER the end of the JTable's listener. This is an effect of the invokeLater I presume. But at the time, the state became "BOUND" and the saveChanges method think that the book actually in the fields is new and tries to save it.
How could I make it work? I tried a call to invokeAndWait but it freezes the program (it is definitely stopped, even after 1 minute or more).
I am developing an RCP Application, and am using Nebula's NatTable for that.
When it comes to selection, I am failing to understand how I am supposed to use it.
What I want is:
I want to have entire Rows selected. I was able do that using the RowOnlySelectionConfiguration and the RowOnlySelectionBindings.
If I select a row, I want the selection to stay there and not be cleared when some data in that row gets updated. How do I do that?
If a row is selected, and the position of the element in that row changes (e.g. one of the previous elements is removed, and the position changes to index - 1), I want the selection to change the position with the element, so that the same element is selected after the change. How do I do that?
I have seen that the documentation talks about a PreserveSelectionModel that can be used for that:
If you used the PreserveSelectionStructuralChangeEventHandler workaround in previous versions for not clearing the selection on structural changes, you will notice that this workaround will not work anymore. If you still need that behavior, you are now able to achieve the same by configuring and setting a SelectionModel instance like this:
SelectionModel model = new SelectionModel(selectionLayer);
// configure to not clear the selection on structural changes
model.setClearSelectionOnChange(false);
selectionLayer.setSelectionModel(model);
If you expect that the selection should update and move with structural changes (e.g. sorting), try to use the PreserveSelectionModel.
https://www.eclipse.org/nattable/nandn/nandn_120.php
So I guess I have to use the PreserveSelectionModel? But there I can't call setClearSelectionOnChange(false). Does it do that by default?
And how do I use the PreserveSelectionModel? What do I pass in the constructor?
I implement my own BodyLayerStack, in a class called TableBodyLayerStack, where I tried this in the constructor:
public TableBodyLayerStack(IUniqueIndexLayer underlyingLayer) {
super(underlyingLayer);
columnReorderLayer = new ColumnReorderLayer(underlyingLayer);
columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);
selectionLayer = new SelectionLayer(columnHideShowLayer, null, true, false);
PreserveSelectionModel<?> selectionModel = new PreserveSelectionModel<>(
selectionLayer, null, null);
selectionLayer.setSelectionModel(selectionModel);
selectionLayer.registerEventHandler(new SelectEventHandler(selectionLayer));
viewportLayer = new ViewportLayer(selectionLayer);
setUnderlyingLayer(viewportLayer);
registerCommandHandler(new CopyDataCommandHandler(selectionLayer));
}
Then, in the contructor of my implementation of the GridLayer, I do this:
// ...
bodyLayer = new TableBodyLayerStack(eventLayer);
// register different selection move command handler that always moves by row
bodyLayer.getSelectionLayer().addConfiguration(new RowOnlySelectionConfiguration<T>());
// register selection bindings that will perform row selections instead of cell selections
// registering the bindings on a layer that is above the SelectionLayer will consume the
// commands before they are handled by the SelectionLayer
bodyLayer.addConfiguration(new RowOnlySelectionBindings());
// ...
But this is giving me NullPointerExceptions in the PreserveSelectionModel.
Error while painting table: null
java.lang.NullPointerException
at org.eclipse.nebula.widgets.nattable.selection.preserve.PreserveSelectionModel.getRowPositionByRowObject(PreserveSelectionModel.java:520)
at org.eclipse.nebula.widgets.nattable.selection.preserve.PreserveSelectionModel.createMarkerPoint(PreserveSelectionModel.java:559)
at org.eclipse.nebula.widgets.nattable.selection.preserve.PreserveSelectionModel.getSelectionAnchor(PreserveSelectionModel.java:531)
at org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.getSelectionAnchor(SelectionLayer.java:276)
at org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.getConfigLabelsByPosition(SelectionLayer.java:415)
at org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform.getConfigLabelsByPosition(AbstractLayerTransform.java:316)
at org.eclipse.nebula.widgets.nattable.layer.AbstractIndexLayerTransform.getConfigLabelsByPosition(AbstractIndexLayerTransform.java:318)
at org.eclipse.nebula.widgets.nattable.layer.CompositeLayer.getConfigLabelsByPosition(CompositeLayer.java:553)
at org.eclipse.nebula.widgets.nattable.layer.cell.AbstractLayerCell.getConfigLabels(AbstractLayerCell.java:48)
at org.eclipse.nebula.widgets.nattable.layer.AbstractLayer.getCellPainter(AbstractLayer.java:354)
at org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform.getCellPainter(AbstractLayerTransform.java:336)
at org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform.getCellPainter(AbstractLayerTransform.java:336)
at org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform.getCellPainter(AbstractLayerTransform.java:336)
at org.eclipse.nebula.widgets.nattable.layer.AbstractIndexLayerTransform.getCellPainter(AbstractIndexLayerTransform.java:340)
at org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform.getCellPainter(AbstractLayerTransform.java:336)
at org.eclipse.nebula.widgets.nattable.layer.AbstractIndexLayerTransform.getCellPainter(AbstractIndexLayerTransform.java:340)
at org.eclipse.nebula.widgets.nattable.layer.CompositeLayer.getCellPainter(CompositeLayer.java:586)
at org.eclipse.nebula.widgets.nattable.painter.layer.CellLayerPainter.paintCell(CellLayerPainter.java:171)
at org.eclipse.nebula.widgets.nattable.painter.layer.CellLayerPainter.paintLayer(CellLayerPainter.java:81)
at org.eclipse.nebula.widgets.nattable.painter.layer.GridLineCellLayerPainter.paintLayer(GridLineCellLayerPainter.java:106)
at org.eclipse.nebula.widgets.nattable.selection.SelectionLayerPainter.paintLayer(SelectionLayerPainter.java:95)
at org.eclipse.nebula.widgets.nattable.layer.CompositeLayer$CompositeLayerPainter.paintLayer(CompositeLayer.java:913)
at org.eclipse.nebula.widgets.nattable.painter.layer.NatLayerPainter.paintLayer(NatLayerPainter.java:43)
at org.eclipse.nebula.widgets.nattable.NatTable.paintNatTable(NatTable.java:408)
at org.eclipse.nebula.widgets.nattable.NatTable.paintControl(NatTable.java:403)
...
I guess it is because I pass null values in the constructor of my PreserveSelectionModel. But how do I use it instead? What do I have to pass as arguments for the constructor? Where do I get the values from?
Any help is appreciated.
you are on the wrong track to achieve your goals. First I will answer your questions:
So I guess I have to use the PreserveSelectionModel?
No, the PreserveSelectionModel is intended to preserve the selection for cell selections. You want to preserve the selection for whole rows. So you need to use the RowSelectionModel.
But there I can't call setClearSelectionOnChange(false). Does it do that by default?
Yes
But this is giving me NullPointerExceptions in the PreserveSelectionModel. I guess it is because I pass null values in the constructor of my PreserveSelectionModel.
Yes
What do I have to pass as arguments for the constructor? Where do I get the values from?
The second parameter is IRowDataProvider<T>, so it is the IDataProvider for the body.
The third parameter is IRowIdAccessor<T>. You need to create an implementation that provides an unique id, so a row can be identified without knowing the position or index in the underlying collection.
So what you need to do is something like this:
selectionLayer.setSelectionModel(new RowSelectionModel<Person>(
selectionLayer, bodyDataProvider, new IRowIdAccessor<Person>() {
#Override
public Serializable getRowId(Person rowObject) {
return rowObject.getId();
}
}));
But of course you need to provide the IDataProvider and also the IRowIdAccessor to your TableBodyLayerStack if you want to keep it generic.
Also note that you don't have to call SelectionLayer#registerEventHandler() yourself! This is done internally by calling SelectionLayer#setSelectionModel().
You can find several examples in the NatTable Examples Application at https://www.eclipse.org/nattable/ (the Try it! button on the right side).
For your question the Tutorial Examples -> Layers -> Selection -> RowSelectionExample seems to be the one to look at.
I have and app that connects to a socket connection and that connections sends me a lot of info.. lets say 300 orders per second (maybe more).. I have a class (it is like a listener, that reacts to some event and that event has the order) that receives that order.. creates an object and then adds it to an ObservableList (which is the source of a tableView).. that way my GUI shows that order. But here comes the problem, if that order already exists on the observableList.. i can't add it ..and i must update it (wich i do).. but some times.. with some orders this condition doesn't work and the order its added again.
Im gonna show you how it's work with some code.
public class ReceivedOrderListener
{
ev = Event; //Supose that this is the event with the order
if(!Repository.ordersIdMap.containsKey(ev.orderID))
{
Platform.runLater(new Runnable()
{
#Override public void run()
{
Repository.ordersCollection.add(ev.orderVo);
}
}
});
Repository.ordersIdMap.put(ev.orderID, ev.orderVo);
}
Ok now.. this is a resume of my code. The ev is my event with all the info of the order, the orderID is the key that i use to see if the order already exists or not (and yeah is unique). The "Repository" is a singleton class, the "ordersCollection" is a ObservableList, the "ordersIdMap" is a HashMap
If ReceivedOrderListener is executed by multiple threads, then it looks like "check-then-act" race condition.
-> ORDER1 comes to the listener
T1 checks ordersIdMap.containsKey(ORDER1) it returs false
T1 proceeds to do Platform.runLater to add the order
-> ORDER1 comes to the listener again
-> T2 checks ordersIdMap.containsKey(ORDER1) it returs false again
now T1 proceeds to do ordersIdMap.put(ORDER1)
-> T2 proceeds to do Platform.runLater to add the order again
String path = workspaceField.getText();
//prefs.remove("workspaceDirectory");
prefs.put("workspaceDirectory", path);
splitPane = commands.getSplitPane();
WebScrollPane oldTree = (WebScrollPane) splitPane.getLeftComponent();
splitPane.remove(oldTree);
WebScrollPane newTree = commands.createFileTree();
splitPane.setLeftComponent(newTree);
dialog.dispose();
The above code gets a file path from a text field, then puts that in a String preference called "workspaceDirectory". The issue is that that preference does not change. The commented prefs.remove call removes the preference successfully, but it doesn't change the preference when prefs.put("workspaceDirectory", path) is called. I don't receive any errors.
The method createFileTree():
public WebScrollPane createFileTree() {
fileTree = new WebFileTree(prefs.get("workspaceDirectory", WorkspaceManager.createWorkspaceDirectory()));
fileTreeScrollPane = new WebScrollPane(fileTree);
fileTreeScrollPane.setVerticalScrollBarPolicy(WebScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
fileTree.addMouseListener(new FileTreeListener(this));
return fileTreeScrollPane;
}
That's all createFileTree does, but it doesn't affect anything. If I comment the code out that changes the components, prefs.put does nothing. Any ideas what causes this or is stopping the preference from being changed?
From the javadoc for java.util.Preferences:
All of the methods that modify preferences data are permitted to operate asynchronously; they may return immediately, and changes will eventually propagate to the persistent backing store with an implementation-dependent delay. The flush method may be used to synchronously force updates to the backing store. Normal termination of the Java Virtual Machine will not result in the loss of pending updates -- an explicit flush invocation is not required upon termination to ensure that pending updates are made persistent.
So, if you make a change to the preferences and then immediately try to read that change, your results may not be what you would expect.
I have a non-sticky service that's called on a regular basis via a broadcastreceiver to start a thread that'll perform some tasks. While the thread is running an ongoing notification shows some progress information, and a button to bring up a status page.
This status page shows a lists of items curerntly being processed, this list is a static ArrayList used by both the thread and this activity. When the status Activity is started I have a null check:
if(Global.statusItems == null)
{
Global.statusItems = new ArrayList<StatusPageItem>();
}
The thread is still running, and has perfectly fine access to the ArrayList, but as soon as the Status Activity is brought up it'll recreate the ArrayList as if it were null.
So far I haven't been able solve the issue without saving the list using an ObjectOutputStream and reloading when the status page is started. Is there a more elegant solution I could use?
Regards,
Quint.
Is it possible that your service is running on a different process?
You need to make sure that the 2 lines of code (null test and creation of a new list) are atomic and that the allocation is visible from other threads.
The easiest way to do that is to synchronize that piece of code:
synchronized(Global.class) {
if(Global.statusItems == null) {
Global.statusItems = new ArrayList<StatusPageItem>();
}
}
However, if you need to read the list from one thread and write to it from another thread, you will need to add extra synchronization when adding/removing/iterating to make sure that both treads see the same list - if you don't, it is possible that the writing thread adds an item to the list but the reading thread does not see it.
The easiest way would be to use a thread safe implementation of list:
synchronized(Global.class) {
if(Global.statusItems == null) {
Global.statusItems = new CopyOnWriteArrayList<StatusPageItem>();
}
}
If memory / object creation is a concern (CopyOnWriteArrayList is not very efficient from that perspective), you can also use a synchronized collection instead:
synchronized(Global.class) {
if(Global.statusItems == null) {
Global.statusItems = Collections.synchronizedList(new ArrayList<StatusPageItem>());
}
}
In that case, make sure you lock on the collection when iterating:
synchronized(Global.statusItems) {
for (StatusPageItem item : Global.statusItems) {
}
}