[WPF自定義控制元件庫]排序、篩選以及高亮
1. 如何讓列表的內容更容易查詢
假設有這麼一個列表(資料來源在本地),由於內容太多,要查詢到其中某個想要的資料會比較困難。要優化這個列表,無非就是排序、篩選和高亮。
改造過的結果如上。
2. 排序
在WPF中要實現資料排序的功能有很多種,例如用Linq,但這種場景的標準做法是使用CollectionViewSource。
CollectionViewSource
是一種資料集合的代理類。它有兩個很重要的屬性:
Source 是資料來源的集合;
View 是經過處理後的資料檢視。
看上去感覺是不是很像資料庫裡的Table和View的關係?
在這個例子裡使用CollectionViewSource
private readonly CollectionViewSource _viewSource; public HighlightSample() { InitializeComponent(); _viewSource = new CollectionViewSource { Source = Employee.AllExecutives }; _viewSource.View.Culture = new System.Globalization.CultureInfo("zh-CN"); _viewSource.View.SortDescriptions.Add(new SortDescription(nameof(Employee.FirstName), ListSortDirection.Ascending)); EmployeeElement.ItemsSource = _viewSource.View; }
這段程式碼為CollectionViewSource
的Source賦值後,把CollectionViewSource
的View作為ListBox的資料來源。其中SortDescriptions用於描述View的排序方式。如果包含中文,別忘記將Culture
設定為zh-cn
。
至此排序的功能就實現了。文件中還提到CollectionViewSource
的其它資訊:
您可以將集合檢視作為繫結源集合,可用於導航和顯示集合中基於排序、 篩選和分組查詢,而無需操作基礎源集合本身的所有頂層。 如果Source實現INotifyCollectionChanged介面,所做的更改引起CollectionChanged事件傳播到View。
由於View不會更改Source,因此每個Source都可以有多個關聯的View。 使用View,可以通過不同方式顯示相同資料。 例如,可能希望在頁面左側顯示按優先順序排序的任務,而在頁面右側顯示按區域分組的任務。
3. 篩選
CollectionViewSource
的View屬性型別為ICollectionView介面,它提供了Filter屬性用於實現資料的過濾。在這個例子裡實現如下:
_viewSource.View.Filter = (obj) => (obj as Employee).DisplayName.ToLower().Contains(FilterElement.Text);
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
{
if (_viewSource != null)
_viewSource.View.Refresh();
}
這段程式碼實現了當輸入框的文字改變時重新整理View的功能。其中Refresh方法用於重新建立View,也就是重新整理檢視。
ICollectionView
還提供了一個DeferRefresh函式,這個函式用於進入延遲迴圈,該迴圈可用於將更改合併到檢視並延遲自動重新整理,在需要多次操作並重新整理資料量大的集合時可以用這個函式。
4. 高亮
<TextBox x:Name="FilterElement"
TextChanged="OnFilterTextChanged"/>
<ListBox Name="EmployeeElement"
Grid.Row="1"
Height="200"
Margin="0,8,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName}"
kino:TextBlockService.HighlightText="{Binding ElementName=FilterElement,Path=Text}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
UWP的高亮可以使用TextHighlighter這個類,實現起來很簡單。WPF中的高亮則是使用TextBlockService.HighlightText
附加屬性宣告要高亮的文字,然後將TextBlock的Text替換為處理過的Inlines
,使用方式如上。
private static void MarkHighlight(TextBlock target, string highlightText)
{
var text = target.Text;
target.Inlines.Clear();
if (string.IsNullOrWhiteSpace(text))
return;
if (string.IsNullOrWhiteSpace(highlightText))
{
target.Inlines.Add(new Run { Text = text });
return;
}
while (text.Length > 0)
{
var runText = string.Empty;
var index = text.IndexOf(highlightText, StringComparison.InvariantCultureIgnoreCase);
if (index > 0)
{
runText = text.Substring(0, index);
target.Inlines.Add(new Run { Text = runText, Foreground = _noHighlightBrush });
}
else if (index == 0)
{
runText = text.Substring(0, highlightText.Length);
target.Inlines.Add(new Run { Text = runText });
}
else if (index == -1)
{
runText = text;
target.Inlines.Add(new Run { Text = runText, Foreground = _noHighlightBrush });
}
text = text.Substring(runText.Length);
}
}
這是實現程式碼。其實用Regex.Split
程式碼會好看很多,但懶得改了。
本來應該是高亮匹配的文字,但實際使用中發覺把未匹配的文字置灰更好看,就這樣實現了。
5. 結語
這篇文章介紹了使用CollectionViewSource
實現的排序、篩選功能,以及使用附加屬性和Inlines
實現高亮功能。
不過這樣實現的高亮功能有個問題:不能定義高亮(或者低亮)的顏色,不管在程式碼中還是在XAML中。一種可行的方法是參考ToolTipService定義一大堆附加屬性,例如這樣:
<TextBox x:Name="FilterElement"
ToolTipService.ToolTip="Filter Text"
ToolTipService.HorizontalOffset="10"
ToolTipService.VerticalOffset="10"
TextChanged="OnFilterTextChanged"/>
這種方式的缺點是這一大堆附加屬性會導致程式碼變得很複雜,難以維護。ToolTipService還可以建立一個ToolTip類,把這個類設定為附加屬性的值:
<TextBox x:Name="FilterElement"
TextChanged="OnFilterTextChanged">
<ToolTipService.ToolTip>
<ToolTip Content="Filter Text"
HorizontalOffset="10"
VerticalOffset="10"/>
</ToolTipService.ToolTip>
</TextBox>
這種方式比較容易維護,但有人可能不明白ToolTipService.ToolTip
屬性的值為什麼既可以是文字(或圖片等其它內容),又可以是ToolTip型別,XAML如何識別。關於這一點我在下一篇文章會講解,並且重新實現高亮的功能以支援Style等功能。
也可以參考SearchableTextBlock寫一個高亮的文字框,一了百了,但我希望通過這個有趣的功能多介紹幾種知識。
6. 參考
CollectionViewSource Class (System.Windows.Data) Microsoft Docs
TextBlock.Inlines Property (System.Windows.Controls) Microsoft Docs
A WPF Searchable TextBlock Control with Highlighting WPF
7. 原始碼
TextBlockService.cs at master