1

I created a JProgressBar in a GUI application, and setted it to "indeterminate", but I don't like that it bounces instead of restarting every time it reaches the end. What can I do to fix this graphic setting?

CuriousCI
  • 138
  • 1
  • 11

2 Answers2

3

Change the UI of the JProgressBar.

The UI is the class that paints the progress bar. The BasicProgressBarUI, makes the box bounce by default. You just have to write your own class extending MetalProgressBarUI (if you're using Metal) and overriding getBox(Rectangle), which is the method that stores the position of the box in the given rectangle.

Use JProgressBar.setUI to apply the UI to your progress bar. You can also change the defaults with UIManager.put("ProgressBarUI", "fullyQualifiedClassName") to change the default UI for a progress bar.

Snowy_1803
  • 656
  • 2
  • 8
  • 24
  • Thanks a lot! So I can change the look and feel of evey element? – CuriousCI Dec 28 '18 at 11:10
  • 1
    Use setUI on the JProgressBar to use your own UI. The L&F is defining default UIs, colors, borders, etc. that you can override. – Snowy_1803 Dec 28 '18 at 11:13
  • I'm not understanding how to override it. Can you please write an example code where the rectangle remains still at the center of the progress bar? Sorry for bothering you too much... – CuriousCI Dec 28 '18 at 13:34
  • `r.x = componentsInnards.x + (int) Math.round(delta * numFrames/2)` – Snowy_1803 Dec 28 '18 at 14:02
  • Use getAnimationIndex() to get the current frame, and adapt if you want to support vertical progressbars. – Snowy_1803 Dec 28 '18 at 14:04
  • The problem is that it says "componentInnards has private acces in BasicProgressBarUI", the same for "delta" and "numFrames"... – CuriousCI Dec 28 '18 at 14:08
  • Use SwingUtilities.calculateInnerArea(progressBar, new Rectangle()) to get it – Snowy_1803 Dec 28 '18 at 14:32
  • Thanks a lot for the help! – CuriousCI Dec 28 '18 at 14:37
  • Basic swing UI classes have often private field and methods, that we have to recreate. Just look at the source code, which is included in the Oracle JDK. Remember to accept an answer! – Snowy_1803 Dec 28 '18 at 14:52
1

As Snowy_1803 has already said, you would need to override the BasicProgressBarUI#getBox(...):

import java.awt.*;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.swing.*;
import javax.swing.plaf.basic.BasicProgressBarUI;

public final class MainPanel extends JPanel implements HierarchyListener {
  private transient SwingWorker<String, Void> worker;

  private MainPanel() {
    super(new BorderLayout());

    BoundedRangeModel model = new DefaultBoundedRangeModel();
    JProgressBar progressBar = new JProgressBar(model) {
      @Override public void updateUI() {
        super.updateUI();
        setUI(new OneDirectionProgressBarUI());
      }
    };

    List<JProgressBar> list = Arrays.asList(new JProgressBar(model), progressBar);

    JPanel p = new JPanel(new GridLayout(5, 1));
    list.forEach(bar -> p.add(makePanel(bar)));

    JButton button = new JButton("Test start");
    button.addActionListener(e -> {
      if (Objects.nonNull(worker) && !worker.isDone()) {
        worker.cancel(true);
      }
      worker = new BackgroundTask();
      list.forEach(bar -> {
        bar.setIndeterminate(true);
        worker.addPropertyChangeListener(new ProgressListener(bar));
      });
      worker.execute();
    });

    Box box = Box.createHorizontalBox();
    box.add(Box.createHorizontalGlue());
    box.add(button);
    box.add(Box.createHorizontalStrut(5));

    addHierarchyListener(this);
    add(p);
    add(box, BorderLayout.SOUTH);
    setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
  }

  @Override public void hierarchyChanged(HierarchyEvent e) {
    boolean isDisplayableChanged = (e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0;
    if (isDisplayableChanged && !e.getComponent().isDisplayable() && Objects.nonNull(worker)) {
      worker.cancel(true);
      worker = null;
    }
  }

  private static Component makePanel(Component cmp) {
    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.HORIZONTAL;
    c.insets = new Insets(5, 5, 5, 5);
    c.weightx = 1d;

    JPanel p = new JPanel(new GridBagLayout());
    p.add(cmp, c);
    return p;
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new MainPanel());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

class OneDirectionProgressBarUI extends BasicProgressBarUI {
  @Override
  protected Rectangle getBox(Rectangle r) {
    Rectangle rect = super.getBox(r);

    boolean vertical = progressBar.getOrientation() == JProgressBar.VERTICAL;
    Insets ins = new Insets(0, 0, 0, 0); // progressBar.getInsets();

    int currentFrame = getAnimationIndex();
    int framecount = getFrameCount() / 2;
    currentFrame = currentFrame % framecount;

    // @see com/sun/java/swing/plaf/windows/WindowsProgressBarUI.java
    // this code adjusts the chunk size to properly account for the
    // size and gap specified in the XP style. It also does it's own
    // box placement for the chunk animation. This is required because
    // the inherited algorithm from BasicProgressBarUI goes back and
    // forth whereas XP only goes in one direction. XP also has ghosted
    // trailing chunks to create the illusion of speed. This code
    // adjusts the pixel length of the animation to account for the
    // trails.
    if (!vertical) {
      rect.y = rect.y + ins.top;
      rect.height = progressBar.getHeight() - ins.top - ins.bottom;
      int len = progressBar.getWidth() - ins.left - ins.right;
      len += rect.width * 2; // add 2x for the trails
      double delta = (double) (len) / (double) framecount;
      rect.x = (int) (delta * currentFrame) + ins.left;
    } else {
      rect.x = rect.x + ins.left;
      rect.width = progressBar.getWidth() - ins.left - ins.right;
      int len = progressBar.getHeight() - ins.top - ins.bottom;
      len += rect.height * 2; // add 2x for the trails
      double delta = (double) (len) / (double) framecount;
      rect.y = (int) (delta * currentFrame) + ins.top;
    }
    return rect;
  }
}

class BackgroundTask extends SwingWorker<String, Void> {
  @Override public String doInBackground() {
    try { // dummy task
      Thread.sleep(5000);
    } catch (InterruptedException ex) {
      return "Interrupted";
    }
    int current = 0;
    int lengthOfTask = 100;
    while (current <= lengthOfTask && !isCancelled()) {
      try { // dummy task
        Thread.sleep(50);
      } catch (InterruptedException ex) {
        return "Interrupted";
      }
      setProgress(100 * current / lengthOfTask);
      current++;
    }
    return "Done";
  }
}

class ProgressListener implements PropertyChangeListener {
  private final JProgressBar progressBar;

  protected ProgressListener(JProgressBar progressBar) {
    this.progressBar = progressBar;
    this.progressBar.setValue(0);
  }

  @Override public void propertyChange(PropertyChangeEvent e) {
    String strPropertyName = e.getPropertyName();
    if ("progress".equals(strPropertyName)) {
      progressBar.setIndeterminate(false);
      int progress = (Integer) e.getNewValue();
      progressBar.setValue(progress);
    }
  }
}
aterai
  • 9,658
  • 4
  • 35
  • 44