在原生的JProgressBar中,并没提供详细控制百分比的途径。
计算百分比是由其内部维护的BoundedRangeModel所决定,而且其侦听器是重要所在。
通常情况,必须通过setValue来触发侦听器,使JProgressBar重绘百分比区域的图形。
一开始我以为此侦听器是由BoundedRangeModel内部自动绑定的,事实并非如此,毫无踪影。偶然在网上看见根据百分比而改变背景颜色,才醒悟BoundedRangeModel是数据模型,而处理图形应是ProgressBarUI。
果然,在BasicProgressBarUI里找到了此侦听器。
private class Handler implements ChangeListener, PropertyChangeListener, HierarchyListener { // ChangeListener public void stateChanged(ChangeEvent e) { BoundedRangeModel model = progressBar.getModel(); int newRange = model.getMaximum() - model.getMinimum(); int newPercent; int oldPercent = getCachedPercent(); if (newRange > 0) { newPercent = (int)((100 * (long)model.getValue()) / newRange); } else { newPercent = 0; } if (newPercent != oldPercent) { setCachedPercent(newPercent); progressBar.repaint(); } }
progressBar.repaint()将会发送一个PaintEvent通知描绘。其后接受到,便自动跳回至BasicProgressBarUI调用paint(Graphics g, JComponent c)实现描绘。
该实现描绘包含了描绘百分比方法paintString(Graphics g, int x, int y...),核心内容如下。
private void paintString(Graphics g, int x, int y, int width, int height, int fillStart, int amountFull, Insets b) { if (!(g instanceof Graphics2D)) { return; } Graphics2D g2 = (Graphics2D)g; String progressString = progressBar.getString(); g2.setFont(progressBar.getFont()); Point renderLocation = getStringPlacement(g2, progressString, x, y, width, height); Rectangle oldClip = g2.getClipBounds(); if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) { g2.setColor(getSelectionBackground()); SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y); g2.setColor(getSelectionForeground()); g2.clipRect(fillStart, y, amountFull, height); SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y); } else { // VERTICAL g2.setColor(getSelectionBackground()); AffineTransform rotate = AffineTransform.getRotateInstance(Math.PI/2); g2.setFont(progressBar.getFont().deriveFont(rotate)); renderLocation = getStringPlacement(g2, progressString, x, y, width, height); SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y); g2.setColor(getSelectionForeground()); g2.clipRect(x, fillStart, width, amountFull); SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y); } g2.setClip(oldClip); }
progressBar.getString()便是获取百分比字符串化的途径,但这是一个私有方法,并且多次调用了sun.swing私有类包,硬生生地耦合了JProgressBar。
大概流程:
当然,如果你敢善用sun.swing私有类包,即可解决这些耦合问题,否则……
public class PercentProgressBar extends JProgressBar { private transient NumberFormat numberFormat; private NumberFormat createPercentFormat(){ NumberFormat nf = NumberFormat.getPercentInstance(); nf.setMinimumFractionDigits(minFractionDigits()); // also affect MaximumFractionDigits nf.setMaximumFractionDigits(maxFractionDigits()); return nf; } protected int minFractionDigits() { return 0; } protected int maxFractionDigits() { return 3; } @Override public String getString() { if (progressString != null) return progressString; if (numberFormat == null) { numberFormat = this.createPercentFormat(); } return numberFormat.format( getPercentComplete() ); } @Override public void updateUI() { super.setUI(new BasicProgressBarUI() { @Override protected void installListeners() { super.installListeners(); super.progressBar.removeChangeListener(super.changeListener); super.changeListener = new ChangeListener() { private final double fd = Math.pow(10, 2+maxFractionDigits()); private int cachedPercent = 0; /** * @see BasicProgressBarUI.Handler#stateChanged(ChangeEvent) */ @Override public void stateChanged(ChangeEvent e) { final int percent = (int) (getPercentComplete()*fd +0.5); if(cachedPercent == percent) return; // ignore cachedPercent = percent; // update newPercent progressBar.repaint(); } }; super.progressBar.addChangeListener(super.changeListener); } }); } /** * Test */ public static void main(String[] args) { final JProgressBar progressBar = new PercentProgressBar(); progressBar.setFont(new java.awt.Font("Dialog", java.awt.Font.PLAIN, 20)); progressBar.setStringPainted(true); progressBar.setMaximum( 1234567890 ); javax.swing.JFrame frame = new javax.swing.JFrame(); frame.getContentPane().add( progressBar ); frame.setSize(100, 100); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); frame.setVisible(true); javax.swing.SwingUtilities.invokeLater(new Runnable() { private java.util.Random random = new java.util.Random(); @Override public void run() { int value = progressBar.getValue(); if(value >= progressBar.getMaximum()){ System.out.println("100%"); return; } int rd = random.nextInt(1000000); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } progressBar.setValue( value+(1+rd) ); javax.swing.SwingUtilities.invokeLater(this); } }); } }
附带简易测试,见截图。