接着昨天的MyJTextField,我们继续为JTextField增强,今天我们为MyJTextField增加一个泡泡提示。先看图片:

当输入第三个字符'a'时,由于昨天我们的MyJTextField做了处理,所以'a'不能被输入,而且弹出泡泡提示你下次不要了哟!
同样的,下一张图片:
为MyJTextField增加泡泡提示功能要做以下几件事情:
1. 泡泡窗口
2. 显示的时候计算位置
这里我使用了Bernhard Pauler的BalloonTip包的BalloonBorder代码,改造了BalloonTip代码,好废话不说,放上完整代码后再解释:
1 /**
2 * @(#)MyJTextField.java 0.1.1 2007-9-8
3 */
4 package ruislan;
5
6 import java.awt.BorderLayout;
7 import java.awt.Color;
8 import java.awt.Component;
9 import java.awt.Container;
10 import java.awt.Font;
11 import java.awt.Point;
12 import java.awt.event.ComponentAdapter;
13 import java.awt.event.ComponentEvent;
14 import java.awt.event.KeyAdapter;
15 import java.awt.event.KeyEvent;
16
17 import javax.swing.Icon;
18 import javax.swing.ImageIcon;
19 import javax.swing.JDialog;
20 import javax.swing.JFrame;
21 import javax.swing.JLabel;
22 import javax.swing.JLayeredPane;
23 import javax.swing.JPanel;
24 import javax.swing.JTextField;
25 import javax.swing.border.EmptyBorder;
26
27 import net.java.balloontip.BalloonBorder;
28
29
30 /**
31 * Custom JTextField.
32 *
33 * @version 0.1.1, 2007-9-9
34 * @author ruislan <a href="mailto:z17520@126.com"/>
35 */
36 public class MyJTextField extends JTextField {
37 private static final Color TIP_COLOR = new Color(255, 255, 225);
38 private int limit = Integer.MAX_VALUE;
39 private boolean numberOnly;
40 private CoolToolTip numberTip;
41 private CoolToolTip limitTip;
42 private ImageIcon tipIcon;
43
44 public MyJTextField() {
45 initComponents();
46 initEventListeners();
47 }
48
49 private void initComponents() {
50 tipIcon = new ImageIcon(MyJTextField.class.getResource("tip.gif"));
51
52 numberTip = new CoolToolTip(this, TIP_COLOR, getColumns(), 10);
53 numberTip.setText("只能输入数字!");
54 numberTip.setIcon(tipIcon);
55 numberTip.setIconTextGap(10);
56
57 limitTip = new CoolToolTip(this, TIP_COLOR, getColumns(), 10);
58 limitTip.setIcon(tipIcon);
59 limitTip.setIconTextGap(10);
60 }
61
62 private void initEventListeners() {
63 addKeyListener(new KeyAdapter() {
64 @Override
65 public void keyTyped(KeyEvent e) {
66 if (getText().length() + 1 > limit) {
67 deleteInputChar(e);
68 limitTip.setVisible(true);
69 return;
70 } else {
71 limitTip.setVisible(false);
72 }
73 if (numberOnly) {
74 char input = e.getKeyChar();
75 if (!Character.isDigit(input)) {
76 numberTip.setVisible(true);
77 deleteInputChar(e);
78 } else {
79 numberTip.setVisible(false);
80 }
81 }
82 }
83
84 private void deleteInputChar(KeyEvent source) {
85 source.setKeyChar((char) KeyEvent.VK_CLEAR);
86 }
87 });
88 }
89
90 public void setMaxTextLength(int limit) {
91 if (limit < 0) {
92 return;
93 }
94 this.limit = limit;
95 limitTip.setText(String.format("超过最大长度 \"%d\"", limit));
96 }
97
98 public int getMaxTextLength() {
99 return limit;
100 }
101
102 public void setNumberOnly(boolean numberOnly) {
103 this.numberOnly = numberOnly;
104 }
105
106 public boolean isNumberOnly() {
107 return this.numberOnly;
108 }
109
110 private class CoolToolTip extends JPanel {
111 private JLabel label = new JLabel();
112 private boolean haveShowPlace;
113
114 private Component attachedComponent;
115
116 public CoolToolTip(Component attachedComponent, Color fillColor,
117 int borderWidth, int offset) {
118 this.attachedComponent = attachedComponent;
119
120 label.setBorder(new EmptyBorder(borderWidth, borderWidth,
121 borderWidth, borderWidth));
122 label.setBackground(fillColor);
123 label.setOpaque(true);
124 label.setFont(new Font("system", 0, 12));
125
126 setOpaque(false);
127 this.setBorder(new BalloonBorder(fillColor, offset));
128 this.setLayout(new BorderLayout());
129 add(label);
130
131 setVisible(false);
132
133 // if the attached component is moved while the balloon tip is
134 // visible, we need to move as well
135 attachedComponent.addComponentListener(new ComponentAdapter() {
136 public void componentMoved(ComponentEvent e) {
137 if (isShowing()) {
138 determineAndSetLocation();
139 }
140 }
141 });
142
143 }
144
145 private void determineAndSetLocation() {
146 Point location = attachedComponent.getLocation();
147 setBounds(location.x, location.y - getPreferredSize().height,
148 getPreferredSize().width, getPreferredSize().height);
149 }
150
151 public void setText(String text) {
152 label.setText(text);
153 }
154
155 public void setIcon(Icon icon) {
156 label.setIcon(icon);
157 }
158
159 public void setIconTextGap(int iconTextGap) {
160 label.setIconTextGap(iconTextGap);
161 }
162
163 public void setVisible(boolean show) {
164 if (show) {
165 determineAndSetLocation();
166 findShowPlace();
167 }
168 super.setVisible(show);
169 }
170
171 private void findShowPlace() {
172 if (haveShowPlace) {
173 return;
174 }
175 // we use the popup layer of the top level container (frame or
176 // dialog) to show the balloon tip
177 // first we need to determine the top level container
178 Container parent = attachedComponent.getParent();
179 JLayeredPane layeredPane;
180 while (true) {
181 if (parent instanceof JFrame) {
182 layeredPane = ((JFrame) parent).getLayeredPane();
183 break;
184 } else if (parent instanceof JDialog) {
185 layeredPane = ((JDialog) parent).getLayeredPane();
186 break;
187 }
188 parent = parent.getParent();
189 }
190 layeredPane.add(this, JLayeredPane.POPUP_LAYER);
191 haveShowPlace = true;
192 }
193 }
194 }
195
测试代码:
1 package ruislan.examples;
2
3 import java.awt.Container;
4 import java.awt.Dimension;
5 import java.awt.GridBagLayout;
6 import java.awt.Toolkit;
7
8 import javax.swing.JFrame;
9
10 import ruislan.MyJTextField;
11
12 public class MyJTextFieldTest {
13
14 public static void main(String[] args) {
15 JFrame frame = new JFrame();
16 Container contentPane = frame.getContentPane();
17 contentPane.setLayout(new GridBagLayout());
18 final MyJTextField textField = new MyJTextField();
19 textField.setNumberOnly(true);
20 textField.setColumns(15);
21 textField.setMaxTextLength(5);
22 contentPane.add(textField);
23
24 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
25 frame.setSize(320, 240);
26 centerWindow(frame);
27 frame.setTitle("MyJTextField");
28 frame.setVisible(true);
29 }
30
31 public static void centerWindow(Container window) {
32 // get the size of the screen
33 Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
34
35 // determine the new location of the window
36 int w = window.getSize().width;
37 int h = window.getSize().height;
38 int x = (dim.width - w) / 2;
39 int y = (dim.height - h) / 2;
40
41 // move the window
42 window.setLocation(x, y);
43 }
44 }
45
就以上代码解释一下
1. 为什么CoolToolTip不使用JWindow而使用JPanel?
不错,JWindow可以放置在屏幕的任意位置,而且也是我的首选,但是JWindow是调用native代码来绘图,没有提供可以改变形状的API(我们需要泡泡嘛),所以使用了JPanel,那么这就具有了局限性,第一个就是我们在显示泡泡的时候必须找到最高级的组件来显示,以便覆盖到其下的所有子组件,第二个就是显示的区域受到了最高级组件的局限,例如JFrame上只有一个MyJTextField,而JFrame.pack了之后大小刚好是MyJTextField的大小,这样就看不到CoolToolTip了。
2. 为什么CoolToolTip在setVisible的时候才找一个要show的地方?
这个操作在Bernhard Pauler的BalloonTip代码中是由构造方法来完成的,但是这里就会出现一个问题,对,NullPointer,因为Swing不像SWT,是在component.add(child)方法调用的时候才设置child的Parent,而非在构造child的时候将component放入,所以我们只有懒加载来避免这个情况发生。说到懒加载我发表一下一个题外意见,在下认为懒加载在swing中不要用得太过频繁,用户体验是第一位的,在下宁愿在启动程序的时候看进度条也不愿在启动某个视图的时候突然卡死一小会。
3. 还有什么没有做的?
例如当输入Esc、Backspace、Enter等等按键的时候CoolToolTip也会跳出来show一把,我们必须过滤掉它们!
今天就到这里吧,下一次我们让MyJTextField更酷一点,我们为它加上Beep声音,当我们输入了非法字符的时候就beep、beep的叫唤,没有声音也没有泡泡提示的话有些人很可能是以为自己的键盘坏掉了。
本人就对QQ的登录窗口很有意见,每次我开机都是Eclipse和QQ一起打开的,一般操作都是先登录QQ,但是有一天我QQ后打开的,我正在happy coding,突然输入的字符全乱了套,我以为是输入的问题,结果是英文也,然后我认为可能是eclipse的问题(因为有的时候eclipse的代码编辑区就无端不让操作了),换成了记事本,结果也是这个情况,然后就有点慌以为键盘坏掉了,毕竟换一个笔记本键盘很贵的说(在找问题其间多次路过QQ的登录窗口,我还嫌其碍事最小化了),维修部电话打过之后他们叫我拿过去,我就准备关电脑,也不知道怎的,在我关闭了一系列的程序包括QQ登录窗口之后,我突然又想最后确定一下,当然结果很明显又好了,这个时候我就像修复程序BUG一样,找到恢复正常前的最后一个动作,重复操作直到又不正常为止,终于发现是QQ登录框的问题,气煞我也,差点就花钱换一个键盘了...
posted on 2007-09-09 13:32
ruislan 阅读(5174)
评论(2) 编辑 收藏