I call a method which lists all the files in a directory, and adds them to a JTable:
addFilesWithSubsButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
reverseLoadingVisibility(loaderLabel); //set Visible
addFilesWithSubs2(chooser, loaderLabel);
}
});
public void addFilesWithSubs2(JFileChooser chooser, JLabel loaderLabel) {
//loading all files ....
//when every file is listed:
//Set invisible
reverseLoadingVisibility(loaderLabel);
}
The another method change reverse the visibility of the JLabel in which the loading .gif is.
public void reverseLoadingVisibility(JLabel loaderLabel) {
loaderLabel.setVisible(!loaderLabel.isVisible());
}
The problem is: the gif doesn't play, freezes while the files are added to the JTable.
UPDATE: Still have problem the loading gif freezes
addFilesButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
reverseLoadingVisibility(loaderLabel);
try {
new AddFiles().doInBackground(
chooser, CHOOSERTITLE,
lastDictionary,
sdf,
filesTable,
model,
columnNames,
loaderLabel);
} catch (Exception e) {
e.printStackTrace();
}
}
});
public class AddFiles extends SwingWorker{
#Override
protected Void doInBackground() throws Exception {
return null;
}
protected void doInBackground(JFileChooser chooser, String CHOOSERTITLE,
String lastDictionary,
SimpleDateFormat sdf,
JTable filesTable,
DefaultTableModel model,
String[] columnNames,
JLabel loaderLabel) throws Exception {
//Set visible
reverseLoadingVisibility(loaderLabel);
chooser.setDialogTitle(CHOOSERTITLE);
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
//
chooser.setAcceptAllFileFilterUsed(true);
//TODO: this changed to chooser
if (chooser.showOpenDialog(chooser) == JFileChooser.APPROVE_OPTION) {
// create a file that is really a directory
File aDirectory = new File(chooser.getSelectedFile().toString());
lastDictionary = chooser.getSelectedFile().toString();
// get a listing of all files in the directory
String[] filesInDir = aDirectory.list();
// TODO
System.out.println("Number of files: " + filesInDir.length);
// have everything i need, just print it now
for ( int i=0; i<filesInDir.length; i++ )
{
File currentFile = new File(aDirectory + "\\" + filesInDir[i]);
System.out.println(filesInDir[i] );
System.out.println(aDirectory );
System.out.println(currentFile.length()/1024 + " KB");
System.out.println(sdf.format((currentFile).lastModified()));
// Avoid duplicates
int row = 0;
boolean duplicate = false;
for (; row < filesTable.getRowCount(); row++) {
if (model.getValueAt(row, 1).equals(filesInDir[i]) &&
model.getValueAt(row, 3).equals(aDirectory)
) {
duplicate = true;
break;
}
System.out.println("model.getValueAt(row, 1) " + model.getValueAt(row, 1));
System.out.println(filesInDir[i]);
System.out.println("model.getValueAt(row, 3) " + model.getValueAt(row, 3));
System.out.println(aDirectory);
}
if (!duplicate && currentFile.isFile()) {
model.addRow(new Object[]{
filesTable.getRowCount()+1,
filesInDir[i],
null,
aDirectory,
currentFile.length()/1024 + " KB",
sdf.format((currentFile).lastModified())
});
}
}
}
else {
System.out.println("No Selection ");
}
// Readjust columns
adjustTableColumns(filesTable, columnNames);
//Set unvisible
reverseLoadingVisibility(loaderLabel);
}
...
That's why because all files are loaded in the EDT (Event Dispatch Thread) (hopefully you launch your application using SwingUtilities.invokerLater() method) which cause all swing components to freeze. For more details read this java document by oracle: Initial Threads.
In order to solve your problem, you have to use a SwingWorker. A class responsible for heavy background tasks in Swing applications. With a simple google search, you can take an idea from here: How do I use SwingWorker in Java?
UPDATE in order to answer OP's comment.
The truth is that your code is a little bit big, and the most important, it is not an SSCCE.
In order to give you one more hand to find the solution you are looking for, i have created an SSCCE, using a SwingWorker that does something "heavy". In our case, the something heavy, is to write 1000 lines in a .txt file, but each line, thread (our worker) will sleep for 10ms.
Take it a look, run it if you want (i recommend it). Some extra comments inside the code, do not forget to check them.
package test;
import java.awt.BorderLayout;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class SwingWorkerExample extends JFrame {
/*
* Worker has Void doInBackground a.k.a, doInBackground method needs to return nothing.
* Worker needs to process-publish Integers.
*/
private SwingWorker<Void, Integer> writeToFileWorker = null;
private JLabel gifLabel;
private JButton doSomethingHeavy;
public SwingWorkerExample() {
super("Just a test.");
createWorker();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
gifLabel = new JLabel();
ImageIcon gif = new ImageIcon("C:/Users/George/Desktop/giphy.gif");
gifLabel.setIcon(gif);
gifLabel.setVisible(false); //Initialy non visible
gifLabel.setHorizontalTextPosition(JLabel.CENTER);
gifLabel.setVerticalTextPosition(JLabel.BOTTOM);
gifLabel.setHorizontalAlignment(JLabel.CENTER);
getContentPane().add(gifLabel, BorderLayout.CENTER);
doSomethingHeavy = new JButton("Do something heavy in another thread and start dancing...");
doSomethingHeavy.addActionListener(e -> {
//Before start the worker, show gif and disable the button
doSomethingHeavy.setEnabled(false);
gifLabel.setVisible(true);
writeToFileWorker.execute();
});
getContentPane().add(doSomethingHeavy, BorderLayout.PAGE_END);
setSize(500, 300);
setLocationRelativeTo(null);
}
private void createWorker() {
writeToFileWorker = new SwingWorker<Void, Integer>() {
#Override
protected Void doInBackground() throws Exception {
File fileToWrite = new File(System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "hello_worlds.txt");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileToWrite));) {
for (int line = 0; line < 1000; line++) {
writer.append("Hello World! My name is Swing Worker.");
writer.append(System.lineSeparator());
Thread.sleep(10);
publish(line);
}
}
return null;
}
/*
* Runs in Event Dispatch Thread (EDT)
*/
#Override
protected void process(List<Integer> chunks) {
int line = chunks.get(0);//First parameter is the line
gifLabel.setText("Written " + line + " lines in the txt.");
super.process(chunks);
}
/*
* Runs in Event Dispatch Thread (EDT)
*/
#Override
protected void done() {
//When swing worker is finished, a.k.a the heavy work, stop the gif and enable the button
gifLabel.setVisible(false);
doSomethingHeavy.setEnabled(true);
super.done();
}
};
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
SwingWorkerExample swe = new SwingWorkerExample();
swe.setVisible(true);
});
}
}
Small preview:
My guess would be that your addFilesWithSubs2-Method is blocking the UI thread. If you have long running tasks, than you have to execute them in a separate thread e.g. SwingWorker
Related
I'm quite the beginner when it comes to java & coding in general, so I apologise for any overly obvious questions asked. I've just completed part of an application which reads data from an SQL database, then sends some stuff to print to socket depending on what information is read. I'm now trying to learn swing and get a GUI working with the application. Currently I have 2 forms, the first is used to select a printer, then the second will (hopefully) work as a log/ console which tells the user what and when stuff is happening. I've got the code and the forms together in a project.
I was wanting to find out how I can make the class which has my code in run when a Jbutton is pressed on a GUI, as well as how I can stop it from running when a different JButton is pressed.
The code from the Swing Form (Form2.java) is as follows:
package com.company;
import javax.swing.*;
public class Form2
{
private JTextArea jtaConsole;
private JPanel Jframer;
private JButton stopButton;
private JButton startButton;
public Form2(String message)
{
JFrame frame = new JFrame("Print Application");
frame.setContentPane(this.Jframer);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setResizable(true);
frame.setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
jtaConsole.append(" Printer selected: " + message + "\n");
}
}
And the code from the class I want the JButton to run is as follows:
package com.company;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.sql.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ZebraCode
{
public static void main(String[] args)
{
{
while (true)
{
//SQL login.
String connectionString = "jdbc:sqlserver://:;database=;user=;password=!!;";
//Select Data.
String SQL = "SELECT TOP 2 [PK_PrintQueueID],[FK_PrinterID],[FK_BarcodeTypeID],[Barcode],[Quantity],[QueueDate],[ProcessedDate] FROM [Brad].[dbo].[PrintQueue] -- WHERE ProcessedDate IS NULL";
//Connection Variable & Time Settings.
Connection connection = null;
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
try
{
connection = DriverManager.getConnection(connectionString);
Statement stmt = connection.createStatement();
Statement stmt2 = null;
ResultSet rs = stmt.executeQuery(SQL);
while (rs.next())
{
// Get barcode value to split & Set date.
String FK_BarcodeTypeID = rs.getString("FK_BarcodeTypeID");
String barcode = rs.getString("Barcode");
String[] parts = barcode.split("-");
String part1 = parts[0];
String SQL2 = "UPDATE PrintQueue SET ProcessedDate = '" + dateFormat.format(date) + "' WHERE PK_PrintQueueID = '" + rs.getString("PK_PrintQueueID")+"'";
stmt2 = connection.createStatement();
stmt2.executeUpdate(SQL2);
// Action based on type of barcode.
if (FK_BarcodeTypeID.equals("1"))
{
// Type 128 barcode.
String zpl = "^XA^BY2,3,140^FT80,200^BCN,Y,N,N^FD>:" + rs.getString("Barcode") + "^FS^FT200,250^A0N,42,40^FH^FD" + part1 + "^FS^XZ";
printlabel(zpl);
System.out.println("New serialized barcode added.\nPrinting: " + (rs.getString("Barcode")));
System.out.println("Process date: " + dateFormat.format(date) + ".\n");
}
else
{
// Type 39 barcode.
String zpl = "CT~~CD,~CC^~CT~ ^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR4,4~SD15^JUS^LRN^CI0^XZ^XA^MMT^PW674^LL0376 ^LS0 ^BY2,3,151^FT84,249^BCN,,Y,N^FD>:" + rs.getString("Barcode") + "^FS ^PQ1,0,1,Y^XZ";
printlabel(zpl);
System.out.println("New un-serialized barcode added.\nPrinting: " + (rs.getString("Barcode")));
System.out.println("Process date: " + dateFormat.format(date) + ".\n");
}
}
} catch (SQLException e)
{
e.printStackTrace();
}
try
{
//Makes execution sleep for 5 seconds.
Thread.sleep(5000);
}
catch (InterruptedException ez)
{
}
}
}
}
//Printer Info.
public static void printlabel(String zpl)
{
try
{
Socket clientSocket;
clientSocket = new Socket("", );
DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());
outToServer.writeBytes(zpl);
clientSocket.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
Any tutorials or direction as to how I can learn this would be appreciated.
Thanks!
You want to add an action listener.. here is an example. Below are two examples on how to do so using lambdas and not using one.
JButton button = new JButton("Click Me!");
// Without lambda
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// Code to execture when clicked
}
});
//With lambda
button.addActionListener(e -> {
//code to execute when clicked
});
I'd also advise you to do a little reading, http://www.tutorialspoint.com/design_pattern/mvc_pattern.htm
Your question is a bit broad but let me offer some suggestions:
First off, you really don't want to have a JButton run the database code unchanged as doing this would be shoehorning a linear console program into an event-driven GUI, a recipe for disaster. Note that as written all your database code is held within a single static main method, and so there would be no way for the GUI to be able to control the running of that code. Either it runs or it doesn't, that's it, and no easy way for the database code to return its data to the GUI.
Instead first change that database code so that it is much more modular and OOP-friendly, including creating proper classes with state (instance fields) and behavior (instance methods), and getting almost all that code out of the static main method.
What I'm asking you to do is to create a proper model for your GUI, aka your view. Only after doing this would you have your GUI create a model object and call its methods on button push within your ActionListener. You will also want to call any long-running code within a background thread such as can be obtained with a SwingWorker.
Other issues:
You never initialize your JPanel or JTextArea variables, and so you're both adding a null variable as your JFrame's JPanel and calling methods on a null JTextArea variable, both of which will throw NullPointerExceptions.
Here's a part of code I developed to better understand Java gui. I'm also a begginer.
It's three classes: starter class, ongoing non gui processes, gui with the swingworker method. Simple, works, can safely update a lot of gui components from Swingworkers process method (passes a class instance as argument). Whole code so it can be copy/pasted:
package structure;
public class Starter {
public static void main(String[] args) {
Gui1 thegui = new Gui1();
}
}
LOGIC
package structure;
public class Logical {
String realtimestuff;
public String getRealtimestuff() {
return realtimestuff;
}
public void setRealtimestuff(String realtimestuff) {
this.realtimestuff = realtimestuff;
}
//MAIN LOGICAL PROCESS..
public void process(){
// do important realtime stuff
if (getRealtimestuff()=="one thing"){
setRealtimestuff("another thing");
}else{setRealtimestuff("one thing");
}
// sleep a while for readibility of label in GUI
//System.out.println(getRealtimestuff());
try {
Thread.sleep(250);
} catch (InterruptedException e) {
System.out.println("sleep interrupted");
return;
}
}
}
GUI CLASS with Swingworker
package structure;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JLabel;
import java.util.List;
import javax.swing.*;
public class Gui1 {
final class Dataclass{
String stringtosend;
public Dataclass(String jedan){
//super();
this.stringtosend = jedan;
}
}
// EDT constructor
JFrame frame;
public Gui1(){
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
initialize();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public void initialize() {
// JUST A FRAME WITH A PANEL AND A LABEL I WISH TO UPDATE
frame = new JFrame();
frame.setBounds(100, 100, 200, 90);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
frame.getContentPane().add(panel, BorderLayout.NORTH);
JLabel lblNovaOznaka = new JLabel();
panel.add(lblNovaOznaka);
frame.setVisible(true);
SwingWorker <Void, Dataclass> worker = new SwingWorker <Void, Dataclass>(){
#Override
protected Void doInBackground() throws Exception {
Logical logic = new Logical();
boolean looper = true;
String localstring;
while (looper == true){
logic.process();
localstring = logic.getRealtimestuff();
publish(new Dataclass(localstring));
}
return null;
}
#Override
protected void process(List<Dataclass> klasa) {
// do a lot of things in background and then pass a loto of arguments for gui updates
Dataclass xxx = klasa.get(getProgress());
lblNovaOznaka.setText(xxx.stringtosend);
}
};
worker.execute();
}
}
I am making a program to check the stock market for a symbol and I got that far, and added a basic gui to it. I am stumped on how to make it check every hour and create a green up arrow if it increased and red down arrow if it decreased.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
public class QuoteTracker {
JFrame frame;
JPanel mainPanel;
JLabel enterLabel;
JLabel resultLabel;
JTextField text;
JTextField result;
JButton query;
JButton redArrow;
JButton greenArrow;
String url;
public static void main(String[] args) {
new QuoteTracker().buildGui();
}
public class checkingQuote implements Runnable {
#Override
public void run() {
while (true) {
try {
checkQuote(url);
//if increase in value green button
System.out.println("Sleeping");
Thread.sleep(1000 * 60 * 60);
System.out.println("Waking");
} catch (InterruptedException ie) {
ie.printStackTrace();
break;
}
}
}
}
public void checkQuote(String symbol) {
try {
String url = "http://finance.yahoo.com/q?s=" + symbol + "&ql=0";
this.url = url;
Document doc = Jsoup.connect(url).get();
Elements css = doc.select("p > span:first-child > span");
result.setText(css.text());
} catch (IOException e) {
}
}
public void buildGui() {
frame = new JFrame("QuoteTracker");
mainPanel = new JPanel();
enterLabel = new JLabel("enter symbol ");
resultLabel = new JLabel("result ");
text = new JTextField(4);
result = new JTextField(8);
query = new JButton("query");
query.addActionListener(new queryListener());
mainPanel.add(enterLabel);
mainPanel.add(text);
mainPanel.add(query);
mainPanel.add(resultLabel);
mainPanel.add(result);
frame.getContentPane().add(mainPanel);
frame.setSize(300, 400);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class queryListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent ev) {
checkQuote(text.getText());
}
}
}
Do I even need a thread? I've never made one before and tried to add things that made sense. I am thinking I either need a thread or to use java's Timer?
Use SwingWorker to execute long running task in the background while updating the UI based on some results from that long running task. That means, it is actually about two different threads communicating to each other - Worker Threads and Event Dispatch Thread (EDT)
But before that, I want to point some few notes about your code.
Invoke the initialization of your UI in the EDT. That is, instead of just straightly calling new QuoteTracker().buildGui(), call it inside the run method of a Runnable passed to SwingUtilities.invokeLater (like this)
Classes should start in capital letter as per the Java standard.
To apply SwingWorker in you existing code, you can do the following :
First, you must place your checkQuote method in some other class (ideally a service class) then modify your checkQuote method to return the String that is set to the textfield result. Something like this
public Class QuoteService{
public String checkQuote(String symbol) {
try {
String url = "http://finance.yahoo.com/q?s=" + symbol + "&ql=0";
this.url = url;
Document doc = Jsoup.connect(url).get();
Elements css = doc.select("p > span:first-child > span");
return css.text();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}
You can then make your QuoteTracker class to focus mainly in the UI part of your application. Just create the service object as instance level field so that you can freely call checkQuote method within your the class.
Invoke SwingWorker when the button is clicked.
public void actionPerformed(ActionEvent ev) {
new SwingWorker<Void, String>() {
#Override // this method is done in the Worker Thread
protected Void doInBackground() throws Exception {
while(true){
String res = checkQuote(text.getText());
publish(res); //will call the process method
Thread.sleep(1000 * 60 * 60); //1 hour
}
}
#Override // this method is done in the EDT
protected void process(List<String> resultList){
String res = resultList.get(0);
if(!"".equals(res)){
result.setText(res);
}
}
#Override // this method is done in the EDT. Executed after executing the doInBackground() method
protected void done() {
//... clean up
}
}.execute();
}
Note that done() will be executed after the execution of doInBackground() is finished, which means, in the code I posted, it will never be executed since the while loop used to periodically call checkQuote is infinite. Just modify it so that you can interrupt that loop according to your need
Further Read : Concurrency in Swing
You can use thread and normal while loop in main thread as well, but at the very first , you need to start you thread and that thread must refer your object.
Add following line in public void buildGui() {
Thread t1 = new Thread(new checkingQuote());
t1.start();
This will start you thread, for testing purpose i have modified checkingQuote class
public class checkingQuote implements Runnable {
int count = 0;
#Override
public void run() {
System.out.println("Inside Runner");
while (true) {
try {
count++;
checkQuote(url);
//if increase in value green button
result.setText(""+count);
System.out.println("Sleeping");
Thread.sleep(1000);
System.out.println("Waking");
} catch (InterruptedException ie) {
ie.printStackTrace();
break;
}
}
}
}
I am seeing number change in the text box.... same way you can alter the logic to get and show the quotes.. but you must keep the value for previous quote to compare with the latest code to show green and red notification...
In gui application it is better to use Timer, also you may use ScheduledThreadPoolExecutor. But in the second case notice, that your scheduled tasks may run in non-GUI thread. As you can't invoke ATW/Swing directly from another thread, you should wrap any call to Swing into SwingUtilities.invokeLater() method.
Also notice, that when you do something durable inside GUI thread, the whole GUI becomes unrepsonsive. So, to achieve a better responsiveness, you would query in a separate thread, and expose results to Swing through invokeLater after quotes have checked. So your checkQuote method may be rewritten this way:
public void checkQuote(String symbol) {
try {
final String url = "http://finance.yahoo.com/q?s=" + symbol + "&ql=0";
Document doc = Jsoup.connect(url).get();
Elements css = doc.select("p > span:first-child > span");
final String text = css.text();
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
this.url = url;
result.setText(text);
}
}
} catch (IOException e) {
// Don't swallow exceptions
logger.error("Something gone wrong", e);
}
}
public void checkQuote() {
final String symbol = text.getText();
new Thread(new Runnable() {
#Override public void run() {
checkQuote(symbol);
}
}).start();
}
and call it from Timer and from button click listener.
I have found the following code from web regarding the drag and drop support in Java. I tested the code. Its working fine. I am using Netbeans. I found that even after I closed the application, the program is still in running mode in Netbeans.
Can anybody explain this?
Thanks in advance.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
public class DropTest2 extends JFrame implements DropTargetListener {
DropTarget dt;
JTextArea ta;
public DropTest2() {
super("Drop Test");
setSize(300, 300);
// addWindowListener(new BasicWindowMonitor());
getContentPane().add(
new JLabel("Drop a list from your file chooser here:"),
BorderLayout.NORTH);
ta = new JTextArea();
ta.setBackground(Color.white);
getContentPane().add(ta, BorderLayout.CENTER);
// Set up our text area to recieve drops...
// This class will handle drop events
dt = new DropTarget(ta, this);
setVisible(true);
}
#Override
public void dragEnter(DropTargetDragEvent dtde) {
System.out.println("Drag Enter");
}
#Override
public void dragExit(DropTargetEvent dte) {
System.out.println("Drag Exit");
}
#Override
public void dragOver(DropTargetDragEvent dtde) {
System.out.println("Drag Over");
}
#Override
public void dropActionChanged(DropTargetDragEvent dtde) {
System.out.println("Drop Action Changed");
}
#Override
public void drop(DropTargetDropEvent dtde) {
try {
// Ok, get the dropped object and try to figure out what it is
Transferable tr = dtde.getTransferable();
DataFlavor[] flavors = tr.getTransferDataFlavors();
for (int i = 0; i < flavors.length; i++) {
System.out.println("Possible flavor: " + flavors[i].getMimeType());
// Check for file lists specifically
if (flavors[i].isFlavorJavaFileListType()) {
// Great! Accept copy drops...
dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
ta.setText("Successful file list drop.\n\n");
// And add the list of file names to our text area
java.util.List list = (java.util.List) tr.getTransferData(flavors[i]);
for (int j = 0; j < list.size(); j++) {
ta.append(list.get(j) + "\n");
}
// If we made it this far, everything worked.
dtde.dropComplete(true);
return;
} // Ok, is it another Java object?
else if (flavors[i].isFlavorSerializedObjectType()) {
dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
ta.setText("Successful text drop.\n\n");
Object o = tr.getTransferData(flavors[i]);
ta.append("Object: " + o);
dtde.dropComplete(true);
return;
} // How about an input stream?
else if (flavors[i].isRepresentationClassInputStream()) {
dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
ta.setText("Successful text drop.\n\n");
ta.read(new InputStreamReader(
(InputStream) tr.getTransferData(flavors[i])),
"from system clipboard");
dtde.dropComplete(true);
return;
}
}
// Hmm, the user must not have dropped a file list
System.out.println("Drop failed: " + dtde);
dtde.rejectDrop();
} catch (Exception e) {
e.printStackTrace();
dtde.rejectDrop();
}
}
public static void main(String args[]) {
new DropTest2();
}
}
Set this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); for your JFrame in your constructor: DropTest2()
According to the javadoc, the method setDefaultCloseOperation(int operation) does the following:
Sets the operation that will happen by default when the user initiates a "close" on this frame. You must specify one of the following choices:
- DO_NOTHING_ON_CLOSE (defined in WindowConstants): Don't do anything; require the program to handle the operation in the windowClosing method of a registered WindowListener object.
- HIDE_ON_CLOSE (defined in WindowConstants): Automatically hide the frame after invoking any registered WindowListener objects.
- DISPOSE_ON_CLOSE (defined in WindowConstants): Automatically hide and dispose the frame after invoking any registered WindowListener objects.
- EXIT_ON_CLOSE (defined in JFrame): Exit the application using the System exit method. Use this only in applications.
I need to stop user making multiple clicks on a JButton while the first click still execute.
I was able to came with a solution for this issue but I do not completelly understand why it's working.
Bellow I posted the code (trimmed to a minimum) that works and the one that does not work.
In first example (good) if you run it and click the button multiple times only one action is considered as for the second example (bad) if you click the mouse multiple times you get action executed at least twice.
The second (bad) example simply does not use invokeLater() method.
Where the difference in behaviour cames from?
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long t = System.currentTimeMillis();
System.out.println("Action received");
task.setText("Working...");
task.setEnabled(false);
SwingUtilities.invokeLater(new Thread() {
#Override
public void run() {
try {
sleep(2 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
task.setEnabled(true);
task.setText("Test");
}
});
}
});
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
And now the "wrong" code
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long t = System.currentTimeMillis();
System.out.println("Action received");
task.setText("Working...");
task.setEnabled(false);
SwingUtilities.invokeLater(new Thread() {
#Override
public void run() {
try {
sleep(2 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
//SwingUtilities.invokeLater(new Runnable() {
//public void run() {
task.setEnabled(true);
task.setText("Test");
//}
//});
}
});
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
After info provided by #kleopatra and #Boris Pavlović here is the code I created that seems to work pretty decent.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
task.setText("Working...");
task.setEnabled(false);
SwingWorker worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Event " + evt + " name" + evt.getPropertyName() + " value " + evt.getNewValue());
if ("DONE".equals(evt.getNewValue().toString())) {
task.setEnabled(true);
task.setText("Test");
}
}
});
worker.execute();
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
you have two choises
1) JButton#setMultiClickThreshhold
2) you have to split this idea to the two separated actions inside actionListener or Action
1st. step, JButton#setEnabeld(false);
2nd. step, then call rest of code wrapped to the javax.swing.Action (from and dealyed by javax.swing.Timer), SwingWorker or Runnable#Thread
Okay, here's a code snippet using an Action
it disable's itself on performed
it spawns a task, at the end of which is enables itself again. Note: for simplicity here the task is simulated by a Timer, real-world would spawn a SwingWorker to do the background work, listening to its property changes and enable itself on receiving a done
set as the button's action
The code:
Action taskAction = new AbstractAction("Test") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Action received ");
setEnabled(false);
putValue(NAME, "Working...");
startTask();
}
// simulate starting a task - here we simply use a Timer
// real-world code would spawn a SwingWorker
private void startTask() {
ActionListener l = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
putValue(NAME, "Test");
setEnabled(true);
}
};
Timer timer = new Timer(2000, l);
timer.setRepeats(false);
timer.start();
}};
JButton task = new JButton(taskAction);
There are two more ways.
You can define a flag. Set it when action start and reset back after the end. Check the flags in the actionPerformed. If inProgress==true just do nothing.
Another way is to remove the listener and assign it back after the action ends.
The right way is using a SwingWorker. When user click the button before submmiting a job to the SwingWorker the state of the button should be changed to disabled JButton#setEnabled(false). After the SwingWorker finished the job state of the button should be reset to enabled. Here's Oracle's tutorial on SwingWorker
After years of dealing with the frustration of this problem, I've implemented a solution that I think is the best.
First, why nothing else works:
JButton::setMutliclickThreshold() is not really an optimal solution, because (as you said) there is no way to know how long to set the threshold. This is only good to guard against double-click happy end-users because you have to set an arbitrary threshold.
JButton::setEnabled() is an obviously fragile solution that will only make life much more difficult.
So, I've created the SingletonSwingWorker. Now, Singletons are called anti-patterns, but if implemented properly, they can be a very powerful. Here is the code:
public abstract class SingletonSwingWorker extends SwingWorker {
abstract void initAndGo();
private static HashMap<Class, SingletonSwingWorker> workers;
public static void runWorker(SingletonSwingWorker newInstance) {
if(workers == null) {
workers = new HashMap<>();
}
if(!workers.containsKey(newInstance.getClass()) || workers.get(newInstance.getClass()).isDone()) {
workers.put(newInstance.getClass(), newInstance);
newInstance.initAndGo();
}
}
}
This will enable you to create classes which extend SingletonSwingWorker and guarantee only one instance of that class will be executable at one time. Here is an example implementation:
public static void main(String[] args) {
final JFrame frame = new JFrame();
JButton button = new JButton("Click");
button.setMultiClickThreshhold(5);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
DisplayText_Task.runWorker(new DisplayText_Task(frame));
}
});
JPanel panel = new JPanel();
panel.add(button);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
static class DisplayText_Task extends SingletonSwingWorker {
JFrame dialogOwner;
public DisplayText_Task(JFrame dialogOwner) {
this.dialogOwner = dialogOwner;
}
JDialog loadingDialog;
#Override
void initAndGo() {
loadingDialog = new JDialog(dialogOwner);
JProgressBar jpb = new JProgressBar();
jpb.setIndeterminate(true);
loadingDialog.add(jpb);
loadingDialog.pack();
loadingDialog.setVisible(true);
execute(); // This must be put in the initAndGo() method or no-workie
}
#Override
protected Object doInBackground() throws Exception {
for(int i = 0; i < 100; i++) {
System.out.println(i);
Thread.sleep(200);
}
return null;
}
#Override
protected void done() {
if(!isCancelled()) {
try {
get();
} catch (ExecutionException | InterruptedException e) {
loadingDialog.dispose();
e.printStackTrace();
return;
}
loadingDialog.dispose();
} else
loadingDialog.dispose();
}
}
In my SwingWorker implementations, I like to load a JProgressBar, so I always do that before running doInBackground(). With this implementation, I load the JProgressBar inside the initAndGo() method and I also call execute(), which must be placed in the initAndGo() method or the class will not work.
Anyways, I think this is a good solution and it shouldn't be that hard to refactor code to refit your applications with it.
Very interested in feedback on this solution.
Note that when you are modifying anything in GUI your code must run on Event Dispatch thread using invokeLater or invokeAndWait if you are in another thread. So second example is incorrect as you are trying to modify enabled state from another thread and it can cause unpredictable bugs.
I have a JDesktopPane containing some JInternalFrames. I want some menus on the menubar to be activated only when one of the JInternalFrames is selected. I've tried using VetoableChangeListener, with the following code in it:
JInternalFrame selectedFrame = desk.getSelectedFrame();
if ((selectedFrame != null)) {
imageMenu.setEnabled(Boolean.TRUE);
} else {
imageMenu.setEnabled(Boolean.FALSE);
}
But the results are not what I expected - for example, the menu is enabled only the second time I add a frame. when I close all frames, it remains enabled.
How can I make this work?
you have to read basic tutorial about JInternalFrames with link to the InternalFrameListener,
but another and look like as better way is programatically to know those event in all cases and evety times is by adding PropertyChangeListener as shows examples Getting All Frames in a JDesktopPane Container, by adding PropertyChangeListener you can listeng for these events
Add an InternalFrameListener to each internal frame added to the desktop pane, and each time an event is triggered, execute the code you have shown in your question.
This code could be better written though:
setEnabled takes a primitive boolean as argument, not a java.lang.Boolean. Use true and false rather than Boolean.TRUE and Boolean.FALSE.
The expression (selectedFrame != null) evaluates as a boolean. Just write
imageMenu.setEnabled(selectedFrame != null);
instead of
if ((selectedFrame != null)) {
imageMenu.setEnabled(Boolean.TRUE);
} else {
imageMenu.setEnabled(Boolean.FALSE);
}
I would just create a custom event and fire it when a JInternalFrame gets focus (isActivated).
The menu items would listen for this event, intercept it and set their status enabled or disabled accordingly.
The advantage here is that you don't have to handle what menu items should be available for which types of internal frames, just fire the appropriate event. It'll make your life easier if you add more internal frames in the future.
This answer is based on the answer by #mKorbel. This example shows one of the ways to detect focus between internal frames as is demonstrated here:
package com.apexroot.sandbox;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
/**
* author grants unlimited license to modify, reuse and redistribute. based on
* the suggestion by #mKorbel on stackoverflow at
* http://stackoverflow.com/questions/7219860/jinternalframe-selection
* please keep a URL to the original version in the source code.
* http://javajon.blogspot.com/2015/08/windowfocuslistener-for-jinternalframe.html
*
* #author Apexroot
*/
public class InternalFrameFocusListenerExample {
public static final String INTERNAL_FRAME_FOCUS_EVENT_PROPERTY = "selected";
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
final JFrame jFrame = new JFrame();
final JDesktopPane jDesktopPane = new JDesktopPane();
final JInternalFrame[] jInternalFrames = new FocusInternalFrame[3];
for (int i = 0; i < jInternalFrames.length; i++) {
jInternalFrames[i] = new FocusInternalFrame();
}
jFrame.dispose();
jFrame.setContentPane(jDesktopPane);
jDesktopPane.setPreferredSize(new Dimension(400, 200));
jFrame.pack();
jFrame.setVisible(true);
for (int i = 0; i < jInternalFrames.length; i++) {
jDesktopPane.add(jInternalFrames[i]);
jInternalFrames[i].setLocation(10 + 60 * i, 10 + 40 * i);
jInternalFrames[i].setVisible(true);
}
}
});
}
public static class FocusInternalFrame extends JInternalFrame {
public FocusInternalFrame() {
final JLabel jLabel = new JLabel("placeholder for pack();");
setContentPane(jLabel);
pack();
this.addPropertyChangeListener(
INTERNAL_FRAME_FOCUS_EVENT_PROPERTY,
new LabelFocusListener(jLabel));
}
}
private static class LabelFocusListener implements PropertyChangeListener {
private final JLabel jLabel;
public LabelFocusListener(JLabel jLabel) {
this.jLabel = jLabel;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
// please keep a URL to the original version in the source code.
// http://javajon.blogspot.com/2015/08/windowfocuslistener-for-jinternalframe.html
if (INTERNAL_FRAME_FOCUS_EVENT_PROPERTY.equals(
evt.getPropertyName())) {
final Object oldValue = evt.getOldValue();
final Object newValue = evt.getNewValue();
if (oldValue instanceof Boolean
&& newValue instanceof Boolean) {
boolean wasInFocus = (Boolean) oldValue;
boolean isInFocus = (Boolean) newValue;
if (isInFocus && !wasInFocus) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
// focus gained
jLabel.setText("focus gained");
}
});
} else if (wasInFocus && !isInFocus) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
// focus lost
jLabel.setText("focus lost");
}
});
}
}
}
}
}
}