I am using this piece of code to try and remove the item that get's selected from a combobox and I want to do something further with it, but the problem is that the value selected should be removed from the list. The following is what I am using to fill the dropdown
unpickedRoles = FXCollections.observableArrayList();
rollenDropdown.setItems(unpickedRoles);
unpickedRoles.addAll(Rol.DIRECTIE, Rol.MANAGER, Rol.MVOC, Rol.STAKEHOLDER);
#FXML
private void selectRol(ActionEvent event) {
Rol selected = rollenDropdown.getSelectionModel().getSelectedItem();
if (selected != null) {
rollenDropdown.getSelectionModel().clearSelection();
rollenDropdown.getItems().remove(selected);
}
}
Now whenever the code get's called when a selection is made apart from the first one, the code seems to get's recalled internally by the rollenDropdown.getSelectionModel().clearSelection(); function, how could I remove the selection without the javafx action getting recalled? And how come this only happens when not selecting the first one?
EDIT: It probably has something to do with an item getting deselected thus recalling the method
EDIT 2: Adding a null check does not help
Kind regards
Jasper
If you are concerned about the unspecified time at which Platform.runLater executes the action, you can try the below approach.
This approach is effectively using the AnimationTimer to run the desired actions at the end of the current pulse.
Considering its possible performance issues (mentioned in the doc),I would prefer to use Platform.runLater only in multi threading situations.
#FXML
private void selectRol(ActionEvent event) {
Rol selected = rollenDropdown.getSelectionModel().getSelectedItem();
if (selected != null) {
doEndOfPulse(() -> {
rollenDropdown.setValue(null);
rollenDropdown.getItems().remove(selected);
});
}
}
/**
* Executes the provided runnable at the end of the current pulse.
*
* #param runnable runnable to execute
*/
public static void doEndOfPulse(final Runnable runnable) {
new AnimationTimer() {
#Override
public void handle(final long now) {
runnable.run();
stop();
}
}.start();
}
According to How to remove selected elements from a ComboBox I had to use Platform.runLater() in order to make sure that the property change is dealt with before doing anything with the selection. This is the working code:
#FXML
private void selectRol(ActionEvent event) {
Rol selected = rollenDropdown.getSelectionModel().getSelectedItem();
if (selected != null) {
Platform.runLater(() -> {
rollenDropdown.setValue(null);
rollenDropdown.getItems().remove(selected);
});
}
}
Related
I have an interface method which is supposed to return a Future object.
Future<Result> doSomething()
The implementation of this method shows some ui (javafx).
One of the ui elements has a listener, that needs to be called in order to receive the actual result, I need.
How do I achieve this?
Is there a better solution?
Here an example action I need to wait for:
// this is some framework method I cannot change
#Override
public Data execute(Data data) {
Future<Data> dataFuture = handler.doSomething(data);
// this should basically wait until the user clicked a button
return dataFuture.get();
}
// handler implementation
public Future<Data> doSomething(Data data) {
// the question is how to implement this part, to be able to
// return a future object
Button button = new Button("Wait until click");
// create thread that waits for the button click ?!????
// modify incoming data object when the button was clicked
// somehow create the Future object that's bound to the button click
return future;
}
This is what I want to achieve:
my method doSomething shows a new scene(ui) with a button on it
and returns immedeately the future object
future.get() waits until the user pressed the button
limitations: it has to be done with no extra library and on >=Java7
Use a javafx.concurrent.Task. It derives from FutureTask. There are extensive examples in the linked javadoc on Task usage.
Oracle also provide a tutorial which discusses Task usage:
Concurrency in JavaFX
I think this is what you want, but I may have understood the question, if so, please edit the question a bit to clarify requirements (perhaps with an mcve). The bit that makes me a little unsure is the part in your title "waiting for ui event?", I'm not quite sure what that means in this context.
This is a solution I was searching for. It's not very nice, since the Thread.sleep doesn't convince me.
but now you propably get an idea of what I want to achieve
// make sure this is not called on the ui thread
public Future<Data> doSomething(Data data) {
WaitingFuture future = new WaitingFuture(data);
Platform.runLater(() -> {
Button button = new Button("Wait until click");
button.setOnAction(future);
// show button on ui...
});
favouriteExecutorService.submit(future);
return future;
}
static class WaitingFuture extends Task<Data> implements EventHandler<ActionEvent> {
private Data data;
WaitingFuture(Data originalData) {
this.data = originalData;
}
private Data waitingData;
#Override
public void handle(ActionEvent event) {
waitingData = data.modify();
}
#Override
protected Data call() throws Exception {
while (waitingData == null) {
Thread.sleep(100);
}
return waitingData;
}
}
At first see my GUI:
Background:
I am trying to use some functions (CRUD) of MongoDB collection by a GUI.
At first user has to choose an existing Database from the very first ComboBox. When user chooses an option private void jComboBoxDBNamePopupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) function would load all the collections under that database. Here blog is chosen.
Then user would choose a collection among the existing collections from second ComboBox. When user chooses a collection private void jComboBoxCollectionNamePopupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) function would call a function named refreshTable() to load all the Documents under that collection. Here posts collection is chosen.
While choosing option from second ComboBox, if the chosen collection have more than thousand Documents, it would ask user to confirm whether he actually wants to load the Documents or not as it might take time or could be a memory issue.
It confirmation would be done through a JOptionPane.showConfirmDialog(...).
Problem:
While choosing collection posts, it shows the Dialog Box. But clicking on Yes or No does not gives any response. But why?
The buttons are red underlined in the picture.
Code:
My public boolean refreshTable() function is:
public boolean refreshTable() {
collections = db.getCollection((String) jComboBoxCollectionName.getSelectedItem());
if(collections.count()>1000){
int ret = JOptionPane.showConfirmDialog(this, "The table contains more than thousand row.\nThis may slow down the process and could cause Memory error.Are you sure to continue?","Too Large Collection ("+collections.count()+" Rows)",YES_NO_OPTION, INFORMATION_MESSAGE);
if(ret!=YES_OPTION) return true;
}
//Some irrelevant codes
return false;
}
Study:
I have searched it on Google and could not solve the issue. The followings are some questions on StackOverflow, but I could not figure out solution from them.
JOptionPane Confirm Dialog Box
JOptionPane YES/No Options Confirm Dialog Box Issue -Java
JoptionPane ShowConfirmDialog
Project Repository:
My Project repository is here. You could have a look if needed.
This is probably because JOptionPane methods should be called on the event dispatch thread (EDT) of Swing while you are inovoking it on a different thread.
You should try calling refreshTable by using SwingUtilities utility methods, eg:
SwingUtilities.invokeLater(() -> refreshTable());
A guess only since we don't have a minimal example program from you -- but if this JOptionPane is called amidst long-running or CPU-intensive code, and if the code is run on the Swing event thread, it will freeze the Swing event thread and thus freeze your GUI. If you're not taking care to call long-running or CPU-intensive code within background threads, you will want to do so, such as by use of a SwingWorker.
I looked at your code, and you're starting your GUI on the EDT:
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
// this will be run on the EDT
new UserInterface().setVisible(true);
}
});
and so Jack's recommendation is unnecessary, but you're making all your database calls on the EDT and are not following Swing threading rules, something that will freeze your pogram, and so my recommendations are what you need to follow. You will first need to learn the basics of Swing threading, and so I recommend that you look at this tutorial: Lesson: Concurrency in Swing
You could get by with two SwingWorkers and two JPropertyChangeListeners:
A SwingWorker, say called GetCollectionsWorker, that extends SwingWorker<Collections, Void>. I have no idea what the first generic parameter should be other than it should be whatever type your collections variable is. This worker would simply return db.getCollection(selection); from its doInBackground() method.
A SwingWorker, say called CreateTableModelWorker, that extends SwingWorker<DefaultTableModel, Void> and that is passed collections into its constructor and that creates your DefaultTableModel from the data held by Collections.
A PropertyChangeListener, say called GetCollectionsListener, that listens on the CreateTableModelWorker, for when it is done, in other words its newValue is SwingWorker.StateValue.DONE, and then that asks the user if he wants to continue, and if so, this calls the second SwingWorker.
A PropertyChangeListener, say called CreateTableModelWorker, that listens to the CreateTableModelWorker for when it is done, and that puts the table model into the JTable.
For what it's worth, I'd implement somewhere along these lines in the code below. Again, the big unknown for me is what type the collections variable represents, and for that reason, the first SwingWorker's generic parameter would need to be fixed and changed from Collections to whatever it is that you're using:
// change to a void method
public void refreshTable() {
String selection = (String) jComboBoxCollectionName.getSelectedItem();
// SwingWorker to get collections
GetCollectionsWorker getCollectionsWorker = new GetCollectionsWorker(selection);
getCollectionsWorker.addPropertyChangeListener(new GetCollectionsListener());
getCollectionsWorker.execute(); // run worker on background thread
}
// FIXME: Generic type Collections is wrong -- need to use correct type, whatever type collections is
private class GetCollectionsWorker extends SwingWorker<Collections, Void> {
private String selection;
public GetCollectionsWorker(String selection) {
this.selection = selection;
}
#Override
protected Collections doInBackground() throws Exception {
// do database work here in a background thread
return db.getCollection(selection);
}
}
// class that listens for completion of the GetCollectionsWorker worker
class GetCollectionsListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
// all this is done on the EDT
if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
// if worker is done, first get worker from listener
GetCollectionsWorker worker = (GetCollectionsWorker) evt.getSource();
try {
// then extract the data that it's returning
collections = worker.get();
// then offer user option of continuing or not
if (collections.count() > 1000) {
int ret = JOptionPane.showConfirmDialog(UserInterface.this,
"The table contains more than thousand row.\nThis may slow down the process and could cause Memory error.Are you sure to continue?",
"Too Large Collection (" + collections.count() + " Rows)", YES_NO_OPTION, INFORMATION_MESSAGE);
if (ret != YES_OPTION) {
return;
}
}
// our next worker, one to create table model
CreateTableModelWorker createModelWorker = new CreateTableModelWorker(collections);
// be notified when it is done
createModelWorker.addPropertyChangeListener(new CreateModelListener());
createModelWorker.execute(); // run on background thread
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
// worker to create table model on background thread
class CreateTableModelWorker extends SwingWorker<DefaultTableModel, Void> {
private Collections collections;
public CreateTableModelWorker(Collections collections) {
this.collections = collections;
}
#Override
protected DefaultTableModel doInBackground() throws Exception {
documents = collections.find().into(new ArrayList<Document>());
Set<String> colNames = new HashSet<>();
for (Document doc : documents) {
for (String key : doc.keySet()) {
colNames.add(key);
}
}
columns = colNames.toArray();
Object[][] elements = new Object[documents.size()][columns.length];
int docNo = 0;
for (int i = 0; i < columns.length; i++) {
if (((String) columns[i]).equalsIgnoreCase("_id")) {
_idcol = i;
break;
}
}
for (Document doc : documents) {
for (int i = 0; i < columns.length; i++) {
if (doc.containsKey(columns[i])) {
elements[docNo][i] = doc.get(columns[i]);
}
}
docNo++;
}
DefaultTableModel model = new DefaultTableModel(elements, columns);
return model;
}
}
private class CreateModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
// all this is done on the EDT
if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
// if worker is done, first get worker from listener
CreateTableModelWorker worker = (CreateTableModelWorker) evt.getSource();
try {
DefaultTableModel model = worker.get();
jTableResultTable.setModel(model);
UserInterface.this.model = model;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
I am using this code to create a JCheckBox
private final JCheckBox cbDisplayMessage = new JCheckBox("Display");
cbDisplayMessage.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if(e.getItemSelectable() == cbDisplayMessage) {
if(cbDisplayMessage.isSelected()) {
cbDisplayMessage.setSelected(false);
} else {
cbDisplayMessage.setSelected(true);
}
}
}
});
When I run this it causes an StackOverflow error on setSelected(true). Can't figure out what I am doing wrong. Any ideas appreciated....
You can try with ActionListener instead of ItemListener as shown below without causing StackOverflow error.
cbDisplayMessage.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (cbDisplayMessage.isSelected()) {
cbDisplayMessage.setSelected(false);
} else {
cbDisplayMessage.setSelected(true);
}
}
});
There is no need to check the source of the event again because you are sure that you have added this listener on the same object. This is required only if same listener is added for more components.
-- EDIT--
Now Your requirement is clear to me. If you want to toggle the state of the check box then there is no need to do it using listener because that's the default behavior of the check box.
Your listener is called every time the state changes, but you trigger a new state change from within that listener, so each state change results in that listener being called over and over again until your stack is full. Your setup has to be a bit more complicated to do something like that - if you want to change the state of the component you're listening to, you'll want to remove its listener(s), fire your programmatic state change, then re-add them.
I wanna add mouse over listener to SpanElement, which i created by:
SpanElement span = Document.get().createSpanElement();
span.setInnerText("my text");
I found in google how to do it with Label-wrapper, but I wanna to do it without any wrappers. Is it possible?
Thank you.
It is possible without JSNI too.
So with your element:
SpanElement span = Document.get().createSpanElement();
span.setInnerText("my text");
to add event listener directly to element:
Event.sinkEvents(span, Event.ONCLICK);
Event.setEventListener(span, new EventListener() {
#Override
public void onBrowserEvent(Event event) {
if(Event.ONCLICK == event.getTypeInt()) {
//do your on click action
}
}
});
...and it looks really ugly ;)
as you notice - the event listener is "common" for all dom events conceived by this element. so to make sure you handle the proper event you should check event type when you sink more than one Event type (this time it's overhead - as we sinked only CLICK event's bit). And as to sinking -> this initializes the element to take part in gwt global dom event dispatching system - the event are handled globaly to decrease number of closures so minimize memory leaks in older IE browsers.
on one more thing. you can set only one event listener per element - if you set a new one it overwrites the previous one. So i assuming somwehere later you want to add MOUSEOVER listener to your span and not to clear off allready added CLICK listener you might do something like this:
//add mouseover event bit to existing sunk event bits
Event.sinkEvents(span, Event.getEventsSunk(span) | Event.ONMOUSEOVER);
final EventListener oldListener = Event.getEventListener(span);
Event.setEventListener(span, new EventListener() {
#Override
public void onBrowserEvent(Event event) {
if(Event.ONMOUSEOVER == event.getTypeInt()) {
//your mouseover action
}
if(oldListener != null) {
oldListener.onBrowserEvent(event);
}
}
});
or adding more events at once:
//add mouseover event bit to existing sunk event bits
Event.sinkEvents(span, Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT);
Event.setEventListener(span, new EventListener() {
#Override
public void onBrowserEvent(Event event) {
switch(event.getTypeInt()) {
case Event.ONCLICK:
break;
case Event.ONMOUSEOVER:
break;
case Event.ONMOUSEOUT:
break;
}
}
});
so after saying that all you probably aprecciate using a label widget wrapping your span ;)
Label.wrap(span).addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
//do your on click action
}
});
And last thing do not be afraid of widget even if you want to do DOM programming- look at them as something like jquery node wrapper object. they're not heavy but give much power.
you can also wrap widgets directly over existing DOM elements without attaching them to "panel infrastructure" .
I'm trying to update the tab being displayed, however it seems to wait until the end of the method and then update. Is there a way to make the tab being displayed update immediately?
Here is an example of the code where I'm having this issue:
private static void someButtonMethod()
{
Button = new JButton("My Button");
Button(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
tabs.setSelectedIndex(1);
// Do some other things (In my case run a program that takes several seconds to run).
runProgram();
}
});
}
The reason for this is that the method is being executed in the Event Dispatch thread, and any repaint operations will also occur in this thread. One "solution" is to update the tab index and then schedule the remaining work to be invoked later on the EDT; this should cause the tab state to be updated immediately; e.g.
public void actionPerformed(ActionEvent evt) {
tab.setSelectedIndex(1);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Do remaining work.
}
});
}
EDIT
Per your comment below an example of how to invoke a SwingWorker in order to call your runProgram method would look something like this:
// Typed using Void because runProgram() has no return value.
new SwingWorker<Void, Void>() {
protectedVoid doInBackground() {
runProgram();
return null; // runProgram() doesn't return anything so return null.
}
protected void done() {
// Called on the EDT when the background computation has completed.
// Could insert code to update UI here.
}
}.execute()
However, I sense a bigger problem here: The fact that you are seeing a significant delay in updating the tab makes me think you are performing long running calculations on the EDT. If this is the case you should consider performing this work on a background thread. Take a look at the SwingWorker class.