zoukankan      html  css  js  c++  java
  • [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

  • 相关阅读:
    13 文件操作
    10 dict嵌套与升级
    08 连接和顺序列表
    01 Python 逻辑运算
    了解bordercollapse
    orchard模块编写的错误及其解决办法
    orchard文档之理解内容处理器
    orchard文档之理解数据访问
    orchard文档之orchard工作原理
    orchard文档之创建自定义表单
  • 原文地址:https://www.cnblogs.com/dino623/p/sort_filter_highlight.html
Copyright © 2011-2022 走看看