• category: swing folder: TableCellOfIndeterminateProgressBar title: JTableのセルに不確定進捗状態のJProgressBarを表示する tags: [JTable, JProgressBar, TableCellRenderer, SwingWorker, Animation] author: aterai pubdate: 2024-01-08T03:30:00+09:00 description: JTableのセルに一定期間だけ不確定進捗状態のアニメーションを描画するJProgressBarを適用します。 image: https://drive.google.com/uc?id=1j8bfUbhh4at4824_s2NPQ89sjHUw6N49

概要

JTableのセルに一定期間だけ不確定進捗状態のアニメーションを描画するJProgressBarを適用します。

サンプルコード

private final JTable table = new JTable(model) {
  @Override public void updateUI() {
    super.updateUI();
    removeColumn(getColumnModel().getColumn(3));
    JProgressBar progress = new JProgressBar();
    TableCellRenderer renderer = new DefaultTableCellRenderer();
    TableColumn tc = getColumnModel().getColumn(2);
    tc.setCellRenderer((tbl, value, isSelected, hasFocus, row, column) -> {
      Component c;
      if (value instanceof JProgressBar) {
        c = (JProgressBar) value;
      } else if (value instanceof Integer) {
        progress.setValue((int) value);
        c = progress;
      } else {
        c = renderer.getTableCellRendererComponent(
            tbl, Objects.toString(value), isSelected, hasFocus, row, column);
      }
      return c;
    });
  }
};

class IndeterminateProgressBarUI extends BasicProgressBarUI {
  @Override public void incrementAnimationIndex() {
    super.incrementAnimationIndex();
  }
}

class BackgroundTask extends SwingWorker<Integer, Object> {
  private final Random rnd = new Random();

  @Override protected Integer doInBackground() throws InterruptedException {
    int lengthOfTask = calculateTaskSize();
    int current = 0;
    int total = 0;
    while (current <= lengthOfTask && !isCancelled()) {
      publish(100 * current / lengthOfTask);
      total += doSomething();
      current++;
    }
    return total;
  }

  private int calculateTaskSize() throws InterruptedException {
    int total = 0;
    JProgressBar indeterminate = new JProgressBar() {
      @Override public void updateUI() {
        super.updateUI();
        setUI(new IndeterminateProgressBarUI());
      }
    };
    indeterminate.setIndeterminate(true);
    // Indeterminate loop:
    for (int i = 0; i < 200; i++) {
      int iv = rnd.nextInt(50) + 1;
      Thread.sleep(iv);
      ProgressBarUI ui = indeterminate.getUI()
      ((IndeterminateProgressBarUI) ui).incrementAnimationIndex();
      publish(indeterminate);
      total += iv;
    }
    return 1 + total / 100;
  }

  private int doSomething() throws InterruptedException {
    int iv = rnd.nextInt(50) + 1;
    Thread.sleep(iv);
    return iv;
  }
}
View in GitHub: Java, Kotlin

解説

  • SwingWorkerJProgressBar#setIndeterminate(true)を設定して不確定進捗状態にしたJProgressBarを生成してTableModelの対象セルに設定
  • SwingWorkerから進捗状況をIntegerで受け取る場合はセルレンダラーとして進捗状態を表示するJProgressBarを一つだけ使いまわして使用する(JTableのセルにJProgressBarを表示参照)が、不確定進捗状態アニメーションを表示する場合はSwingWorkerからセルごとに異なるJProgressBar自体を受け取ってセルレンダラーにする必要がある
    • 一つだけのJProgressBarを使用すると複数行で不確定進捗状態アニメーションを実行する場合やセルサイズが変更されて再描画する場合にアニメーション速度が速くなるなどの不具合が発生する
  • 不確定進捗状態アニメーションはJProgressBarが親コンテナに配置されて表示可能状態になっていないと開始、描画されないので、このサンプルではBasicProgressBarUI#incrementAnimationIndex()をオーバーライドしてpublicにし、SwingWorderで一定期間SwingWorder#publish(...)と同時にこれを実行してアニメーションのコマを進めている

if (value instanceof JProgressBar) {
  label.setIcon(makeImageIcon(url, tbl, row, column));
  c = label;
} else if (...) 
// ...
public static Icon makeImageIcon(URL url, JTable table, int row, int col) {
  if (Objects.nonNull(url)) {
    ImageIcon icon = new ImageIcon(url);
    icon.setImageObserver((img, infoflags, x, y, w, h) -> {
      if (!table.isShowing()) {
        return false;
      }
      if ((infoflags & (FRAMEBITS | ALLBITS)) != 0) {
        int vr = table.convertRowIndexToView(row);
        int vc = table.convertColumnIndexToView(col);
        table.repaint(table.getCellRect(vr, vc, false));
      }
      return (infoflags & (ALLBITS | ABORT)) == 0;
    });
    return icon;
  } else {
    return UIManager.getIcon("html.missingImage");
  }
}

参考リンク

コメント