I have a text field and a Search button. If the user enters precisely 13 digits (a barcode) then I want to trigger the Search automatically.
I have a DocumentListener on the text field and am processing the insertUpdate method to determine that 13 digits have been entered. I could call the search code directly at that point (and it does work) but although the 13th character has been typed, it isn't actually displayed on the screen until the search has completed.
I would prefer instead to trigger the Search button and have tried two ways:
DocumentListener dlBarcode = new DocumentAdaptor() {
public void insertUpdate(DocumentEvent e) {
String value = jtBarcode.getText();
if (isBarcode(value)) {
ActionEvent ae = new ActionEvent((Object)jbSearch,
ActionEvent.ACTION_PERFORMED, "");
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(ae);
}
}
};
The second is to use:
jbSearch.dispatch(ae);
Neither method appears to cause the ActionListener on jbSearch to be triggered. Can you please suggest what I am doing wrong?
Don't try programatically "click" the button. It's not being pressed, so why try to fool your code into thinking it is? Separating an action from the gesture that performed the action is an important principle to follow. By analogy, think of the ignition on your car. Turning the key triggers the ignition. So if I wanted to design a remote car starter, would I create a mechanical robot that physically inserts and turns a key, or should my system simply signal the ignition directly?
Simply define a method, call it performSearch or whatever, and have both your ActionListener on the button and your DocumentListener each call that method on their own.
One note: don't forget to actually register your document listener with the text control you're using.
I could call the search code directly at that point (and it does work) but although the 13th character has been typed, it isn't actually displayed on the screen until the search has completed.
Wrap the call to the search code in a SwingUtilities.invokeLater(). This will place the search at the end of the Event Dispatch Thread, so the text field will be updated before the search is started.
DocumentListener dlBarcode = new DocumentAdaptor() {
public void insertUpdate(DocumentEvent e) {
String value = jtBarcode.getText();
if (isBarcode(value)) {
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
invokeSearchMethod();
}
});
}
}
};
camickr's suggestion of using invokeLater strangely didn't work as the 13th character didn't get painted until the search had finished. Mark's comment suggesting using a SwingWorker did the trick. My DocumentListener code now looks like:
DocumentListener dlBarcode = new DocumentAdaptor() {
public void insertUpdate(DocumentEvent e) {
String value = jtBarcode.getText();
if (isBarcode(value)) {
PerformSearch ps = new PerformSearch(value);
ps.execute();
}
}
And my PerformSearch looks like:
class PerformSearch extends SwingWorker<Product, Void> {
private String key = null;
public PerformSearch(String key) {
this.key = key;
}
#Override
protected Product doInBackground() throws Exception {
return soap.findProduct(this.key);
}
protected void done() {
Product p = null;
try {
p = get();
} catch (InterruptedException e) {
} catch (ExecutionException e) {
}
prod = p;
if (prod != null) {
... populate text fields
}
else {
... not found dialog
}
}
Thanks for the help. Much appreciated.
Related
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();
}
}
}
}
first of all I'm not an English native speaker so I apologize for any eventual “weird” writing.
I'm developing a Swing Java application on Eclipse that updates a Jpanel. This panel contains several sub-panels, and I'm constantly switching the panels “modes”, what happens to be a MouseListener changing so they respond in a slightly different manner to the user mouse inputs.
Regardless of what the application do, it's happening an error that seems to have no logical explanation to me. At some point in my code I try to update the panels to what I called neutralMode. This happens on the following method:
//Guarded block (see http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html)
private synchronized boolean waitsForUserSatisfactionAnswer()
{
while(!userIndicatedSatisfaction)
{
try {
wait();
} catch (InterruptedException e) {}
}
userIndicatedSatisfaction = false; //reset for future new query
getObjectSetVisualizationPanel().neutralMode();
//getObjectSetVisualizationPanel().queryPatternMode();
return userSatisfied;
}
This updating doesn't work (the call to neutralMode() dont do what is expected). However the call to queryPatternMode() (commented on the line right below) works perfectly. So I decided to COPY queryPatternMode()'s body and PASTE it on neutralMode()'s body ECXATLY THE SAME! AND IT STILL DOESNT WORK!
The methods code is like this:
public void queryPatternMode()
{
System.out.println("Inside queryPatternMode!!!");
System.out.println("panels.size(): " + panels.size());
for (DigitalObjectPanel panel : panels)
{
System.out.println("Inside the loop!!!");
panel.resetBehavior();
panel.setQuerySelectionBehavior(gui);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
panel.validate();
}
});
}
}
public void neutralMode()
{
System.out.println("Inside neutralMode!!!");
System.out.println("panels.size(): " + panels.size());
for (DigitalObjectPanel panel : panels)
{
System.out.println("Inside the loop!!!");
panel.resetBehavior();
panel.setQuerySelectionBehavior(gui);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
panel.validate();
}
});
}
}
What happens is that, when I call neutralMode(), the “panels” collection happens to be empty (panels.size() equals zero). However when I call queryPatternMode() instead, the collection happens to have it's expected size (20 panels). But both methods are equals, and both are called from the same place!!!
What it could be??? Is there any possible explanation for that??
It definitely looks like a synchronisation issue. You should check how many threads are accessing the collection 'panels'.
It is just a stroke of luck that it works for you with queryPatternMode() all the time, and not with neutralMode(). On another fine day, it might be other way around.
The "username" will not be printed when the button (action) is triggered the first time. When the button is pressed the second time, the value is printed out once. On third click the value is printed out twice.. Can someone point out my mistake?
*This is my first question, do give hints on posting better questions :D
Here's the main method:
public class DMS implements ActionListener{
private static String username;
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
LoginFrame login = new LoginFrame();
login.setVisible(true);
}
public DMS(JTextField textField1) {
DMS.username = textField1.getText();
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(username);
}
}
And here's the action listener, which is in a jframe:
private void cmd_loginActionPerformed(java.awt.event.ActionEvent evt) {
ActionListener actionListener = new DMS(JTextField1);
someButton.addActionListener(actionListener);
}
When the control is transferred to your action listener, you are trying to print the username that you got during the initialization - which is empty.
What you need to do is:
Keep a reference of the JTextField in your DMS class
On actionPerformed(), get the data from the component.
This will ensure that you are always fetching the latest information.
Hope that helps.
Try removing this statement from cmd_loginActionPerformed so that its not being invoked every time an ActionEvent occurs
someButton.addActionListener(actionListener);
Since you have not shown the complete code, it cannot be seen when cmd_loginActionPerformed is called, but from the described behavior, it seems you are adding an action listener every time an action happens. You need to attach an action listener only once (this will fix your incremental printing), and that has to be done before any user action occurs, i.e. do it in initialization (this will fix the non-print issue on first action).
username will assigned once at the time of DMS object creation.
public DMS(JTextField textField1) {
DMS.username = textField1.getText();
}
To get the last value from the form we need to call getText(); from the actionPerformed(ActionEvent e) method
public void actionPerformed(ActionEvent e) {
DMS.username = textField1.getText();
System.out.println(username);
}
i have a textfield, when the user inserts a certain number of characters the program should put it in a JTable and clears the textfield, but it fires an event for Jtextfield.setText("");
Here's my code:
jTextField2.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
printIt();
}
#Override
public void removeUpdate(DocumentEvent e) {
}
#Override
public void changedUpdate(DocumentEvent e) {
}
private void printIt() {
//DocumentEvent.EventType type = documentEvent.getType();
String code=jTextField2.getText().trim();
// if(type.toString().trim().length()==13)
if (code.length()==4) {
list.add(code);
mod.addRow(new Object[]{code});
jTextField2.setText("");
}
}
});
}
To update the text field while using a DocumentListener you need to wrap the code in a SwingUtilities.invokeLater() so the code is executed after all updates have been done to the Document.
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
jTextField2.setText("");
}
});
You can't modify the textfield's doucment inside a DocumentListener. Use a DocumentFilter to modify text.
Document listeners should not modify the contents of the document; The
change is already complete by the time the listener is notified of the
change. Instead, write a custom document that overrides the
insertString or remove methods, or both.
In another part.
You may want to change the document's text within a document listener.
However, you should never modify the contents of a text component from
within a document listener. If you do, the program will likely
deadlock. Instead, you can use a formatted text field or provide a
document filter.
All quotes from here
Tutorial How to write a DocumentFilter
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Making FEST to wait for the application to load
NOTE: This question is basically identical to this question. Since there were no answers to that question, I decided to extend the example from there into a runnable SSCE, and provide some additional information, hoping to get some help.
So, the question is about how you should handle component lookups when the sought component might not yet exist. Look at this simple one label GUI.
public class MyFrame extends JFrame {
JLabel theLabel;
public MyFrame() {
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
theLabel = new JLabel();
theLabel.setName("theLabelName");
computeLabelContentOnWorkerThread();
}
private void computeLabelContentOnWorkerThread() {
new SwingWorker<String, Void>() {
#Override
protected String doInBackground() throws Exception {
Thread.sleep(5000);
return "Info from slow database connection";
}
#Override
protected void done() {
try {
theLabel.setText(get());
add(theLabel);
pack();
setVisible(true);
} catch (InterruptedException ignore) {
} catch (ExecutionException ignore) {
}
}
}.execute();
}
}
And this test case:
public class TestOfDelayedComponent extends FestSwingJUnitTestCase {
FrameFixture frameWrapper;
#Before
public void onSetUp() {
MyFrame frame = GuiActionRunner.execute(new GuiQuery<MyFrame>() {
protected MyFrame executeInEDT() {
return new MyFrame();
}
});
frameWrapper = new FrameFixture(robot(), frame);
frameWrapper.show();
}
#Test
public void testLabelContent() {
String labelContent = frameWrapper.label("theLabelName").text();
assertTrue(labelContent.equals("Info from slow database connection"));
}
}
What happens? The construction of the label component is delegated to a slow worker thread. So the label will not appear right away when the GUI appears. When the test case is run, the label has not appeared, so when executing the component lookup at frameWrapper.label("theLabelName"), a ComponentLookupException is thrown.
The question is how do I prevent this exception from being thrown? If it was a top level component, I could do WindowFinder.findFrame("title").withTimeout(10000) to get a FrameFinder object which finds can find frames even if there is a delay before they appear. What I want is something similar to that, but for other types of components, such as e.g. a JLabel.
NOTE: Surely, it wouldn't be all that difficult to implement the functionality by yourself. It would be rather simple to do:
while(noComponentFound and notReachedTimeout){
look for component using FEST
sleep for a short delay
}
However, it would be nice to not be forced to clutter the test scripts with such loops. It feels as if waiting for components is not a too unusual task in test scripts. So, in my opinion, there ought to be support for doing this in FEST. Maybe this is not the case? Is it not possible to wait for components?
There is a way you can write Conditions for pausing and waiting. Here is the example for your needed while(noComponentFound and notReachedTimeout). This can be done with Pause.pause(new ComponentFoundCondition(...),timeout_milis). Example:
frame = WindowFinder.findFrame("frame0").using(robot);
//Wait for the event of loading tables in the GUI before we fail looking for them concurrently
final GenericTypeMatcher<JTable> matcher = new GenericTypeMatcher<JTable>(JTable.class) {
#Override protected boolean isMatching(JTable table){ return (table instanceof myTable && table.getColumnCount()<20); } //Condition has to be totally identitary for one particular component. If not, we will never reach condition and so a timeout will be thrown in next line
};
Pause.pause(new ComponentFoundCondition("Waiting for myTable to load before I look for it...", frame.robot.finder(), matcher, frame.target), 50000); //frame.target argument can be omitted. We also put an associated 50 sec timeout even if the condition is never satisfied
fixedTable = frame.table(matcher); //Look finally for the table now we are sure its loaded
You can play with different matchers. For example, if there were just one type of table myTable under the frame it would be much simple:
final ComponentMatcher matcher = new TypeMatcher(myTable.class); // We could use an easier TypeMatcher, because we would not need to add any additional distinction apart from the class type
Pause.pause(new Condition("Waiting for myTable to load...") { // If we are totally sure that there will be only one occurrence, we can use here the simplest form like above new ComponentFoundCondition("DebugMsg", frame.robot.finder(), matcher, frame.target)
#Override public boolean test() { return !frame.robot.finder().findAll(frame.target, matcher).size().isEmpty(); } // If not we need a custom condition that checks for >0 instead of =1 results like ComponentNotFound.
}, 50000);
The problem is too that (component->frame).table(matcher) does not accept a TypeMatcher, but just a GenericMatcher, so we should create GenericMatcher after all anyway
IF you can not find anything, theres always the alternative of fixing estatically a Pause.pause(5, TimeUnit.SECONDS);
I don't use Fest but Pause.pause looks interesting on this page:
http://www.pushing-pixels.org/2009/09/23/using-fest-swing-to-test-flamingo-components.html