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

来源:互联网 发布:电话回拨软件 编辑:程序博客网 时间:2024/04/28 12:15

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

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

 

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

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

 

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

1. JComboBox

2. JTextField + PopupMenu + JList

 

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

 

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

 

一个使用的样例:

Java代码  收藏代码
  1. JTextField textField = new JTextField();  
  2. // 创建对象并绑定一个JTextComponent  
  3. AutoCompleteExtender autoCompleteExtender = new AutoCompleteExtender(textField, data, null);  
  4. // 设置参数  
  5. autoCompleteExtender.setMatchDataAsync(true);  
  6. autoCompleteExtender.setSizeFitComponent();  
  7. autoCompleteExtender.setMaxVisibleRows(6);  
  8. autoCompleteExtender.setCommitListener(new CommitListener() {  
  9.     // 值最终被选中时会触发该函数  
  10.     public void commit(String value) {  
  11.         System.out.println("commit:" + value);  
  12.     }  
  13. });  
 

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的搜索栏的,能跟随光标和显示提示。

 

最终显示效果: 

 

最后放上代码:

Java代码  收藏代码
  1. /* 
  2.  * To change this template, choose Tools | Templates 
  3.  * and open the template in the editor. 
  4.  */  
  5. package swingstudy.autocomplete;  
  6.   
  7. import java.awt.Dimension;  
  8. import java.awt.Point;  
  9. import java.awt.event.KeyAdapter;  
  10. import java.awt.event.KeyEvent;  
  11. import java.awt.event.MouseAdapter;  
  12. import java.awt.event.MouseEvent;  
  13. import java.awt.event.MouseMotionAdapter;  
  14. import java.util.ArrayList;  
  15. import java.util.concurrent.BlockingQueue;  
  16. import java.util.concurrent.LinkedBlockingQueue;  
  17. import java.util.concurrent.ThreadPoolExecutor;  
  18. import java.util.concurrent.TimeUnit;  
  19. import javax.swing.BorderFactory;  
  20. import javax.swing.DefaultListModel;  
  21. import javax.swing.JList;  
  22. import javax.swing.JPopupMenu;  
  23. import javax.swing.JScrollPane;  
  24. import javax.swing.ListSelectionModel;  
  25. import javax.swing.ScrollPaneConstants;  
  26. import javax.swing.SwingUtilities;  
  27. import javax.swing.ToolTipManager;  
  28. import javax.swing.text.JTextComponent;  
  29. import swingstudy.autocomplete.AutoCompleteExtender.DataProvider.DataChangeListener;  
  30.   
  31. /** 
  32.  * 
  33.  * @author Univasity 
  34.  */  
  35. public class AutoCompleteExtender {  
  36.   
  37.     public static final int DefaultMaxVisibleRows = 5;  
  38.     /** 
  39.      * 绑定的文本组件 
  40.      */  
  41.     private JTextComponent textComponent;  
  42.     /** 
  43.      * 用于显示结果列表的弹出菜单组件 
  44.      */  
  45.     private JPopupMenu popupMenu;  
  46.     /** 
  47.      * 用于展示匹配结果的列表组件 
  48.      */  
  49.     private JList resultList;  
  50.     /** 
  51.      * 为列表提供滚动支持的组件 
  52.      */  
  53.     private JScrollPane scrollPane;  
  54.     /** 
  55.      * 数据提供器 
  56.      */  
  57.     private DataProvider dataProvider;  
  58.     /** 
  59.      * 标记匹配数据是否发生改变 
  60.      */  
  61.     private boolean matchDataChanged;  
  62.     /** 
  63.      * 记录当前被匹配的文本 
  64.      */  
  65.     private String matchedText;  
  66.     /** 
  67.      * 原始的编辑文本 
  68.      */  
  69.     private String originalEditText;  
  70.     /** 
  71.      * 数据匹配器 
  72.      */  
  73.     private DataMatchHelper dataMatchHelper;  
  74.     /** 
  75.      * 确定监听器,默认为按下'回车键'或鼠标点选会被触发 
  76.      */  
  77.     private CommitListener commitListener;  
  78.     /** 
  79.      * 默认的数据改变监听器 
  80.      */  
  81.     private final DataChangeListener DefaultDataChangeListener = new DataChangeListener() {  
  82.   
  83.         public void dataChanged(int action, Object value) {  
  84.             notifyDataChanged();  
  85.         }  
  86.     };  
  87.     /** 
  88.      * 线程池 
  89.      */  
  90.     private BlockingQueue<Runnable> queue; // 用于储存任务的队列  
  91.     private ThreadPoolExecutor executor; // 线程池对象  
  92.     private boolean matchDataAsync = false// 是否异步进行匹配操作  
  93.     private int resultListWidth;  
  94.     private boolean widthWithScrollBar;  
  95.     private boolean autoSizeToFit;  
  96.   
  97.     /** 
  98.      * 指定绑定的对象,数据提供器和匹配器来构建一个对象 
  99.      * @param textComponent 不能为null 
  100.      * @param dataProvider 不能为null 
  101.      * @param dataMatchHelper 如果为null,则使用默认的匹配器 
  102.      */  
  103.     public AutoCompleteExtender(JTextComponent textComponent, DataProvider dataProvider, DataMatchHelper dataMatchHelper) {  
  104.         if (textComponent == null) {  
  105.             /** 
  106.              * 确保绑定的插件不为null 
  107.              */  
  108.             throw new IllegalArgumentException("textComponent不能为null!");  
  109.         }  
  110.         if (dataProvider == null) {  
  111.             /** 
  112.              * 确保数据提供器不为null 
  113.              */  
  114.             throw new IllegalArgumentException("dataProvider不能为null!");  
  115.         }  
  116.         this.textComponent = textComponent;  
  117.         this.dataProvider = dataProvider;  
  118.         this.dataProvider.setDataChangeListener(DefaultDataChangeListener);  
  119.         if (dataMatchHelper == null) {  
  120.             this.dataMatchHelper = new DefaultDataMatchHelper();  
  121.         } else {  
  122.             this.dataMatchHelper = dataMatchHelper;  
  123.         }  
  124.         /** 
  125.          * 初始化数据 
  126.          */  
  127.         resetAll();  
  128.     }  
  129.   
  130.     /** 
  131.      * 指定绑定的对象,匹配数据和匹配器来构建一个对象 
  132.      * @param textComponent 不能为null 
  133.      * @param data 初始的匹配数据 
  134.      * @param dataMatchHelper 如果为null,则使用默认的匹配器 
  135.      */  
  136.     public AutoCompleteExtender(JTextComponent textComponent, Object[] data, DataMatchHelper dataMatchHelper) {  
  137.         if (textComponent == null) {  
  138.             /** 
  139.              * 确保绑定的插件不为null 
  140.              */  
  141.             throw new IllegalArgumentException("textComponent不能为null!");  
  142.         }  
  143.         this.textComponent = textComponent;  
  144.         this.dataProvider = new DefaultDataProvider();  
  145.         if (data != null) {  
  146.             for (Object value : data) {  
  147.                 this.dataProvider.appendData(value);  
  148.             }  
  149.         }  
  150.         this.dataProvider.setDataChangeListener(DefaultDataChangeListener);  
  151.         if (dataMatchHelper == null) {  
  152.             this.dataMatchHelper = new DefaultDataMatchHelper();  
  153.         } else {  
  154.             this.dataMatchHelper = dataMatchHelper;  
  155.         }  
  156.         /** 
  157.          * 初始化数据 
  158.          */  
  159.         resetAll();  
  160.     }  
  161.   
  162.     public DataProvider getDataProvider() {  
  163.         return dataProvider;  
  164.     }  
  165.   
  166.     /** 
  167.      * 设置为默认配置,原有数据将被清空 
  168.      */  
  169.     public synchronized void resetAll() {  
  170.         initTextComponent();  
  171.         initResultList();  
  172.         initValues();  
  173.         setFocusOnTextComponent();  
  174.         updateUI();  
  175.     }  
  176.   
  177.     /** 
  178.      * 刷新当前UI 
  179.      */  
  180.     public synchronized void updateUI() {  
  181.         popupMenu.pack();  
  182.         popupMenu.updateUI();  
  183.     }  
  184.   
  185.     /** 
  186.      * 清空匹配结果 
  187.      */  
  188.     public synchronized void clearMatchResult() {  
  189.         collapse();  
  190.         if (queue != null) {  
  191.             queue.clear();  
  192.         }  
  193.         ((DefaultListModel) resultList.getModel()).removeAllElements();  
  194.     }  
  195.   
  196.     /** 
  197.      * 标记匹配数据改变了 
  198.      */  
  199.     private void notifyDataChanged() {  
  200.         matchDataChanged = true;  
  201.     }  
  202.   
  203.     public void setCommitListener(CommitListener commitListener) {  
  204.         this.commitListener = commitListener;  
  205.     }  
  206.   
  207.     /** 
  208.      * 获取当前被匹配的文本 
  209.      * @return 
  210.      */  
  211.     public synchronized String getMatchText() {  
  212.         return matchedText;  
  213.     }  
  214.   
  215.     /** 
  216.      * 获取当前匹配结果 
  217.      * @return 
  218.      */  
  219.     public synchronized Object[] getMatchResult() {  
  220.         return ((DefaultListModel) resultList.getModel()).toArray();  
  221.     }  
  222.   
  223.     /** 
  224.      * 获取当前选中的值 
  225.      * @return 
  226.      */  
  227.     public synchronized Object getSelectedValue() {  
  228.         return resultList.getSelectedValue();  
  229.     }  
  230.   
  231.     /** 
  232.      * 确定指定的文本为最终选定 
  233.      * @param text 
  234.      */  
  235.     public synchronized void commitText(String text) {  
  236.         originalEditText = text;  
  237.         textComponent.setText(text);  
  238.         if (commitListener != null) {  
  239.             commitListener.commit(text);  
  240.         }  
  241.     }  
  242.   
  243.     /** 
  244.      * 获取当前选中项的索引值 
  245.      * @return 
  246.      */  
  247.     public synchronized int getSelectedIndex() {  
  248.         return resultList.getSelectedIndex();  
  249.     }  
  250.   
  251.     /** 
  252.      * 选中指定的索引值 
  253.      * @param index 
  254.      */  
  255.     public synchronized void setSelectedIndex(int index) {  
  256.         if (index < 0 || index >= getResultCount()) {  
  257.             return;  
  258.         }  
  259.         resultList.setSelectedIndex(index);  
  260.         // 使选中项处于可视范围内  
  261.         resultList.ensureIndexIsVisible(index);  
  262.     }  
  263.   
  264.     /** 
  265.      * 打开结果列表(如果未成匹配,则自动执行匹配处理,如果无有效结果则不会被展开)(焦点会转移到列表) 
  266.      * @return 
  267.      */  
  268.     public synchronized boolean expand() {  
  269.         if (!hasMatched()) {  
  270.             if (doMatch()) {  
  271.                 // 展开列表  
  272.                 updateExpandListUI();  
  273.                 popupMenu.show(textComponent, 0, textComponent.getHeight());  
  274.             }  
  275.         } else if (getResultCount() > 0) {  
  276.             popupMenu.setVisible(true);  
  277.         }  
  278.         return popupMenu.isVisible();  
  279.     }  
  280.   
  281.     /** 
  282.      * 关闭结果列表(数据不会被清空,再次打开时直接重新显示) 
  283.      */  
  284.     public synchronized void collapse() {  
  285.         removeSelectionInterval();  
  286.         popupMenu.setVisible(false);  
  287.     }  
  288.   
  289.     /** 
  290.      * 判断结果列表是否被打开 
  291.      * @return 
  292.      */  
  293.     public synchronized boolean isExpanded() {  
  294.         return popupMenu.isVisible();  
  295.     }  
  296.   
  297.     /** 
  298.      * 获取当前结果列表的条目数 
  299.      * @return 
  300.      */  
  301.     public synchronized int getResultCount() {  
  302.         return ((DefaultListModel) resultList.getModel()).getSize();  
  303.     }  
  304.   
  305.     /** 
  306.      * 获取一次最多的显示行数(超出的部分需通过拖动滚动条显示) 
  307.      * @param rows 
  308.      */  
  309.     public synchronized void setMaxVisibleRows(int rows) {  
  310.         resultList.setVisibleRowCount(rows);  
  311.     }  
  312.   
  313.     /** 
  314.      * 把焦点设置到文本编辑框上 
  315.      */  
  316.     public synchronized void setFocusOnTextComponent() {  
  317.         textComponent.requestFocus();  
  318.     }  
  319.   
  320.     /** 
  321.      * 把焦点设置到结果列表上 
  322.      */  
  323.     public synchronized void setFocusOnExpandList() {  
  324.         resultList.requestFocus();  
  325.     }  
  326.   
  327.     /** 
  328.      * 判断焦点是否在文本编辑框上 
  329.      * @return 
  330.      */  
  331.     public synchronized boolean isFocusOnTextComponent() {  
  332.         return textComponent.isFocusOwner();  
  333.     }  
  334.   
  335.     /** 
  336.      * 判断焦点是否在结果列表上 
  337.      * @return 
  338.      */  
  339.     public synchronized boolean isFocusOnExpandList() {  
  340.         return resultList.isFocusOwner();  
  341.     }  
  342.   
  343.     /** 
  344.      * 取消当前列表上的选中状态(使selectedIndex==-1) 
  345.      */  
  346.     public synchronized void removeSelectionInterval() {  
  347.         final int selectedIndex = resultList.getSelectedIndex();  
  348.         resultList.removeSelectionInterval(selectedIndex, selectedIndex);  
  349.     }  
  350.   
  351.     /** 
  352.      * 判断是否已经匹配过了(匹配前应进行检测,避免重复匹配操作) 
  353.      * @return 
  354.      */  
  355.     public synchronized boolean hasMatched() {  
  356.         if (matchDataChanged) {  
  357.             return false;  
  358.         }  
  359.         if (matchedText == null || matchedText.length() < 1) {  
  360.             return false;  
  361.         }  
  362.         String text = textComponent.getText();  
  363.         if (text == null || !text.equals(matchedText)) {  
  364.             return false;  
  365.         }  
  366.         return true;  
  367.     }  
  368.   
  369.     /** 
  370.      * 执行匹配操作 
  371.      * @return 
  372.      */  
  373.     public synchronized boolean doMatch() {  
  374.         // 清空原有结果  
  375.         clearMatchResult();  
  376.   
  377.         matchedText = textComponent.getText();  
  378.         originalEditText = matchedText;  
  379.         String keyWord = matchedText;  
  380.         if (keyWord != null) {  
  381.             keyWord = matchedText.trim();  
  382.         }  
  383.   
  384.         if (dataMatchHelper != null) {  
  385.             if (!dataMatchHelper.isMatchTextAccept(keyWord)) {  
  386.                 return false;  
  387.             }  
  388.         }  
  389.   
  390.         if (matchDataAsync) {  
  391.             doMatchAsync(keyWord);  
  392.             matchDataChanged = false;  
  393.             return true;  
  394.         } else {  
  395.             doMatchSync(keyWord);  
  396.             matchDataChanged = false;  
  397.             return getResultCount() > 0;  
  398.         }  
  399.     }  
  400.   
  401.     /** 
  402.      * 设置异步匹配数据 
  403.      * @param async 
  404.      */  
  405.     public synchronized void setMatchDataAsync(boolean async) {  
  406.         if (this.matchDataAsync != async) {  
  407.             this.matchDataAsync = async;  
  408.             if (async) {  
  409.                 queue = new LinkedBlockingQueue<Runnable>();  
  410.                 // 创建一个最多运行2个任务,支持10个任务, 允许延时20秒的线程池  
  411.                 executor = new ThreadPoolExecutor(21020, TimeUnit.SECONDS, queue);  
  412.             } else {  
  413.                 if (queue != null) {  
  414.                     queue.clear();  
  415.                 }  
  416.                 if (executor != null) {  
  417.                     executor.shutdown();  
  418.                 }  
  419.                 queue = null;  
  420.                 executor = null;  
  421.             }  
  422.         }  
  423.     }  
  424.   
  425.     /** 
  426.      * 判断当前是否异步匹配 
  427.      * @return 
  428.      */  
  429.     public synchronized boolean isMatchDataAsync() {  
  430.         return this.matchDataAsync;  
  431.     }  
  432.   
  433.     /** 
  434.      * 在结果列表上显示过于选中项的提示条 
  435.      * @param asNeed 是否根据需要显示(true->文本长度超出显示范围时才显示) 
  436.      */  
  437.     public synchronized void showToolTipsWithSelectedValue(boolean asNeed) {  
  438.         Object value = resultList.getSelectedValue();  
  439.         if (value != null) {  
  440.             // 显示提示  
  441.             String txt = value.toString();  
  442.             if (txt != null) {  
  443.                 if (asNeed) {  
  444.                     // 超出范围才显示提示  
  445.                     int txtW = SwingUtilities.computeStringWidth(resultList.getFontMetrics(resultList.getFont()), txt);  
  446.                     if (txtW >= resultList.getFixedCellWidth()) {  
  447.                         resultList.setToolTipText(txt);  
  448.                         return;  
  449.                     }  
  450.                 } else {  
  451.                     resultList.setToolTipText(txt);  
  452.                     return;  
  453.                 }  
  454.             }  
  455.         }  
  456.         resultList.setToolTipText(null);  
  457.     }  
  458.   
  459.     /** 
  460.      * 在结果列表上显示指定的文本作为提示 
  461.      * @param text 
  462.      */  
  463.     public void showToolTips(String text) {  
  464.         if (text != null) {  
  465.             resultList.setToolTipText(text);  
  466.         } else {  
  467.             resultList.setToolTipText(null);  
  468.         }  
  469.     }  
  470.   
  471.     /** 
  472.      * 获取一次最多可见行数 
  473.      * @return 
  474.      */  
  475.     public synchronized int getMaxVisibleRows() {  
  476.         return resultList.getVisibleRowCount();  
  477.     }  
  478.   
  479.     /** 
  480.      * 获取结果列表单元项的宽度 
  481.      * @return 
  482.      */  
  483.     public synchronized int getListCellWidth() {  
  484.         return resultList.getFixedCellWidth();  
  485.     }  
  486.   
  487.     /** 
  488.      * 获取结果列表单元项的高度 
  489.      * @return 
  490.      */  
  491.     public synchronized int getListCellHeight() {  
  492.         return resultList.getFixedCellHeight();  
  493.     }  
  494.   
  495.     public synchronized void setListCellSize(int cellWidth, int cellHeight) {  
  496.         resultList.setFixedCellWidth(cellWidth);  
  497.         resultList.setFixedCellHeight(cellHeight);  
  498.         autoSizeToFit = false;  
  499.         updateExpandListUI();  
  500.     }  
  501.   
  502.     public synchronized void setListWidth(int width, boolean withScrollBar) {  
  503.         this.resultListWidth = width;  
  504.         this.widthWithScrollBar = withScrollBar;  
  505.         autoSizeToFit = false;  
  506.         updateExpandListUI();  
  507.     }  
  508.   
  509.     /** 
  510.      * 使大小贴合组件 
  511.      */  
  512.     public synchronized void setSizeFitComponent() {  
  513.         autoSizeToFit = true;  
  514.         updateExpandListUI();  
  515.     }  
  516.   
  517.     /** 
  518.      * 指定点是否在文本框范围内 
  519.      * @param p 
  520.      * @return 
  521.      */  
  522.     public synchronized boolean isTextFieldContains(Point p) {  
  523.         if (p == null) {  
  524.             return false;  
  525.         }  
  526.         return textComponent.contains(p);  
  527.     }  
  528.   
  529.     /** 
  530.      * 指定点是否在结果列表范围内 
  531.      * @param p 
  532.      * @return 
  533.      */  
  534.     public synchronized boolean isExpandListContains(Point p) {  
  535.         if (p == null) {  
  536.             return false;  
  537.         }  
  538.         return resultList.contains(p);  
  539.     }  
  540.   
  541.     private synchronized void initTextComponent() {  
  542.         textComponent.setVisible(true);  
  543.         textComponent.setEnabled(true);  
  544.         textComponent.setEditable(true);  
  545.         // 必须先删除再添加,否则会重复....  
  546.         textComponent.removeKeyListener(DefaultTextFieldKeyAdapter);  
  547.         textComponent.addKeyListener(DefaultTextFieldKeyAdapter);  
  548.     }  
  549.   
  550.     private synchronized void initResultList() {  
  551.         /** 
  552.          * list 
  553.          */  
  554.         if (resultList != null) {  
  555.             resultList.removeAll();  
  556.         } else {  
  557.             resultList = new JList(new DefaultListModel());  
  558.             resultList.addMouseListener(DefaultResultListMouseAdapter);  
  559.             resultList.addMouseMotionListener(DefaultResultListMouseMotionAdapter);  
  560.         }  
  561.         resultList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);  
  562.         resultList.setVisibleRowCount(DefaultMaxVisibleRows);  
  563.         // 允许提示框  
  564.         ToolTipManager.sharedInstance().registerComponent(resultList);  
  565.   
  566.         /** 
  567.          * scroll pane 
  568.          */  
  569.         if (scrollPane == null) {  
  570.             scrollPane = new JScrollPane(resultList);  
  571.         }  
  572.         scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);  
  573.   
  574.         /** 
  575.          * popup menu 
  576.          */  
  577.         if (popupMenu == null) {  
  578.             popupMenu = new JPopupMenu();  
  579.         }  
  580.         popupMenu.add(scrollPane);  
  581.         popupMenu.setVisible(false);  
  582.         popupMenu.setFocusable(false);  
  583.         popupMenu.setBorder(BorderFactory.createEmptyBorder()); // 去掉边框  
  584.     }  
  585.   
  586.     private synchronized void initValues() {  
  587.         setCommitListener(null);  
  588.   
  589.         matchedText = null;  
  590.         matchDataChanged = true;  
  591.         this.matchDataAsync = false;  
  592.         originalEditText = textComponent.getText();  
  593.     }  
  594.   
  595.     /** 
  596.      * 根据给定的值执行匹配操作(该操作为异步的) 
  597.      * @param content 
  598.      * @return 
  599.      */  
  600.     private synchronized void doMatchAsync(String content) {  
  601.         final String matchText = content;  
  602.   
  603.         if (queue != null) {  
  604.             queue.clear();  
  605.         }  
  606.   
  607.         executor.execute(new Runnable() {  
  608.   
  609.             public void run() {  
  610.                 /** 
  611.                  * 进行匹配 
  612.                  */  
  613.                 doMatchInner(matchText);  
  614.                 /** 
  615.                  * 如果无匹配项,关闭当前显示 
  616.                  */  
  617.                 if (getResultCount() > 0) {  
  618.                     updateExpandListUI();  
  619.                 } else {  
  620.                     collapse();  
  621.                 }  
  622.             }  
  623.         });  
  624.     }  
  625.   
  626.     /** 
  627.      * 根据给定的值执行匹配操作(该操作为同步的) 
  628.      * @param content 
  629.      * @return 
  630.      */  
  631.     private synchronized void doMatchSync(String content) {  
  632.         /** 
  633.          * 进行匹配 
  634.          */  
  635.         doMatchInner(content);  
  636.     }  
  637.   
  638.     /** 
  639.      * 处理匹配(内部调用) 
  640.      * @param matchText 
  641.      */  
  642.     private void doMatchInner(String matchText) {  
  643.         if (dataProvider != null) {  
  644.             DefaultListModel listModel = (DefaultListModel) resultList.getModel();  
  645.             for (Object value : dataProvider.toArray()) {  
  646.                 if (dataMatchHelper != null) {  
  647.                     if (dataMatchHelper.isDataMatched(matchText, value)) {  
  648.                         listModel.addElement(value);  
  649.                     }  
  650.                 } else {  
  651.                     // 直接添加  
  652.                     listModel.addElement(value);  
  653.                 }  
  654.             }  
  655.         }  
  656.     }  
  657.   
  658.     /** 
  659.      * 设置当前选项为最终选定值 
  660.      */  
  661.     private void commitTextBySelectedValue() {  
  662.         Object value = getSelectedValue();  
  663.         if (value != null) {  
  664.             commitText(value.toString());  
  665.         }  
  666.         collapse();  
  667.     }  
  668.   
  669.     /** 
  670.      * 转移焦点到文本编辑框,并关闭结果列表 
  671.      */  
  672.     private void changeFocusToTextField() {  
  673.         // 取消选择  
  674.         removeSelectionInterval();  
  675.         // 转移焦点到文本框  
  676.         setFocusOnTextComponent();  
  677.         // 设置为原本编辑的文本值  
  678.         textComponent.setText(originalEditText);  
  679.     }  
  680.   
  681.     /** 
  682.      * 设置当前选中项的值到文本框 
  683.      */  
  684.     private void showCurrentSelectedValue() {  
  685.         Object value = getSelectedValue();  
  686.         if (value != null) {  
  687.             textComponent.setText(value.toString());  
  688.         }  
  689.     }  
  690.   
  691.     /** 
  692.      * 刷新结果列表的显示(焦点会转移到列表) 
  693.      */  
  694.     private synchronized void updateExpandListUI() {  
  695.         DefaultListModel listModel = (DefaultListModel) resultList.getModel();  
  696.         int dataSize = listModel.getSize();  
  697.   
  698.         int preferredWidth = 0;  
  699.         if (autoSizeToFit) {  
  700.             /** 
  701.              * 自动使大小贴合组件 
  702.              */  
  703.             resultList.setFixedCellWidth(textComponent.getWidth());  
  704.             resultList.setFixedCellHeight(textComponent.getHeight());  
  705.             preferredWidth = textComponent.getWidth();  
  706.             if (dataSize > resultList.getVisibleRowCount()) {  
  707.                 preferredWidth += scrollPane.getVerticalScrollBar().getPreferredSize().width;  
  708.             }  
  709.         } else {  
  710.             /** 
  711.              * 使用自定义的大小 
  712.              */  
  713.             preferredWidth = resultListWidth;  
  714.             if (dataSize > resultList.getVisibleRowCount()) {  
  715.                 if (!widthWithScrollBar) {  
  716.                     preferredWidth += scrollPane.getVerticalScrollBar().getPreferredSize().width;  
  717.                 }  
  718.             }  
  719.         }  
  720.   
  721.         int preferredHeight = Math.min(resultList.getVisibleRowCount(), dataSize) * resultList.getFixedCellHeight() + 3// 多预留一些空间,这个值可自己调整不是很准的  
  722.   
  723.         scrollPane.setPreferredSize(new Dimension(preferredWidth, preferredHeight));  
  724.         resultList.updateUI();  
  725.         popupMenu.pack();  
  726.     }  
  727.     /** 
  728.      * 默认提供的结果列表上鼠标运动事件处理器 
  729.      */  
  730.     private MouseMotionAdapter DefaultResultListMouseMotionAdapter = new MouseMotionAdapter() {  
  731.   
  732.         @Override  
  733.         public void mouseMoved(MouseEvent e) {  
  734.             /** 
  735.              * 该操作结果是: 
  736.              * 选中鼠标所在选项,并显示提示 
  737.              */  
  738.             Point p = e.getPoint();  
  739.             if (isExpandListContains(p)) {  
  740.                 /** 
  741.                  * 鼠标在列表区域内移动时 
  742.                  */  
  743.                 int index = p.y / getListCellHeight();  
  744.                 // 光标跟随  
  745.                 setSelectedIndex(index);  
  746.                 // 文本超长时显示提示  
  747.                 showToolTipsWithSelectedValue(true);  
  748.                 // 焦点回归到文本编辑框  
  749.                 setFocusOnTextComponent();  
  750.             }  
  751.         }  
  752.     };  
  753.     /** 
  754.      * 默认提供的结果列表上鼠标按键事件处理器 
  755.      */  
  756.     private final MouseAdapter DefaultResultListMouseAdapter = new MouseAdapter() {  
  757.   
  758.         @Override  
  759.         public void mouseClicked(MouseEvent e) {  
  760.             /** 
  761.              * 该操作结果是: 
  762.              * 设置编辑框文字为选中项,关闭结果列表,焦点回到编辑框,同时触发commit监听器 
  763.              */  
  764.             Point p = e.getPoint();  
  765.             if (isExpandListContains(p)) {  
  766.                 /** 
  767.                  * 鼠标点击列表项时 
  768.                  */  
  769.                 int index = p.y / getListCellHeight();  
  770.                 // 选中该项  
  771.                 setSelectedIndex(index);  
  772.                 //   
  773.                 if (getSelectedIndex() == index) {  
  774.                     commitTextBySelectedValue();  
  775.                 }  
  776.                 // 焦点回归到文本编辑框  
  777.                 setFocusOnTextComponent();  
  778.             }  
  779.         }  
  780.     };  
  781.     /** 
  782.      * 默认提供的文本编辑框上键盘按键事件处理器 
  783.      */  
  784.     private final KeyAdapter DefaultTextFieldKeyAdapter = new KeyAdapter() {  
  785.   
  786.         @Override  
  787.         public void keyPressed(KeyEvent e) {  
  788.             /** 
  789.              * 只对处于当前焦点时作处理 
  790.              */  
  791.             if (!e.getComponent().isFocusOwner()) {  
  792.                 return;  
  793.             }  
  794.   
  795.             switch (e.getKeyCode()) {  
  796.   
  797.                 case KeyEvent.VK_ENTER:  
  798.                     /** 
  799.                      * 该操作结果是: 
  800.                      * 设置编辑框文字为选中项,关闭结果列表,焦点回到编辑框,同时触发commit监听器 
  801.                      */  
  802.                     commitTextBySelectedValue();  
  803.                     break;  
  804.   
  805.                 case KeyEvent.VK_DOWN:  
  806.                     /** 
  807.                      * 该操作结果是: 
  808.                      * 1.如果结果列表未打开,打开结果列表,并选中第一项,设置编辑框文字 
  809.                      * 2.如果当前选中项为最后一项,让焦点回到编辑框 
  810.                      * 3.否则,下移选项,并改变编辑框文字为当前选项 
  811.                      */  
  812.                     if (isExpanded()) {  
  813.                         /** 
  814.                          * 如果列表处于展开状态 
  815.                          */  
  816.                         final int selectedIndex = getSelectedIndex();  
  817.                         if (selectedIndex == getResultCount() - 1) {  
  818.                             /** 
  819.                              * 并且选中项为最后一项 
  820.                              */  
  821.                             // 将焦点集中到文本框  
  822.                             changeFocusToTextField();  
  823.                         } else {  
  824.                             /** 
  825.                              * 否则 
  826.                              */  
  827.                             // 下移一项  
  828.                             setSelectedIndex(selectedIndex + 1);  
  829.                             showCurrentSelectedValue();  
  830.                             setFocusOnTextComponent();  
  831.                         }  
  832.                     } else {  
  833.                         if (expand()) {  
  834.                             /** 
  835.                              * 成功打开结果列表 
  836.                              */  
  837.                             // 选中第一项  
  838.                             setSelectedIndex(0);  
  839.                         }  
  840.                     }  
  841.                     break;  
  842.   
  843.                 case KeyEvent.VK_UP:  
  844.                     /** 
  845.                      * 该操作结果是: 
  846.                      * 1.如果结果列表未打开,打开结果列表,并选中最后一项,设置编辑框文字 
  847.                      * 2.如果当前选中项为第一项,让焦点回到编辑框 
  848.                      * 3.否则,上移选项,并改变编辑框文字为当前选项 
  849.                      */  
  850.                     if (isExpanded()) {  
  851.                         /** 
  852.                          * 如果列表处于展开状态 
  853.                          */  
  854.                         final int selectedIndex = getSelectedIndex();  
  855.                         if (selectedIndex == 0) {  
  856.                             /** 
  857.                              * 并且选中项为第一项 
  858.                              */  
  859.                             // 将焦点集中到文本框  
  860.                             changeFocusToTextField();  
  861.                         } else {  
  862.                             /** 
  863.                              * 否则 
  864.                              */  
  865.                             if (selectedIndex == -1) {  
  866.                                 // 移到最后一项  
  867.                                 setSelectedIndex(getResultCount() - 1);  
  868.                             } else {  
  869.                                 // 上移一项  
  870.                                 setSelectedIndex(selectedIndex - 1);  
  871.                             }  
  872.                             showCurrentSelectedValue();  
  873.                         }  
  874.                     } else {  
  875.                         if (expand()) {  
  876.                             /** 
  877.                              * 成功打开结果列表 
  878.                              */  
  879.                             // 选中最后一项  
  880.                             setSelectedIndex(getResultCount() - 1);  
  881.                         }  
  882.                     }  
  883.                     break;  
  884.   
  885.                 case KeyEvent.VK_LEFT:  
  886.                 case KeyEvent.VK_RIGHT: // 左右的操作相同  
  887.                     /** 
  888.                      * 该操作结果是: 
  889.                      * 设置编辑文字为选中项,并关闭结果列表,焦点回到编辑框 
  890.                      */  
  891.                     if (isExpanded()) {  
  892.                         /** 
  893.                          * 如果列表处于展开状态 
  894.                          */  
  895.                         if (getSelectedIndex() != -1) {  
  896.                             /** 
  897.                              * 并且有选项被选中了 
  898.                              */  
  899.                             showCurrentSelectedValue();  
  900.                         }  
  901.                         collapse();  
  902.                     }  
  903.                     // 转移焦点到文本编辑框  
  904.                     changeFocusToTextField();  
  905.                     break;  
  906.             }  
  907.             /** 
  908.              * 为了确保焦点始终处于编辑框 
  909.              */  
  910.             setFocusOnTextComponent();  
  911.         }  
  912.   
  913.         @Override  
  914.         public void keyReleased(KeyEvent e) {  
  915.             if (!e.getComponent().isFocusOwner()) {  
  916.                 return;  
  917.             }  
  918.   
  919.             int keyCode = e.getKeyCode();  
  920.             if (keyCode == KeyEvent.VK_UP  
  921.                     || keyCode == KeyEvent.VK_DOWN  
  922.                     || keyCode == KeyEvent.VK_LEFT  
  923.                     || keyCode == KeyEvent.VK_RIGHT  
  924.                     || keyCode == KeyEvent.VK_ENTER /*|| keyCode == KeyEvent.VK_BACK_SPACE*/) {  
  925.                 return;  
  926.             }  
  927.             /** 
  928.              * 打开结果列表 
  929.              */  
  930.             expand();  
  931.             /** 
  932.              * 为了确保焦点始终处于编辑框 
  933.              */  
  934.             setFocusOnTextComponent();  
  935.         }  
  936.     };  
  937.   
  938.     /********************************************************* 
  939.      *                 定义的一些接口 
  940.      */  
  941.     public interface CommitListener {  
  942.   
  943.         public void commit(String value);  
  944.     }  
  945.   
  946.     /** 
  947.      * 数据提供接口 
  948.      * @author Univasity 
  949.      */  
  950.     public interface DataProvider {  
  951.   
  952.         public Object getData(int index);  
  953.   
  954.         public void appendData(Object value);  
  955.   
  956.         public void insertData(int index, Object value);  
  957.   
  958.         public void replaceData(int index, Object value);  
  959.   
  960.         public void replaceData(Object oldValue, Object newValue);  
  961.   
  962.         public void removeDataAt(int index);  
  963.   
  964.         public void removeData(Object value);  
  965.   
  966.         public void clear();  
  967.   
  968.         public int getSize();  
  969.   
  970.         public Object[] toArray();  
  971.   
  972.         public void setDataChangeListener(DataChangeListener listener);  
  973.   
  974.         /** 
  975.          * 数据改变监听接口 
  976.          */  
  977.         public interface DataChangeListener {  
  978.   
  979.             public static final int APPEND = 1;  
  980.             public static final int INSERT = 2;  
  981.             public static final int REPLACE = 3;  
  982.             public static final int REMOVE = 4;  
  983.             public static final int CLEAR = 5;  
  984.   
  985.             public void dataChanged(int action, Object value);  
  986.         }  
  987.     }  
  988.   
  989.     public interface DataMatchHelper {  
  990.   
  991.         /** 
  992.          * 判断指定的用于匹配文本是否被允许 
  993.          * @param text 
  994.          * @return 
  995.          */  
  996.         public boolean isMatchTextAccept(String text);  
  997.   
  998.         /** 
  999.          * 判断给定的值是否与文本值匹配 
  1000.          * @param matchedText 
  1001.          * @param data 
  1002.          * @return 
  1003.          */  
  1004.         public boolean isDataMatched(String matchText, Object data);  
  1005.     }  
  1006.   
  1007.     /********************************************************* 
  1008.      *                       默认的实现 
  1009.      */  
  1010.     private class DefaultDataProvider implements DataProvider {  
  1011.   
  1012.         private ArrayList data;  
  1013.         private DataChangeListener listener;  
  1014.   
  1015.         public DefaultDataProvider() {  
  1016.             data = new ArrayList();  
  1017.         }  
  1018.   
  1019.         public synchronized Object getData(int index) {  
  1020.             return data.get(index);  
  1021.         }  
  1022.   
  1023.         public synchronized void appendData(Object value) {  
  1024.             if (data.add(value)) {  
  1025.                 if (listener != null) {  
  1026.                     listener.dataChanged(DataChangeListener.APPEND, value);  
  1027.                 }  
  1028.             }  
  1029.         }  
  1030.   
  1031.         public synchronized void insertData(int index, Object value) {  
  1032.             data.add(index, value);  
  1033.             if (listener != null) {  
  1034.                 listener.dataChanged(DataChangeListener.INSERT, value);  
  1035.             }  
  1036.         }  
  1037.   
  1038.         public synchronized void replaceData(int index, Object value) {  
  1039.             if (data.set(index, value).equals(value)) {  
  1040.                 if (listener != null) {  
  1041.                     listener.dataChanged(DataChangeListener.REPLACE, value);  
  1042.                 }  
  1043.             }  
  1044.         }  
  1045.   
  1046.         public synchronized void replaceData(Object oldValue, Object newValue) {  
  1047.             int index = data.indexOf(oldValue);  
  1048.             if (data.set(index, newValue).equals(newValue)) {  
  1049.                 if (listener != null) {  
  1050.                     listener.dataChanged(DataChangeListener.REPLACE, newValue);  
  1051.                 }  
  1052.             }  
  1053.         }  
  1054.   
  1055.         public synchronized void removeDataAt(int index) {  
  1056.             Object value = data.get(index);  
  1057.             data.remove(index);  
  1058.             if (listener != null) {  
  1059.                 listener.dataChanged(DataChangeListener.REMOVE, value);  
  1060.             }  
  1061.         }  
  1062.   
  1063.         public synchronized void removeData(Object value) {  
  1064.             if (data.remove(value)) {  
  1065.                 if (listener != null) {  
  1066.                     listener.dataChanged(DataChangeListener.REMOVE, value);  
  1067.                 }  
  1068.             }  
  1069.         }  
  1070.   
  1071.         public synchronized void clear() {  
  1072.             data.clear();  
  1073.             if (listener != null) {  
  1074.                 listener.dataChanged(DataChangeListener.CLEAR, null);  
  1075.             }  
  1076.         }  
  1077.   
  1078.         public synchronized int getSize() {  
  1079.             return data.size();  
  1080.         }  
  1081.   
  1082.         public synchronized Object[] toArray() {  
  1083.             return data.toArray();  
  1084.         }  
  1085.   
  1086.         public synchronized void setDataChangeListener(DataChangeListener listener) {  
  1087.             this.listener = listener;  
  1088.         }  
  1089.     }  
  1090.   
  1091.     /** 
  1092.      * 默认的数据匹配助手 
  1093.      */  
  1094.     private class DefaultDataMatchHelper implements DataMatchHelper {  
  1095.   
  1096.         public boolean isMatchTextAccept(String text) {  
  1097.             return (text != null && text.length() > 0);  
  1098.         }  
  1099.   
  1100.         public boolean isDataMatched(String matchText, Object value) {  
  1101.             if (value != null && value.toString().indexOf(matchText) != -1) {  
  1102.                 return true;  
  1103.             }  
  1104.             return false;  
  1105.         }  
  1106.     }  
  1107. }  
原创粉丝点击