在自定义 JToolTip 里面显示组件
我们经常可以看到很多 Windows 程序显示出各种各样好看的工具栏提示, 不是默认的那种只有文字的提示... 但是 Java Swing 默认的那个, 似乎只有那个一行文字, well, how can we change it? 请参阅: JFC -- Chapter 24 - Building a Custom Component http://mail.phys-iasi.ro/Library/Computing/jfc_unleashed/ch24.htm
笔者基于文中的介绍写出了自己的解决方案. First, 写一个自定义的 ToolTipUI 来显示 JToolTip, 实现下列逻辑:
1. 如果没有在 JToolTip 中包含子组件(还记得嘛, 所有 JComponent 都是容器), 那么按照普通的模式显示 JToolTip, 稍微改进了一点, 就是可以显示多行的文字, 例如 "a\nb" 这样的文字就被显示为两行, 而不是默认的 JToolTip 显示的单行文本.
2. 如果包含了子组件, 就忽略设置的提示文本, 转而显示里面包含的组件, 这样就实现了在 JToolTip 中显示组件的功能. 源代码如下:
import java.awt.*;
import java.util.StringTokenizer;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicToolTipUI;
/**
* The ComponentToolTipUI class may be registered with the UIManager
* to replace the ToolTipUI for JToolTip instances. When used, it
* divides the ToolTip into multiple lines, if has child components,
* all child components will be displayed instead of only some tooltip
* text. Each line is divided by
* the '\ n' character.
* <p>
* @author Mike Foley
**/
public class ComponentToolTipUI extends BasicToolTipUI
{
/**
* The single shared UI instance.
**/
static ComponentToolTipUI sharedInstance = new ComponentToolTipUI();
/**
* The margin around the text.
**/
static final int MARGIN = 2;
/**
* The character to use in the StringTokenizer to
* separate lines in the ToolTip. This could be the
* system property end of line character.
**/
static final String lineSeparator = "\n";
/**
* MultiLineToolTipUI, constructor.
* <p>
* Have the constructor be protected so we can be subclassed,
* but not created by client classes.
**/
protected ComponentToolTipUI()
{
super();
}
/**
* Create the UI component for the given component.
* The same UI can be shared for all components, so
* return our shared instance.
* <p>
* @param c The component to create the UI for.
* @return Our shared UI component instance.
**/
public static ComponentUI createUI(JComponent c)
{
return sharedInstance;
}
/**
* Paint the ToolTip. Use the current font and colors
* set for the given component.
* <p>
* @param g The graphics to paint with.
* @param c The component to paint.
**/
public void paint(Graphics g, JComponent c)
{
// Paints each of the components in this container.
if(c.getComponentCount() > 0) {
c.paintComponents(g);
return;
}
// If no components, then paint a muiltiple line tooltip
//
// Determine the size for each row.
//
Font font = c.getFont();
FontMetrics fontMetrics = c.getFontMetrics(font);
int fontHeight = fontMetrics.getHeight();
int fontAscent = fontMetrics.getAscent();
//
// Paint the background in the tip color.
//
g.setColor(c.getBackground());
Dimension size = c.getSize();
g.fillRect(0, 0, size.width, size.height);
//
// Paint each line in the tip using the
// foreground color. Use a StringTokenizer
// to parse the ToolTip. Each line is left
// justified, and the y coordinate is updated
// through the loop.
//
g.setColor(c.getForeground());
int y = 2+fontAscent;
String tipText = ((JToolTip)c).getTipText();
StringTokenizer tokenizer = new StringTokenizer(tipText, lineSeparator);
int numberOfLines = tokenizer.countTokens();
for (int i = 0; i < numberOfLines; i++)
{
g.drawString(tokenizer.nextToken(), MARGIN, y);
y += fontHeight;
}
}
// paint
/**
* The preferred size for the ToolTip is the width of
* the longest row in the tip, and the height of a
* single row times the number of rows in the tip.
*
* @param c The component whose size is needed.
* @return The preferred size for the component.
**/
public Dimension getPreferredSize(JComponent c)
{
// If has children components
if(c.getComponentCount() > 0) {
return c.getLayout().preferredLayoutSize(c);
}
//
// Determine the size for each row.
//
Font font = c.getFont();
FontMetrics fontMetrics = c.getFontMetrics(font);
int fontHeight = fontMetrics.getHeight();
//
// Get the tip text string.
//
String tipText = ((JToolTip)c).getTipText();
//
// Empty tip, use a default size.
//
if (tipText == null)
return new Dimension(2 * MARGIN, 2 * MARGIN);
//
// Create a StringTokenizer to parse the ToolTip.
//
StringTokenizer tokenizer = new StringTokenizer(tipText, lineSeparator);
int numberOfLines = tokenizer.countTokens();
//
// Height is number of lines times height of a single line.
//
int height = numberOfLines * fontHeight;
//
// Width is width of longest single line.
//
int width = 0;
for (int i = 0; i < numberOfLines; i++)
{
int thisWidth = fontMetrics.stringWidth(tokenizer.nextToken());
width = Math.max(width, thisWidth);
}
//
// Add the margin to the size, and return.
//
return (new Dimension(width + 2 * MARGIN, height + 2 * MARGIN));
}
// getPreferredSize
}
// ComponentToolTipUI
接着, 我们定义一个包含了其它组件的 JToolTip:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MyTooltip extends javax.swing.JToolTip
{
JButton jButton1 = new JButton();
FlowLayout flowLayout1 = new FlowLayout();
JTextField jTextField1 = new JTextField();
public MyTooltip()
{
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}
public void setTipText(String tipText) {
super.setTipText(tipText);
// button.setText(tipText);
}
private void jbInit() throws Exception {
// jLabel1.setMaximumSize(new Dimension(400, 300));
// jLabel1.setMinimumSize(new Dimension(400, 300));
// jLabel1.setPreferredSize(new Dimension(400, 300));
jButton1.setText("This is a button included in the tool tip.");
this.setLayout(flowLayout1);
this.setOpaque(true);
jTextField1.setText("jTextField1");
this.add(jButton1, null);
this.add(jTextField1, null);
}
}
最后, 我们编写一个测试类来测试这个 JToolTip:
import java.awt.event.*;
import javax.swing.*;
public class MyButton extends javax.swing.JButton
{
public MyButton()
{
}
public JToolTip createToolTip() {
JToolTip tip = new MyTooltip();
tip.setComponent(this);
tip.setTipText(getToolTipText());
return tip;
}
public static void main(String[] args)
{
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
ex.printStackTrace();
}
try
{
String multiLineToolTipUIClassName =
"ComponentToolTipUI";
// System.out.println(multiLineToolTipUIClassName);
UIManager.put( "ToolTipUI", multiLineToolTipUIClassName );
UIManager.put( multiLineToolTipUIClassName,
Class.forName( multiLineToolTipUIClassName ) );
}
catch( ClassNotFoundException cnfe )
{
System.err.println( "MultiLine ToolTip UI class not found" );
System.err.println( cnfe );
}
final JFrame frame = new JFrame("Test JToolTip");
frame.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(WindowEvent e) {
frame.dispose();
System.exit(0);
}
});
MyButton button = new MyButton();
button.setText("这个按钮显示一个包含组件的工具提示");
button.setToolTipText("提示");
frame.getContentPane().add(button);
JButton buttonPlain = new JButton("这是一个包含多行文本提示的普通按钮");
buttonPlain.setToolTipText("Line 1\nLine 2\n");
frame.getContentPane().add(java.awt.BorderLayout.SOUTH, buttonPlain);
// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.show();
}
}
编译并运行最后一个类: MyButton, 就可以看到包含了可以交互的按钮和文本框, 以及一个显示两行文本的工具栏提示。