如果JTabbedPane有很多页签,而每个页签的标题起得不是太直观的话,用户往往很难发现哪个页签是自己想要的。如果要求用户一个个点开每个页签去寻找,这显然是很不友好的做法。
还有一种做法是为每个页签添加一个普通的文本提示框来描述这个页签的功能。但俗话说“A Picture is Worth a Thousand Words”,如果给这个页签的一个缩略图提示框的话应该能起到更好的效果。
图中的所略图是在鼠标移动到“高级设置”页签时显示的。为了标明,上面贴了个鼠标截图。
现它涉及一个技术,即如何在页签显示之前获得这个页签的缩略图。本文采用的是Swing的渲染器机制,将提示框的Graphics对象传递给该页签面板ToolTip容器的paint方法,由它来完成提示框图像的渲染。
先看效果图:

下面贴出代码:
- public class SnapTipTabbedPane extends JTabbedPane {
- /***/
- private static final long serialVersionUID = 1L;
- // 这个是缩略图缩略比例
- private double scaleRatio = 0.25;
- // 这儿放添加页签与其ToolTipText之间的映射,
- // 后面定义的ImageToolTip通过它来获取当前页签的容器组件
- private HashMap<String, Component> maps = new HashMap<String, Component>();
- public SnapTipTabbedPane() {
- addContainerListener(new ContainerListener() {
- @Override
- public void componentAdded(ContainerEvent e) {
- String tip = "tab" + e.getChild().hashCode();
- maps.put(tip, e.getChild());
- }
- @Override
- public void componentRemoved(ContainerEvent e) {
- maps.remove("tab" + e.getChild());
- }
- });
- }
- @Override
- public void insertTab(String title, Icon icon, Component component,
- String tip, int index) {
- // 这个提示文字将作为访问页签面板的主键,提示框并不显示它
- tip = "tab" + component.hashCode();
- // 由于这儿tip不为空,调用父类的insertTab将该页签注册成有提示框的
- super.insertTab(title, icon, component, tip, index);
- }
- @Override
- public void addTab(String title, Component component) {
- this.insertTab(title, null, component, title, getComponentCount());
- }
- @Override
- public void addTab(String title, Icon icon, Component component) {
- this.insertTab(title, icon, component, title, getComponentCount());
- }
- @Override
- public void addTab(String title, Icon icon, Component component, String tip) {
- this.insertTab(title, icon, component, tip, getComponentCount());
- }
- /**
- * 覆盖JComponent的createToolTip,提供自定义的ImageToolTip来显示图片
- */
- @Override
- public JToolTip createToolTip() {
- ImageToolTip tooltip = new ImageToolTip();
- tooltip.setComponent(this);
- return tooltip;
- }
- /**
- * 图片提示框的实现类
- * @version 1.0
- */
- class ImageToolTip extends JToolTip {
- private static final long serialVersionUID = 1L;
- /**
- * 重新根据图片大小来定义preferredSize,
- *
- * 注意一定要重载这个方法 否则父类会使用当前的TipText来计算弹出提示框的大小
- */
- @Override
- public Dimension getPreferredSize() {
- // 获取当前提示文字,并根据以它作为主键提取出对应面板组件。
- // 注意这儿提示文字没有显示作用,只是用来访问当前组件
- String tip = getTipText();
- Component component = (Component) maps.get(tip);
- if (component != null) {
- // 根据面板的大小乘以缩略比例得到该preferredSize
- return new Dimension(
- (int) (getScaleRatio() * component.getWidth()),
- (int) (getScaleRatio() * component.getHeight()));
- } else {
- return super.getPreferredSize();
- }
- }
- /**
- * 画提示框的图片内容
- */
- @Override
- public void paintComponent(Graphics g) {
- // 同上面方法一样根据提示文本获取当前rollover的面板
- String tip = getTipText();
- Component component = (Component) maps.get(tip);
- if (component instanceof JComponent) {
- // 必须是轻量级组件才能渲染
- JComponent jcomponent = (JComponent) component;
- Graphics2D g2d = (Graphics2D) g;
- AffineTransform _oldAt = g2d.getTransform();
- // 设置缩略比例
- g2d.transform(AffineTransform.getScaleInstance(getScaleRatio(),
- getScaleRatio()));
- //凡是采用
- // Swing轻量级组件渲染的要保证组件不是doubleBuffered
- // 否则会影响画到屏幕上的效果,屏幕往往会花掉。
- ArrayList<JComponent> dbcomponents = new ArrayList<JComponent>();
- // 这儿采用递归的方式将组件树上的所有组件都禁止掉双缓冲
- // 属性,并且将这些组件放在一个数组中,以备恢复用
- updateDoubleBuffered(jcomponent, dbcomponents);
- // 渲染
- jcomponent.paint(g);
- // 恢复双缓冲属性
- resetDoubleBuffered(dbcomponents);
- // 恢复以前的transform
- g2d.setTransform(_oldAt);
- }
- }
- // 这个方法递归遍历以component为根的组件树,将组件树上的所有组件禁止其双缓冲
- private void updateDoubleBuffered(JComponent component,
- ArrayList<JComponent> dbcomponents) {
- if (component.isDoubleBuffered()) {
- dbcomponents.add(component);
- component.setDoubleBuffered(false);
- }
- for (int i = 0; i < component.getComponentCount(); i++) {
- Component c = component.getComponent(i);
- if (c instanceof JComponent) {
- updateDoubleBuffered((JComponent) c, dbcomponents);
- }
- }
- }
- // 根据前面记录的数组恢复双缓冲
- private void resetDoubleBuffered(ArrayList<JComponent> dbcomponents) {
- for (JComponent component : dbcomponents) {
- component.setDoubleBuffered(true);
- }
- }
- }
- /**
- * 获取缩放比例
- *
- * @return
- */
- public double getScaleRatio() {
- return scaleRatio;
- }
- /**
- * 设置缩放比例
- *
- * @param scaleRatio
- */
- public void setScaleRatio(double scaleRatio) {
- this.scaleRatio = scaleRatio;
- }
- }
图中的所略图是在鼠标移动到“高级设置”页签时显示的。为了标明,上面贴了个鼠标截图。