Still relatively new to Swing but after a couple of hours of search, I couldn't find the answer online, hence this post (sorry if already answered and I overlooked it).
I'm using JFreeChart in a Swing application. Some charts are relatively heavy (180k data points) and the JFreeChart's ChartPanel needs ~6 seconds to do its first paintComponent().
Hence I would like to show a "Please wait" message in a dialog while the component paints (no need to show progress with a SwingWorker). I tried to override the paintComponent method but unfortunately the message never appears on screen (I guess the thread directly goes into painting the chart, without taking the time to paint the dialog).
My code looks like this:
public class CustomizedChartPanel extends ChartPanel{
private static final long serialVersionUID = 1L;
private JDialog dialog = null;
boolean isPainted = false;
public CustomizedChartPanel(JFreeChart chart) { super(chart); }
@Override
public void paintComponent(Graphics g) {
//At first paint (which can be lengthy for large charts), show "please wait" message
if (! isPainted){
dialog = new JDialog();
dialog.setUndecorated(true);
JPanel panel = new JPanel();
panel.add(new JLabel("Please wait"));
dialog.add(panel);
dialog.pack();
GuiHelper.centerDialog(dialog); //Custom code to center the dialog on the screen
dialog.setVisible(true);
dialog.repaint();
}
super.paintComponent(g);
if (! isPainted){
isPainted = true;
dialog.dispose();
super.repaint();
}
}
}
Any pointer on how to solve this / best practices would be very much appreciated!
Thanks,
Thomas
UPDATE:
Thanks for the hints & debate: very helpful.
I started implementing the suggested solution with the invokeLater() as I'm fearing that the JLayer solution won't work since it's also running on the EDT.
Unfortunately I'm having a null pointer exception when the paintComponent() is called by the invokeLater().
My code looks like this:
@Override
public void paintComponent(Graphics graph) {
//At first paint (which can be lengthy for large charts), show "please wait" message
if (! isPainted){
isPainted = true;
dialog = new JDialog();
dialog.setUndecorated(true);
JPanel panel = new JPanel();
panel.add(new JLabel("Please wait"));
panel.add(new JLabel("Please wait !!!!!!!!!!!!!!!!!!!!!!!!!!!!!"));
dialog.add(panel);
dialog.pack();
GuiHelper.centerDialog(dialog); //Custom code to center the dialog on the screen
dialog.setVisible(true);
dialog.repaint();
RunnableRepaintCaller r = new RunnableRepaintCaller(this, graph, dialog);
SwingUtilities.invokeLater(r);
}
else super.paintComponent(graph); //NULL POINTER EXCEPTION HERE (invoked by runnable class)
}
And the runnable class is:
public class RunnableRepaintCaller implements Runnable{
private ChartPanel target;
private Graphics g;
private JDialog dialog;
public RunnableRepaintCaller(ChartPanel target, Graphics g, JDialog dialog){
this.target = target;
this.g = g;
this.dialog = dialog;
}
@Override
public void run() {
System.out.println(g);
target.paintComponent(g);
dialog.dispose();
}
}
Again, any pointer would be much appreciated !
Thomas
Here is an example and/but it uses SwingWorker. You should seriously consider using this because if somehow the OS invalidates your frame and the loading of your JFreeChart is done on the EDT (Event Dispatching Thread), then your GUI will look frozen.
It also allows you to give better user feedback while you are processing the data. (sorry if the code is a bit long but most of the interesting code is in the initUI and the SwingWorker).
Note: instead of the dialog, you could use the JLayer (if you use Java 7), but this was unnecessary in my example.
The code is highly inspired from http://www.vogella.com/articles/JFreeChart/article.html
/**
* This code was directly taken from: http://www.vogella.com/articles/JFreeChart/article.html
* All credits goes to him for this code.
*
* Thanks to him.
*/
import java.util.List;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PiePlot3D;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
import org.jfree.util.Rotation;
public class PieChart extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
initUI();
}
});
}
protected static void initUI() {
// First we create the frame and make it visible
final PieChart demo = new PieChart("Comparison");
demo.setSize(500, 270);
demo.setVisible(true);
// Then we display the dialog on that frame
final JDialog dialog = new JDialog(demo);
dialog.setUndecorated(true);
JPanel panel = new JPanel();
final JLabel label = new JLabel("Please wait...");
panel.add(label);
dialog.add(panel);
dialog.pack();
// Public method to center the dialog after calling pack()
dialog.setLocationRelativeTo(demo);
// allowing the frame and the dialog to be displayed and, later, refreshed
SwingWorker<JFreeChart, String> worker = new SwingWorker<JFreeChart, String>() {
@Override
protected JFreeChart doInBackground() throws Exception {
publish("Loading dataset");
// simulating the loading of the Dataset
try {
System.out.println("Loading dataset");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// This will create the dataset
PieDataset dataset = demo.createDataset();
publish("Loading JFreeChart");
// simulating the loading of the JFreeChart
try {
System.out.println("Loading JFreeChart");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// based on the dataset we create the chart
JFreeChart chart = demo.createChart(dataset, "Which operating system are you using?");
// we put the chart into a panel
return chart;
}
@Override
protected void process(List<String> chunks) {
label.setText(chunks.get(0));
dialog.pack();
dialog.setLocationRelativeTo(demo);
dialog.repaint();
}
@Override
protected void done() {
try {
// Retrieve the created chart and put it in a ChartPanel
ChartPanel chartPanel = new ChartPanel(this.get());
// add it to our frame
demo.setContentPane(chartPanel);
// Dispose the dialog.
dialog.dispose();
// We revalidate to trigger the layout
demo.revalidate();
// Repaint, just to be sure
demo.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
};
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
worker.execute();
}
});
dialog.setVisible(true);
}
public PieChart(String applicationTitle) {
super(applicationTitle);
}
/** * Creates a sample dataset */
private PieDataset createDataset() {
DefaultPieDataset result = new DefaultPieDataset();
result.setValue("Linux", 29);
result.setValue("Mac", 20);
result.setValue("Windows", 51);
return result;
}
/** * Creates a chart */
private JFreeChart createChart(PieDataset dataset, String title) {
JFreeChart chart = ChartFactory.createPieChart3D(title, // chart title
dataset, // data
true, // include legend
true, false);
PiePlot3D plot = (PiePlot3D) chart.getPlot();
plot.setStartAngle(290);
plot.setDirection(Rotation.CLOCKWISE);
plot.setForegroundAlpha(0.5f);
return chart;
}
}
You can use JLayer
as explained here.
This is specifically for a busy indicator as you want.
Further more you can keep the JPanel
with setEnabled(false)
till your data completely loads. This prevents unwanted clicks on the JPanel
.
It's been a long time since I've done anything in Java, but as far as I know, the repaint()
method does not actually cause any drawing to happen. It simply flags the control as needing to be redrawn at the soonest possible opportunity. You need to call the paint()
method directly if you want a component to be drawn immediately.
You need to start the waiting Dialog in a new Thread. I don't know how you create your chart, but here is a sample
SwingUtilities.invokeLater(new Runnable() {
public void run() {
dialog = new JDialog();
dialog.setUndecorated(true);
JPanel panel = new JPanel();
panel.add(new JLabel("Please wait"));
dialog.add(panel);
GuiHelper.centerDialog(dialog);
dialog.setVisible(true);
Thread performer = new Thread(new Runnable() {
public void run() {
dialog.setVisible(false);
//Here the code that prepare the chart
}
});
performer.start();
}
});