WPF製作的一個小功能,輸入智慧提示(IntelliSense)
最近WPF專案中遇到一個需求,需要給一個RichTextBox新增智慧提示(IntelliSense)功能。
分析下具體的需求,在使用者鍵入"@"符號時,應該顯示一個彈出框,把所有使用者列出。使用者可以通過鍵盤、滑鼠等進行選擇。使用者列表可能資料比較多,那麼使用者還應該可以輸入字元進行篩選。用過各種IDE開發工具的童鞋應該對這樣的效果很瞭解了,具體效果如下
輸入@符號的效果:
篩選的效果:
再談談具體的開發思路.
1.如何製作可以實現列表選擇功能的彈出框
方法很多,Popup+ListBox可以完美解決.此處我為了省程式碼,直接用的ListBox
2.如何在鍵入@符號時,將ListBox顯示在@符號之後
在VisualStudio的智慧提示裡,當我們觸發了IntelliSense時,提示框會顯示,並且與下一字元的插入點對齊。TextPointer類提供了方法可以獲取到某個插入點的座標:
RichTextBox textbox = this.RichTextBox;
TextPointer start = textbox.Selection.Start;
Rect rect = start.GetCharacterRect(LogicalDirection.Forward);
Point point = rect.BottomLeft;
我們可以註冊RichTextBox的鍵盤事件,判斷使用者是否鍵入@符號。@符號是由“Shift”+“2”組成,Keyboard.Modifiers可以獲取到組合鍵:
if (Keyboard.Modifiers == ModifierKeys.Shift && e.Key == Key.D2)
{
//TODO: 使用者鍵入了@符號
}
在TODO裡,將ListBox移動到得到Point位置即可。
3.如何實現選擇和篩選
篩選功能很簡單,儲存一個區域性變數,在智慧提示框顯示後記錄用戶輸入,智慧提示框隱藏後清空,智慧提示框中的資料按照該變數進行過濾即可。
當智慧提示框顯示的時候,使用者是可以鍵入“上”,“下”鍵進行移動選擇的。也許在敲了幾次方向鍵,使用者還想繼續輸入字元進行篩選。最開始,我是用的ListBox自動的選擇功能,當用戶敲入方向鍵,我就將鍵盤焦點(WPF中分鍵盤焦點和邏輯焦點,這個也是困擾我很久的問題)設定到ListBox上,那麼使用者就可以敲入“上”,“下”鍵進行移動選擇了。看起來很簡單,但是這樣是有問題的,因為使用者如果想繼續敲入字元篩選,我還必須將鍵盤焦點重新設定到RichTextBox上,否則使用者的敲擊是無效的。
後來突然想通了,在使用者敲擊“上”,“下”鍵時,只需要呼叫ListBox的MoveCurrentToPrevious()和MoveCurrentToNext()即可,這樣給使用者的錯覺還是有了上下移動的效果。焦點不在ListBox上時,這樣的移動可能造成當前選中項超出了顯示範圍之外,那麼可以通過ListBox的ScrollIntoView()方法,將選中物件滾動到檢視中。
下面是擷取的一段程式碼:
//如果按了向下鍵,則把選中項下移
if (e.Key == Key.Down)
{
if (UserList.CurrentPosition != UserList.Count - 1)
{
lbUser.Items.MoveCurrentToNext();
lbUser.ScrollIntoView(lbUser.Items.CurrentItem);
}
e.Handled = true;
}
//如果按了向上鍵,則把選中項上移
if (e.Key == Key.Up)
{
if (UserList.CurrentPosition != 0)
{
lbUser.Items.MoveCurrentToPrevious();
lbUser.ScrollIntoView(lbUser.Items.CurrentItem);
}
e.Handled = true;
}
4.實現選中和取消
選中功能就更簡單了,分別加入對滑鼠雙擊,空格,回車,TAB等的判斷,將ListBox的當前選中項的文字插入到RichTextBox中即可。需要注意的是,此處要對單擊做判定,由於單擊ListBox會使鍵盤焦點設定到其之上,因此要強制將鍵盤焦點從ListBox移開。判斷ESC鍵,使ListBox隱藏即可實現取消功能。
5.如何實現擴充套件
做一個功能最重要的就是考慮以後的重用,此處可以公開KeyBoard.Modifyers,KeyCode,IEnumable<T>為依賴屬性,前2個代表在敲入什麼組合鍵時會彈出智慧提示框,最後一個是彈出內容的資料來源。由於此處的篩選功能是在控制元件內部,那麼我們可以定義一個介面,包含一個Name屬性。
public interface IData
{
//用於篩選和插入的名稱
string Name { get; set; }
}
將上面的IEnumable<T>改為IEnumable<IData>。
以後的呼叫方,只需要將這3箇中的一個或多個傳入,即可實現智慧提示功能。