Zhiming010
Zhiming010

Reputation: 313

Jlabel text multiplying and overlapping when using update(graphic)

I've written this code to update my progress bar progress message. Basically, when I have length processes that run on for() loops, I throw this in the loop, and have it update with prog = i and max = an array max.

for(int i = 1; i <= array.length-1; i++){
//Process
setPrg(this.lbl, ths.prg, i, array.length-1, "Doing so process");
}

-

public void setPrg(JLabel lbl, JProgressBar prg, int prog, int max, String msg) {
    prg.setMaximum(max);
    if (prog <= max) {
        prg.setValue(prog);
    }
    prg.update(prg.getGraphics());
    if (prog >= max) {
        setMessage(lbl, "");
    } else {
        setMessage(lbl, msg);
    }
}

    public void setMessage(JLabel lbl, String msg) {
        lbl.setText(msg);
        lbl.update(lbl.getGraphics());
    }

I do this with multiple consecutive for() loops, which each have unique messages describing the process.

The problem is, for some reason the label text overlaps over itself. For example, if I have one loop that runs this code:

setPrg(this.lbl, ths.prg, i, array.length-1, "This is process 1");

And one that runs this code:

setPrg(this.lbl, ths.prg, i, array.length-1, "The second process this is");

Then the text from the two messages will overlap as soon as the second process starts, instead of "This is process 1" disappearing.

On the other hand, if I don't include this line:

lbl.update(lbl.getGraphics());

No text will show up at all during the loop, and only the last text set for the label will show up after the loop.

Help much appreciated!

Here's the full program:

package test;

import javax.swing.JLabel;
import javax.swing.JProgressBar;

public class Main extends javax.swing.JFrame {

    public Main() {
        initComponents();
    }
 public void setPrg(JLabel lbl, JProgressBar prg, int prog, int max, String msg) {
        prg.setMaximum(max);
        if (prog <= max) {
            prg.setValue(prog);
        }
        prg.update(prg.getGraphics());
        if (prog >= max) {
            setMessage(lbl, "");
        } else {
            setMessage(lbl, msg);
        }
    }

    public void setMessage(JLabel lbl, String msg) {
        lbl.setText(msg);
        lbl.update(lbl.getGraphics());
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jButton1 = new javax.swing.JButton();
        jProgressBar1 = new javax.swing.JProgressBar();
        jLabel1 = new javax.swing.JLabel();
        jTextField1 = new javax.swing.JTextField();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jButton1.setText("Button");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        jLabel1.setText("jLabel1");

        jTextField1.setText("jTextField1");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(246, 246, 246)
                        .addComponent(jLabel1))
                    .addGroup(layout.createSequentialGroup()
                        .addGap(226, 226, 226)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                            .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(jButton1)))
                    .addGroup(layout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 540, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap(108, Short.MAX_VALUE)
                .addComponent(jButton1)
                .addGap(22, 22, 22)
                .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(18, 18, 18)
                .addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(40, 40, 40))
        );

        pack();
    }// </editor-fold>                        

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
        for (int i = 1; i <= 10000; i++) {
            this.setPrg(jLabel1, jProgressBar1, i, 10000, "Message 1");
        }
                for (int i = 1; i <= 10000; i++) {
            this.setPrg(jLabel1, jProgressBar1, i, 10000, "Second Message");
        }
    }                                        

    public static void main(String args[]) {
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        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(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Main().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify                     
    public javax.swing.JButton jButton1;
    public javax.swing.JLabel jLabel1;
    public javax.swing.JProgressBar jProgressBar1;
    public javax.swing.JTextField jTextField1;
    // End of variables declaration                   
}

Upvotes: 1

Views: 452

Answers (2)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285403

Your problem is a classic one of running long-running code on the Swing event thread, as well as making invalid Swing graphics calls. The solution is to do long-running code in a background thread, such as the doInBackground method of a SwingWorker, and to take care to make all Swing calls on the Swing event thread.

All of this is well explained in tutorials that are easy to find, including

For example, compile and run this:

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;

import javax.swing.*;

@SuppressWarnings("serial")
public class Main4b extends JPanel {
    private static final int MAX = 10000;
    private static final int PREF_W = 400;
    private static final int PREF_H = 200;
    private ButtonAction buttonAction = new ButtonAction();
    private JButton button = new JButton(buttonAction);
    private JTextField jTextField1 = new JTextField("jTextField1", 10);
    private JLabel jLabel1 = new JLabel("jLabel1");
    private JProgressBar jProgressBar1 = new JProgressBar(0, MAX);

    public Main4b() {
        jProgressBar1.setStringPainted(true);

        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
        add(new JPanel() {{add(button);}});
        add(new JPanel() {{add(jTextField1);}});
        add(new JPanel() {{add(jLabel1);}});
        add(jProgressBar1);
        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    }

    @Override
    public Dimension getPreferredSize() {
        Dimension superSz = super.getPreferredSize();
        if (isPreferredSizeSet()) {
            return superSz;
        }
        int prefW = Math.max(superSz.width, PREF_W);
        int prefH = Math.max(superSz.height, PREF_H);
        return new Dimension(prefW, prefH);
    }

    private class ButtonAction extends AbstractAction {
        public ButtonAction() {
            super("Button");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            setEnabled(false); // make the button non-pressable

            // create background thread worker
            MyWorker myWorker = new MyWorker();

            // add propertychange listener to it
            myWorker.addPropertyChangeListener(new WorkerListener());

            // run the worker thread
            myWorker.execute();
        }
    }

    // background thread
    private static class MyWorker extends SwingWorker<Void, Integer> {
        private static final String MY_PROGRESS = "my progress";
        private int myProgress = 0;

        @Override
        protected Void doInBackground() throws Exception {
            // all this code is run in a background thread

            // do this twice
            for (int j = 0; j < 2; j++) {

                // iterate from 0 to 10,000
                for (int i = 0; i < MAX; i++) {
                    Thread.sleep(1); // small delay so we can see what we're doing
                    setMyProgress(i + j * MAX); // send output to listeners
                }
            }
            return null;
        }

        public int getMyProgress() {
            return myProgress;
        }

        // myProgress is a "bound" field, one that will notify listeners
        // if it is changed
        public void setMyProgress(int myProgress) {
            int oldValue = this.myProgress;
            int newValue = myProgress;
            this.myProgress = myProgress;
            // notify all listeners
            firePropertyChange(MY_PROGRESS, oldValue, newValue);
        }

    }

    private class WorkerListener implements PropertyChangeListener {
        private String message = "Message %d: %05d";

        public WorkerListener() {
            jLabel1.setText(String.format(message, 1, 0));
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            // all this code is run on the Swing event thread

            // listen for changes to my progress bound field
            if (MyWorker.MY_PROGRESS.equals(evt.getPropertyName())) {
                int value = (int) evt.getNewValue(); // get value
                int newMsgIndex = 1 + value / MAX; // message number 
                jProgressBar1.setValue(value % MAX); // set value on progress bar
                jLabel1.setText(String.format(message, newMsgIndex, value % MAX));
            } else if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                // worker is done
                jLabel1.setText(""); // reset JLabel
                buttonAction.setEnabled(true);  // re-enable JButton's Action
                MyWorker myWorker = (MyWorker) evt.getSource();
                try {
                    // always call this to catch and handle any exceptions that 
                    // may have been thrown from within the worker
                    myWorker.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }

    }

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

        JFrame frame = new JFrame("Main");
        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(() -> createAndShowGui());
    }
}

Upvotes: 2

Alexander
Alexander

Reputation: 1421

When you are using Swing you are automatically in a multi threaded environment. One is the main-Thread and Swing uses the EDT for UI repainting.

Use SwingUtilities.invokeLater() to execute JProgressBar.setValue() and JLabel.setValue() on the EventDispatchThread. This ensures they will be updated accordingly.

An example snippet of how to use SwingUtilities.

    final JProgressBar prg = new JProgressBar();
    final JLabel lbl = new JLabel();

    SwingUtilities.invokeLater(new Runnable() {

        @Override
        public void run() {
            prg.setValue(70);
            lbl.setText("Text");
        }
    });

And you will see, that your calls to lbl.update(lbl.getGraphics()); are no longer needed.

Upvotes: 4

Related Questions