I have a simple JPanel (otherJPanel) with a label below, both inside another JPanel (mainJPanel). mainJPanel
overrides paintComponent
, and repaint()
is called manually elsewhere and the label text and the background of otherJPanel
are set dynamically.
However, when I move the label inside otherJPanel
then paintComponent
is never called. Any idea why?
To test this I have created the test project below with labels in both positions. However, with this paintComponent
is not called within the processing loop, what am I doing wrong?
package uk.co.moons.gui.components.testapps;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DisplayJFrame extends javax.swing.JFrame {
/** Creates new form DisplayJFrame */
public DisplayJFrame() {
initComponents();
}
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
displayJPanel1 = new uk.co.moons.gui.components.DisplayJPanel();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setName("Form"); // NOI18N
displayJPanel1.setName("displayJPanel1"); // NOI18N
getContentPane().add(displayJPanel1, java.awt.BorderLayout.CENTER);
pack();
}// </editor-fold>
public void draw() {
displayJPanel1.revalidate();
displayJPanel1.repaint();
}
public void setTO(TestObject to) {
displayJPanel1.setTo(to);
}
public static void main(String args[]) {
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(DisplayJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(DisplayJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(DisplayJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(DisplayJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
DisplayJFrame df = new DisplayJFrame();
df.setVisible(true);
TestObject to = new TestObject();
df.setTO(to);
for (int i = 10; i < 20; i++) {
to.setValue(i);
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(DisplayJFrame.class.getName()).log(Level.SEVERE, null, ex);
}
df.draw();//df.repaint();
}
}
});
}
// Variables declaration - do not modify
private uk.co.moons.gui.components.DisplayJPanel displayJPanel1;
// End of variables declaration
}
package uk.co.moons.gui.components;
import java.awt.Graphics;
import uk.co.moons.gui.components.testapps.TestObject;
public class DisplayJPanel extends javax.swing.JPanel {
private TestObject to = null;
/** Creates new form DisplayJPanel */
public DisplayJPanel() {
initComponents();
}
public TestObject getTo() {
return to;
}
public void setTo(TestObject to) {
this.to = to;
}
@Override
public void paintComponent(Graphics g) {
System.out.println("paintComponent");
super.paintComponents(g);
if (to != null) {
int i = to.getValue();
System.out.println("paintComponent " + i);
jLabel1.setText(String.valueOf(i));
jLabel2.setText(String.valueOf(i + 1));
}
}
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jPanel1 = new javax.swing.JPanel();
jLabel2 = new javax.swing.JLabel();
jLabel1 = new javax.swing.JLabel();
setName("Form"); // NOI18N
setLayout(new java.awt.BorderLayout());
jPanel1.setName("jPanel1"); // NOI18N
jPanel1.setLayout(new java.awt.BorderLayout());
jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(uk.co.moons.gui.controlpanel.ControlPanelApp.class).getContext().getResourceMap(DisplayJPanel.class);
jLabel2.setText(resourceMap.getString("jLabel2.text")); // NOI18N
jLabel2.setBorder(javax.swing.BorderFactory.createEtchedBorder());
jLabel2.setName("jLabel2"); // NOI18N
jPanel1.add(jLabel2, java.awt.BorderLayout.CENTER);
add(jPanel1, java.awt.BorderLayout.CENTER);
jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
jLabel1.setBorder(javax.swing.BorderFactory.createEtchedBorder());
jLabel1.setName("jLabel1"); // NOI18N
add(jLabel1, java.awt.BorderLayout.PAGE_END);
}// </editor-fold>
// Variables declaration - do not modify
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JPanel jPanel1;
// End of variables declaration
}
package uk.co.moons.gui.components.testapps;
public class TestObject {
private int value=0;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
You're calling Thread.sleep(...)
on the Swing event thread which is causing your entire application to sleep:
for (int i = 10; i < 20; i++) {
to.setValue(i);
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(DisplayJFrame.class.getName()).log(
Level.SEVERE, null, ex);
}
df.draw();
}
Don't do this; use a Swing Timer instead.
i.e.,
public void run() {
final DisplayJFrame df = new DisplayJFrame();
df.setVisible(true);
final TestObject to = new TestObject();
df.setTO(to);
int timerDelay = 100;
final int maxCount = 20;
new Timer(timerDelay, new ActionListener() {
private int count = 0;
@Override
public void actionPerformed(ActionEvent evt) {
if (count < maxCount) {
df.repaint();
to.setValue(count);
count++;
} else {
((Timer) evt.getSource()).stop();
}
}
}).start();
// for (int i = 10; i < 20; i++) {
// to.setValue(i);
// try {
// Thread.sleep(100);
// } catch (InterruptedException ex) {
// Logger.getLogger(DisplayJFrame.class.getName()).log(
// Level.SEVERE, null, ex);
// }
//
// df.draw();
// }
}
Also unrelated problem, but super.paintComponents(g);
is not the super for the paintComponent(g)
method. That s on the end of paintComponents will mess you up royal.
// TODO: delete this method
public void paintComponent(Graphics g) {
System.out.println("paintComponent");
// super.paintComponents(g); // *** !! No -- this is not the super method ***
super.paintComponent(g);
if (to != null) {
int i = to.getValue();
System.out.println("paintComponent " + i);
// of course you'll never set a JLabel's text from within this method
jLabel1.setText(String.valueOf(i));
jLabel2.setText(String.valueOf(i + 1));
}
}
Edit
You state in comments:
Ok, why would I not want to set a JLabel's text inside paintComponent?
The paintComponent
method is for painting and painting only. It should not be used to set the state of the program or components for several reasons including that you don't have full control over when or even if this method is called (as you're finding out), and so the state of your code should not depend on its getting called.
How should I get the JLabel to update whenever repaint is called?
Again, you should not use repainting to trigger this. What is the JLabel supposed to display? Why do you feel that it must change on a repaint?
Edit 2
I thought paintComponent was the place to do it. My display comprises a hierarchy of many nested components the labels and backgrounds of which are continuously changing. Rather than using paintComponent should I be retrieving the components from my main processing loop and setting the values there?
No, don't allow outside classes to directly manipulate your view like so. Instead separate out your model and view and use listeners such as a PropertyChangeListener as well as public methods to do this for you.
For instance, you can easily update your JLabel text by making your TestObject's value field a "bound property". This can easily be done by giving the class a PropertyChangeSupport object (actually since this is Swing, a SwingPropertyChangeSupport object), and allow outside classes to listen to changes in the value's value like so:
class DisplayJPanel extends javax.swing.JPanel {
private TestObject to = null;
public DisplayJPanel() {
initComponents();
}
public TestObject getTo() {
return to;
}
public void setTo(final TestObject to) {
this.to = to;
// add listener to listen and react to changes in to value's state
to.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (TestObject.VALUE.equals(evt.getPropertyName())) {
setLabel2Text(String.valueOf(to.getValue()));
}
}
});
}
private void initComponents() {
// etc...
}
public void setLabel2Text(String text) {
jLabel2.setText(text);
}
private javax.swing.JLabel jLabel2;
private javax.swing.JPanel jPanel1;
}
class TestObject {
public static final String VALUE = "value";
private int value = 0;
private SwingPropertyChangeSupport propChangeSupport = new SwingPropertyChangeSupport(this);
public int getValue() {
return value;
}
public void setValue(int value) {
int oldValue = this.value;
int newValue = value;
this.value = newValue;
propChangeSupport.firePropertyChange(VALUE, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propChangeSupport.removePropertyChangeListener(listener);
}
}