`
univasity
  • 浏览: 809947 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

[Swing]实现一个文本自动完成工具

阅读更多

 

//-------------------------------------------------------------------------------- 2010.10.17

重新整理了代码,发觉弄得太复杂了,还是按需要再独自实现吧,主要是实现思路清晰就行... 下面更新了代码。

 

//--------------------------------------------------------------------------------

最近学习Swing的开发,要用到一个像浏览器搜索栏那样的带有自动完成功能的文本框,但发觉Swing中并没有现成的实现,网上爬文然后动手写了一个,分享下。

 

网上主要介绍的实现方式有两种:

1. JComboBox

2. JTextField + PopupMenu + JList

 

第二种实现效果更贴切些。

 

我的实现思路是方便使用,于是封装成一个辅助工具,只要传一个JTextComponent给他,并设置数据就能用了。

 

一个使用的样例:

JTextField textField = new JTextField();
// 创建对象并绑定一个JTextComponent
AutoCompleteExtender autoCompleteExtender = new AutoCompleteExtender(textField, data, null);
// 设置参数
autoCompleteExtender.setMatchDataAsync(true);
autoCompleteExtender.setSizeFitComponent();
autoCompleteExtender.setMaxVisibleRows(6);
autoCompleteExtender.setCommitListener(new CommitListener() {
    // 值最终被选中时会触发该函数
    public void commit(String value) {
        System.out.println("commit:" + value);
    }
});
 

1.

构造函数AutoCompleteExtender(JTextComponent , DataProvider , DataMatchHelper ),其中JTextComponent为需要绑定的组件,DataProvider是我封装的一个用于提供数据的接口,DataMatchHelper是一个定义如何进行匹配的接口。

 

2.  

其中setMatchDataAsync(true)是设置异步匹配数据,就是匹配时先返回界面再新建县城进行循环匹配操作,为了避免数据过多时造成延迟。但经测试,即使设为false,10000的数据量也并没发现明显延迟....

 

3.

setSizeFitComponent(true),使弹出列表自动切合文本组件的大小。当然也提供了setWidth的函数。

 

4.

setMaxVisibleRows(6)为设置一次最多可见的个数为6个,和JList.setVisibleRowCount()一致。

 

5.

CommitListener是我设置的一个用于监听确定事件的监听器,目前按firefox的操作规则是鼠标点中或回车键。

 

基本上目前实现的效果是模仿firefox的搜索栏的,能跟随光标和显示提示。

 

最终显示效果:

 

最后放上代码:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package swingstudy.autocomplete;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.text.JTextComponent;
import swingstudy.autocomplete.AutoCompleteExtender.DataProvider.DataChangeListener;

/**
 *
 * @author Univasity
 */
public class AutoCompleteExtender {

    public static final int DefaultMaxVisibleRows = 5;
    /**
     * 绑定的文本组件
     */
    private JTextComponent textComponent;
    /**
     * 用于显示结果列表的弹出菜单组件
     */
    private JPopupMenu popupMenu;
    /**
     * 用于展示匹配结果的列表组件
     */
    private JList resultList;
    /**
     * 为列表提供滚动支持的组件
     */
    private JScrollPane scrollPane;
    /**
     * 数据提供器
     */
    private DataProvider dataProvider;
    /**
     * 标记匹配数据是否发生改变
     */
    private boolean matchDataChanged;
    /**
     * 记录当前被匹配的文本
     */
    private String matchedText;
    /**
     * 原始的编辑文本
     */
    private String originalEditText;
    /**
     * 数据匹配器
     */
    private DataMatchHelper dataMatchHelper;
    /**
     * 确定监听器,默认为按下'回车键'或鼠标点选会被触发
     */
    private CommitListener commitListener;
    /**
     * 默认的数据改变监听器
     */
    private final DataChangeListener DefaultDataChangeListener = new DataChangeListener() {

        public void dataChanged(int action, Object value) {
            notifyDataChanged();
        }
    };
    /**
     * 线程池
     */
    private BlockingQueue<Runnable> queue; // 用于储存任务的队列
    private ThreadPoolExecutor executor; // 线程池对象
    private boolean matchDataAsync = false; // 是否异步进行匹配操作
    private int resultListWidth;
    private boolean widthWithScrollBar;
    private boolean autoSizeToFit;

    /**
     * 指定绑定的对象,数据提供器和匹配器来构建一个对象
     * @param textComponent 不能为null
     * @param dataProvider 不能为null
     * @param dataMatchHelper 如果为null,则使用默认的匹配器
     */
    public AutoCompleteExtender(JTextComponent textComponent, DataProvider dataProvider, DataMatchHelper dataMatchHelper) {
        if (textComponent == null) {
            /**
             * 确保绑定的插件不为null
             */
            throw new IllegalArgumentException("textComponent不能为null!");
        }
        if (dataProvider == null) {
            /**
             * 确保数据提供器不为null
             */
            throw new IllegalArgumentException("dataProvider不能为null!");
        }
        this.textComponent = textComponent;
        this.dataProvider = dataProvider;
        this.dataProvider.setDataChangeListener(DefaultDataChangeListener);
        if (dataMatchHelper == null) {
            this.dataMatchHelper = new DefaultDataMatchHelper();
        } else {
            this.dataMatchHelper = dataMatchHelper;
        }
        /**
         * 初始化数据
         */
        resetAll();
    }

    /**
     * 指定绑定的对象,匹配数据和匹配器来构建一个对象
     * @param textComponent 不能为null
     * @param data 初始的匹配数据
     * @param dataMatchHelper 如果为null,则使用默认的匹配器
     */
    public AutoCompleteExtender(JTextComponent textComponent, Object[] data, DataMatchHelper dataMatchHelper) {
        if (textComponent == null) {
            /**
             * 确保绑定的插件不为null
             */
            throw new IllegalArgumentException("textComponent不能为null!");
        }
        this.textComponent = textComponent;
        this.dataProvider = new DefaultDataProvider();
        if (data != null) {
            for (Object value : data) {
                this.dataProvider.appendData(value);
            }
        }
        this.dataProvider.setDataChangeListener(DefaultDataChangeListener);
        if (dataMatchHelper == null) {
            this.dataMatchHelper = new DefaultDataMatchHelper();
        } else {
            this.dataMatchHelper = dataMatchHelper;
        }
        /**
         * 初始化数据
         */
        resetAll();
    }

    public DataProvider getDataProvider() {
        return dataProvider;
    }

    /**
     * 设置为默认配置,原有数据将被清空
     */
    public synchronized void resetAll() {
        initTextComponent();
        initResultList();
        initValues();
        setFocusOnTextComponent();
        updateUI();
    }

    /**
     * 刷新当前UI
     */
    public synchronized void updateUI() {
        popupMenu.pack();
        popupMenu.updateUI();
    }

    /**
     * 清空匹配结果
     */
    public synchronized void clearMatchResult() {
        collapse();
        if (queue != null) {
            queue.clear();
        }
        ((DefaultListModel) resultList.getModel()).removeAllElements();
    }

    /**
     * 标记匹配数据改变了
     */
    private void notifyDataChanged() {
        matchDataChanged = true;
    }

    public void setCommitListener(CommitListener commitListener) {
        this.commitListener = commitListener;
    }

    /**
     * 获取当前被匹配的文本
     * @return
     */
    public synchronized String getMatchText() {
        return matchedText;
    }

    /**
     * 获取当前匹配结果
     * @return
     */
    public synchronized Object[] getMatchResult() {
        return ((DefaultListModel) resultList.getModel()).toArray();
    }

    /**
     * 获取当前选中的值
     * @return
     */
    public synchronized Object getSelectedValue() {
        return resultList.getSelectedValue();
    }

    /**
     * 确定指定的文本为最终选定
     * @param text
     */
    public synchronized void commitText(String text) {
        originalEditText = text;
        textComponent.setText(text);
        if (commitListener != null) {
            commitListener.commit(text);
        }
    }

    /**
     * 获取当前选中项的索引值
     * @return
     */
    public synchronized int getSelectedIndex() {
        return resultList.getSelectedIndex();
    }

    /**
     * 选中指定的索引值
     * @param index
     */
    public synchronized void setSelectedIndex(int index) {
        if (index < 0 || index >= getResultCount()) {
            return;
        }
        resultList.setSelectedIndex(index);
        // 使选中项处于可视范围内
        resultList.ensureIndexIsVisible(index);
    }

    /**
     * 打开结果列表(如果未成匹配,则自动执行匹配处理,如果无有效结果则不会被展开)(焦点会转移到列表)
     * @return
     */
    public synchronized boolean expand() {
        if (!hasMatched()) {
            if (doMatch()) {
                // 展开列表
                updateExpandListUI();
                popupMenu.show(textComponent, 0, textComponent.getHeight());
            }
        } else if (getResultCount() > 0) {
            popupMenu.setVisible(true);
        }
        return popupMenu.isVisible();
    }

    /**
     * 关闭结果列表(数据不会被清空,再次打开时直接重新显示)
     */
    public synchronized void collapse() {
        removeSelectionInterval();
        popupMenu.setVisible(false);
    }

    /**
     * 判断结果列表是否被打开
     * @return
     */
    public synchronized boolean isExpanded() {
        return popupMenu.isVisible();
    }

    /**
     * 获取当前结果列表的条目数
     * @return
     */
    public synchronized int getResultCount() {
        return ((DefaultListModel) resultList.getModel()).getSize();
    }

    /**
     * 获取一次最多的显示行数(超出的部分需通过拖动滚动条显示)
     * @param rows
     */
    public synchronized void setMaxVisibleRows(int rows) {
        resultList.setVisibleRowCount(rows);
    }

    /**
     * 把焦点设置到文本编辑框上
     */
    public synchronized void setFocusOnTextComponent() {
        textComponent.requestFocus();
    }

    /**
     * 把焦点设置到结果列表上
     */
    public synchronized void setFocusOnExpandList() {
        resultList.requestFocus();
    }

    /**
     * 判断焦点是否在文本编辑框上
     * @return
     */
    public synchronized boolean isFocusOnTextComponent() {
        return textComponent.isFocusOwner();
    }

    /**
     * 判断焦点是否在结果列表上
     * @return
     */
    public synchronized boolean isFocusOnExpandList() {
        return resultList.isFocusOwner();
    }

    /**
     * 取消当前列表上的选中状态(使selectedIndex==-1)
     */
    public synchronized void removeSelectionInterval() {
        final int selectedIndex = resultList.getSelectedIndex();
        resultList.removeSelectionInterval(selectedIndex, selectedIndex);
    }

    /**
     * 判断是否已经匹配过了(匹配前应进行检测,避免重复匹配操作)
     * @return
     */
    public synchronized boolean hasMatched() {
        if (matchDataChanged) {
            return false;
        }
        if (matchedText == null || matchedText.length() < 1) {
            return false;
        }
        String text = textComponent.getText();
        if (text == null || !text.equals(matchedText)) {
            return false;
        }
        return true;
    }

    /**
     * 执行匹配操作
     * @return
     */
    public synchronized boolean doMatch() {
        // 清空原有结果
        clearMatchResult();

        matchedText = textComponent.getText();
        originalEditText = matchedText;
        String keyWord = matchedText;
        if (keyWord != null) {
            keyWord = matchedText.trim();
        }

        if (dataMatchHelper != null) {
            if (!dataMatchHelper.isMatchTextAccept(keyWord)) {
                return false;
            }
        }

        if (matchDataAsync) {
            doMatchAsync(keyWord);
            matchDataChanged = false;
            return true;
        } else {
            doMatchSync(keyWord);
            matchDataChanged = false;
            return getResultCount() > 0;
        }
    }

    /**
     * 设置异步匹配数据
     * @param async
     */
    public synchronized void setMatchDataAsync(boolean async) {
        if (this.matchDataAsync != async) {
            this.matchDataAsync = async;
            if (async) {
                queue = new LinkedBlockingQueue<Runnable>();
                // 创建一个最多运行2个任务,支持10个任务, 允许延时20秒的线程池
                executor = new ThreadPoolExecutor(2, 10, 20, TimeUnit.SECONDS, queue);
            } else {
                if (queue != null) {
                    queue.clear();
                }
                if (executor != null) {
                    executor.shutdown();
                }
                queue = null;
                executor = null;
            }
        }
    }

    /**
     * 判断当前是否异步匹配
     * @return
     */
    public synchronized boolean isMatchDataAsync() {
        return this.matchDataAsync;
    }

    /**
     * 在结果列表上显示过于选中项的提示条
     * @param asNeed 是否根据需要显示(true->文本长度超出显示范围时才显示)
     */
    public synchronized void showToolTipsWithSelectedValue(boolean asNeed) {
        Object value = resultList.getSelectedValue();
        if (value != null) {
            // 显示提示
            String txt = value.toString();
            if (txt != null) {
                if (asNeed) {
                    // 超出范围才显示提示
                    int txtW = SwingUtilities.computeStringWidth(resultList.getFontMetrics(resultList.getFont()), txt);
                    if (txtW >= resultList.getFixedCellWidth()) {
                        resultList.setToolTipText(txt);
                        return;
                    }
                } else {
                    resultList.setToolTipText(txt);
                    return;
                }
            }
        }
        resultList.setToolTipText(null);
    }

    /**
     * 在结果列表上显示指定的文本作为提示
     * @param text
     */
    public void showToolTips(String text) {
        if (text != null) {
            resultList.setToolTipText(text);
        } else {
            resultList.setToolTipText(null);
        }
    }

    /**
     * 获取一次最多可见行数
     * @return
     */
    public synchronized int getMaxVisibleRows() {
        return resultList.getVisibleRowCount();
    }

    /**
     * 获取结果列表单元项的宽度
     * @return
     */
    public synchronized int getListCellWidth() {
        return resultList.getFixedCellWidth();
    }

    /**
     * 获取结果列表单元项的高度
     * @return
     */
    public synchronized int getListCellHeight() {
        return resultList.getFixedCellHeight();
    }

    public synchronized void setListCellSize(int cellWidth, int cellHeight) {
        resultList.setFixedCellWidth(cellWidth);
        resultList.setFixedCellHeight(cellHeight);
        autoSizeToFit = false;
        updateExpandListUI();
    }

    public synchronized void setListWidth(int width, boolean withScrollBar) {
        this.resultListWidth = width;
        this.widthWithScrollBar = withScrollBar;
        autoSizeToFit = false;
        updateExpandListUI();
    }

    /**
     * 使大小贴合组件
     */
    public synchronized void setSizeFitComponent() {
        autoSizeToFit = true;
        updateExpandListUI();
    }

    /**
     * 指定点是否在文本框范围内
     * @param p
     * @return
     */
    public synchronized boolean isTextFieldContains(Point p) {
        if (p == null) {
            return false;
        }
        return textComponent.contains(p);
    }

    /**
     * 指定点是否在结果列表范围内
     * @param p
     * @return
     */
    public synchronized boolean isExpandListContains(Point p) {
        if (p == null) {
            return false;
        }
        return resultList.contains(p);
    }

    private synchronized void initTextComponent() {
        textComponent.setVisible(true);
        textComponent.setEnabled(true);
        textComponent.setEditable(true);
        // 必须先删除再添加,否则会重复....
        textComponent.removeKeyListener(DefaultTextFieldKeyAdapter);
        textComponent.addKeyListener(DefaultTextFieldKeyAdapter);
    }

    private synchronized void initResultList() {
        /**
         * list
         */
        if (resultList != null) {
            resultList.removeAll();
        } else {
            resultList = new JList(new DefaultListModel());
            resultList.addMouseListener(DefaultResultListMouseAdapter);
            resultList.addMouseMotionListener(DefaultResultListMouseMotionAdapter);
        }
        resultList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        resultList.setVisibleRowCount(DefaultMaxVisibleRows);
        // 允许提示框
        ToolTipManager.sharedInstance().registerComponent(resultList);

        /**
         * scroll pane
         */
        if (scrollPane == null) {
            scrollPane = new JScrollPane(resultList);
        }
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        /**
         * popup menu
         */
        if (popupMenu == null) {
            popupMenu = new JPopupMenu();
        }
        popupMenu.add(scrollPane);
        popupMenu.setVisible(false);
        popupMenu.setFocusable(false);
        popupMenu.setBorder(BorderFactory.createEmptyBorder()); // 去掉边框
    }

    private synchronized void initValues() {
        setCommitListener(null);

        matchedText = null;
        matchDataChanged = true;
        this.matchDataAsync = false;
        originalEditText = textComponent.getText();
    }

    /**
     * 根据给定的值执行匹配操作(该操作为异步的)
     * @param content
     * @return
     */
    private synchronized void doMatchAsync(String content) {
        final String matchText = content;

        if (queue != null) {
            queue.clear();
        }

        executor.execute(new Runnable() {

            public void run() {
                /**
                 * 进行匹配
                 */
                doMatchInner(matchText);
                /**
                 * 如果无匹配项,关闭当前显示
                 */
                if (getResultCount() > 0) {
                    updateExpandListUI();
                } else {
                    collapse();
                }
            }
        });
    }

    /**
     * 根据给定的值执行匹配操作(该操作为同步的)
     * @param content
     * @return
     */
    private synchronized void doMatchSync(String content) {
        /**
         * 进行匹配
         */
        doMatchInner(content);
    }

    /**
     * 处理匹配(内部调用)
     * @param matchText
     */
    private void doMatchInner(String matchText) {
        if (dataProvider != null) {
            DefaultListModel listModel = (DefaultListModel) resultList.getModel();
            for (Object value : dataProvider.toArray()) {
                if (dataMatchHelper != null) {
                    if (dataMatchHelper.isDataMatched(matchText, value)) {
                        listModel.addElement(value);
                    }
                } else {
                    // 直接添加
                    listModel.addElement(value);
                }
            }
        }
    }

    /**
     * 设置当前选项为最终选定值
     */
    private void commitTextBySelectedValue() {
        Object value = getSelectedValue();
        if (value != null) {
            commitText(value.toString());
        }
        collapse();
    }

    /**
     * 转移焦点到文本编辑框,并关闭结果列表
     */
    private void changeFocusToTextField() {
        // 取消选择
        removeSelectionInterval();
        // 转移焦点到文本框
        setFocusOnTextComponent();
        // 设置为原本编辑的文本值
        textComponent.setText(originalEditText);
    }

    /**
     * 设置当前选中项的值到文本框
     */
    private void showCurrentSelectedValue() {
        Object value = getSelectedValue();
        if (value != null) {
            textComponent.setText(value.toString());
        }
    }

    /**
     * 刷新结果列表的显示(焦点会转移到列表)
     */
    private synchronized void updateExpandListUI() {
        DefaultListModel listModel = (DefaultListModel) resultList.getModel();
        int dataSize = listModel.getSize();

        int preferredWidth = 0;
        if (autoSizeToFit) {
            /**
             * 自动使大小贴合组件
             */
            resultList.setFixedCellWidth(textComponent.getWidth());
            resultList.setFixedCellHeight(textComponent.getHeight());
            preferredWidth = textComponent.getWidth();
            if (dataSize > resultList.getVisibleRowCount()) {
                preferredWidth += scrollPane.getVerticalScrollBar().getPreferredSize().width;
            }
        } else {
            /**
             * 使用自定义的大小
             */
            preferredWidth = resultListWidth;
            if (dataSize > resultList.getVisibleRowCount()) {
                if (!widthWithScrollBar) {
                    preferredWidth += scrollPane.getVerticalScrollBar().getPreferredSize().width;
                }
            }
        }

        int preferredHeight = Math.min(resultList.getVisibleRowCount(), dataSize) * resultList.getFixedCellHeight() + 3; // 多预留一些空间,这个值可自己调整不是很准的

        scrollPane.setPreferredSize(new Dimension(preferredWidth, preferredHeight));
        resultList.updateUI();
        popupMenu.pack();
    }
    /**
     * 默认提供的结果列表上鼠标运动事件处理器
     */
    private MouseMotionAdapter DefaultResultListMouseMotionAdapter = new MouseMotionAdapter() {

        @Override
        public void mouseMoved(MouseEvent e) {
            /**
             * 该操作结果是:
             * 选中鼠标所在选项,并显示提示
             */
            Point p = e.getPoint();
            if (isExpandListContains(p)) {
                /**
                 * 鼠标在列表区域内移动时
                 */
                int index = p.y / getListCellHeight();
                // 光标跟随
                setSelectedIndex(index);
                // 文本超长时显示提示
                showToolTipsWithSelectedValue(true);
                // 焦点回归到文本编辑框
                setFocusOnTextComponent();
            }
        }
    };
    /**
     * 默认提供的结果列表上鼠标按键事件处理器
     */
    private final MouseAdapter DefaultResultListMouseAdapter = new MouseAdapter() {

        @Override
        public void mouseClicked(MouseEvent e) {
            /**
             * 该操作结果是:
             * 设置编辑框文字为选中项,关闭结果列表,焦点回到编辑框,同时触发commit监听器
             */
            Point p = e.getPoint();
            if (isExpandListContains(p)) {
                /**
                 * 鼠标点击列表项时
                 */
                int index = p.y / getListCellHeight();
                // 选中该项
                setSelectedIndex(index);
                // 
                if (getSelectedIndex() == index) {
                    commitTextBySelectedValue();
                }
                // 焦点回归到文本编辑框
                setFocusOnTextComponent();
            }
        }
    };
    /**
     * 默认提供的文本编辑框上键盘按键事件处理器
     */
    private final KeyAdapter DefaultTextFieldKeyAdapter = new KeyAdapter() {

        @Override
        public void keyPressed(KeyEvent e) {
            /**
             * 只对处于当前焦点时作处理
             */
            if (!e.getComponent().isFocusOwner()) {
                return;
            }

            switch (e.getKeyCode()) {

                case KeyEvent.VK_ENTER:
                    /**
                     * 该操作结果是:
                     * 设置编辑框文字为选中项,关闭结果列表,焦点回到编辑框,同时触发commit监听器
                     */
                    commitTextBySelectedValue();
                    break;

                case KeyEvent.VK_DOWN:
                    /**
                     * 该操作结果是:
                     * 1.如果结果列表未打开,打开结果列表,并选中第一项,设置编辑框文字
                     * 2.如果当前选中项为最后一项,让焦点回到编辑框
                     * 3.否则,下移选项,并改变编辑框文字为当前选项
                     */
                    if (isExpanded()) {
                        /**
                         * 如果列表处于展开状态
                         */
                        final int selectedIndex = getSelectedIndex();
                        if (selectedIndex == getResultCount() - 1) {
                            /**
                             * 并且选中项为最后一项
                             */
                            // 将焦点集中到文本框
                            changeFocusToTextField();
                        } else {
                            /**
                             * 否则
                             */
                            // 下移一项
                            setSelectedIndex(selectedIndex + 1);
                            showCurrentSelectedValue();
                            setFocusOnTextComponent();
                        }
                    } else {
                        if (expand()) {
                            /**
                             * 成功打开结果列表
                             */
                            // 选中第一项
                            setSelectedIndex(0);
                        }
                    }
                    break;

                case KeyEvent.VK_UP:
                    /**
                     * 该操作结果是:
                     * 1.如果结果列表未打开,打开结果列表,并选中最后一项,设置编辑框文字
                     * 2.如果当前选中项为第一项,让焦点回到编辑框
                     * 3.否则,上移选项,并改变编辑框文字为当前选项
                     */
                    if (isExpanded()) {
                        /**
                         * 如果列表处于展开状态
                         */
                        final int selectedIndex = getSelectedIndex();
                        if (selectedIndex == 0) {
                            /**
                             * 并且选中项为第一项
                             */
                            // 将焦点集中到文本框
                            changeFocusToTextField();
                        } else {
                            /**
                             * 否则
                             */
                            if (selectedIndex == -1) {
                                // 移到最后一项
                                setSelectedIndex(getResultCount() - 1);
                            } else {
                                // 上移一项
                                setSelectedIndex(selectedIndex - 1);
                            }
                            showCurrentSelectedValue();
                        }
                    } else {
                        if (expand()) {
                            /**
                             * 成功打开结果列表
                             */
                            // 选中最后一项
                            setSelectedIndex(getResultCount() - 1);
                        }
                    }
                    break;

                case KeyEvent.VK_LEFT:
                case KeyEvent.VK_RIGHT: // 左右的操作相同
                    /**
                     * 该操作结果是:
                     * 设置编辑文字为选中项,并关闭结果列表,焦点回到编辑框
                     */
                    if (isExpanded()) {
                        /**
                         * 如果列表处于展开状态
                         */
                        if (getSelectedIndex() != -1) {
                            /**
                             * 并且有选项被选中了
                             */
                            showCurrentSelectedValue();
                        }
                        collapse();
                    }
                    // 转移焦点到文本编辑框
                    changeFocusToTextField();
                    break;
            }
            /**
             * 为了确保焦点始终处于编辑框
             */
            setFocusOnTextComponent();
        }

        @Override
        public void keyReleased(KeyEvent e) {
            if (!e.getComponent().isFocusOwner()) {
                return;
            }

            int keyCode = e.getKeyCode();
            if (keyCode == KeyEvent.VK_UP
                    || keyCode == KeyEvent.VK_DOWN
                    || keyCode == KeyEvent.VK_LEFT
                    || keyCode == KeyEvent.VK_RIGHT
                    || keyCode == KeyEvent.VK_ENTER /*|| keyCode == KeyEvent.VK_BACK_SPACE*/) {
                return;
            }
            /**
             * 打开结果列表
             */
            expand();
            /**
             * 为了确保焦点始终处于编辑框
             */
            setFocusOnTextComponent();
        }
    };

    /*********************************************************
     *                 定义的一些接口
     */
    public interface CommitListener {

        public void commit(String value);
    }

    /**
     * 数据提供接口
     * @author Univasity
     */
    public interface DataProvider {

        public Object getData(int index);

        public void appendData(Object value);

        public void insertData(int index, Object value);

        public void replaceData(int index, Object value);

        public void replaceData(Object oldValue, Object newValue);

        public void removeDataAt(int index);

        public void removeData(Object value);

        public void clear();

        public int getSize();

        public Object[] toArray();

        public void setDataChangeListener(DataChangeListener listener);

        /**
         * 数据改变监听接口
         */
        public interface DataChangeListener {

            public static final int APPEND = 1;
            public static final int INSERT = 2;
            public static final int REPLACE = 3;
            public static final int REMOVE = 4;
            public static final int CLEAR = 5;

            public void dataChanged(int action, Object value);
        }
    }

    public interface DataMatchHelper {

        /**
         * 判断指定的用于匹配文本是否被允许
         * @param text
         * @return
         */
        public boolean isMatchTextAccept(String text);

        /**
         * 判断给定的值是否与文本值匹配
         * @param matchedText
         * @param data
         * @return
         */
        public boolean isDataMatched(String matchText, Object data);
    }

    /*********************************************************
     *                       默认的实现
     */
    private class DefaultDataProvider implements DataProvider {

        private ArrayList data;
        private DataChangeListener listener;

        public DefaultDataProvider() {
            data = new ArrayList();
        }

        public synchronized Object getData(int index) {
            return data.get(index);
        }

        public synchronized void appendData(Object value) {
            if (data.add(value)) {
                if (listener != null) {
                    listener.dataChanged(DataChangeListener.APPEND, value);
                }
            }
        }

        public synchronized void insertData(int index, Object value) {
            data.add(index, value);
            if (listener != null) {
                listener.dataChanged(DataChangeListener.INSERT, value);
            }
        }

        public synchronized void replaceData(int index, Object value) {
            if (data.set(index, value).equals(value)) {
                if (listener != null) {
                    listener.dataChanged(DataChangeListener.REPLACE, value);
                }
            }
        }

        public synchronized void replaceData(Object oldValue, Object newValue) {
            int index = data.indexOf(oldValue);
            if (data.set(index, newValue).equals(newValue)) {
                if (listener != null) {
                    listener.dataChanged(DataChangeListener.REPLACE, newValue);
                }
            }
        }

        public synchronized void removeDataAt(int index) {
            Object value = data.get(index);
            data.remove(index);
            if (listener != null) {
                listener.dataChanged(DataChangeListener.REMOVE, value);
            }
        }

        public synchronized void removeData(Object value) {
            if (data.remove(value)) {
                if (listener != null) {
                    listener.dataChanged(DataChangeListener.REMOVE, value);
                }
            }
        }

        public synchronized void clear() {
            data.clear();
            if (listener != null) {
                listener.dataChanged(DataChangeListener.CLEAR, null);
            }
        }

        public synchronized int getSize() {
            return data.size();
        }

        public synchronized Object[] toArray() {
            return data.toArray();
        }

        public synchronized void setDataChangeListener(DataChangeListener listener) {
            this.listener = listener;
        }
    }

    /**
     * 默认的数据匹配助手
     */
    private class DefaultDataMatchHelper implements DataMatchHelper {

        public boolean isMatchTextAccept(String text) {
            return (text != null && text.length() > 0);
        }

        public boolean isDataMatched(String matchText, Object value) {
            if (value != null && value.toString().indexOf(matchText) != -1) {
                return true;
            }
            return false;
        }
    }
}
 

 

可能还不是很完善,但目前给出了一些函数,方便自定义自己的一些监听器,实现自己的规则,具体可参考代码中的Default...Adapter实现。

 

以上仅供参考,大家多多交流。

  • 大小: 6.6 KB
0
0
分享到:
评论
8 楼 dongxurr123 2014-01-21  

非常好用,正好一直在找这个!正在消化中~~
7 楼 langwolf 2013-03-16  
爽歪歪,谢谢分享!!!
6 楼 univasity 2011-12-11  
sd6733531 写道
  楼主真棒。
不知道对swt有研究吗?

暂无深入研究,有时需要就用用,翻翻API...都是WindowBuidler完全可视化的,哈哈。
5 楼 sd6733531 2011-10-24  
  楼主真棒。
不知道对swt有研究吗?
4 楼 univasity 2010-11-24  
小蔫嘟 写道
兄弟感谢你,帮助不小,代码有些深奥啊。希望能交个朋友。

呵呵,多多交流。实现是都点复杂了,网上有更简单的,思路就是JTextField + PopupMenu + JList,可以按需实现。我的邮箱joopererer@yahoo.com.cn。
3 楼 univasity 2010-11-24  
小蔫嘟 写道
我们现在的需求是能在下拉的内容后加上一个 删除 的小图标。点击后删除。你能否在你的代码上给个明示。。。赶紧不尽


可以实现的,目前通过JList来展示结果,只要设置自定义的CellRenderer就可以了。当然还要加上自己的逻辑处理。具体可以参考SUN提供的资料http://download.oracle.com/javase/tutorial/uiswing/components/list.html。
2 楼 小蔫嘟 2010-11-19  
我们现在的需求是能在下拉的内容后加上一个 删除 的小图标。点击后删除。你能否在你的代码上给个明示。。。赶紧不尽
1 楼 小蔫嘟 2010-11-19  
兄弟感谢你,帮助不小,代码有些深奥啊。希望能交个朋友。

相关推荐

    Swing实现编辑器(支持语法高亮)

    Swing是Java编程语言中的一个图形用户界面(GUI)工具包,它是Java Foundation Classes (JFC)的一部分,由Sun Microsystems开发并引入Java平台。Swing提供了一系列组件,用于创建丰富的桌面应用程序,包括按钮、文本框...

    swing实现的仿qq截图小工具

    本项目是使用Swing实现的一个仿QQ截图小工具,它旨在提供类似QQ截图的功能,让用户在桌面环境中能够方便地进行屏幕截图并进行编辑。 首先,Swing组件库提供了丰富的组件,如JFrame、JButton、JPanel等,这些组件...

    swing 文本编辑器

    Swing 是 Java 的一个图形用户界面(GUI)工具包,它是 Java Foundation Classes (JFC) 的一部分,由 Sun Microsystems 开发。Swing 提供了一系列组件,用于构建功能丰富的桌面应用程序,包括文本编辑器。在 Swing ...

    Swing文本编辑器

    Swing是Java编程语言中的一个图形用户界面(GUI)工具包,它是Java Foundation Classes (JFC)的一部分,由Sun ...在实际应用中,Swing文本编辑器可以作为一个基础,进一步扩展成满足特定需求的专业文本编辑工具。

    swing效果点击下拉框自动填充文本框

    这个功能在许多GUI应用中都非常常见,例如用户选择一个选项后,相应的文本框会自动更新为所选选项的详细信息。以下是对这一知识点的详细说明: 首先,我们需要了解Swing中的基本组件。`JComboBox`是Swing提供的一种...

    java swing文本编辑器

    总之,创建一个Java Swing文本编辑器涉及到多个技术层面,包括组件使用、事件处理、文件操作、文本格式化以及可能的扩展功能实现。通过深入理解和实践这些知识点,开发者可以构建出功能完备且用户友好的文本编辑器。

    swing中英切换

    Swing是Java编程语言中的一个图形用户界面(GUI)工具包,它是Java Foundation Classes (JFC)的一部分。在Swing中实现中英切换是一项常见的需求,特别是在开发多语言应用时。Swing提供了强大的本地化(Localization)...

    easyX简单实现文本编辑器

    为了实现文本编辑功能,我们需要在内存中维护一个文本缓冲区,每次用户输入或修改文本时,更新缓冲区并重新绘制窗口。 4. **文本读取**:程序需要能够打开和加载现有的文本文件。这通常涉及`fopen`、`fgets`等文件...

    QQ聊天窗口泡泡模式的Swing实现

    在Java的GUI编程中,Swing是一个非常重要的库,它提供了丰富的组件和工具来创建桌面应用程序。本篇文章将深入探讨如何使用Swing实现QQ聊天窗口中的“泡泡模式”,这是一种常见且吸引人的消息显示方式。 首先,Swing...

    Swing界面内容抓取工具

    而"demo"文件可能是一个示例或者演示程序,让用户了解工具的实际效果和操作流程。 总的来说,Swing界面内容抓取工具是开发者和测试人员的有力辅助工具,它简化了从Swing应用中获取界面数据的过程,提高了工作效率。...

    swingTextEditor:使用Java swing的简单文本编辑器

    "swingTextEditor"项目就是一个基于Java Swing实现的简单文本编辑器示例。在这个项目中,我们将深入探讨Java Swing如何用于创建一个基本的文本编辑器。 首先,Swing 提供了 `JFrame` 类,它是所有窗口的基础。在`...

    swing实现Widget

    3. **JTextField**: 提供一个单行文本输入框,用户可以在这里输入文本。 4. **JTextArea**: 用于多行文本输入和显示,支持滚动条。 5. **JCheckBox**: 用于提供二元选择,用户可以勾选或取消勾选。 6. **...

    swing做的日志分析工具(光界面就相当给力)

    总的来说,这款基于Swing的日志分析工具提供了一个强大的平台,让用户能够有效地管理和分析日志数据。它的易用性、源码开放性以及作为工具的实用性,使其成为开发者和运维人员的强大助手。通过不断学习和定制,用户...

    java Frame实现的Java文本编辑器

    Java Frame实现的Java文本编辑器是一种基于Swing或者JavaFX的替代方案,它利用了`org.eclipse.swt`库,这是一个强大的图形用户界面(GUI)工具包,尤其在跨平台的Java应用中广受欢迎。`org.eclipse.swt`是Eclipse...

    学习JAVA,自己写的一个文本编辑器

    另一个标签“工具”暗示了这个文本编辑器可能包含了一些实用功能,比如自动完成、语法高亮、代码折叠等,这些都是现代文本编辑器的常见特性。开发者可能使用了Java的标准库或其他第三方库来实现这些功能。 在压缩包...

    swing开发的源代码

    Swing是Java编程语言中的一个图形用户界面(GUI)工具包,它是Java Foundation Classes (JFC)的一部分。Swing提供了一套丰富的组件,用于创建桌面应用程序,包括按钮、文本框、菜单、滚动面板等。Swing是完全由Java...

    基于swing的编辑器

    【基于Swing的编辑器】是一种使用Java Swing库开发的文本编辑工具,它提供了一个基本的用户界面,供用户创建、查看和编辑文本文件。Swing是Java Foundation Classes (JFC)的一部分,它为Java应用程序提供了丰富的...

    (转)使用FEST-Swing测试GUI

    【标签】:“源码”与“工具”两个标签表明这篇博客内容将涉及FEST-Swing的源代码理解和实际应用,以及它是如何作为一个测试工具来提高GUI测试效率的。 【正文】: FEST-Swing是一个开源Java库,它的设计目的是...

    java实现单文档文本编辑器

    虽然这个文本编辑器没有实现多文档功能,但对于初学者来说,它是一个很好的学习Java GUI编程和文本处理的基础项目。通过理解并扩展这个项目,开发者可以逐渐掌握更复杂的应用程序开发技能,如多线程、插件系统、以及...

Global site tag (gtag.js) - Google Analytics