I have a Java application used to run tournaments in which I built an auto-suggestion feature that gets names from a database and displays them in a JPopupMenu. I haven't been able to replicate this bug on demand, but once in a while one of the JPopupMenus will disappear like normal with the exception that an outline of where it was is still on the screen and is displayed over everything including other programs even if my application is minimized.
Here is a screenshot of what I'm talking about:
You can see that underneath "Espinoza" some remnant of the JPopupMenu is still being displayed. This sometimes contains text inside and other times just has the background color only in an empty box. This remnant is purely cosmetic and I haven't found any way of interacting with it either physically or programatically (hot-coding).
Here is the method I'm using to display the JPopupMenu:
private void resetLastNamePopup() {
Thread t = new Thread() {
#Override
public void run() {
lnPopup.setVisible(false);
lnPopup.removeAll();
if(DBHSDatabaseIntermediary.isConnected()) {
if(!(fnTextField.getText().equals("") && lnTextField.getText().equals(""))) {
JMenuItem item = null;
String[] names = DBHSDatabaseIntermediary.getLastNames(fnTextField.getText(), lnTextField.getText());
for(int i=0; i < names.length; i++) {
if(!names[i].equals(lnTextField.getText().trim())) {
item = new JMenuItem(names[i]);
item.addActionListener(lnActionListener);
item.addMouseListener(NewPlayerPanel.this);
lnPopup.add(item);
}
}
if(names.length > 0 && !names[0].equals("")) {
lnPopup.setVisible(true);
}
lnPopup.grabFocus();
}
}
}// ends run()
};
t.start();
}
Thank you in advance.
Swing methods and constructors must be called on the AWT event dispatch thread. You are calling those methods on a different thread. The result is “undefined behavior”—which usually means things will work sometimes, but not all the time.
You need to separate Swing calls from database calls, which is done using EventQueue.invokeLater (or its alias, SwingUtilities.invokeLater):
private void resetLastNamePopup() {
lnPopup.setVisible(false);
lnPopup.removeAll();
final String fn = fnTextField.getText();
final String ln = lnTextField.getText();
Thread t = new Thread() {
#Override
public void run() {
if(DBHSDatabaseIntermediary.isConnected()
&& !fn.isEmpty() && !ln.isEmpty()) {
final String[] names =
DBHSDatabaseIntermediary.getLastNames(fn, ln);
// Rebuild JPopupMenu in AWT event thread.
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
for (String name : names) {
if (!name.equals(ln)) {
JMenuItem item = new JMenuItem(name);
item.addActionListener(lnActionListener);
lnPopup.add(item);
}
}
if (names.length > 0 && !names[0].isEmpty()) {
lnPopup.setVisible(true);
lnPopup.grabFocus();
}
}
});
}
}// ends run()
};
t.start();
}
For more information, see the javax.swing package contract, and Concurrency in Swing in the Java Tutorials.
Related
My requirement is to use addDocumentListener, the doSearchCmb basically narrows down items in combobox, function is working if keypressed is used. If I remove the function Runnable doSearchCmb and put the narrowing down of items in insertUpdate without using invokeLater, I get an error of 'Attempt to mutate notification' exception.
In my current code, my screen freezes after I type a letter. After waiting several minutes, I get the error of java.lang.OutOfMemoryError: Java heap space.
I tried to add return; after combo.repaint();, my screen didn't freeze, there's no java heap space error but nothing happened at all. I attached the code without the return.
What can I do here to remain the use of addDocumentListener and the function which narrows down the items of the combobox?
TCombo combo = new TCombo();
JTextComponent editor = (JTextComponent) combo.getEditor().getEditorComponent();
editor.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent arg0) {
}
public void insertUpdate(DocumentEvent arg0) {
searchCmb();
}
public void removeUpdate(DocumentEvent arg0) {
searchCmb();
}
private void searchCmb() {
Runnable doSearchCmb = new Runnable() {
#Override
public void run() {
String item = combo.getEditor().getItem().toString().trim();
boolean isEmpty = item.equals("");
CmbElement[] foundList = null;
String toFind = "";
List list = new ArrayList(0);
if (!isEmpty) {
combo.removeAllItems();
combo.setItems(elements);
for (int i = 1; i < elements.length; i++) {
if (elements[i].getName().contains(toFind)) {
if (i == 1) {
list.add("");
}
list.add(elements[i]);
}
foundList = (CmbElement[]) list.toArray(new CmbElement[list.size()]);
}
if (list.size() > 0) {
combo.removeAllItems();
combo.setItems(foundList);
} else {
combo.removeAllItems();
if (toFind.equals("")) {
combo.setItems(elements);
}
list.add(new DCmbElement("", ""));
foundList = (CmbElement[]) list.toArray(new CmbElement[list.size()]);
combo.setItems(foundList);
}
combo.repaint();
}
}
};
SwingUtilities.invokeLater(doSearchCmb);
}
});
CmbElement:
public abstract interface CmbElement {
public abstract String getKey();
public abstract String getName();
}
Note: Narrow down items in combo box means when user inputs a letter, or paste a word, the items in combo box gets filtered using the current letter or word as parameter. It searches through the items and narrows it down.
For reference the behavior is like the image here: jcombobox filter in java - Look and feel independent
My function indicated in run() is working fine if keypressed of keylistener is used, but my requirement is to use addDocumentListener
I have a method like below.
ProgressWindow is a sub class of JFrame containing JProgressBar.
addProgress() increments a value in the JProgressBar.
If I call this method from a method in another class, a frame of ProgressWindow will show up but not JProgressBar and some JLabels inside the frame. They show up after the last line (System.out.println("finish")).
If I call this method in a main method in the class containing this method, then every component (Bar, labels...) instantly shows up.
What can I do for showing the window correctly?
static void search(){
ProgressWindow window = new ProgressWindow();
window.setVisible(true);
ExecutorService execs = Executors.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
Collection<Callable<Void>> processes = new LinkedList<>();
for (int i = 0; i < 100; i++) {
processes.add(new Callable<Void>() {
#Override
public Void call() throws Exception {
progressWindow.addProgress(); // increment progress value
return null;
}
});
}
try {
execs.invokeAll(processes);
} catch (Exception e) {
e.printStackTrace();
} finally {
execs.shutdown();
}
System.out.println("finish");
The main problem is you seem to be calling search from the context of the Event Dispatching Thread.
The problem occurs because you are using execs.invokeAll which blocks until all the callables have finished running.
This means that the EDT is unable to process new events in Event Queue, including repaint events, this is why your UI is coming to a stand still...
There are a number of issues you are now going to face...
You should never update/modify a UI component from any thread other than the EDT
You should block the EDT for any reason
You seem to want to know when the search is complete, so you know need some kind of event notification...
The first thing we need is some way to be notified that the search has completed, this means you can no longer rely on search returning when the search is complete...
public interface SearchListener {
public void searchCompleted();
}
Next we need an intermeditate search method that builds the UI and ensure that the search is launched within it's own Thread...
static void search(final SearchListener listener) {
final ProgressWindow window = new ProgressWindow();
window.setVisible(true);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
search(listener, window);
}
});
t.start();
}
Then we need to modify the original search method to utilise the SearchListener interface to provide notification when the search is complete...
static void search(final SearchListener listener, final ProgressWindow window){
ExecutorService execs = Executors.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
Collection<Callable<Void>> processes = new LinkedList<>();
for (int i = 0; i < 100; i++) {
processes.add(new Callable<Void>() {
#Override
public Void call() throws Exception {
// This method needs to ensure that
// what ever it does to the UI, it is done from within
// the context of the EDT!!
progressWindow.addProgress();
return null;
}
});
}
try {
execs.invokeAll(processes);
} catch (Exception e) {
e.printStackTrace();
} finally {
execs.shutdown();
}
System.out.println("finish");
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
listener.searchCompleted();
}
});
}
Now, without the source code for addProgress, I might be tempted to use
processes.add(new Callable<Void>() {
#Override
public Void call() throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
progressWindow.addProgress();
}
});
return null;
}
});
}
Instead...
Take a look at Concurrency in Swing for more details
Sounds like you what you're wanting to do is invoke the setVisible on the Swing UI thread, you can do this with invokeAndWait or invokeLater.
So something like:
final ProgressWindow window = new ProgressWindow();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
window.setVisible(true);
}
});
So my JProgressBar I have set up doesn't work the way I want it. So whenever I run the program it just goes from 0 to 100 instantly. I tried using a ProgressMonitor, a Task, and tried a SwingWorker but nothing I tried works.
Here is my program:
int max = 10;
for (int i = 0; i <= max; i++) {
final int progress = (int)Math.round(
100.0 * ((double)i / (double)max)
);
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);
}
jProgressBar2.setValue(progress);
}
});
}
#MadProgrammer Here is my attempt at making a swing worker and writing each name to the document and updating the progress bar. The program gets to around 86 percent and stops, never creating the finished document. The program creates a blank document. Here are the two methods first is the SwingWorker object I made:
public class GreenWorker extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
int max = greenList.size();
XWPFParagraph tmpParagraph;
XWPFRun tmpRun;
FileInputStream file =
new FileInputStream(location + "GreenBandList.docx");
gDocument = new XWPFDocument(OPCPackage.open(file));
for (int i = 0; i < max; i++) {
tmpParagraph = gDocument.getParagraphs().get(0);
tmpRun = tmpParagraph.createRun();
if (greenList.get(i).length() == 1) {
tmpRun.setBold(true);
tmpRun.setText(greenList.get(i));
tmpRun.setBold(false);
} else {
tmpRun.setText(greenList.get(i));//Write the names to the Word Doc
}
int progress = Math.round(((float) i / max) * 100f);
setProgress(progress);
}
return null;
}
}
And here is the code for the button that starts it and has my property change event.
private void GenerateGreenList() throws IOException, InterruptedException {
//Need to fix the bug that removes the Letter Header in Yellow Band list
//********************************************************************\\
//Delete the old list and make a new one
File templateFile = new File(location + "\\backup\\GreenTemplate.docx");
FileUtils.deleteQuietly(new File(location + "GreenBandList.docx"));
FileUtils.copyFile(templateFile, new File(location +
"GreenBandList.docx"));
//Get the New Entries
String[] entries = jTextPane3.getText().split("\n");
for (String s : entries) {
if (s != null) {
greenList.add(s);
}
}
//Resort the list
Collections.sort(greenList);
//Write the names to the document
GreenWorker worker = new GreenWorker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
jProgressBar2.setValue((Integer) evt.getNewValue());
}
}
});
worker.execute();
if (worker.isDone()) {
try {
gDocument.write(new FileOutputStream(new File(location + "GreenBandList.docx")));
////////////////////////////////////////////////////////////
} catch (IOException ex) {
Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);
}
JOptionPane.showMessageDialog(null, "Green Band List Created!");
jProgressBar2.setValue(0);
}
}
I used the property change listener from one of your other posts but I don't really understand what the one you wrote does or what it does in general?
Swing is a single threaded environment, that is, there is a single thread which is responsible for processing all the events that occur within the system, including repaint events. Should anything block this thread for any reason, it will prevent Swing from processing any new events, including, repaint events...
So all this ...
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex); }
jProgressBar2.setValue(progress);
}
});
Is constantly pausing the Event Dispatching Thread, preventing it from actually doing any updates (or at least spacing them randomly)...
It's also likely that your outer loop is been run from within the context of the EDT, meaning that until it exists, nothing in the Event Queue will be processed. All your repaint requests will be consolidated down to a single paint request and voila, instant filled progress bar...
You really should use a SwingWorker - I know you said you tried one, but you've not shown any code as to your attempt in this regards, so it's difficult to know why it didn't work, however...
SwingWorker and JProgressBar example
SwingWorker and JProgressBar example
SwingWorker and JProgressBar example
SwingWorker and JProgressBar example
SwingWorker and dual welding JProgressBar example
SwingWorker and JProgressBar example
And forgive me if we haven't said this a few times before :P
You are evoking Thread.sleep inside the EvokeLater which means that it is running on another thread than your for loop. i.e., your for loop is completing instantaneously (well, however long it takes to loop from 1 to 100, which is almost instantaneously).
Move Thread.sleep outside of EvokeLater and it should work as you intend.
int max = 10;
for (int i = 0; i <= max; i++) {
final int progress = (int)Math.round(
100.0 * ((double)i / (double)max)
);
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
jProgressBar2.setValue(progress);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);
}
}
Edit: agree with #MadProgrammer. It appears this is just an illustrative question, but you should make sure whatever you're trying to accomplish here you use a SwingWorker for.
I want to update a JLabel each time with the name of the file which I am downloading using FTPClient. I tried repaint(), validate(), revalidate(), first invalidate() and immediately validate()/revalidate() but still nothing is working.
My Code goes as follows:
if(ae.getActionCommand()=="Download"){
int[] row_indexes=table.getSelectedRows();
notifylb.setText("Downloading files");
this.validate();
for(int i=0;i<row_indexes.length;i++)
{
String fn=table.getValueAt(row_indexes[i], 0).toString();
notifylb.setText("Downloading: "+fn); // fn contains filename
this.validate();
this.downloadFtpfile(fn);
}
notifylb.setText("SUCCESSFULLY DOWNLOADED FILE(s) !");
this.validate();
}
Suggestions:
Don't use == to compare Strings. Instead use the equals(...) or equalsIgnoreCase(...) methods. The == operator returns true if the two String objects are the same, but this isn't what matters to you, but rather you want to check if both Strings hold the same characters in the same order, and this is what the two methods above check.
You're currently downloading your files on the Swing event dispatch thread or EDT, and this will not only prevent your JLabel from updating but will also cause your GUI to freeze since this thread is responsible for drawing all Swing graphics including its own components, and for Swing interaction with the user.
Calling repaint(), revalidate(), invalidate(), etc... will do nothing to solve this.
To solve this, do the downloading or any long-running process in a background thread. One way to do this is by creating a new Thread, loading it with a Runnable, and calling start, but there's a better way that is tailor made for Swing GUI's and that's to create a SwingWorker object and do your background coding in its doInBackground() method. The SwingWorker tutorials can help you figure out how to do this, and if you get stuck in your attempt, please come on back with your code.
You'll probably not want to mention your urgency as this often has the opposite effect intended. Please remember that we are all volunteers, that your urgency is truly your urgency not ours, and that no one likes to feel rushed or pressured to do something for someone else, especially volunteers.
Best of luck and welcome to stackoverflow.
Edit
Since you've seen an example of using plain Threads, I figured I'd post an example of what doing this with a SwingWorker object could look like:
if (ae.getActionCommand().equalsIgnoreCase("Download")) {
final int[] row_indexes = table.getSelectedRows();
notifylb.setText("Downloading files");
final List<String> fileNames = new ArrayList<String>();
for (int i = 0; i < row_indexes.length; i++) {
fileNames.add(table.getValueAt(row_indexes[i], 0).toString());
}
SwingWorker<String, String> downloadSwingWorker = new SwingWorker<String, String>(){
#Override
protected String doInBackground() throws Exception {
for (String fileName : fileNames) {
publish("Downloading: " + fileName);
downloadFtpfile(fileName);
}
return "SUCCESSFULLY DOWNLOADED FILE(s) !";
}
#Override
protected void process(List<String> chunks) {
for (String text : chunks) {
notifylb.setText(text);
}
}
#Override
protected void done() {
try {
String text = get();
notifylb.setText(text);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
downloadSwingWorker.execute();
}
Edit 2: corrected as per kleopatra's suggestion
#Hovercraft-Full-Of-Eels explain very clear, but if you need the code, here it is how to write it.
final JButton finalButton = button; // this is your button will trigger download
final JLabel finalLabel = finalLabel;
final JTable finalTable = table;
if(ae.getActionCommand().equals("Download"))
{
finalButton.setEnabled(false); //disable button, so user can not start it for twice until ftp finished.
Thread thread = new Thread(new Runnable()
{
#Override
public void run()
{
int[] row_indexes = finalTable.getSelectedRows();
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
finalLabel.setText("Downloading files");
}
});
for(int i = 0; i < row_indexes.length; i++)
{
final String fn = finalTable.getValueAt(row_indexes[i], 0).toString();
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
finalLabel.setText("Downloading: " + fn); // fn contains filename
}
});
this.downloadFtpfile(fn);
}
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
finalLabel.setText("SUCCESSFULLY DOWNLOADED FILE(s) !");
finalButton.setEnabled(true); //enable the button
}
});
}
});
thread.start();
};
I'm trying to use threads to run a lenghty operation in the background and update the UI.
Here's what i'm trying to do:
on a button click, display a popupjframe with a message "Inserting into DB"
create a new thread to insert 1000s of entries into a database.
when the entries are inserted, i want the popupjframe to disappear and display a joptionpane with yes, no buttons
on clicking the yes button i want to display another frame with a report/details about the insertion process
Here's my code:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
//display popupframe first
jFrame1.pack();
jFrame1.setVisible(true);
jFrame1.setLocationRelativeTo(getFrame());
Thread queryThread = new Thread() {
public void run() {
runQueries();
}};
queryThread.start();
}
//runqueries method inserts into DB
private void runQueries() {
for (int i = 0; i <= 50000; i++) {
insertintoDB();
updateProgress(i);
}
}
//update the popupjframe
private void updateProgress(final int queryNo) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (queryNo == 50000) { //means insertion is done
jFrame1.setVisible(false);
int n = JOptionPane.showConfirmDialog(getFrame(), menuBar, null, JOptionPane.YES_NO_OPTION);
if (n == 1) { //NO option was selected
return;}
else
//display another popupframe with details/report of inserting process
}});
}
Is my approach correct??
How and when do i stop/interrupt the "queryThread"??
Is it correct if i make the popupjframe in the runqueries method itself (after the for loop) and display the joptionpane??
Thanks in advance.
Look at the documentation for SwingWorker. It does exactly what you are trying to do. Create a subclass, and call runQueries from doInBackground(), and then do what your runnable does (minus the if queryNo check) in done(). There are third party versions of this class if you are not using java 1.6.
class DbSwingWorker extends SwingWorker<Void, Integer> {
#Override
protected Void doInBackground() throws Exception {
for (int i = 0; i <= 50000; i++) {
insertintoDB();
publish(i); //if you want to do some sort of progress update
}
return null;
}
#Override
protected void done() {
int n = JOptionPane.showConfirmDialog(getFrame(), menuBar, null, JOptionPane.YES_NO_OPTION);
if (n == 1) { //NO option was selected
return;
} else {
//display another popupframe with details/report of inserting process
}
}
}
The original, non-1.6 version can be found here: https://swingworker.dev.java.net/