user2211678
user2211678

Reputation: 679

updating JFrame again

The following code shown below will add 3 JLabels to a JFrame and then remove the 3 JLabels. After 2 seconds it will repaint back the 3 JLabels.

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class RepaintFrameTest extends JFrame {
    private JPanel panel = new JPanel();
    private JLabel labelOne = new JLabel("label1");
    private JLabel labelTwo = new JLabel("label2");
    private JLabel labelThree = new JLabel("label3");
    public RepaintFrameTest() { 
        panel.add(labelOne);
        panel.add(labelTwo);
        panel.add(labelThree);
        add(panel);
        pack();
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setSize(1024,768);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main(String args[]) {
        RepaintFrameTest frameOne = new RepaintFrameTest();
        frameOne.getContentPane().removeAll();   
        frameOne.getContentPane().validate();
        frameOne.getContentPane().repaint();
        new Thread(new RepaintThread()).start();
    }
}

class RepaintThread implements Runnable {
    @Override
    public void run() {
        try   {              
            Thread.sleep(2000);            
        }catch (InterruptedException interruptedException)  {                           
            System.out.println( "Thread is interrupted when it is sleeping" +interruptedException);             
        } 
        RepaintFrameTest frameTwo = new RepaintFrameTest();
        frameTwo.getContentPane().validate();
        frameTwo.getContentPane().repaint();
    }
}

A slight problem I faced is that it's repainting to a new frame(frameTwo) instead of an old one(frameOne).

How do I make it such that it will repaint the existing frame instead of repainting to a new one?

Upvotes: 1

Views: 276

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347204

You are violating the single thread rules of Swing...

See Concurrency in Swing for more details.

Essentially, you should never update any UI component from any thread context other than the Event Dispatching Thread.

Instead, you should use either a SwingWorker or, probably more relevent to your problem, a javax.swing.Timer

The problem is, as you've clearly noted, is the reference to frameTwo has nothing to do with the reference of frameOne, they are completely separate objects and therefore have nothing in common with each other

What you need to be able to do is pass some kind of reference of the object you want updated to the class doing the updating...

In this example, I simply created a reapplyLabels method in RepaintFrameTest and used a Swing Timer set for 2 a second delay, to call this method...

Personally, I'd prefer to pass an interface as it exposes less and de-couples the code, but this is just a simple example...

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class RepaintFrameTest extends JFrame {

    private JPanel panel = new JPanel();
    private JLabel labelOne = new JLabel("label1");
    private JLabel labelTwo = new JLabel("label2");
    private JLabel labelThree = new JLabel("label3");

    public RepaintFrameTest() {
        add(panel);
    }

    public void reapplyLabels() {
        panel.add(labelOne);
        panel.add(labelTwo);
        panel.add(labelThree);

        revalidate();
        repaint();
    }

    public static void main(String args[]) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                RepaintFrameTest frame = new RepaintFrameTest();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(400, 400);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                Timer timer = new Timer(2000, new RepaintAction(frame));
                timer.setRepeats(false);
                timer.start();
            }
        });
    }

    public static class RepaintAction implements ActionListener {

        private RepaintFrameTest frame;

        public RepaintAction(RepaintFrameTest frame) {
            this.frame = frame;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            frame.reapplyLabels();
        }
    }

}

While there's not enough context to make a full judement call, I also agree with HovercraftFullOfEels, a CardLayout would achieve the same result, probably with less mess...

Upvotes: 2

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285403

  1. Simpy use a CardLayout to swap views in and out.
  2. Use a Swing Timer rather than your current background Thread as your current code is not Swing thread safe and carries risk of pernicious unpredictable thread exceptions.
  3. I would give the main JPanel the CardLayout, and then add a JLabel holding JPanel with a suitable String constant.
  4. I'd then add to the main CardLayout using JPanel, an empty JLabel using another String constant in the add method. i.e., add(new JLabel(), BLANK_COMPONENT);
  5. I'd then in my Swing Timer simply call next() on my CardLayout object, passing in the main CardLayout-using component: cardLayout.next(myMainJPanel);

For example,

import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.event.*;

import javax.swing.*;

public class RepaintTest extends JPanel {
   private static final int PREF_W = 400;
   private static final int PREF_H = PREF_W;
   private static final int LABEL_COUNT = 3;
   private static final String LABEL_PANEL = "label panel";
   private static final Object BLANK_COMPONENT = "blank component";
   private static final int TIMER_DELAY = 2000;
   private CardLayout cardLayout = new CardLayout();

   public RepaintTest() {
      JPanel labelPanel = new JPanel();
      for (int i = 0; i < LABEL_COUNT; i++) {
         labelPanel.add(new JLabel("Label " + (i + 1)));
      }

      setLayout(cardLayout);
      add(labelPanel, LABEL_PANEL);
      add(new JLabel(), BLANK_COMPONENT);

      new Timer(TIMER_DELAY, new TimerListener()).start();

   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }

   private class TimerListener implements ActionListener {
      @Override
      public void actionPerformed(ActionEvent e) {
         cardLayout.next(RepaintTest.this);
      }
   }

   private static void createAndShowGui() {
      RepaintTest mainPanel = new RepaintTest();

      JFrame frame = new JFrame("RepaintTest");
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

Upvotes: 3

Related Questions