I'm setting up a mock program in an MVC fashion with Swing. I'm having troubles calling setText on a JLabel from an external Thread. I know Swing has its own threading model (setText is thread-safe though), so I've also tried forcing the setText call to be carried out by the EDT via SwingUtilities functions, with no results.
This is what the classes look like - I've omitted the irrelevant chunks, suppose they have references to each other and the GUI's layout is properly set up:
public class View extends JFrame
{
private final JLabel label = new JLabel("idle");
private final JButton button = new JButton("press me");
public View(final Controller controller)
{
this.controller = controller;
button.addListener(e -> controller.input());
}
public void refresh(final boolean value)
{
label.setText(value ? "YEP" : "NOPE");
}
}
public class Controller
{
private final ExecutorService service =
Executors.newSingleThreadExecutor();
public void input()
{
service.submit((Runnable) () ->
{
try
{
view.refresh(true);
Thread.sleep(1000);
}
catch(final InterruptedException exception)
{
exception.printStackTrace();
}
finally
{
view.refresh(false);
}
});
}
}
Expected behavior:
Button is pressed
input() is called on controller
A new thread is created, which first sets label's text to "YEP"
The newly created thread simulates some delay with Thread.sleep()
It awakens and sets label's text to "NOPE"
What I get is that after the call at point 3 the label disappears, but is drawn again at 5, showing the text "NOPE".
What am I doing wrong?
Related
I have a swing application that is quite slow to start up because it has to load a thousand pictures into the GUI. It takes 10 seconds to start up.
It is a single thread application, how could I code multithread to speed up the task? The following code is in a for loop of 1000 iterations.
ImageIcon icon = new ImageIcon(Files.readAllBytes(coverFile.toPath()));
// ImageIcon icon = createImageIcon(coverFile);
JLabel label = null;
if (coverCount % 2 == 0) {
label = createLabel(coverFile, movieFile, icon, SwingConstants.LEFT);
} else {
label = createLabel(coverFile, movieFile, icon, SwingConstants.CENTER);
}
box.add(label);
The images are being loaded and put into a Box sequentially. I have two difficulties if I want to do it multithread
How does a thread return value to parent
How to achieve non-blocking call back which add the image to the
box sequentially
Thank you.
How does a thread return value to parent
Use a call-back mechanism. For Swing that would mean using a SwingWorker and notifying the GUI of thread completion either in the worker's done() method or by adding a PropertyChangeListener to the worker, listening to the worker's "state" property, for when it changes to SwingWorker.StateValue.DONE
How to achieve non-blocking call back which add the image to the box sequentially
The SwingWorker has a publish/process method pair that allows sending data sequentially from the background thread via the publish method, and then handle the data sequentially on the event thread within the process method. This requires use of a SwingWorker<VOID, Image> or SwingWorker<VOID, Icon> or something similar, the 2nd generic parameter indicating the type of object sent via this mechanism.
For example:
public class MyWorker extends SwingWorker<Void, Icon> {
#Override
protected Void doInBackground() throws Exception {
boolean done = false;
while (!done) {
// TODO: create some_input here -- possibly a URL or File
Image image = ImageIO.read(some_input);
Icon icon = new ImageIcon(image);
publish(icon);
// TODO: set done here to true when we ARE done
}
return null;
}
#Override
protected void process(List<Icon> chunks) {
for (Icon icon : chunks) {
// do something with the icon here
// on the event thread
}
}
}
And to use it within a GUI:
// may need constructor to pass GUI into worker
final MyWorker myWorker = new MyWorker();
myWorker.addPropertyChangeListener(evt -> {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
// We're done!
// call get to trap errors
try {
myWorker.get();
} catch (InterruptedException | ExecutionException e) {
// TODO: real error handling needed here
e.printStackTrace();
}
}
});
myWorker.execute(); // start worker in a background thread
For more on Swing concurrency, please check out Lesson: Concurrency in Swing
Multithreading will speed up the application but I think doing a lazy load is a better approach (you can do both). You can't be displaying all these images at the same time so I suggest you load the images that will be visible at the start and after that load the image as needed this will hugely increase your performance and use less memory/resource.
If you really want to load all 1000 images:
It is enough to use one background thread, so that you don't slow down the main Swing Event loop thread.
Create a custom class which implements runnable, and has references to all the context to do the job. Like so:
public static class IconLoader implements Runnable{
private List<File> movies;
private File coverFile;
private JPanel box;
public IconLoader(JPanel box, File coverFile, List<File> movies) {
this.box = box;
this.coverFile = coverFile;
this.movies = movies;
}
#Override
public void run() {
for(int coverCount=0;coverCount<movies.size();coverCount++) {
try {
final JLabel label;
File movieFile = movies.get(coverCount);
ImageIcon icon = new ImageIcon(Files.readAllBytes(coverFile.toPath()));
// ImageIcon icon = createImageIcon(coverFile);
if (coverCount % 2 == 0) {
label = createLabel(coverFile, movieFile, icon, SwingConstants.LEFT);
} else {
label = createLabel(coverFile, movieFile, icon, SwingConstants.CENTER);
}
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
box.add(label);
}
});
}catch(IOException e) {
e.printStackTrace();
}
}
}
private JLabel createLabel(File coverFile, File movieFile, ImageIcon icon, int direction) {
//Create the label and return
return null;
}
}
Then start the loading process during your app initialization, by passing the runnable to a new thread, and starting the thread. Like so:
new Thread( new IconLoader(box, coverFile, movies) ).start();
I want to know how I can do a communication between the Java Swing Application and between my leap motion listener.
Because I want that in my application when I click on a button I can change a number with the number of finger see by the leap motion.
I have one Java Swing application :
public class KidCountingFrame extends JFrame
And one Leap Motion Runnable:
public class LeapMouse implements Runnable
{
#Override
public void run()
{
CustomListener l = new CustomListener();
Controller c = new Controller();
c.addListener(l);
}
}
Which is launching a Leap Motion Listener... :
public class CustomListener extends Listener
Maybe I have to use a design pattern ?
* UPDATE : *
I try to applied the ProgressBarDemo on my project and to follow explications.
But one error happens when I put the listener in the SwingWorker constructor :
Exception in thread "Thread-1443" java.lang.NullPointerException: null upcall object
Here my updated code :
public class PanelLeapFingers extends JPanel implements ActionListener,
PropertyChangeListener
{
private JButton digitDisplayButton;
private Task task;
class Task extends SwingWorker<Void, Void>
{
public Task()
{
try
{
NbFingersListener l = new NbFingersListener();
Controller c = new Controller();
c.addListener(l);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
#Override
public Void doInBackground()
{
// In background :
// Find how digit fingers are shown by the user
int progress = 0;
//setProgress(????); //I don't really know how call the listener here
setProgress(5); //Here it's just to make a test
return null;
}
#Override
public void done()
{
Toolkit.getDefaultToolkit().beep();
digitDisplayButton.setEnabled(true);
setCursor(null); // turn off the wait cursor
}
}
public PanelLeapFingers()
{
super(new BorderLayout());
digitDisplayButton = new JButton("?");
digitDisplayButton.setFont(new Font("Arial", Font.PLAIN, 40));
digitDisplayButton.setActionCommand("start");
digitDisplayButton.addActionListener(this);
JPanel panel = new JPanel();
panel.add(digitDisplayButton);
add(panel, BorderLayout.PAGE_START);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}
public void actionPerformed(ActionEvent evt)
{
digitDisplayButton.setEnabled(false);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
task = new Task();
task.addPropertyChangeListener(this);
task.execute();
}
public void propertyChange(PropertyChangeEvent evt) {
if ("progress" == evt.getPropertyName()) {
int progress = (Integer) evt.getNewValue();
digitDisplayButton.setText(progress+"");
}
}
}
I'm not sure to be on the good way and I don't understand how I can receive information from my listener in my setProgress( ) function.
EDIT :
Solution : Finally I have decide to use a Singleton Model to communicate between the listener and the Java Swing APP. I save all informations when the Listener is working in the Singleton Model and I recover the information that I need in the Java Swing APP.
As discussed here, the Listener will be called asynchronously, typically from another thread. To avoid blocking the event dispatch thread, create your Listener in the constructor of a SwingWorker and arrange for your doInBackground() implementation to publish() frames of interest; process() can then handle these frames on the event dispatch thread.
Alternatively, poll the Controller at a suitable rate in the ActionListener of javax.swing.Timer.
I am trying to learn ProgressMonitor in Java Swing.
I created this simple test code -
public class ProgressMonitorTest extends JFrame
{
private JPanel contentPane;
private ProgressMonitor progressMonitor;
private JButton button;
private static ProgressMonitorTest frame;
private static boolean isFrameReady;
public JButton getButton()
{
return button;
}
public ProgressMonitor getProgressMonitor()
{
return progressMonitor;
}
/**
* Launch the application.
*/
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
frame = new ProgressMonitorTest();
frame.setVisible(true);
isFrameReady = true;
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
while(!isFrameReady)
{
//
}
frame.getButton().addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
try
{
for(int i=0;i<=10;i++)
{
final int percent = i;
SwingUtilities.invokeAndWait(new Runnable()
{
#Override
public void run()
{
frame.getProgressMonitor().setProgress(percent * 10);
frame.getProgressMonitor().setNote("Completed " + percent*10 + "%.");
}
});
try
{
Thread.sleep(1000);
}
catch(Exception ee)
{
//
}
}
}
catch(Exception es)
{
//
}
}
});
}
/**
* Create the frame.
*/
public ProgressMonitorTest()
{
isFrameReady = false;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
setTitle("Progress Monitor");
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
progressMonitor = new ProgressMonitor(frame, "Update in progress...", "", 0, 10);
button = new JButton("Click Here");
contentPane.add(button);
setContentPane(contentPane);
}
}
A few questions regarding this-
If I remove the isFrameReady check, the program says a NullPointerException at the line where I assign the button's action listener.
If I keep the above check, then clicking on the button does nothing.
Keeping the above check and then debugging this, I let it wait for some time before it gets to the line where the action listener. In this case, it works but immediately quits saying it can't call invokeAndWait from the event handling thread.
What am I missing in all this ? Can someone explain how to get this to work.
If I remove the isFrameReady check, the program says a
NullPointerException at the line where I assign the button's action
listener.
your use of isFrameReady ensures that you have created your frame successfully. inside your main, your posted request to event dispatch thread(EDT) using call EventQueue.invokeLater(new Runnable(){}): removing the check isFrameReady, you were going to call frame.getButton() in main thread but the frame have not been yet created by frame = new ProgressMonitorTest(); in the EDT and thus a NullPointerException occurs.
If I keep the above check, then clicking on the button does nothing.
you should understand by now, that above check is nothing to do with button click. The button is not doing anything because the GUI got freezed for violating swing's single threading rule. Put your incrementing for loop of the actionPerformed method inside another thread as the following code fragement shows and execute it from there. you will see that it works fine.
new Thread(){
public void run()
{
for(int i=0; i<10; i++)
{
//whatever you were doing.
}
}
}.start();
Keeping the above check and then debugging this, I let it wait for
some time before it gets to the line where the action listener. In
this case, it works but immediately quits saying it can't call
invokeAndWait from the event handling thread.
SwingUtitlies.invokeAndWait() blocks the current thread and waits until the EDT is done executing the task given to it. As actionPerformed() function is already running inside EDT, so calling SwingUtitlies.invokeAndWait() from the current thread:EDT would block the current thread:EDT which should not be allowed. Don't use invokeAndWait for this case. you should call SwingUtilities.invokeLater() instead.
However I don't think you will get anything until you understand Swing threading model. Read the javadoc and some internet resource. DO HAVE The book Filthy Rich Clients and try the example the book offered: You will have a greater knowledge in graphical effects then any other resource can provide.
I have a public class AppHelper for displaying some help content using a jframe. There is an exit button on the same JFrame which on click disposes the jframe.
The ActionListener is implemented as a static nested class of the class mentioned above.
Also all the components of the help window are defined in the outer class and all of them are private and static. Also the method that shows the help window is static.
Here is some code that I have implemented:
public class AppHelper {
// helper frame
private static JFrame appHelperFrame;
// helper panel
private static JPanel appHelperPanel;
// helper pane
private static JEditorPane appHelperPane;
// exit helper button
private static JButton exitAppHelperButton;
// constraints
private static GridBagConstraints appHelperPaneCons, exitAppHelperButtonCons;
/**
set layout
*/
private static void setLayoutConstraints () {
// defines layout
}
/**
* initialize the helper elements
* #param void
* #return void
*/
public static void initializeElements () {
// initialize constraints
setLayoutConstraints();
// handler
AppHelper.AppHelperHandler appHelpHandler = new AppHelper.AppHelperHandler();
appHelperFrame = new JFrame("App Help");
appHelperPanel = new JPanel();
appHelperPanel.setLayout(new GridBagLayout());
appHelperPane = new JEditorPane();
exitAppHelperButton = new JButton("Exit");
exitAppHelperButton.addActionListener(appHelpHandler);
java.net.URL helpURL = null;
try {
helpURL = new File("AppHelp.html").toURI().toURL();
} catch (MalformedURLException ex) {
Logger.getLogger(AppHelper.class.getName()).log(Level.SEVERE, null, ex);
}
try {
appHelperPane.setPage(helpURL);
} catch (IOException ex) {
Logger.getLogger(AppHelper.class.getName()).log(Level.SEVERE, null, ex);
}
appHelperPane.setEditable(false);
appHelperFrame.add(appHelperPanel);
appHelperPanel.add(appHelperPane, appHelperPaneCons);
appHelperPanel.add(exitAppHelperButton, exitAppHelperButtonCons);
appHelperFrame.setSize(350, 400);
appHelperFrame.setResizable(false);
appHelperFrame.setVisible(true);
}
/**
* TODO
*/
public static void showAboutApp() {
//throw new UnsupportedOperationException("Not yet implemented");
}
/**
*
* Acts as the handler for the help window components
* Implement actionListener interface.
*/
private static class AppHelperHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if(source == exitAppHelperButton) {
appHelperFrame.dispose();
}
}
}
}
The reason of disposing the JFrame instead of setting it invisible is that I dont want this JFrame to consume memory when this JFrame is not in use.
Now the problem is first time I click on the help button (on some other window) the JFrame is shown. Now when I click the exit button on this help window the JFrame is disposed by the handler. Next time I again click on the help button, the help window is not shown. I wanted to know if there is any error in my code or I need to do some thing else.
The javadoc of Window.dispose() states that
The Window and its subcomponents can be made displayable again by rebuilding the native resources with a subsequent call to pack or show.
And that works too, I've tried it. Just call appHelperFrame.setVisible(true) and that's all. If the window is not activated, try calling appHelperFrame.setState(Frame.NORMAL) which will acitvate it.
You only have to call your initializeElements method once though. Your showAboutApp() method should look something like this:
public static void showAboutApp() {
if (appHelperFrame == null)
initializeElements(); // This also makes the frame visible
else {
appHelperFrame.setVisible(true);
appHelperFrame.setState(Frame.NORMAL);
}
}
Final note:
If you always call this showAboutApp() from the EDT (Event Dispatching Thread) then you're good. If you call this from multiple threads, you might want to execute it in the EDT with like SwingUtilities.invokeAndwait() or SwingUtilities.invokeLater() which also ensures synchronization between multiple threads.
I've hit the infinite loop problem in Swing. Done some research and come across SwingWorker threads but not really sure how to implement them. I've knocked together a simple program that shows the problem. One button starts the infinite loop and I want the other button to stop it but of course due to the Swing single thread problem the other button has frozen. Code below and help appreciated:-
public class Model
{
private int counter;
private boolean go = true;
public void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
}
}
public int getCounter()
{
return counter;
}
public void setGo(boolean value)
{
this.go = value;
}
}
public class View extends JFrame
{
private JPanel topPanel, bottomPanel;
private JTextArea messageArea;
private JButton startButton, cancelButton;
private JLabel messageLabel;
private JScrollPane scrollPane;
public View()
{
setSize(250, 220);
setTitle("View");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
topPanel = new JPanel();
bottomPanel = new JPanel();
messageArea = new JTextArea(8, 20);
messageArea.setEditable(false);
scrollPane = new JScrollPane(messageArea);
messageLabel = new JLabel("Message Area");
topPanel.setLayout(new BorderLayout());
topPanel.add(messageLabel, "North");
topPanel.add(scrollPane, "South");
startButton = new JButton("START");
cancelButton = new JButton("CANCEL");
bottomPanel.setLayout(new GridLayout(1, 2));
bottomPanel.add(startButton);
bottomPanel.add(cancelButton);
Container cp = getContentPane();
cp.add(topPanel, BorderLayout.NORTH);
cp.add(bottomPanel, BorderLayout.SOUTH);
}
public JButton getStartButton()
{
return startButton;
}
public JButton getCancelButton()
{
return cancelButton;
}
public void setMessageArea(String message)
{
messageArea.append(message + "\n");
}
}
public class Controller implements ActionListener
{
private Model theModel;
private View theView;
public Controller(Model model, View view)
{
this.theModel = model;
this.theView = view;
view.getStartButton().addActionListener(this);
view.getCancelButton().addActionListener(this);
}
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.go();
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
}
public class Main
{
public static void main(String[] args)
{
Model model = new Model();
View view = new View();
Controller controller = new Controller(model, view);
view.setVisible(true);
}
}
You can do it easily without implementing any timer, you just need to add two lines to your actionPerformed method:
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.setGo(true); //make it continue if it's just stopped
Thread t = new Thread(new Runnable() { public void run() {theModel.go();}}); //This separate thread will start the new go...
t.start(); //...when you start the thread! go!
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
As your Model.go() is running in a separate thread, the Event Dispatch Thread is free to do its stuff, like drawing the button released again, instead of hanging with the button down.
There's a catch! however, because the thread running Model.go() will run wildly!, it's virtually called as many times per second as your system can.
If you plan to implement some animation or the like, then you will need to:
use a Timer,
or
add some sleep time to the new thread.
Example if you go with threads:
public void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
try {
Thread.sleep(1500); //Sleep for 1.5 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
As you can see I added Thread.sleep(1500) being 1500 the time in milliseconds (1.5 seconds). Thread.sleep can be interrupted for some reasons, so you must catch the InterruptedException.
It's not necessary to go deeper on handling correctly the InterruptedException in this particular case, but if you feel curious about it you can read this nice article.
You are blocking the Event Dispatch Thread (EDT). The thread is responsible to process painting and other UI related requests. Once EDT is blocked the UI will become frozen since it cannot process any events. For more details see The Event Dispatch Thread tutorial.
Consider using timers (How to Use Swing Timers), SwingWorker or an auxiliary background thread. Background thread can communicate with EDT using SwingUtilities.invokeLater(). This mechanism is already implemented in SwingWorker, so it may be easier to go with it. It depends on functionality that is required.
Use a javax.swing.Timer to do the go() work once, (with some optional delay), using start() and stop() in the event handling.
I decided to use a SwingWorker thread and below is the updated Controller class. It does what I need it to do but my question is, is it the correct way and is it clean code? Also, I've tried getting the output of the model.go() method into the view's textarea as per the commented out lines but not been succesful, anyone know how?
public class Controller implements ActionListener
{
private Model theModel;
private View theView;
private SwingWorker<Void, Void> worker;
public Controller(Model model, View view)
{
this.theModel = model;
this.theView = view;
view.getStartButton().addActionListener(this);
view.getCancelButton().addActionListener(this);
}
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.setGo(true);
worker = new SwingWorker<Void, Void>()
{
#Override
protected Void doInBackground()
{
// theView.setMessageArea(theModel.getCounterToString());
return theModel.go();
}
#Override
protected void done()
{
// theView.setMessageArea(theModel.getCounterToString());
}
};
worker.execute();
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
}
public class Model
{
public Void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
}
return null;
}