JProgressBar显示含小数百分比

在原生的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

大概流程:

  • JProgressBar#setValue(int)
  • BasicProgressBarUI.Handler#stateChanged(ChangeEvent)
  • JProgressBar#repaint()
  • BasicProgressBarUI#paint(Graphics,JComponent)
  • BasicProgressBarUI#paintString(Graphics,int,int,int,int,int,int,Insets)
  • JProgressBar#getString()


当然,如果你敢善用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);
            }
        });
		
    }
	
}

附带简易测试,见截图。








你可能感兴趣的:(java,百分比,小数,JProgressBar)