接手项目时对方所提的需求,其实用javaweb来实现的话基本没有难度,但是要用Swing去实现这个功能,还是一件蛮棘手的事情,中间也遇到了好多衍生出来的问题,利用这篇博客做一个记录。
先上一张最后的效果图:
可以根据输入框中文字的改变生成不同的提示,并且可以用键盘进行选择。
实现这个功能所用到的基本组件是JTextFiled和JLIst。下面介绍一下遇到的问题与解决方案
问题一:如何让JLIst浮在整个JFrame的最上层
实际的开发过程得出结论:JLIst必须是整个Frame中最上层的组件,这样当它出现时才可以覆盖掉其他的组件而不是其他组件产生位移为其腾置空间。所用的方法是JLayeredPane。
JLayeredPane 为 JFC/Swing 容器添加了深度,允许组件在需要时互相重叠。Integer 对象指定容器中每个组件的深度,其中编号较高的组件位于其他组件之上。
DEFAULT_LAYER:Z_order的Layer数值为0,以整数对象Integer(0)来表示,一般我们加入的组件若没有标记是第几层,默认值就 把组件放在此Default Layer中。
PALETTE_LAYER:Z_order的Layer数值为100,以整数对象Integer(100)来表示,位于Default Layer之上,一般用于放置可移动的 工具栏(Floatable Toolbar).
MODAL_LAYER:Z_order的Layer数值为200,以整数对象Integer(200)来表示,位于PALETTE_LAYER之上,一般用于放置对话框 (Dialog Box).
POPUP_LAYER:Z_order的Layer数值为300,以整数对象Integer(300)来表示,位于MODAL_LAYER之上,一般用于快捷菜单(Poup Menu)与工具栏提示(Tool Tips)中.
DRAG_LAYER:Z_order的Layer数值为400,以整数对象Integer(400)来表示,位于POPUP_LAYER之上,一般用于拖曳组件使其在不同区域上.
问题二:如何准确地定位到所选择的JTextFiled
这个问题的产生源于po主的布局中采用流式布局,因此JTextFiled的坐标并不是相对固定的。但是在放置JList的时候setBounds函数是相对于父容器的坐标。第一张效果图中选中的JTextFiled放在多个嵌套的JPanel中,需要获取到此JTextFiled准确坐标点的方法如下:
private Point caculateTipBoxPoint() {
int x = 0;
int y = 0;
Container parent = textField;
while (parent != textField.getRootPane()) {
x += parent.getX();
y += parent.getY();
parent = parent.getParent();
}
y += textField.getHeight();
Point point = new Point(x, y);
return point;
}
每次用getParent方法获取当前容器的父容器,不断更新选中JTextFiled的偏移量。getRootPane方法获取到最根部的容器(JFrame),这样就可以得到Point。
问题三:如何监听JTextFiled中的文字变化
这个问题比较容易解决,对该JTextFiled加上DocumentListener即可,这样就可以监听其中的文字变化了。有一点比较奇怪的是DocumentListener中需要实现insertUpdate、removeUpdate、changedUpdate三个方法,但是只要对前两个方法进行操作就足够了。
问题四:如何实现键盘选择
在JList中设置KeyListener,并实现其中的keyPressed方法。因为在实际的操作中有往上、往下以及enter选中三种状态,所以判断KeyEvent中键盘VK_DOWN、VK_UP、VK_ENTER三个状态。
为了更好的复用上面的几个功能,po主自定义了一个类TipBox来进行封装。代码如下:
public class TipBox extends JList<String> { // 继承JList
private JTextField textField; // 选中的JTextField
private CondicateStrategy strategy;
public TipBox(JTextField textField, CondicateStrategy strategy) {
this.strategy = strategy;
this.textField = textField;
this.textField.getDocument().addDocumentListener(new MyDocumentListener());
this.textField.addKeyListener(new MyKeyListener());
Point point = caculateTipBoxPoint();
this.setBounds(new Rectangle(point, new Dimension(textField.getWidth(), 200)));
this.setVisible(false);
}
private Point caculateTipBoxPoint() {
int x = 0;
int y = 0;
Container parent = textField;
while (parent != textField.getRootPane()) {
x += parent.getX();
y += parent.getY();
parent = parent.getParent();
}
y += textField.getHeight();
Point point = new Point(x, y);
return point;
}
private void textChanged() {
if (this.textField.isEnabled()) {
String prefix = this.textField.getText();
TipBox.this.setVisible(false);
if (!prefix.isEmpty()) {
List<String> condicate = this.strategy.matchPrefix(prefix);
if (!condicate.isEmpty()) {
TipBox.this.setListData(new Vector<>(condicate));
TipBox.this.setVisible(true);
}
}
}
}
class MyKeyListener implements KeyListener {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int keycode = e.getKeyCode();
if (keycode == e.VK_DOWN) {
int index = TipBox.this.getSelectedIndex();
if (index < TipBox.this.getLastVisibleIndex()) {
TipBox.this.setSelectedIndex(++index);
}
} else if (keycode == e.VK_UP) {
int index = TipBox.this.getSelectedIndex();
if (index > TipBox.this.getFirstVisibleIndex()) {
TipBox.this.setSelectedIndex(--index);
}
} else if (keycode == e.VK_ENTER) {
String value = TipBox.this.getSelectedValue();
if (value != null) {
textField.setText(value);
TipBox.this.setVisible(false);
}
}
}
@Override
public void keyReleased(KeyEvent e) {
}
}
class MyDocumentListener implements DocumentListener {
@Override
public void insertUpdate(DocumentEvent e) {
TipBox.this.textChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
TipBox.this.textChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
}
}
// 接口,实现根据前缀在数据库进行查找
public interface CondicateStrategy {
List<String> matchPrefix(String prefix);
}
}
这里还有一个策略的问题,就是在TipBox类中定义了一个接口CondicateStrategy,这样在实际的编程过程中,就不用对我的封装类进行判断修改,而只要实现该接口即可。
可参考:
PS:这里只提供了一个解决的思路,因为商业问题不能提供源代码,而且此功能只是整个程序的一部分,还需要各位根据自己的实际需求灵活变动,与主程序相互配合。不过Po主觉得现在应该很少在使用Swing的人了吧(:-D)