tags:

views:

687

answers:

1

I have a custom class that extends JLabel. For specific instances of that class, I want to add some spacing to the text on the left side. I need the spacing as I'm setting the background of this JLabel and I don't want the text to bump up right next to the edge of the colored background. I fished around quite a bit and implemented this (inside the paint function):

if (condition) {
    bgColor = Color.red;
    setBackground(bgColor);
    setOpaque(true);
    // This line merely adds some padding on the left
    setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
}
else {
    setOpaque(false);
}

This appears to work in that it adds the spacing I want, however it has an unfortunate side effect in that it appears to break the repainting of the whole rest of the application...it appears that only that particular component is repainting and not the rest of the application. I eventually tracked it down to the setBorder call specifically...setting ANY kind of border appears to cause the same broken behavior. We have two different versions of our application, one that runs in Java 1.5 and one that runs in Java 1.6, the Java 1.6 version appears to work correctly while the Java 1.5 version doesn't. It is not possible to upgrade the older version to Java 1.6...I need something that will work in Java 1.5. Also, I tried this (just to see what it looked like):

setHorizontalTextPosition(JLabel.CENTER);

And that also appears to break the repainting in exactly the same way. I looked through the source of our application and found other places where we set borders (including empty borders), but couldn't find any on JLabels (only panels, buttons, etc). Anybody see anything like this before? Know how to fix it? Or perhaps another way to obtain the spacing I require that may work around the bug? Thanks.

+3  A: 

The problem is that you're calling that code inside the paint method. You should not do that because it will freeze the EDT with unwanted loops in the swing painting pipeline.

You should put that code on the constructor and change the component design state elsewhere on the app life cycle.

If you want to know a little bit more about Swing painting please read the "Swing painting pipeline" post on pushing-pixels.org.

Note that you can use BorderFactory.createCompoundBorder to combine any two borders. Then you can set spacing with the emptyBorder and any other to draw the outer border.

EDIT: Example added.

package com.stackoverflow.swing.paintpipeline;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.Border;


public class JLabelSetBorderPaintProblem extends JLabel {

    public JLabelSetBorderPaintProblem(String text) {
     super(text);
    }

    /*
     * @see javax.swing.JComponent paint(java.awt.Graphics)
     */
    @Override
    public void paint(Graphics g) {
     super.paint(g);
     // You can not call setBorder here.

     // Please check javadoc.
    }

    /*
     * @see javax.swing.JComponent paintBorder(java.awt.Graphics)
     */
    @Override
    protected void paintBorder(Graphics g) {
     super.paintBorder(g);
     // Here is where the Swing painting pipeline draws the current border
     // for the JLabel instance.

     // Please check javadoc.
    }

    // Start me here!
    public static void main(String[] args) {
     // SetBorder will dispatch an event to Event Dispatcher Thread to draw the
     // new border around the component - you must call setBorder inside EDT.
     // Swing rule 1.
     SwingUtilities.invokeLater(new Runnable() {

      @Override public void run() {
       // Inside EDT
       JFrame frame = new JFrame("JLabel setBorder example");
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       // Add the JLabel
       final JLabelSetBorderPaintProblem label = new JLabelSetBorderPaintProblem("Just press or wait...");
       frame.add(label);

       // And change the border...
       label.addMouseListener(new MouseAdapter() {
        @Override public void mousePressed(MouseEvent e) {
         label.setBorder(BORDERS.get(new Random().nextInt(BORDERS.size())));
        }
       });

       // ...whenever you want
       new Timer(5000, new ActionListener() {
        @Override public void actionPerformed(ActionEvent e) {
         label.setBorder(BORDERS.get(new Random().nextInt(BORDERS.size())));
        }
       }).start();

       frame.pack();
       frame.setVisible(true);
      }
     });

    }

    public static final List<Border> BORDERS;
    static {
     BORDERS = new ArrayList<Border>();
     BORDERS.add(BorderFactory.createLineBorder(Color.BLACK));
     BORDERS.add(BorderFactory.createLineBorder(Color.RED));
     BORDERS.add(BorderFactory.createEtchedBorder());
     BORDERS.add(BorderFactory.createTitledBorder("A border"));
    }
}
hfernandes
That definitely seems to be the crux of the issue. We solved the problem by creating the border as a global static (like the other borders in the application) and then still setting the border in the paint method. I still don't have a good enough grasp on exactly what is going on here, but this seems to solve the issue so I'm going to call it good enough.
Morinar