<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    TWaver - 專注UI技術(shù)

    http://twaver.servasoft.com/
    posts - 171, comments - 191, trackbacks - 0, articles - 2
      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

    讓JTextField添加“自動完成”功能

    Posted on 2012-06-12 14:28 TWaver 閱讀(2568) 評論(4)  編輯  收藏
         在越來越重視“用戶體驗”的今天,一個簡單的文本框也演進(jìn)的越來越智能了。比如Google的搜索,當(dāng)我們輸入搜索關(guān)鍵字的過程中,文本框就會動態(tài)的下拉列出最常輸入的近似文字,以便我們快速輸入要查詢的內(nèi)容。當(dāng)然一直抄襲Google的百度自然也是一樣。類似的例子還有很多,例如一般的郵件客戶端,在敲入地址時,也會動態(tài)列出符合要求的地址,方便快速錄入,也會減少出錯。


         那么,Swing的文本框要做到這一點是否容易呢?網(wǎng)上的例子也能搜索到一些,不過要么功能做的太簡單,要么實現(xiàn)的代碼太繁瑣羅嗦。還有一些商業(yè)的Swing組件,則完全是要付費的。本文結(jié)合了2BizBox免費ERP軟件開發(fā)中的實踐,嘗試了一種非常簡單、有效的方法來制作這一效果。

         首先仔細(xì)觀察這種效果:它外觀上、本質(zhì)上,都完全是一個文本框,而不是下拉框。所以,我們不想把它做成下拉框,也就是不想從JComboBox繼承。另外,下拉列表提示的出現(xiàn),是完全異步、動態(tài)的,它僅僅作為提示,不能干預(yù)正常的文本框的輸入。最后,那個下拉列表的外觀和行為則完全是一個JComboBox的下拉列表行為。所以,這個“可自動完成的JTextField”應(yīng)當(dāng)是一個JTextField和JComboBox下拉列表部分的結(jié)合體。
         經(jīng)過以上分析,思路基本確定:它本質(zhì)是一個JTextField,但是又結(jié)合利用了一個JComboBox的下拉列表。二者合而為一即可。那么是從誰繼承呢?JTextField嗎?
         仔細(xì)想想,繼承并不是最好的方法。俗話說:繼承是混蛋。能不繼承就不要繼承。為啥呢?繼承,意味著別人只能繼承你的類,才能使用這一功能。假如你的項目已經(jīng)寫了一萬多個界面,想給這里面的一些文本框增加這種智能提示功能,難道要對所有代碼進(jìn)行修改,讓那些東西重新繼承你的類嗎?這無疑是個爛主意。所以,那些剛學(xué)會OO的童鞋,總是喜歡動不動就要繼承的思路,并不妥當(dāng)。如果我們只是提供一個Util方法,對已經(jīng)存在的普通JTextField實例處理一下,就可以具有智能提示,豈不是更好?
         要做到JTextField和JComboBox這兩個組件的結(jié)合,這里使用了非常“怪異”的一個絕招,你絕對想不到:把一個JComboBox塞到JTextField的身體里面,并讓它看不見。看一下代碼:
    1 JTextField txtInput = new JTextField();
    2 JComboBox cbInput = new JComboBox();
    3 txtInput.setLayout(new BorderLayout());
    4 txtInput.add(cbInput, BorderLayout.SOUTH);

         什么?把JTextField設(shè)置一個layout?并且還add一個JComboBox且放在SOUTH?我相信你絕對聞所未聞這種事情。怎么看都是怪胎啊。不要緊,把JComboBox的高度變成0,別人就看不出破綻了:
    1 JComboBox cbInput = new JComboBox(model) {
    2     public Dimension getPreferredSize() {
    3         return new Dimension(super.getPreferredSize().width, 0);
    4     }
    5 };

         雖然combo看不見,但是它實實在在存在于文本框的身體里,且位于其下方。我們的思路是:當(dāng)文本框輸入內(nèi)容時,我們判斷下拉框中是否有符合要求的列表,如果有,就馬上主動彈出下拉;否則就讓下拉消失。
         監(jiān)控文本框輸入并不難:給它的document增加listener就行了。這里我們使用了“不區(qū)分大小寫”、“和輸入字符串開頭相同的項”的規(guī)則進(jìn)行過濾。將所有備選字符串置于單獨一個數(shù)組中,每次用戶輸入后,動態(tài)過濾出符合條件的字符串,動態(tài)添加到JComboBox中,并將其下拉列表Popup出來即可:
     1 txtInput.getDocument().addDocumentListener(new DocumentListener() {
     2     public void insertUpdate(DocumentEvent e) {
     3         updateList();
     4     }
     5 
     6     public void removeUpdate(DocumentEvent e) {
     7         updateList();
     8     }
     9 
    10     public void changedUpdate(DocumentEvent e) {
    11         updateList();
    12     }
    13 
    14     private void updateList() {
    15         setAdjusting(cbInput, true);
    16         model.removeAllElements();
    17         String input = txtInput.getText();
    18         if (!input.isEmpty()) {
    19             for (String item : items) {
    20                 if (item.toLowerCase().startsWith(input.toLowerCase())) {
    21                     model.addElement(item);
    22                 }
    23             }
    24         }
    25         cbInput.setPopupVisible(model.getSize() > 0);
    26         setAdjusting(cbInput, false);
    27     }
    28 });

        此外,為了更方便操作,我們再增加幾個快捷鍵:當(dāng)輸入ESC,主動關(guān)掉下拉列表;當(dāng)輸入回車或空格,直接把第一項符合要求的字符串輸入文本框:
     1 txtInput.addKeyListener(new KeyAdapter() {
     2 
     3     @Override
     4     public void keyPressed(KeyEvent e) {
     5         setAdjusting(cbInput, true);
     6         if (e.getKeyCode() == KeyEvent.VK_SPACE) {
     7             if (cbInput.isPopupVisible()) {
     8                 e.setKeyCode(KeyEvent.VK_ENTER);
     9             }
    10         }
    11         if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_DOWN) {
    12             e.setSource(cbInput);
    13             cbInput.dispatchEvent(e);
    14             if (e.getKeyCode() == KeyEvent.VK_ENTER) {
    15                 txtInput.setText(cbInput.getSelectedItem().toString());
    16                 cbInput.setPopupVisible(false);
    17             }
    18         }
    19         if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
    20             cbInput.setPopupVisible(false);
    21         }
    22         setAdjusting(cbInput, false);
    23     }
    24 });

          還有一個非常重要的技術(shù)要點要進(jìn)行說明。在popup列表彈出的時候,我們希望用箭頭能夠上下移動選擇條目,但是又同時希望當(dāng)前的光標(biāo)和焦點不要離開文本框。這個好像非常難實現(xiàn)啊!請看我們是如何做到的:在監(jiān)控到上下箭頭輸入時候,把當(dāng)前的鍵盤事件的source動態(tài)修改為JComboBox,然后派發(fā)給JComboBox。也就是說,本來事件是輸入到文本框的,我們把郵遞員攔截下來,把收件人改一下,繼續(xù)交給郵遞員進(jìn)行派發(fā)。這樣,就做到“移花接木”了:
    1 if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_DOWN) {
    2     e.setSource(cbInput);
    3     cbInput.dispatchEvent(e);
    4     if (e.getKeyCode() == KeyEvent.VK_ENTER) {
    5         txtInput.setText(cbInput.getSelectedItem().toString());
    6         cbInput.setPopupVisible(false);
    7     }
    8 }

          最后,為了演示效果,我們放一些數(shù)據(jù)到下拉列表中。放什么呢?自己造假數(shù)據(jù)太麻煩了,干脆用Java中的“所有國家”的數(shù)據(jù)吧,簡單省事:
    1 Locale[] locales = Locale.getAvailableLocales();
    2 for (int i = 0; i < locales.length; i++) {
    3     String item = locales[i].getDisplayName();
    4     items.add(item);
    5 }

          最后看一下效果,完全符合我們的預(yù)期:
          以下是完整代碼:
      1 import java.awt.*;
      2 import java.awt.event.*;
      3 import java.util.*;
      4 
      5 import javax.swing.*;
      6 import javax.swing.event.*;
      7 
      8 import twaver.*;
      9 
     10 public class Test {
     11 
     12     public static void main(String[] args) throws Exception {
     13         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
     14         JFrame frame = new JFrame();
     15         frame.setTitle("Auto Completion Test");
     16         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     17         frame.setBounds(200, 200, 500, 400);
     18 
     19         ArrayList<String> items = new ArrayList<String>();
     20         Locale[] locales = Locale.getAvailableLocales();
     21         for (int i = 0; i < locales.length; i++) {
     22             String item = locales[i].getDisplayName();
     23             items.add(item);
     24         }
     25         JTextField txtInput = new JTextField();
     26         setupAutoComplete(txtInput, items);
     27         txtInput.setColumns(30);
     28         frame.getContentPane().setLayout(new FlowLayout());
     29         frame.getContentPane().add(txtInput, BorderLayout.NORTH);
     30         frame.setVisible(true);
     31     }
     32 
     33     private static boolean isAdjusting(JComboBox cbInput) {
     34         if (cbInput.getClientProperty("is_adjusting") instanceof Boolean) {
     35             return (Boolean) cbInput.getClientProperty("is_adjusting");
     36         }
     37         return false;
     38     }
     39 
     40     private static void setAdjusting(JComboBox cbInput, boolean adjusting) {
     41         cbInput.putClientProperty("is_adjusting", adjusting);
     42     }
     43 
     44     public static void setupAutoComplete(final JTextField txtInput, final ArrayList<String> items) {
     45         final DefaultComboBoxModel model = new DefaultComboBoxModel();
     46         final JComboBox cbInput = new JComboBox(model) {
     47             public Dimension getPreferredSize() {
     48                 return new Dimension(super.getPreferredSize().width, 0);
     49             }
     50         };
     51         setAdjusting(cbInput, false);
     52         for (String item : items) {
     53             model.addElement(item);
     54         }
     55         cbInput.setSelectedItem(null);
     56         cbInput.addActionListener(new ActionListener() {
     57             @Override
     58             public void actionPerformed(ActionEvent e) {
     59                 if (!isAdjusting(cbInput)) {
     60                     if (cbInput.getSelectedItem() != null) {
     61                         txtInput.setText(cbInput.getSelectedItem().toString());
     62                     }
     63                 }
     64             }
     65         });
     66 
     67         txtInput.addKeyListener(new KeyAdapter() {
     68 
     69             @Override
     70             public void keyPressed(KeyEvent e) {
     71                 setAdjusting(cbInput, true);
     72                 if (e.getKeyCode() == KeyEvent.VK_SPACE) {
     73                     if (cbInput.isPopupVisible()) {
     74                         e.setKeyCode(KeyEvent.VK_ENTER);
     75                     }
     76                 }
     77                 if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_DOWN) {
     78                     e.setSource(cbInput);
     79                     cbInput.dispatchEvent(e);
     80                     if (e.getKeyCode() == KeyEvent.VK_ENTER) {
     81                         txtInput.setText(cbInput.getSelectedItem().toString());
     82                         cbInput.setPopupVisible(false);
     83                     }
     84                 }
     85                 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
     86                     cbInput.setPopupVisible(false);
     87                 }
     88                 setAdjusting(cbInput, false);
     89             }
     90         });
     91         txtInput.getDocument().addDocumentListener(new DocumentListener() {
     92             public void insertUpdate(DocumentEvent e) {
     93                 updateList();
     94             }
     95 
     96             public void removeUpdate(DocumentEvent e) {
     97                 updateList();
     98             }
     99 
    100             public void changedUpdate(DocumentEvent e) {
    101                 updateList();
    102             }
    103 
    104             private void updateList() {
    105                 setAdjusting(cbInput, true);
    106                 model.removeAllElements();
    107                 String input = txtInput.getText();
    108                 if (!input.isEmpty()) {
    109                     for (String item : items) {
    110                         if (item.toLowerCase().startsWith(input.toLowerCase())) {
    111                             model.addElement(item);
    112                         }
    113                     }
    114                 }
    115                 cbInput.setPopupVisible(model.getSize() > 0);
    116                 setAdjusting(cbInput, false);
    117             }
    118         });
    119         txtInput.setLayout(new BorderLayout());
    120         txtInput.add(cbInput, BorderLayout.SOUTH);
    121     }
    122 }

    評論

    # re: 讓JTextField添加“自動完成”功能  回復(fù)  更多評論   

    2012-06-12 14:37 by 杭州房產(chǎn)
    老師說的真的很清楚,對于我們想要學(xué)習(xí)變成的人真的很有幫助,謝謝老師。

    # re: 讓JTextField添加“自動完成”功能  回復(fù)  更多評論   

    2012-06-18 09:25 by allenny
    幾行

    # re: 讓JTextField添加“自動完成”功能  回復(fù)  更多評論   

    2012-07-05 10:13 by 唐軍虎
    誰都知道,繼承是面向?qū)ο蟮幕舅枷胫唬还膭罾^承,我懷疑作者的水平。
    全部的代碼,從上到下,過程執(zhí)行,有幾個函數(shù),但是總體來說,封裝性極差。

    # re: 讓JTextField添加“自動完成”功能  回復(fù)  更多評論   

    2012-07-05 10:15 by 唐軍虎
    另外,程序的可讀性極差,這不是用來欣賞的代碼,而是用來害人的代碼,不看為好。

    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 久久99热精品免费观看牛牛| 亚洲一区二区三区免费在线观看 | 亚洲成a人片在线观看天堂无码| 亚洲国产精品无码久久青草| a视频在线免费观看| jlzzjlzz亚洲jzjzjz| 亚洲成AV人在线观看网址| 18禁美女黄网站色大片免费观看| 亚洲а∨精品天堂在线| 婷婷亚洲久悠悠色悠在线播放| 毛片免费在线播放| 色欲A∨无码蜜臀AV免费播| 亚洲国产美女精品久久久 | 国产成人va亚洲电影| 亚洲性猛交xx乱| 久久久久久亚洲精品不卡| 最近最好的中文字幕2019免费| 国产一级a毛一级a看免费视频 | 国产精品免费视频观看拍拍| 亚洲一级免费毛片| 亚洲AV永久青草无码精品| 啊v在线免费观看| 国产精品无码免费视频二三区| free哆啪啪免费永久| 在线人成精品免费视频| 日本在线免费观看| 男女交性无遮挡免费视频| 亚洲avav天堂av在线网毛片| 中国亚洲呦女专区| 精品久久亚洲中文无码| 亚洲狠狠狠一区二区三区| 综合自拍亚洲综合图不卡区| 精品亚洲一区二区| 国产亚洲美女精品久久久久狼| 亚洲综合最新无码专区| 亚洲视频在线一区二区| 中文字幕在亚洲第一在线| 中文字幕亚洲不卡在线亚瑟| 亚洲无线观看国产精品| 国产精品亚洲A∨天堂不卡| 亚洲国产成人精品不卡青青草原|