通通WPF隨筆(1)——基於lucene.NET讓ComboBox擁有強大的下拉聯想功能


 


 

  我一直很疑惑百度、谷哥搜索框的下拉聯想功能是怎么實現的?是不斷地查詢數據庫嗎?其實到現在我也不知道,他們是怎么實現這么高效的。后來在博客園無意邂逅了“鹿神”,搜索引擎唉,聽起來就很高端。於是研究了一段時間后就產生了這個WPF的下拉聯想控件。

名稱:

簡拼:

全拼:

區號:

郵編:

              

  這么強大的功能代碼一定會復雜吧?不是的哦,親~代碼只有短短幾句哦

界面如下:(下拉框后面的數字為查詢的延時,可見效率還是很高滴)

XAML:

<cop:CopAutoCompleted url="{Binding Text, ElementName=DirTextBox}" columnNames="{Binding Text, ElementName=UCSearchColTextBox}" 
textName
="{Binding Text, ElementName=TextNameTextBox}" maxItems="{Binding Text, ElementName=TextNameTextBox_Copy}"> <cop:CopAutoCompleted.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding City}" /> <TextBlock Text=" (" Foreground="#FF383838"/> <TextBlock Text="{Binding Spell}" Foreground="#FF383838" /> <TextBlock Text=")" Foreground="#FF383838"/> </StackPanel> </DataTemplate> </cop:CopAutoCompleted.ItemTemplate> </cop:CopAutoCompleted>

屬性介紹:(該控件繼承於ComboBox,只是多了下面4個屬性)

  url:設置索引所在的文件夾(稍后會介紹如何創建索引)

  columnNames:設置需要檢索的列名

  textName:選擇下拉項后顯示在text里的列名

  maxItems:下拉框最多顯示多少項(如果顯示內容過多的話會有延時的感覺,經測試延時是由於后台banding的數據集合改變跟新到界面時產生的,不是lucene的效率問題)

  ItemTemplate:玩WPF的都懂的,設置下拉顯示數據的布局內容。這樣的話就有了很高的可擴展性和靈活性。

 

1.總體思路


  (1)創建lucene索引:在網上找一個全國城市的數據庫,用代碼提取出來,分別對里面的各列創建索引。

  (2)查詢索引:通過lucene的PrefixQuery類構造查詢語句,就可以實現前綴查詢出整體。

  (3)ComboBox綁定:這里數據源綁定到ObservableCollection<dynamic>集合(自動通知,方便啊),而其中的每一項為根據查詢出的每一項結果動態構造的對象。所以用到dynamic運行時解析。

  

 

2.詳細設計


  基本知識我這里就不詳細說了,可參看文章最后的參考文獻。

1、創建lucene索引

  我在網上找全國城市數據庫時找找到的一個比較全面的是Access的,所以這里特地寫了一個創建索引的功能:                    

 

  (之前比較流行通用數據庫訪問層,我基於反射自己寫了一個通用數據庫DBHelper,由於電腦上沒有數據庫環境,所以只測試了AccessSqlite

   其實就是根據查詢結果,對需要創建索引的列添加lucene的索引。代碼如下:

View Code
private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            //設置索引文件夾
            var directory = FSDirectory.GetDirectory(DirTextBox.Text, true);

            //創建一個索引,采用StandardAnalyzer對句子進行分詞
            IndexWriter indexWriter = new IndexWriter(directory, new StandardAnalyzer());

            var columnName= ColumnNameTextBox.Text.Split(',');

            //設置數據庫連接字符串
            if (ComboBox1.Text=="Sqlite")
            {
                helper=new CopDb.CopDbHelper(CopDb.CopDbHelper.CopDbType.Sqlite,ConnTestBox.Text);
            }
            if (ComboBox1.Text=="Access")
            {
                helper = new CopDb.CopDbHelper(CopDb.CopDbHelper.CopDbType.Access, ConnTestBox.Text);
            }           
            
            int timeOut = Environment.TickCount;
            var read = helper.ExecuteReader(SQLStrTextBox.Text);
            SqlTimeTextBox.Text = (Environment.TickCount - timeOut).ToString();
            while (read.Read())
            {
                //創建文檔
                Document doc = new Document();
                //添加字段
                foreach (var item in columnName)
                {
                    doc.Add(new Field(item, read[item].ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
                }
                indexWriter.AddDocument(doc);
            }
            read.Close();

           //對索引文件進行優化
           indexWriter.Optimize();
           indexWriter.Close();
           MessageBox.Show("創建索引完成");
        }

 

2、查詢索引

  

  就是構造lucene查詢query時用PrefixQuery類就行,如下:

 var cols = SearchColTextBox.Text.Split(',');

            BooleanQuery query = new BooleanQuery();

            foreach (var item in cols)
            {
                query.Add(new PrefixQuery(new Term(item, SearchTextBox.Text)),BooleanClause.Occur.SHOULD);
            }
           //query.parse:注入查詢條件
           var hits = search.Search(query);

 

 

3、ComboBox綁定數據源

  

  數據源為ObservableCollection<dynamic>類型集合,后台我們只用動態構造出每一個查詢對象添加進集合里即可。初始化dynamic對象時還不能用ExpandoObject,雖然ExpandoObject很方便,但是這是一個封閉類,不能繼承。ComboBox在選中其中一項顯示到文本框里時,其實是執行了選中項數據源的ToString()方法。所以不能重載ExpandoObjectToString()方法。所以這里自定義了一個輕量級的ExpandoObject類,繼承於DynamicObject實現。

 代碼:

class dyData:DynamicObject
        {
            public dyData(string colName)
            {
                this.colName = colName;
            }
            //ToString時需要輸出的屬性
            public string colName { get; set; }
            //用於存儲屬性名和對應的值
            Dictionary<string, object> data = new Dictionary<string, object>();
            //綁定時獲取對應屬性的值
            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                return data.TryGetValue(binder.Name,out result);
            }
            //用於添加屬性和對應的值
            public void SetValue(string name, object value)
            {
               data.Add(name, value);
            }

            //重寫Tostring方法
            public override string ToString()
            {
                try
                {
                    return data[colName].ToString();
                }
                catch (Exception ex)
                {
                    MessageBox.Show("找不到列名"+colName,"設置text要顯示的項名時出錯",MessageBoxButton.OK,MessageBoxImage.Error);
                    return null;
                }
            }

        }

  這樣就實現了一個簡易的ExpandoObject了。接下來遍歷查詢結果,通過SetValue動態創建對象的屬性,添加進ObservableCollection<dynamic>數據集合,ComboBox直接數據綁定即可。

 

下載:demo

 

參考文獻:

  lucene,你也會(7篇)——第一篇 快速入門

  使用Lucene.Net實現全文檢索

  WPF地區選擇控件(內附下載地址)

  

后記


  其實相同的功能我用查詢數據庫的方法,也實現過了,但是耗時每次都是100多毫秒。lucene估計有個緩存吧,速度會越來越快,而且經常被查尋的東西優先級別會提高,排在前面。

  以我的經驗,寫關於美工的文章比邏輯的獲得的關注和推薦多得多。我也很想把通通玩Blend美工這個系列寫下去,畢竟我大部分的粉絲都來源於這個系列。但是,最近幾個月,都在糾結WF、WCF等等邏輯方面的,對美工沒什么好的創意。  

  寫博客圖個什么?不就是作為一個平凡的碼農,想要得到更多人的關注和認可,讓我覺得自己其實和民工還是有點區別的。

  對了,我之前嵌在博客里的silverlight為什么都顯示不出來了?xap文件我都是放在博客園的文件里的。求大神解答。

  

 

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM