I have main frame (with JFrame field) asi view, then presenter (created in view's constructor) that adds listeners to buttons and stuff. I do that like this:
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
MyWindow window = new MyWindow();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
MyWindow invokes in it's constructor only one method - intialize - that only creates GUI fields. Finally (literally last line of it's code) it creates presenter.
Presenter should show new JDialog on certain events in main view. It has one method, that makes my GUI freeze. It looks like this:
protected double[] getParams(final Class<?> indicatorClass) {
ParametrizableDialog dialog = dialogs.get(indicatorClass); // works well
List<Double> params = new ArrayList<Double>();
dialog.setParams(params);
dialog.setModal(true);
dialog.setLocationRelativeTo(view.getFrame());
dialog.setVisible(true);
System.out.println(params); // it actually works, params are obtained from JDialog as user input
return Doubles.toArray(params); // guava
}
ParametrizableDialog is normal JDialog that implements one method interface that sets List<Double> parameters like this:
public class ParametrizableDialog extends JDialog implements Parametrizable {
protected List<Double> params;
#Override
public void setParams(final List<Double> params) {
this.params = params;
}
}
Now, does anybody know what mistake did I make and why does my GUI freeze?
Thanks!
If a GUI freezes, its generally because your are blocking the EDT. Read the section from the Swing tutorial on Concurrency for more information.
We can't tell what you are doing because your code isn't complete. For example you don't even add any components to the GUI. So who knows what you are doing in the code you left out.
For more help post your SSCCE that demonstrates the problem.
Related
Alright, so I have a null layout JPanel with a single JLabel in it. The JLabel is positioned at (0,0). What I'm trying to do is use a while loop in a new Thread to sleep the new Thread and then shift the JLabel 10px down by using SwingUtilities.invokeLater . The problem is that the UI gets updated in a laggy sort of way. It doesn't update every time it should, but skips lots of updates and shifts in big chunks. I know I can easily do it with Timer, but the point is understanding Threads better. Thanks in advance!
Code:
private void start(){
Updater up = new Updater();
up.start();
}
public void updatePosition(){
int y = label1.getLocation.y;
label.setBounds(0,y+10, 10,10);
}
private class Updater extends Thread{
public void run(){
while(!shouldQuit){
try{
Updater.sleep(100);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updatePosition();
}
});
}catch(Exception e){
System.out.println(e);
}
}
}
}
EDIT:
I got it to work by replacing the while loop with a call to a new Thread instance in the updatePosition() method, to settle things down a bit. And also, it wasn't only the Thread that was causing the problem, so I had to force the panel to re-layout it's subviews by calling revalidate() on it.
Here's how it looks (the fully working one):
private void start(){
new Updater().start();
}
public void updatePosition(){
int y = label1.getLocation.y;
label.setBounds(0,y+10, 10,10);
panel.revalidate();
if(!shouldQuit) new Updater().start();
}
private class Updater extends Thread{
public void run(){
try{
Updater.sleep(100);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updatePosition();
}
});
}catch(Exception e){
System.out.println(e);
}
}
}
You should try to use visibility and GridLayout to maximize movement. You can use a int var to count threads and reciprocate that to the label. As well, you should be using your ability o create Updaters, more and smoother. Just do the start() mwthod while trolling for threads :-)
You could have something besides an infinity call to start. I think you've lost the inheritance from the class, itself. The object label1 must ave been lost in tbe fray. If that's not it, then I'm pretty sure I'm not really able to answer this one.
I'm fairly new to Java and I am trying to make a GUI. This is the code in my GUI.java file. It contains a button and a label. When I click the button, the label is supposed to show "loading..." and enter a static void method in the Main class (in Main.java) called searcher and does stuff. After searcher is done, label becomes "".
Problem: Label doesn't change at all when I press press the button. Seems like neither the setText in the actionListener nor searcher() works. However, the other "stuff" I wanted it to do inside searcher() still works fine. I don't see any errors.
Note: If I try to call searcher() from the main it works fine.
GUI.java:
public class GUI extends JFrame implements ActionListener{
public JButton button = new JButton("Refresh!");
public JLabel label = new JLabel("");
public GUI(){
Container pane = getContentPane();
button.addActionListener(this);
button.setActionCommand("refresh");
pane.add(button);
pane.add(label);
}
}
public void actionPerformed(ActionEvent e) {
if ("refresh".equals(e.getActionCommand())) {
label.setText("Loading...");
Main.searcher(this, "", "");
label.setText("");
}
}
Main.java:
public class Main{
public static void searcher(GUI gu, String popup, String url) {
gu.label.setText("Loading...");
//do stuff
gu.label.setText("");
}
public static void main(String[] args) {
GUI gu = new GUI ();
}
}
EDIT: I've changed to code to use SwingWorker and propertylistener as suggested, but I'm having trouble now. Firstly, 'this' no longer refers to the GUI.. what should I pass in the searcher method to pass the current instance of class GUI?
I'm also getting this error and I'm not really sure how to fix it:
.\GUI.java:77: error: is not abstract and does not override abstract method propertyChange(PropertyChangeEvent) in PropertyChangeListener
PropertyChangeListener propChangeListn = new PropertyChangeListener() {^
public void actionPerformed(ActionEvent e) {
if ("refresh".equals(e.getActionCommand())) {
label.setText("Loading...");
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
public Void doInBackground() throws Exception {
Main.searcher(this, "", "http://maple.fm/api/2/search?server=0");
return null;
}
};
//worker.addPropertyChangeListener(new propertyChangeListener listener) {
PropertyChangeListener propChangeListn = new PropertyChangeListener() {
public void propertyChanged(PropertyChangeEvent pcEvt) {
if (pcEvt.getNewValue() == SwingWorker.StateValue.DONE) {
label.setText("");
}
}
};
worker.addPropertyChangeListener(propChangeListn);
worker.execute();
}
Yours is a classic Swing threading issue where you are tying the Swing event thread with a long-running process, preventing this thread from updating the GUI's graphics or from interacting with the user. The solution is the same as always -- use a background thread to do your long-running processing. If you used a SwingWorker for this, you could even add a PropertyChangeListener to it and then be notified when the worker has completed its task, allowing you to update the GUI with this information.
Google Concurrency in Swing and click on the first hit for more on this.
e.g.,
public void actionPerformed(ActionEvent e) {
if ("refresh".equals(e.getActionCommand())) {
label.setText("Loading...");
// create a SwingWorker object
final SwingWorker<Void, Void> worker = new Swingworker<Void, Void>() {
// long running code would go in doInBackground
public Void doInBackground() throws Exception {
Main.searcher(...);
return null;
}
}
// add a listener to worker to be notified when it is done
worker.addPropertyChangeListener(PropertyChangeListener listener) {
public void propertyChanged(PropertyChangeEvent pcEvt) {
// if the worker is done...
if (pcEvt.getNewValue() == SwingWorker.StateValue.DONE) {
label.setText("");
// you will probably want to call get() on your worker here
// and catch any exceptions that may have occurred.
}
}
}
// it may seem counter-intuitive, but you need to start the worker with
// execute() *after* setting all the above code up.
worker.execute();
}
}
I execute the task in this class and the Dialog pops up as a white box. The print statement IS printing out the progress values I'm expecting, but nothing shows up on the Dialog until after the operation is complete. I can see the progress bar flash visible for a millisecond before the dialog is closed at the end. Absolutely no clue what's going on :\
public class ProgressDialog extends JDialog {
private JProgressBar pb;
private SwingWorker<Boolean, Void> task;
public SwingWorker<Boolean, Void> getTask(){
return task;
}
public ProgressDialog(final String call){
setTitle("Working...");
setLayout(new BorderLayout());
setBounds(300,300,300,100);
pb = new JProgressBar(0, 100);
pb.setValue(0);
pb.setVisible(true);
pb.setStringPainted(true);
add(pb, BorderLayout.CENTER);
setVisible(true);
task = new SwingWorker<Boolean, Void>(){
public Boolean doInBackground(){
switch(call){
case "Category": pb.setValue(Category.getProgress());
while(pb.getValue()<99){
try{
Thread.sleep(500);
} catch (InterruptedException e){
Thread.currentThread().interrupt();
}
pb.setValue(Category.getProgress());
System.out.println(pb.getValue());
repaint();
revalidate();
}
break;
}
return true;
}
public void done(){
dispose();
}
};
}
}
EDIT: tried this change. no dice. Why am I not even getting a progress bar at 0%? It only appears once it is at 100%
public class ProgressDialog extends JDialog {
private JProgressBar pb;
private SwingWorker<Boolean, Integer> task;
public SwingWorker<Boolean, Integer> getTask(){
return task;
}
public ProgressDialog(final String call){
setTitle("Working...");
setLayout(new BorderLayout());
setBounds(300,300,300,100);
pb = new JProgressBar(0, 100);
pb.setValue(0);
pb.setStringPainted(true);
add(pb, BorderLayout.CENTER);
setVisible(true);
task = new SwingWorker<Boolean, Integer>(){
public Boolean doInBackground(){
switch(call){
case "Category": setProgress(Category.getProgress());
while(pb.getValue()<99){
try{
Thread.sleep(500);
} catch (InterruptedException e){
Thread.currentThread().interrupt();
}
setProgress(Category.getProgress());
}
break;
}
return true;
}
public void done(){
//dispose();
}
};
task.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
System.out.println((Integer)evt.getNewValue());
pb.setValue((Integer)evt.getNewValue());
pb.revalidate();
pb.repaint();
}
}
});
}
}
You're trying to set the progress bar's state from within the SwingWorker's doInBackground method, from a background thread -- which makes no sense. The whole reason for using a SwingWorker is to allow you to do a background process in a Swing GUI, so you don't make Swing calls from a background thread, and so that you don't tie up the Swing thread with a long-running bit of code.
You should not make Swing calls from this background process. Instead use the publish/process methods as the tutorials will show you. Or perhaps better, set the SwingWorker's progress field, and use a PropertyChangeListener on the SwingWorker to allow the progress bar to react to it.
Regardless, the bottom line:
Use the SwingWorker to do background work.
Do not make Swing calls from within the SwingWorker's doInBackground method.
Use publish to push data from the background method into the Swing thread realm.
Use the process method to handle this data being pushed.
SwingWorker has a progress property that is also handy to use for allowing Swing code to respond to changes in background states.
If you go this route, use a PropertyChangeListener.
You almost never want to use setBounds(...) or null layout. Trust me as someone who has written hundreds of Swing programs, this one will bite you in the end.
It looks as if your Category is using a static method for getting its progress. Again, this is something you almost never want to do. A progress field suggests state, and this should be part of the instance fields of an object, never static.
Here's an SSCCE to demonstrate how you should be updating your JProgressBar. Copy/paste this and run it.
Notice how we update the progress bar by calling publish(i) which sends the integer to the process() method. The SwingWorker sends results to the process() method in chunks, but we are only using an Integer to update the JProgressBar so all we care about it the LAST chunk. In this SSCCE, we go from 1-1000. If you examine the console, you'll see that a lot of numbers between 1-1000 are being skipped because we are updating too fast for the SwingWorker to catch up (but that's ok. That's why it delivers results in chunks).
NOTE: the process() method was originally designed for programmers to return real-time results from their long-running processes and update the GUI. So, if you were doing a database fetch, you could update a JTable with the results you return. I hate doing things that way, though. So 99% of the time I just use an "indeterminate" JProgressBar and wait till the done() method to publish my results. Occaisionally, however, I'll use a "determinate" JProgressBar and update like we do in this SSCCE. Never have I used process() to return and publish actual data. :) But, that's what it was originally designed to do.
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
/**
*
* #author Ryan
*/
public class Test {
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
go();
}
});
}
public static void go() {
JFrame frame = new JFrame();
JProgressBar jpb = new JProgressBar();
jpb.setIndeterminate(false);
int max = 1000;
jpb.setMaximum(max);
frame.add(jpb);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
new Task(jpb, max).execute();
}
static class Task extends SwingWorker<Void, Integer> {
JProgressBar jpb;
int max;
public Task(JProgressBar jpb, int max) {
this.jpb = jpb;
this.max = max;
}
#Override
protected void process(List<Integer> chunks) {
jpb.setValue(chunks.get(chunks.size()-1)); // The last value in this array is all we care about.
System.out.println(chunks.get(chunks.size()-1));
}
#Override
protected Void doInBackground() throws Exception {
for(int i = 0; i < max; i++) {
Thread.sleep(10); // Sleep for 1/10th of a second
publish(i);
}
return null;
}
#Override
protected void done() {
try {
get();
JOptionPane.showMessageDialog(jpb.getParent(), "Success", "Success", JOptionPane.INFORMATION_MESSAGE);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
EDIT: I created a diagram that should be a helpful reference when handling SwingWorker so you know where to place your code.
In my Swing app, users can click a button to open a dialog panel and enter some values, then they can click "Ok" on that panel to close it and return to the main program, but how can I pass the values they enter to the main program without saving them to a file first ?
There are a couple things you could do:
You could create an Observer/Observable relationship between your app and the dialog, and make the dialog publish an event when the dialog closes with the values in the event.
You could maintain a handle to your dialog in your app, and when you call setVisible(false) on dialog, you can query the dialog for its data.
The UI should usually be layered upon the underlying application. (IMO, the UI should itself be split into layers, but that another rant.) Pass in a relevant part of your application to the creation of your UI. When you are ready for values to go from the UI to the application (which could be an OK button on a multi-page dialog, down to every keystroke), push the data to the application component.
Rather than using just the poor Java Observable/Observer API, I'd rather advise you take a look at an "Event Bus", which should be particularly suited for your situation.
Two implementations of Event Buses:
EventBus very general event bus,
can be used in any situation
GUTS Event Bus specific to Guice dependency injection library
One of these should help you with your current problem.
Just define an interface with a single method (like: "returnToCaller(returntype rt)") in your dialog class.
The Constructor of the dialog takes an instance of this interface as input, and uses it to return results on exit/ok.
The mainmodule (window or whatever) simply instantiates the dialog and thus makes annonymous use of the interface, defininng the return method, sort of like a delegate (c#).
The call then being:
MyDialog md = new MyDialog(new MyDialog.ReturnToCaller() {
public void returnToCaller(ReturnValues rv) {
// Handle the values here
}
});
md.setModal(true);
md.setVisible(true);
I would suggest MVC(Model-view-controller) design. So dialog will be you view and possibly controller. But have to have a domain class which will be your model. For example, if the dialog is created to edit personal data, then you can have class called Person which will hold the data. The dialog should be designed in the way so you can set and get a Person from it.
The class implementing your dialog panel must have a link to your main program, and your main program must provide a method with parameters that will be the values to transmit.
Then your dialog panel class listen to the Ok button, and on the button click, it retrieve the values and use them with the aforementionned method.
class Main {
//...
private Dialog myDialog ;
public Main(){
//...
myDialog = new Dialog(this);
//...
}
//...
public void onDialogOk(String valueA, int valueB)
{
//...
}
}
class Dialog implement ActionListener{
//...
private Main myMain ;
public setMain(Main main){
myMain = main;
}
public Dialog(Main main){
//...
setMain(main) ;
//...
JButton ok = new JButton("ok") ;
ok.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
// retrieve form values
String valueA = ... ;
int valueB = Integer.parse(...);
myMain.onDialogOK(valueA, valueB) ; //DONE
}
}
May be you would like to try this solution:
class MyDialog {
private static String[] returnValues = new String[10]
private static MyDialog dialog;
private MyDialog() {
initDialog()
}
private void closeDialog()
{
dispose();
}
private initDialog()
{
//....
btnOk = new JButton("OK");
jTextField1 = new JTextField();
...
jTextField10 = new JTextField();
...
ActionListener btnOK_click = new ActionListener() {
public void actionPerformed(ActionEvent e)
{
returnValues[0] = jTextField1.getText();
...
returnValues[9] = jTextField10.getText();
closeDialog();
}
}
btnOk.addActionListener(btnOk_click);
}
public static String[] showMyDialog() {
dialog = new MyDialog();
dialog.setVisible(true);
return returnValues;
}
}
Sorry if this question will sound too chaotic, feel free to edit it.
I have an application made entirely in netbeans, which uses SingleFrameApplication and auto-generated the GUI code, named "MyApp", and FrameView, named "MyView". Now, the MyApp somehow has the main() function, but the MyView has all the graphic elements..
I don't entirely understand how that happens, so used it as black box (it somehow created the window, I didn't have to care why). But now, I need the window to be only a window, opened by another JFrame. I don't know, how to accomplish that.
MyApp, which is extending SingleFrameApplication, have these methods:
public class MyApp extends SingleFrameApplication {
#Override protected void startup() {
show(new MyView(this));
}
#Override protected void configureWindow(java.awt.Window root) {
}
public static MyApp getApplication() {
return Application.getInstance(MyApp.class);
}
public static void main(String[] args) {
launch(MyApp.class, args);
}
}
MyView has these methods:
public class MyView extends FrameView {
public MyView(SingleFrameApplication app) {
super(app);
initComponents();
}
private void initComponents() {
//all the GUI stuff is somehow defined here
}
}
Now, I have no clue how the two classes work, I just want this window, defined in MyView, to appear after another window, "ordinary" JFrame. How can I call this MyApp/MyView?
But now, I need the window to be only a window, opened by another JFrame. I don't know, how to accomplish that.
1.) It's not just a window - it's a
Swing Framework Application (Ah, the
perils of GUI builders...); and -
2.) You haven't specified how you want
it "opened by another JFrame";
but something like this should work if you're launching it via a JButton -
JButton launchMyApp = new JButton("launch");
launchMyApp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
String[] args = {};
Application.launch(MyApp.class, args);
}
});