Swing:高性能控件——MessagePane,可用于显示系列信息

这一个自上而下依条显示信息的控件

最新的信息显示在最上面一条

正常情况下,刷新数据时,CPU 占用率极低

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollBar;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class MessagePane<T> extends JComponent {

    /**
     * 允许最大数据行数
     */
    private static final int MAXLINE = 10000;
    /**
     * 数据队列
     */
    private LinkedList<T> datas = new LinkedList<T>();
    /**
     * 本次绘制,需要绘制的行数,正数向下偏移,负数向上偏移
     */
    private int add = 0;
    /**
     * 每行数据的绘制高度
     */
    private int lineH = 1;
    /**
     * 滚动条
     */
    private JScrollBar bar = new JScrollBar(JScrollBar.VERTICAL);
    /**
     * 滚动条当前值
     */
    private int barValue = 0;
    /**
     * 绘制任务
     */
    private Runnable paintRunnable = new Runnable() {
        
        @Override
        public void run() {
            add = 1;
            paintImmediately(0, 0, getWidth(), getHeight());
            add = 0;
        }
    };
    /**
     * 滚动条更新任务
     */
    private Runnable barRunnable = new Runnable() {
        
        @Override
        public void run() {
            bar.setValues(barValue, getHeight() / lineH, 0,
                    datas.size() + 1);
        }
    };
    

    /**
     * 构造一个新的 MessagePane
     */
    public MessagePane() {
        initialize();
    }
    
    /**
     * 初始化
     */
    private void initialize() {
        setOpaque(true);
        setBackground(Color.WHITE);
        setLayout(new BorderLayout());
        add(bar, BorderLayout.EAST);
        bar.addAdjustmentListener(new AdjustmentListener() {

            @Override
            public void adjustmentValueChanged(AdjustmentEvent e) {
                int newValue = bar.getValue();
                if (newValue != barValue) {
                    add = barValue - newValue;
                    barValue = newValue;
                    MessagePane.this.paintImmediately(0, 0,
                            MessagePane.this.getWidth(),
                            MessagePane.this.getHeight());
                    add = 0;
                }
            }
        });
        addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(ComponentEvent e) {
                //控件大小变化时,更新滚动条
                updateBar();
            }
        });
        setFont(UIManager.getFont("Panel.font"));
        lineH = getFontMetrics(getFont()).getHeight() + 2;
        updateBar();
    }

    @Override
    public void setFont(Font font) {
        super.setFont(font);
        if (font != null) {
            lineH = getFontMetrics(font).getHeight() + 2;
        }
    }

    /**
     * 根据当前数据量,更新滚动条属性
     */
    private void updateBar() {
        if (SwingUtilities.isEventDispatchThread()) {
            barRunnable.run();
        } else {
            SwingUtilities.invokeLater(barRunnable);
        }
    }

    /**
     * 标准的添加数据接口,在事件指派线程中调用该方法,可以获得最大的性能优化
     * 
     * @param data - 数据
     */
    public synchronized void addData(T data) {
        datas.addFirst(data);
        if (datas.size() > MAXLINE) {
            //数据超出限定范围时,移除最旧的一条数据
            datas.pollLast();
        } else {
            //数据长度变化时,更新滚动条
            updateBar();
        }
        if (SwingUtilities.isEventDispatchThread()) {
            paintRunnable.run();
        } else {
            try {
                SwingUtilities.invokeAndWait(paintRunnable);
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 标准的清除数据接口
     */
    public synchronized void clearDate() {
        datas.clear();
        //清除数据后,滚动条值归零
        barValue = 0;
        repaint();
        //清除数据后,更新滚动条
        updateBar();
    }

    /**
     * 获得真实的绘制宽度(减去滚动条宽度)
     * 
     * @return
     */
    private int getPaintWidth() {
        return getWidth() - bar.getWidth();
    }

    @Override
    protected void paintComponent(Graphics g) {
        //当调用 repaint 进入该方法时,add 为 0 ,会将整个控件完整绘制一遍。
        //而当设置了 add 然后调用 paintImmediately 进入该方法时,
        //会进行 copyArea,然后只绘制 copy 后的脏区域。
        int w = getPaintWidth();
        int h = getHeight();
        if (add > 0) {
            int move = lineH * add;
            //add 大于 0 表示需要向下整体位移,因此向下 copy
            //重新设置 clip,限制需要绘制的区域
            g.copyArea(0, 0, w, h - move, 0, move);
            g.setClip(0, 0, w, move);
        } else if (add < 0) {
            int move = -lineH * add;
            //add 小于 0 表示需要向上整体位移,因此向上 copy
            //重新设置 clip,限制需要绘制的区域
            g.copyArea(0, move, w, h - move, 0, -move);
            g.setClip(0, h - move, w, move);
        }
        Rectangle clip = g.getClipBounds();
        int clipY = clip.y;
        int clipX = clip.x;
        int clipH = clip.height;
        int y = 0;
        g.setColor(getBackground());
        g.fillRect(clipX, clipY, w, clipH);
        for (int i = barValue; i < datas.size(); i++) {
            y += lineH;
            if (y < clipY) {
                //小于绘制区域上沿时,不需要绘制该条数据
                continue;
            }
            if (y - lineH > clipY + clipH) {
                //由于是从上往下绘制,因此大于绘制区域下沿时,终止绘制
                break;
            }
            g.setColor(getForeground());
            g.drawString(datas.get(i).toString(), 0, y);
        }
    }

    static long show = 0;
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame();
                final MessagePane<Long> messagePane = new MessagePane<Long>();
                frame.setContentPane(messagePane);
                frame.setSize(400, 300);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
                Runnable runnable = new Runnable() {
                    
                    @Override
                    public void run() {
                        while (true) {
                            messagePane.addData(show);
                            try {
                                Thread.sleep(5000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            show++;
                        }
                    }
                };
                new Thread(runnable).start();
                new Thread(runnable).start();
                new Thread(runnable).start();
                new Thread(runnable).start();
            }
        });

    }

}

运行效果图:


测试用例中,同时起了 4 个线程,刷新数据

在 sleep(10) 并且全屏的模式下,CPU 占用率依然接近于 0

有 BUG 可以报告给我


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值