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).
Related
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.
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
As far as i have seen the event:
(1) private void jTabbedPane1StateChanged(javax.swing.event.ChangeEvent evt) {}
Checks whether a new tab is added or an exiting tab is deleted or not.
On googling , i found this code:
(2) ChangeListener changeListener = new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
// my code
}
};
jTabbedPane1.addChangeListener(changeListener);
I guess since it uses stateChanged event , it should do what the same a my first code.
By t way even after using both the codes i could not get the required resuts(ie An event that could be invoked when user changes the tab).
Can anyone suggest me a good event [i am using netbeans GUI environment] for effective action. (I dont want any mouseEvents)
Edit:
I want the following code to be excecuted if the tab changes:
String send3=( jTabbedPane1.getSelectedComponent().getComponentAt(0,0)).getName();
The above code dynamically gets the name of jTextarea (in the current tab) which is created dynamically in the jTabbedPanel.
I just checked my own source code where addChangeListener() works fine. The event is fired whenever the tab is changed by the user or programatically. In stateChanged() itself, the now selected tab is determined by
JTabbedPane p = (JTabbedPane)e.getSource();
int idx = p.getSelectedIndex();
In my application I want the user to save any changes before he leaves a tab (implemented as CTabFolder).
I tried to handle SelectionEvent, but it fires after the tab has been changed (so why does it even have a doit field? Does it fire before change for some other controls?)
Looking on Bugzilla, I've found https://bugs.eclipse.org/bugs/show_bug.cgi?id=193453 and https://bugs.eclipse.org/bugs/show_bug.cgi?id=193064, neither of which is fixed.
Since this requirement is probably common, does anybody have a workaround?
I have a workaround that works with org.eclipse.ui.part.MultiPageEditorPart which is backed by a CTabFolder. I'll adapt it for a straight CTabFolder implementation.
First off use the selection listener:
tabFolder.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
pageChange(tabFolder.indexOf((CTabItem) e.item));
}
});
Then I implement pageChange() like this:
protected void pageChange(int newPageIndex) {
boolean changingPages = this.changingPages;
this.changingPages = true;
int oldPageIndex = tabFolder.getSelectionIndex();
if (isDirty() && !changingPages) {
tabFolder.setSelection(oldPageIndex);
if (canChangePages()) {
tabFolder.setSelection(newPageIndex);
}
}
this.changingPages = false;
}
In canChangePages() I pop up a do you want to save dialog and give the user an opportunity to select yes, no, or cancel. Yes saves the info and returns true. No reverts the info to the last saved state and returns true. Cancel simply returns false. You may simply want to try saving and return false only if the save fails.
It may look weird that I switch back to the old page before calling canChangePages(). This call executes quickly so it gives the illusion the tab never switched. No matter how long canChangePages() takes the user will not see a tab change unless it is approved by that method.
I am trying to write a JTextPane which supports some sort of coloring: as the user is typing the text, I am running some code that colors the text according to a certain algorithm. This works well.
The problem is that the coloring operations is registered with the undo manager (a DefaultDocumentEvent with EventType.CHANGE). So when the user clicks undo the coloring disappears. Only at the second undo request the text itself is rolled back.
(Note that the coloring algorithm is somewhat slow so I cannot color the text as it is being inserted).
If I try to prevent the CHANGE events from reaching the undo manager I get an exception after several undo requests: this is because the document contents are not conforming to what the undoable-edit object expects.
Any ideas?
You could intercept the CHANGE edits and wrap each one in another UndoableEdit whose isSignificant() method returns false, before adding it to the UndoManager. Then each Undo command will undo the most recent INSERT or REMOVE edit, plus every CHANGE edit that occurred since then.
Ultimately, I think you'll find that the styling mechanism provided by JTextPane/StyledDocument/etc. is too limited for this kind of thing. It's slow, it uses too much memory, and it's based on the same Element tree that's used to keep track of the lexical structure of the document. It's okay (I guess) for applications in which the styles are applied by the user, like word processors, but not for a syntax highlighter that has to update the styles constantly as the user types.
There are several examples out there of syntax-highlighting editors based on custom implementations of the Swing JTextComponent, View and Document classes. Some, like JEdit, re-implement practically the whole javax.swing.text package, but I don't think you need to go that far.
How are you trying to prevent the CHANGE events from reaching the undo manager?
Can you not send the UndoManager a lastEdit().die() call immediately after the CHANGE is queued?
I can only assume how you are doing the text colouring. If you are doing it in the StyledDocuments change character attribute method you can get the undo listener and temporarily deregister it from the document for that operation and then once the colour change has finshed then you can reregister the listener.
Should be fine for what you are trying to do there.
hope that helps
I have just been through this problem. Here is my solution:
private class UndoManagerFix extends UndoManager {
private static final long serialVersionUID = 5335352180435980549L;
#Override
public synchronized void undo() throws CannotUndoException {
do {
UndoableEdit edit = editToBeUndone();
if (edit instanceof AbstractDocument.DefaultDocumentEvent) {
AbstractDocument.DefaultDocumentEvent event = (AbstractDocument.DefaultDocumentEvent) edit;
if (event.getType() == EventType.CHANGE) {
super.undo();
continue;
}
}
break;
} while (true);
super.undo();
}
#Override
public synchronized void redo() throws CannotRedoException {
super.redo();
int caretPosition = getCaretPosition();
do {
UndoableEdit edit = editToBeRedone();
if (edit instanceof AbstractDocument.DefaultDocumentEvent) {
AbstractDocument.DefaultDocumentEvent event = (AbstractDocument.DefaultDocumentEvent) edit;
if (event.getType() == EventType.CHANGE) {
super.redo();
continue;
}
}
break;
} while (true);
setCaretPosition(caretPosition);
}
}
It is an inner class in my custom JTextPane, so I can fix the caret position on redo.