JTabbedPane: show task progress in a tab
Asked Answered
N

3

4

I have a simple Swing Java application that performs searches, and the results are shown in a new tab. While the search is running, I want to show a progress icon or animation in the title of the tab. I tried adding a gif icon, but it doesn't animate. Is there a reason why this isn't working?

Nissensohn answered 7/4, 2012 at 14:35 Comment(3)
The 'path of least surprise' for this would be either a JProgressBar amongst the components of the tab of interest, or a common progress bar (outside the tabbed pane) that shows the progress of whatever tab is selected. "Any suggestions? tnx" 1) Don't do that. 2) Spell words like 'thanks' properly, or better still, leave them out - they're noise.Endorse
Thanks, but I want to realize something like the tabs of firefox (with an icon showing loading progress).Nissensohn
Well, yuck, but 'we have the technology' so see my answer. It is left as an exercise for the reader to choose colors (and image style etc.) that do not look like they were thought up by a demented Chimpanzee.Endorse
S
12

The Swing tutorial about progress bars (and showing progress in general) is a very good place to start. It shows you how to perform long-lasting operations on a worker thread by using a SwingWorker, and updating your UI at certain intervals to show progress of the long-lasting operation to the user. There is another tutorial available for more information on the SwingWorker and concurrency in Swing

And as always, this site is filled with examples. For example a previous answer of mine uses the SwingWorker class to show progress to a user

Edit

As I missed the title of tab part of your question. You could create a 'progress icon' and set that on the tab. The SwingWorker can then be used to update the icon.

An example of such an icon is example progress icon, which is basically an image you rotate each time some progress is made. The tabbed pane tutorial shows you how to add icons to your tabs (or even use custom components)

Edit2

As it seems my Mac in combination with JDK1.7 makes it much easier to show an animated gif then on other systems, I created a small SSCCE as well, quite similar to that of Andrew but with a rotating icon which does not look like it has been created by, and I quote, 'demented Chimpanzee'. The rotating icon code comes from this site (I used a stripped down version and added the timer). Only thing I am not too happy about is the fact I need to pass my tabbed pane to the rotating icon to trigger. Possible solution is to pull the timer outside the RotatingIcon class, but hey, it's only an SSCCE . Images are not included but were found with Google.

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTabbedPane;
import javax.swing.Timer;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;

public class ProgressTabbedPane {

  public static void main( String[] args ) {
    EventQueue.invokeLater( new Runnable() {
      @Override
      public void run() {
        JFrame frame = new JFrame( "RotatingIcon" );
        JTabbedPane tabbedPane = new JTabbedPane(  );
        tabbedPane.addTab( "Searching", new RotatingIcon( new ImageIcon( "resources/images/progress-indeterminate.png" ), tabbedPane ),
                           new JLabel( new ImageIcon( "resources/images/rotatingIcon.gif" ) ) );
        frame.getContentPane().add( tabbedPane );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.pack();
        frame.setVisible( true );
      }
    } );
  }

  private static class RotatingIcon implements Icon{
    private final Icon delegateIcon;
    private double angleInDegrees = 90;
    private final Timer rotatingTimer;
    private RotatingIcon( Icon icon, final JComponent component ) {
      delegateIcon = icon;
      rotatingTimer = new Timer( 100, new ActionListener() {
        @Override
        public void actionPerformed( ActionEvent e ) {
          angleInDegrees = angleInDegrees + 10;
          if ( angleInDegrees == 360 ){
            angleInDegrees = 0;
          }
          component.repaint();
        }
      } );
      rotatingTimer.setRepeats( false );
      rotatingTimer.start();
    }

    @Override
    public void paintIcon( Component c, Graphics g, int x, int y ) {
      rotatingTimer.stop();
      Graphics2D g2 = (Graphics2D )g.create();
      int cWidth = delegateIcon.getIconWidth() / 2;
      int cHeight = delegateIcon.getIconHeight() / 2;
      Rectangle r = new Rectangle(x, y, delegateIcon.getIconWidth(), delegateIcon.getIconHeight());
      g2.setClip(r);
      AffineTransform original = g2.getTransform();
      AffineTransform at = new AffineTransform();
      at.concatenate(original);
      at.rotate(Math.toRadians( angleInDegrees ), x + cWidth, y + cHeight);
      g2.setTransform(at);
      delegateIcon.paintIcon(c, g2, x, y);
      g2.setTransform(original);
      rotatingTimer.start();
    }

    @Override
    public int getIconWidth() {
      return delegateIcon.getIconWidth();
    }

    @Override
    public int getIconHeight() {
      return delegateIcon.getIconHeight();
    }
  } 
}

A screenshot for reference. A shame the icons do not rotate in the screenshot. SSCCE screenshot

Store answered 7/4, 2012 at 14:42 Comment(11)
This is all well and good, but the OP wants to display the progress in the tab itself. I don't see your answer addressing this part.Windsail
Oh, I did not see the title of the tab part, only that he was using a tabbed pane. Then indeed this is not an answer. Will see whether I can add something useful to this reply, or delete it otherwiseStore
Thanks for the answer, but in my case I can't use this method: the program can't access search progress (performed by the server). So I have to show a progress icon and hide it only when I receive the response from serverNissensohn
@Store It would be great if the J2SE supported animated GIFs 'out of the box' in more situations. I tried that animated GIF (nice image, BTW) as a tab icon, and no, it remains static.Endorse
@AndrewThompson Agreed. But re-creating the animation with that icon is as simple as using a Swing timer and rotating the icon in each step (see this link for rotating an icon)Store
@Store See my answer for the animation side of it. ;)Endorse
@AndrewThompson on my Mac (JDK1.7) the image nicely rotates without any extra steps. Both in the tab as in a JLabel as content. Will try what I have to do with a static iconStore
@Store I'll add that to my list of 'odd little x-plat gotchas'. Another one I struck was about the Java sound Clip. I was developing on Ubuntu at the time & the JRE implementation it used allowed clips of arbitrary size (until OutOfMemoryError at least). But on Sun's JRE on Windows, 1 second of CD quality audio was all it could load. :( BTW - tested that image animation just now on Win 7 using Java 1.6.0_29.Endorse
@AndrewThompson I added some code. The JLabel uses the icon which I posted here and that simply rotates all by itself. The png is static and uses a Timer to rotate. +1 for your Chimpanzee comment BTWStore
Well shucks. I wish I hadn't up-voted before. Then I could do that now. ;) Nice example, and as usual with you Mac. users - excellent screen-shot. It's probably automatic, but I like the way the window shadow is captured. :)Endorse
@AndrewThompson Happens indeed automatically, can not take credit for that one. And perhaps a nice time to stop commenting, I get the suggestion to move all this to chatStore
E
7

Animated image as tab icon

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

public class ImageOnTab {

    ImageOnTab() {

        final BufferedImage image = new BufferedImage(
            32,32,BufferedImage.TYPE_INT_RGB);
        final JTabbedPane pane = new JTabbedPane();
        ImageIcon icon = new ImageIcon(image);
        pane.addTab( "Progress", icon, new JTree() );

        ActionListener listener = new ActionListener() {

            int x = 0;
            int step = 1;

            public void actionPerformed(ActionEvent ae) {
                Graphics g = image.createGraphics();
                x+=step;
                if (step>0) {
                    if (x>32) {
                        step=-step;
                    }
                } else if (x<0) {
                    step=-step;
                }

                g.setColor(Color.ORANGE);
                g.fillRect(0,0,32,32);

                g.setColor(Color.RED);
                g.fillRect(0,0,x,32);

                g.dispose();

                pane.repaint();
            }
        };

        Timer timer = new Timer(100,listener);
        timer.start();

        JOptionPane.showMessageDialog(null, pane);
    }

    public static void main(String[] args) throws Exception {
        //Create the GUI on the event dispatching thread
        SwingUtilities.invokeLater(new Runnable(){
            @Override
            public void run() {
                new ImageOnTab();
            }
        });
    }
}
Endorse answered 7/4, 2012 at 15:20 Comment(3)
Excellent! See also this related example.Scuttle
@Scuttle Jeesh.. I should have known a) you already had an example for this b) with one of those lovely Mac. screen-shots. c) that I'd already up-voted, and d) that was better than anything I could hack out in a few 'demented' moments! (Except I still like the fact my approach doesn't need to override anything. Not sure if that constitutes 'good design' in this case, or merely 'flogging a dead horse'.)Endorse
I see the two as complementary on all counts; cited.Scuttle
T
4

@Andrew Thompson

It would be great if the J2SE supported animated GIFs 'out of the box' 
in more situations. I tried that animated GIF (nice image, BTW) as a 
tab icon, and no, it remains static. 

I don't want to read whole ...., but put together code by yours and @trashgod's majesty

1) use Htlm (I'm not good in plain Html)

2) use GlassPane with JLabel#(setOpaque(true))

3) use JLayer (JXLayer is better, becasue Sn'Oracle remove important methods == my view)

4) you have to force ..... for Swing JComponents by @aterai

5) Rob's Animated Icon a few times metioned support by Rob for JTabbedPane

code

import java.awt.*;
import java.awt.event.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
//https://mcmap.net/q/978891/-java-jprogressbar-or-equivalent-in-a-jtabbedpane-tab-title/3484251#3484251
public class JTabbedTest {

    private JFrame f = new JFrame();
    private JTabbedPane jtp = new JTabbedPane();
    private URL url = null;

    public JTabbedTest() {
        try {
            url = new URL("http://pscode.org/media/starzoom-thumb.gif");
        } catch (MalformedURLException ex) {
            Logger.getLogger(JTabbedTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        ImageIcon ii = new ImageIcon(url);

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jtp.setPreferredSize(new Dimension(400, 200));
        createTab("Reds", Color.RED);
        createTab("Greens", Color.GREEN);
        createTab("Blues", Color.BLUE);
        f.add(jtp, BorderLayout.CENTER);

        jtp.setTitleAt(2, "<html><img src=" + ii + " width=20 height=20></img></html>");

        // change foreground Color for disabled tab        
        /*jtp.setTitleAt(2, "<html><font color=" + (jtp.isEnabledAt(2) ? "black" : "red") + ">"
                + jtp.getTitleAt(2) + "</font></html>");*/

        Rectangle tabBounds = jtp.getBoundsAt(0);
        Container glassPane = (Container) f.getRootPane().getGlassPane();
        glassPane.setVisible(true);
        glassPane.setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.weightx = 1.0;
        gbc.weighty = 1.0;
        gbc.fill = GridBagConstraints.NONE;
        gbc.insets = new Insets(tabBounds.y + 23, 0, 0, 5);
        gbc.anchor = GridBagConstraints.NORTHEAST;
        JButton button = new JButton("My Button Position", ii);
        button.setPreferredSize(new Dimension(button.getPreferredSize().width, (int) tabBounds.getHeight() - 2));
        glassPane.add(button, gbc);
        f.pack();
        f.setVisible(true);
    }

    private void createTab(String name, Color color) {
        ProgressIcon icon = new ProgressIcon(color);
        jtp.addTab(name, icon, new ColorPanel(jtp, icon));
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JTabbedTest jTabbedTest = new JTabbedTest();
            }
        });
    }

    private static class ColorPanel extends JPanel implements ActionListener {

        private static final Random rnd = new Random();
        private static final long serialVersionUID = 1L;
        private final Timer timer = new Timer(1000, this);
        private final JLabel label = new JLabel("Stackoverflow!");
        private final JTabbedPane parent;
        private final ProgressIcon icon;
        private final int mask;
        private int count;

        public ColorPanel(JTabbedPane parent, ProgressIcon icon) {
            super(true);
            this.parent = parent;
            this.icon = icon;
            this.mask = icon.color.getRGB();
            this.setBackground(icon.color);
            label.setForeground(icon.color);
            this.add(label);
            timer.start();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            this.setBackground(new Color(rnd.nextInt() & mask));
            this.icon.update(count += rnd.nextInt(8));
            this.parent.repaint();
        }
    }

    private static class ProgressIcon implements Icon {

        private static final int H = 16;
        private static final int W = 3 * H;
        private Color color;
        private int w;

        public ProgressIcon(Color color) {
            this.color = color;
        }

        public void update(int i) {
            w = i % W;
        }

        @Override
        public void paintIcon(Component c, Graphics g, int x, int y) {
            g.setColor(color);
            g.fillRect(x, y, w, H);
        }

        @Override
        public int getIconWidth() {
            return W;
        }

        @Override
        public int getIconHeight() {
            return H;
        }
    }
}
Tinishatinker answered 7/4, 2012 at 23:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.