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

[WPF自定义控件库]排序、筛选以及高亮的更多相关文章

  1. [WPF自定义控件库]使用TextBlockHighlightSource强化高亮的功能,以及使用TypeConverter简化调用

    1. 强化高亮的功能 上一篇文章介绍了使用附加属性实现TextBlock的高亮功能,但也留下了问题:不能定义高亮(或者低亮)的颜色.为了解决这个问题,我创建了TextBlockHighlightSou ...

  2. WPF 如何创建自己的WPF自定义控件库

    在我们平时的项目中,我们经常需要一套自己的自定义控件库,这个特别是在Prism这种框架下面进行开发的时候,每个人都使用一套统一的控件,这样才不会每个人由于界面不统一而造成的整个软件系统千差万别,所以我 ...

  3. [WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack)

    原文:[WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack) 1. 什么是滚动轮劫持# 这篇文章介绍一个很简单的继承自ScrollViewer的控件 ...

  4. [WPF自定义控件库]使用WindowChrome自定义RibbonWindow

    原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自 ...

  5. [WPF自定义控件库] 让Form在加载后自动获得焦点

    原文:[WPF自定义控件库] 让Form在加载后自动获得焦点 1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录" ...

  6. [WPF自定义控件库]以Button为例谈谈如何模仿Aero2主题

    1. 为什么选择Aero2 除了以外观为卖点的控件库,WPF的控件库都默认使用"素颜"的外观,然后再提供一些主题包.这样做的最大好处是可以和原生控件或其它控件库兼容,而且对于大部分 ...

  7. [WPF自定义控件库]简单的表单布局控件

    1. WPF布局一个表单 <Grid Width="400" HorizontalAlignment="Center" VerticalAlignment ...

  8. [WPF自定义控件库]使用WindowChrome的问题

    1. 前言 上一篇文章介绍了使用WindowChrome自定义Window,实际使用下来总有各种各样的问题,这些问题大部分都不影响使用,可能正是因为不影响使用所以一直没得到修复(也有可能别人根本不觉得 ...

  9. [WPF自定义控件库] 自定义控件的代码如何与ControlTemplate交互

    1. 前言 WPF有一个灵活的UI框架,用户可以轻松地使用代码控制控件的外观.例设我需要一个控件在鼠标进入的时候背景变成蓝色,我可以用下面这段代码实现: protected override void ...

随机推荐

  1. 12、UVC&V4L2的关系

    UVC是一种usb视频设备驱动.用来支持usb视频设备,凡是usb接口的摄像头都能够支持 V4L2是Linux下的视频采集框架.用来统一接口,向应用层提供API UVC: USB video clas ...

  2. CTF编程题-三羊献瑞(实验吧)解题随记

    题目如下.解题步骤参考的是https://cloud.tencent.com/developer/news/373865中作者的思路. 1.首先,两个四位数相加等于一个五位数,那么这个五位数的第一位必 ...

  3. nexus 搭建maven私服

    1. nexus 下载地址 https://sonatype-download.global.ssl.fastly.net/nexus/oss/nexus-2.14.4-03-bundle.tar.g ...

  4. Android——解决port占用问题导致的模拟器无法识别

    遇到一个问题:昨天模拟器工作还正常,今天eclipse就识别不了了.后来发现是360手机助手占用了5555port造成的,我就纳闷了,平时这个也不是自己主动启动.今天就启动了.废话不多说,就几个步骤就 ...

  5. 摆脱技术思维,转向产品思维——寻找“万能”IDC的苦恼

    背景:近期在新产品的开发任务完毕后一直在为寻找好的IDC和优质的托管服务忙碌.需求源自于我们重点要解决之前老版产品面临的国内外用户訪问速度慢甚至连接不上的问题. 除去架构技术上使用高性能.可扩展的方案 ...

  6. 源代码看CoordinatorLayout.Behavior原理

    在上一篇博客CoordinatorLayout高级使用方法-自己定义Behavior中,我们介绍了怎样去自己定义一个CoordinatorLayout的Behavior.通过文章也能够看出Behavi ...

  7. 用 cctld工具创建带有国家代码的IP地址表

    用 cctld工具创建带有国家代码的IP地址表 cctld tools is creating IP addresses table with Country Code 项目地址 https://gi ...

  8. 用两个栈实现队列与用两个队列实现栈(Python实现)

    用两个栈实现队列: class QueueWithTwoStacks(object): def __init__(self): self._stack1 = [] self._stack2 = [] ...

  9. STM32之CAN通讯接收过滤器过滤分析

    一.前言 学习了CAN通讯,底层的东东CAN控制器已经帮你处理完成,也就是CAN通讯协议已经做好,你按协议格式往对应的位扔数据发送就好,所以使用CAN通讯,我们只需要去关心制定发送的数据间的协议,也就 ...

  10. 通过Ajax进行POST提交JSON类型的数据到SpringMVC Controller的方法

    现在在做的项目用到了SpringMVC框架,需要从前端angular接收请求的JSON数据,为了测试方便,所以直接先用AJAX进行测试,不过刚开始用平时用的ajax方法,提交请求会出现415或者400 ...