How To Get Java Swing To Paint Properly?

Frank :

I have a minimal app to show Java Swing GUI update problem, seems it's not painting the components properly, the errors look like this :

enter image description here

Here is my program :

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import java.util.concurrent.*;

public class Java_Test extends JPanel
{
  static JFrame frame=new JFrame("Java Test");
  static int W=800,H=260,Executor_Count=12;
  JPanel Progress_Panel=new JPanel(),Center_Panel;
  JButton Do_Test_Button=new JButton("Do Test");
  Get_Time Timer=new Get_Time();
  ThreadPoolExecutor executor;
  Progress_Bar Progress_bar;
  Timer Display_Timer=new Timer(1);

  public Java_Test()
  {
    setLayout(new BorderLayout());

    JPanel Top_Panel=new JPanel();
    Top_Panel.setPreferredSize(new Dimension(W-6,60));
    add("North",Top_Panel);

    Do_Test_Button.setFont(new Font("Times New Roman",0,16));
    Do_Test_Button.setBackground(new Color(118,198,250));
    Do_Test_Button.setForeground(new Color(0,28,218));
    Do_Test_Button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Do_Test(); } });
    Top_Panel.add(Do_Test_Button);

    JPanel Center_Panel=new JPanel();
    Center_Panel.setPreferredSize(new Dimension(W-2,170));
    add("Center",Center_Panel);        

    Progress_Panel.setPreferredSize(new Dimension(W-2,160));
    Center_Panel.add(Progress_Panel);    

    JLabel Progress_Label=new JLabel("Progress");
    Progress_Label.setFont(new Font("Times New Roman",0,20));
    Progress_Label.setBackground(new Color(253,253,253));
    Progress_Label.setForeground(new Color(8,68,128));
    Progress_Label.setPreferredSize(new Dimension(W-20,53));
    Progress_Label.setHorizontalAlignment(SwingConstants.CENTER);
    Progress_Panel.add(Progress_Label);

    Progress_bar=new Progress_Bar(620,26);
    Progress_Panel.add(Progress_bar);

    JPanel Time_Used_Panel=new JPanel(new FlowLayout(FlowLayout.CENTER,6,26));
    Time_Used_Panel.setPreferredSize(new Dimension(W-20,60));
    Progress_Panel.add(Time_Used_Panel);

    JLabel Time_Used_Label=new JLabel("Time : ");
    Time_Used_Label.setFont(new Font("Times New Roman",0,14));
    Time_Used_Label.setForeground(new Color(0,0,238));
    Time_Used_Label.setHorizontalAlignment(SwingConstants.CENTER);
    Time_Used_Panel.add(Time_Used_Label);

    Display_Timer.setFont(new Font("Times New Roman",0,14));
    Display_Timer.setForeground(new Color(0,0,238));
    Display_Timer.setPreferredSize(new Dimension(50,17));
    Time_Used_Panel.add(Display_Timer);

    Clock clock=new Clock(0);
    clock.setFont(new Font("Monospaced",Font.PLAIN,16));
    clock.setBackground(new Color(0,110,220));
    clock.setForeground(new Color(250,250,250));
    clock.setOpaque(true);
    clock.setPreferredSize(new Dimension(288,30));
    clock.start();

    JPanel Bottom_Panel=new JPanel();
    Bottom_Panel.setPreferredSize(new Dimension(W-2,50));
    Bottom_Panel.add(clock);

    add("South",Bottom_Panel);
    setPreferredSize(new Dimension(W,H));
  }

  void Do_Test()
  {
    String Info="",Result;
    Out("Do_Test");
    try
    {
      Display_Timer.start();
      Timer.Start();
      Output_Time("[ 1 ]");
      Progress_bar.Set_Progress(1);

      int Task_Count=222;
      executor=new ThreadPoolExecutor(Executor_Count,Executor_Count*2,1,TimeUnit.SECONDS,new LinkedBlockingQueue());
      ArrayList<Future<String>> futures=new ArrayList<>(Task_Count);
      Test_Runner A_Runner;

      try
      {
        for (int i=0;i<Task_Count;i++)
        {
          A_Runner=new Test_Runner();
          futures.add(executor.submit(A_Runner));
        }
        executor.shutdown();
        while (!executor.isTerminated()) { executor.awaitTermination(100,TimeUnit.MILLISECONDS); }

        for (Future<String> future : futures)
        {
          Result=future.get();
          if (Result!=null) Info+=Result;
        }
      }
      catch (Exception e) { e.printStackTrace(); }
    }
    catch (Exception e) { e.printStackTrace(); }
    Output_Time("[ 2 ]");
    Progress_bar.Set_Progress(100);
    Out("Done");
    Display_Timer.stop();
  }

  String Output_Time(String Id)
  {
    Timer.End();
    String Time_Duration=Id+" : Time = "+Timer.Get_Duration_Hour_Minute_Second();
    Out(Time_Duration);
    return Time_Duration;
  }

  private static void Out(String message) { System.out.println(message); }

  static void Create_And_Show_GUI()
  {
    final Java_Test demo=new Java_Test();

    frame.add(demo);
    frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } });
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

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

class Progress_Bar extends JPanel implements Runnable
{
  int W,H,Last_Progress=-99,Progress,counter=0,Unit_Size=20;
  static JProgressBar b=new JProgressBar();
  boolean Started_B=false,Do_Step_B=false;
  Thread Progress_Bar_Runner_Thread;

  public Progress_Bar(int W,int H)
  {
    setPreferredSize(new Dimension(W,H));
    b.setPreferredSize(new Dimension(W-20,H-8));
    b.setStringPainted(true);
    add(b);
    start();
  }

  public void Set_Progress(int Progress)
  {
    if (Progress==1 || (this.Progress<Progress && Progress<=100))
    {
      this.Progress=Progress;
      b.setValue(Progress);
      b.paintImmediately(0,0,b.getWidth(),b.getHeight());
      Out("        Progress = "+Progress+" %");
    }
    if (Progress==1) Started_B=true;
    else if (Progress>=100)
    {
      Started_B=false;
      Do_Step_B=false;
    }
  }

  public void run()
  {
    try
    {
      while (Progress<=100)
      {
        if ((Progress==0 || Progress==50 || Progress==100 || Do_Step_B) && Last_Progress!=Progress)
        {
          b.setValue(Progress);
//          revalidate();
          b.paintImmediately(0,0,b.getWidth(),b.getHeight());
          Last_Progress=Progress;
        }
        Thread.sleep(200);                                                      // Delay the thread
        Do_Step_B=(Started_B && (counter++ % Unit_Size ==0));
        if (Progress<100 && Do_Step_B) Progress++;
      }
    }
    catch (Exception e) { e.printStackTrace(); }
  }

  public void start()
  {
    if (Progress_Bar_Runner_Thread==null)
    {
      Progress_Bar_Runner_Thread=new Thread(this);
      Progress_Bar_Runner_Thread.setPriority(Thread.NORM_PRIORITY);
      Progress_Bar_Runner_Thread.start();
    }
  }

  public void stop() { if (Progress_Bar_Runner_Thread!=null) Progress_Bar_Runner_Thread=null; }

  private static void Out(String message) { System.out.println(message); }
}

class Test_Runner implements Callable<String>
{
  int Start_Index=0,End_Index=6999;
  StringBuilder StrBdr=new StringBuilder();

  public Test_Runner() { }

  public String call() throws InterruptedException
  {
    try { for (int i=Start_Index;i<End_Index;i++) StrBdr.append("Test_Runner + Test_Runner + Test_Runner + Test_Runner + Test_Runner + Test_Runner"); }
    catch (Exception e) {}
    return StrBdr.toString();
  }
}

class Timer extends JLabel implements Runnable
{
  public static final long serialVersionUID=26362862L;
  private Thread Timer_Thread;
  String Time_Text="";
  int updateInterval=1000,Format=0;
  Get_Time Timer=new Get_Time();

  public Timer()
  {
    setFont(new Font("Monospaced",Font.PLAIN,16));
    setVerticalAlignment(SwingConstants.CENTER);
    setHorizontalAlignment(SwingConstants.CENTER);
  }

  public Timer(int Format) { this.Format=Format; }

  public void start()
  {
    Timer.Start();
    if (Timer_Thread==null)
    {
      Timer_Thread=new Thread(this);
      Timer_Thread.setPriority(Thread.NORM_PRIORITY);
      Timer_Thread.start();
    }
  }

  public void run()
  {
    Thread myThread=Thread.currentThread();
    while (Timer_Thread==myThread)
    {
      switch (Format)
      {
        case 1 : Time_Text=Timer.Get_Duration_Hour_Minute_Second();break;
      }
      setText(Time_Text);
      paintImmediately(0,0,getWidth(),getHeight());
      revalidate();
      try { Thread.sleep(updateInterval); }
      catch (InterruptedException e) { } 
    }
  }

  public void stop() { if (Timer_Thread != null) Timer_Thread=null; }
}

class Clock extends JLabel implements Runnable
{
  public static final long serialVersionUID=26362862L;
  private Thread clockThread;
  String Time_Text="";
  int updateInterval=1000,Format=0;
  Get_Time Timer=new Get_Time();

  public Clock() { start(); }

  public Clock(int Format)
  {
    this.Format=Format;
    start();
  }

  public void start()
  {
    setVerticalAlignment(SwingConstants.CENTER);
    setHorizontalAlignment(SwingConstants.CENTER);
    if (clockThread==null)
    {
      clockThread=new Thread(this);
//      clockThread.setPriority(Thread.NORM_PRIORITY);
      clockThread.setPriority(Thread.MIN_PRIORITY);
      clockThread.start();
    }
  }

  public void run()
  {
    Thread myThread=Thread.currentThread();

    while (clockThread==myThread)
    {
      switch (Format)
      {
        case 0 : Time_Text=" "+new java.util.Date().toString().substring(0,19)+" ";break;
      }
      setText(Time_Text);
      paintImmediately(0,0,getWidth(),getHeight());
      revalidate();

      try { Thread.sleep(updateInterval); }
      catch (InterruptedException e) { } 
    }
  }

  public void stop() { if (clockThread != null) clockThread=null; }
}

class Get_Time
{
  private long start,end;
  public int Hours,Minutes,Seconds,Total_Seconds;
  String ST_Hours,ST_Minutes,ST_Seconds;

  public Get_Time() { Reset(); }

  public void Start() { start=System.currentTimeMillis(); }

  public void End()
  {
    int half_second;

    end=System.currentTimeMillis();

    Total_Seconds=(int)(end-start)/1000;
    half_second=(int)(end-start)%1000;

    if (half_second>499) Total_Seconds++;

    Hours=Total_Seconds/3600;
    Minutes=(Total_Seconds%3600)/60;
    Seconds=(Total_Seconds%3600)%60;

    ST_Hours=new String((Hours>9)?""+Hours:"0"+Hours);
    ST_Minutes=new String((Minutes>9)?""+Minutes:"0"+Minutes);
    ST_Seconds=new String((Seconds>9)?""+Seconds:"0"+Seconds);
  }

  public String Get_Duration_Hour_Minute_Second()
  {
    End();
    return ST_Hours+":"+ST_Minutes+":"+ST_Seconds;
  }

  public void Reset() { start=0;end=0; }
}

My computer has a 12 core 3.33 GH Intel Corei7 x 980 CPU, with 24 GB of RAM, running on Windows 7, Java Version = 1.8.0_212

The question is how to fix it ?

Hovercraft Full Of Eels :

Enumeration of Swing threading and painting problems:

All of the following code is called from the Swing event dispatch thread (EDT), and so far so good....

void Do_Test() {
    String Info = "", Result;
    Out("Do_Test");
    try {
        Display_Timer.start();
        Timer.Start();
        Output_Time("[ 1 ]");
        Progress_bar.Set_Progress(1);   // OK to call this on the event thread

        int Task_Count = 222;
        executor = new ThreadPoolExecutor(Executor_Count, Executor_Count * 2, 1,
                TimeUnit.SECONDS, new LinkedBlockingQueue());
        ArrayList<Future<String>> futures = new ArrayList<>(Task_Count);
        Test_Runner A_Runner;

        try {
            for (int i = 0; i < Task_Count; i++) {
                A_Runner = new Test_Runner();
                futures.add(executor.submit(A_Runner));
            }
            executor.shutdown();

OK, here we start seeing problems as you're calling a potentially long while loop on the EDT, potentially blocking it:

while (!executor.isTerminated()) {
    executor.awaitTermination(100, TimeUnit.MILLISECONDS);
}

Same here, as the call to Future#get() is a blocking call:

for (Future<String> future : futures) {
    Result = future.get();
    if (Result != null)
        Info += Result;
}

OK, now we start seeing some truly unusual Swing code where you make Swing calls, including setting a JProgressBar's value off the EDT and then try to force the EDT to do some painting via paintImmediately(....). Making calls off the EDT can result in intermittent and unpredictable errors, and can cause the painting to mess up, and the GUI to sometimes freeze:

class Progress_Bar extends JPanel implements Runnable {
    // .....

    public void Set_Progress(int Progress) {
        if (Progress == 1 || (this.Progress < Progress && Progress <= 100)) {
            this.Progress = Progress;    // ****** being called off the EDT
            b.paintImmediately(0, 0, b.getWidth(), b.getHeight());  // ***** Don't do this
            Out("        Progress = " + Progress + " %");
        }
        // .....
    }

    public void run() {
        try {
            while (Progress <= 100) {
                if ((Progress == 0 || Progress == 50 || Progress == 100 || Do_Step_B)
                        && Last_Progress != Progress) {

                    // ***** called off the EDT -- don't do this
                    b.setValue(Progress);
                    // revalidate();
                    b.paintImmediately(0, 0, b.getWidth(), b.getHeight()); // **** and don't do this
                    Last_Progress = Progress;
                }
                Thread.sleep(200); // Delay the thread
                Do_Step_B = (Started_B && (counter++ % Unit_Size == 0));
                if (Progress < 100 && Do_Step_B)
                    Progress++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // .......
}

Similar strange goings-ons in this class, the Timer class, where you set a JLabel's text off of the EDT:

class Timer extends JLabel implements Runnable { // ....

public void run() {
    Thread myThread = Thread.currentThread();
    while (Timer_Thread == myThread) {
        switch (Format) {
        case 1:
            Time_Text = Timer.Get_Duration_Hour_Minute_Second();
            break;
        }

        // again this is dangerous code **********
        setText(Time_Text);
        paintImmediately(0, 0, getWidth(), getHeight());
        // ....

Same for the Clock class...


Solutions:

To repeatedly call code in a Swing GUI, use a javax.swing.Timer or "Swing Timer". For an example of use of this, please see my implementation of your MCVE, but using a Swing Timer for your threading code above.

The other code, the one that calls long-running tasks, should be done within a SwingWorker. This worker thread usually communicates with the Swing GUI in one of two ways (or both) -- either using a publish/process method pair, or (as in the example below), using a PropertyChangeListener attached to the Worker. Workers have several "bound" properties, fields that notify listeners of change, including the progress property, that can hold a value from 0 to 100, and the SwingWorker.StateValue, or "state" property:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.*;

@SuppressWarnings("serial")
public class JavaTest2 extends JPanel {
    private static final int W = 800;
    private static final int H = 260;
    private Action doTestAction = new DoTestAction("Do Test");
    private JProgressBar progressBar = new JProgressBar(0, 100);
    private MyClockPanel clockPanel = new MyClockPanel();
    private MyTimerPanel timerPanel = new MyTimerPanel();

    public JavaTest2() {
        JPanel topPanel = new JPanel();
        topPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 35, 5));
        topPanel.add(new JButton(doTestAction));

        progressBar.setStringPainted(true);
        JPanel progressPanel = new JPanel(new GridBagLayout());
        progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1.0;
        progressPanel.add(progressBar, gbc);

        JLabel progressLabel = new JLabel("Progress", SwingConstants.CENTER);
        progressLabel.setFont(new Font("Times New Roman", 0, 20));
        progressLabel.setForeground(new Color(8, 68, 128));

        JPanel centralPanel = new JPanel(new BorderLayout(5, 5));
        centralPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        centralPanel.add(progressLabel, BorderLayout.PAGE_START);
        centralPanel.add(progressPanel);

        JPanel clockWrapper = new JPanel();
        clockWrapper.add(clockPanel);
        JPanel bottomPanel = new JPanel();
        bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.PAGE_AXIS));
        bottomPanel.add(timerPanel, BorderLayout.PAGE_START);
        bottomPanel.add(clockWrapper, BorderLayout.PAGE_END);

        setLayout(new BorderLayout());
        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        add(topPanel, BorderLayout.PAGE_START);
        add(bottomPanel, BorderLayout.PAGE_END);
        add(centralPanel);
    }

    @Override
    public Dimension getPreferredSize() {
        Dimension superSize = super.getPreferredSize();
        if (isPreferredSizeSet()) {
            return superSize;
        } else {
            int w = Math.max(superSize.width, W);
            int h = Math.max(superSize.height, H);
            return new Dimension(w, h);
        }
    }

    private class DoTestAction extends AbstractAction {
        private MyWorker myWorker = null;

        public DoTestAction(String name) {
            super(name);
            int mnemonic = (int) name.charAt(0);
            putValue(MNEMONIC_KEY, mnemonic);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (myWorker != null && myWorker.getState() == SwingWorker.StateValue.STARTED) {
                return; // still running
            }
            timerPanel.start();
            progressBar.setValue(0);
            myWorker = new MyWorker();
            myWorker.addPropertyChangeListener(new WorkerListener());
            myWorker.execute();
            setEnabled(false);
        }
    }

    class WorkerListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            // if the worker is changing its progress bound property:
            if (evt.getPropertyName().equals("progress")) {
                int progress = (int) evt.getNewValue();
                // just for safety's sake, limit progress to 100 and no more
                progress = Math.min(progress, 100);
                progressBar.setValue(progress);
            } else if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                // else if worker is done
                try {
                    // get the result to at least trap errors
                    String result = ((MyWorker) evt.getSource()).get();
                    // can display result in the GUI
                    timerPanel.stop();
                } catch (Exception e) {
                    // worker's exception is available to the GUI if desired here
                    e.printStackTrace();
                }
                progressBar.setValue(100);
                doTestAction.setEnabled(true);
            }
        }
    }

    private static class MyWorker extends SwingWorker<String, Void> {
        private static final int EXECUTOR_COUNT = 12;
        private static final int TASK_COUNT = 222;

        @Override
        protected String doInBackground() throws Exception {
            ExecutorService executor = new ThreadPoolExecutor(EXECUTOR_COUNT, EXECUTOR_COUNT * 2, 1,
                    TimeUnit.SECONDS, new LinkedBlockingQueue<>());
            List<Future<String>> futures = new ArrayList<>();
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < TASK_COUNT; i++) {
                Callable<String> aRunner = new ARunner();
                futures.add(executor.submit(aRunner));
            }
            executor.shutdown();
            int index = 0;
            for (Future<String> future : futures) {
                String result = future.get();
                sb.append(result);
                sb.append(" ");
                index++;
                int progress = (100 * index) / TASK_COUNT;
                progress = Math.min(progress, 100);
                setProgress(progress);
            }

            return sb.toString();
        }
    }

    private static class ARunner implements Callable<String> {
        private static final long SLEEP_TIME = 800;

        @Override
        public String call() throws Exception {
            TimeUnit.MILLISECONDS.sleep(SLEEP_TIME);
            return "Foo";
        }
    }

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

        JFrame frame = new JFrame("Java Test 2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}
@SuppressWarnings("serial")
class MyClockPanel extends JPanel {
    private static final Color FG = new Color(250, 250, 250);
    private static final Color BG = new Color(0, 110, 220);
    private static final int TIMER_DELAY = 200;
    private static final Font FONT = new Font("Monospaced", Font.PLAIN, 16);
    private JLabel clockLabel = new JLabel("", SwingConstants.CENTER);
    private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E MMM dd yyyy kk:mm:ss");

    public MyClockPanel() {
        setBackground(BG);
        clockLabel.setForeground(FG);
        clockLabel.setFont(FONT);
        displayDateTime();
        setLayout(new BorderLayout());
        setBorder(BorderFactory.createEmptyBorder(5, 50, 5, 50));
        add(clockLabel);
        new javax.swing.Timer(TIMER_DELAY, e -> {
            displayDateTime();
        }).start();
    }

    private void displayDateTime() {
        LocalDateTime dateTime = LocalDateTime.now();
        String text = dateTime.format(formatter);
        clockLabel.setText(text);
    }
}
@SuppressWarnings("serial")
class MyTimerPanel extends JPanel {
    private static final Color FG = new Color(0, 0, 238);
    private static final Font FONT = new Font("Times New Roman", 0, 14);
    private static final int TIMER_DELAY = 40;
    private static final String FORMAT_TXT = "Elapsed Time: %02d:%02d:%02d";
    private JLabel timerLabel = new JLabel("", SwingConstants.CENTER);
    private LocalDateTime startTime = null;
    private Timer timer = null;

    public MyTimerPanel() {
        timerLabel.setForeground(FG);
        timerLabel.setFont(FONT);
        setLayout(new BorderLayout());
        setBorder(BorderFactory.createEmptyBorder(5, 50, 5, 50));
        timerLabel.setText(String.format(FORMAT_TXT, 0, 0, 0));
        add(timerLabel);
    }

    public void start() {
        stop();
        startTime = LocalDateTime.now();
        timer = new Timer(TIMER_DELAY, e -> incrementTime());
        timer.start();
    }

    public void stop() {
        if (timer != null && timer.isRunning()) {
            timer.stop();
        }
    }

    private void incrementTime() {
        LocalDateTime currentTime = LocalDateTime.now();
        long hours = ChronoUnit.HOURS.between(startTime, currentTime);
        long minutes = ChronoUnit.MINUTES.between(startTime, currentTime) % 60;
        long seconds = ChronoUnit.SECONDS.between(startTime, currentTime) % 60;
        String text = String.format(FORMAT_TXT, hours, minutes, seconds);
        timerLabel.setText(text);
    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=134547&siteId=1