Search code examples
javaswinggraphics2djlayeredpanenull-layout-manager

java- repaint() method is misbehaving - 2?


This question is an extension of java- repaint() method is misbehaving? (Reading it, is optional)

I am working on a Music Player

I am using a JSlider as seek bar and using a JLabel to draw text on screen, such as song name.

I am new to Graphics2D

Here's the minimized code:

public class JSliderDemo extends JFrame
{
    
JLabel label;
JSlider seek = new JSlider();
int  y = 10;    

public JSliderDemo()
{
 setSize(400, 400);
 setLocationRelativeTo(null);
 setDefaultCloseOperation(DISPOSE_ON_CLOSE);
 
 createWindow();
 setVisible(true);
 startThread();
}        
    
public void createWindow()
{
 JPanel panel = new JPanel(new BorderLayout());
 panel.setOpaque(true);
 panel.setBackground(Color.BLUE);
 panel.setBorder(new LineBorder(Color.YELLOW));
 
 JLayeredPane layeredPane = new JLayeredPane();
 layeredPane.setPreferredSize(new Dimension(300, 310));
 
 label = new Component();
 label.setSize(300, 300);
 
 createSlider();

 layeredPane.add(seek, new Integer(50));
 layeredPane.add(label, new Integer(100));
 
 panel.add(layeredPane);
 add(panel);
}        

protected void createSlider()
{
 seek.setUI(new SeekBar(seek, 300, 10, new Dimension(20, 20), 5, 
           Color.DARK_GRAY, Color.RED, Color.RED));
 seek.setOrientation(JProgressBar.HORIZONTAL);
 seek.setOpaque(false);
 seek.setLocation(10, 50);
 seek.setSize(300, 20);
 seek.setMajorTickSpacing(0);
 seek.setMinorTickSpacing(0);
 seek.setMinimum(0);
 seek.setMaximum(1000);    
 seek.setBorder(new MatteBorder(5, 5, 5, 5, Color.CYAN));
}        

protected void startThread()
{
 Thread thread = new Thread(new Runnable(){
 @Override
 public void run()
 {
  try
  {
   while(true)
   {
    if(y == label.getHeight()){y = 1;}   
    label.repaint();
    y += 1;
   
    Thread.sleep(100);   
   }    
  }   
  catch(Exception ex){}
 }
 });
 
 thread.start();
}        
        
protected class Component extends JLabel
{
@Override
public void paintComponent(Graphics g)
{
 Graphics2D gr = (Graphics2D) g;
 gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
 
 gr.setColor(Color.RED);
 gr.setFont(new Font("Calibri", Font.PLAIN, 16));
 gr.drawString("Song Name", 50, y);
 
 gr.dispose();
}       
}          

public static void main(String[] args) 
{
 new JSliderDemo();
}
    
}

The problem is, when I call repaint() for JLabel it automatically repaints JSlider with it even though JSlider is not included in JLabel.

Output :

Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted.........

Now if I remove label.repaint() from the Thread, then the JSlider is not re-painted.

Output:

Slider re-painted
Slider re-painted

Is the repaint() method supposed to work like this?

In my last question, I was told to use Layout Manager and when I did use GridLayout just for checking if it's the solution, then it worked!

Only JLabel was repainted.

But I want to overlap JLabel on JSlider, so I thought of using JLayeredPane. And now, the problem is back.

How can I solve this?

Bottom Line : How can I overlap JLabel on JSlider without leading to repaint() method misbehave ?

OR

Does the repaint() method work like this?


Solution

  • As was already mentioned in the comments, the reason for your JSlider being repainted is that it has overlapping bounds with the JLabel. Even though your label doesn't paint over the area of the slider swing will still mark the overlapping area as dirty (i.e. the overlapping part of the slider will need to be repainted) because swing doesn't know that you are only painting in one part of the component.

    To reduce the amount of repaints you will need to make the size of your JLabel smaller. Preferably only as large as it needs to be by invoking its getPreferredSize() method. You'll then be able to move the text by moving the location of the label.

    Also you shouldn't be doing updates to the gui in a plain Thread. Use javax.swing.Timer instead. It ensures that all updates to the gui happen on the swing event thread, which is where they should be made.

    After making these adjustments to your code the slider is only repainted while the label is actually visually over the slider.

    public class JSliderDemo extends JFrame {
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(JSliderDemo::new);
        }
    
        private final JLabel label = new CustomLabel();
    
        public JSliderDemo() {
            setSize(400, 400);
            setLocationRelativeTo(null);
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    
            createWindow();
            setVisible(true);
            startTimer();
        }
    
        public void createWindow() {
            JPanel panel = new JPanel(new BorderLayout());
    
            JLayeredPane layeredPane = new JLayeredPane();
            layeredPane.setPreferredSize(new Dimension(300, 310));
    
            label.setLocation(0, 0);
            label.setBorder(new LineBorder(Color.RED));
            label.setSize(label.getPreferredSize());
    
            layeredPane.add(createSlider(), Integer.valueOf(50));
            layeredPane.add(label, Integer.valueOf(100));
    
            panel.add(layeredPane);
            setContentPane(panel);
        }
    
        protected JSlider createSlider() {
            JSlider seek = new CustomSlider();
            seek.setOrientation(JProgressBar.HORIZONTAL);
            seek.setOpaque(false);
            seek.setLocation(10, 50);
            seek.setSize(300, 20);
            seek.setMajorTickSpacing(0);
            seek.setMinorTickSpacing(0);
            seek.setMinimum(0);
            seek.setMaximum(1000);
            seek.setBorder(new LineBorder(Color.BLUE));
            return seek;
        }
    
        private void startTimer() {
            new Timer(100, e -> {
                int y = label.getY();
                int maxY = label.getParent().getHeight();
                if (y == maxY) {
                    y = -label.getHeight();
                }
                label.setLocation(label.getX(), y + 1);
                label.repaint();
            }).start();
        }
    
        private static class CustomLabel extends JLabel {
    
            protected CustomLabel() {
                setFont(new Font("Calibri", Font.PLAIN, 16));
                setText("Song Name");
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                System.out.println("Painting Label");
            }
        }
    
        protected static class CustomSlider extends JSlider {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                System.out.println("Painting Slider");
            }
        }
    }