用WPF实现查找结果高亮显示
概述
我们经常会遇到这样的需求:到数据库里查找一些关键字,把带这些关键字的记录返回显示在客户端上。但如果仅仅是单纯地把文本显示出来,那很不直观,用户不能很轻易地看到他们想找的内容,所以通常我们还要做到“高亮显示”。
如果是用BS架构去实现,应该很简单,把相应的关键字加上一些label,然后给label定样式即可,或者直接用js在客户端渲染,减少服务器的负担,但CS架构就相对麻烦一点,我这里用WPF写了一个demo,实现了这个功能的演示:

另外本demo还包括了一些非常有用的wpf的小技巧。
功能介绍
由于这只是一个简单的DEMO,我和以往的风格一样,把它做成了“零配置”,我用一个csv文件和LINQ to Object来取代DBMS,执行一些简单的查询操作。
查询方式分为两种,一种是Full Match,表示全字符匹配,另一种是Any Key,表示用空格断开查询字符串,逐个关键字查询。
这个程序的显示区域使用了ListView控件,之所以使用ListView而不是DataGrid,主要是ListView能很轻易地自适应行高,而DataGrid的行高是固定的,但如果你要换DataGrid去做的话,应该也是同一个道理。
高亮显示功能分析与实现
要实现高亮显示,我们可以这么做:在界面上放置一个TextBlock,叫tbTest,然后执行下面的代码:
tbTest.Inlines.Clear();
tbTest.Inlines.Add( new Run("The"){ Background = Brushes.Yellow });
tbTest.Inlines.Add( " quick brown fox jumps over ");
tbTest.Inlines.Add( new Run("the") { Background = Brushes.Yellow });
tbTest.Inlines.Add( new Run(" lazy dog."));
就能看到这样的“高亮”效果:

遗憾的是Inlines这个属性并非“依赖属性”(Dependency Property),你不能轻易把一个字符串或对象“绑定”给它。我的做法是创建一个用户控件,其中只包含一个TextBlock:
<UserControl x:class="HighlightDispDemo.HighlightTextBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TextBlock Name="innerTextBlock" TextWrapping="Wrap">
</TextBlock>
</UserControl>
再给它增加一个叫“HlContent”的依赖属性,其类型为自定义的HighlightContent:
public static readonly DependencyProperty HighlightContentProperty = DependencyProperty .Register( "HlContent",
typeof(HighlightContent),
typeof( HighlightTextBlock),
new FrameworkPropertyMetadata( null, OnHtContentChanged)); [ Description("获取或设置高亮显示的内容")]
[ Category("Common Properties")]
public HighlightContent HlContent
{
get { return(HighlightContent)GetValue( HighlightContentProperty); }
set { SetValue( HighlightContentProperty, value); }
}
HighlightContent的定义如下:
public enum HighlightContentMode
{
FullMatch,
AnyKey
}; public class HighlightContent
{
public string Content { get; set; }
public static string ToHighlight { get; set; }
public static HighlightContentMode Mode { get; set; }
}
其中ToHighlight属性表示要高亮显示的“键”,而Mode属性则用来指明用“Full Match”还是“Any Key”模式,考虑到同一时间只有一种高亮显示,我把这两个属性定义为static。
“HlContent”的内容变更通知回调函数:
private static void OnHtContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
{
if(sender is HighlightTextBlock)
{
HighlightTextBlock ctrl = sender as HighlightTextBlock ;
HighlightContent content = ctrl.HlContent ;
ctrl.innerTextBlock.Inlines.Clear();
if(content != null)
{
ctrl.innerTextBlock.Inlines.AddRange(MakeRunsFromContent( content));
}
}
} private static IEnumerable<Run> MakeRunsFromContent(HighlightContent content)
{
//此函数功能是:将要显示的字符串根据key及mode,拆分成不同的Run片段
//代码较多,从略
}
这样一来,我们就可以用自定义的HighlightTextBlock来取代Textblock实现绑定了。
绑定到ListView
ListView的默认的Column是肯定不支持“高亮”显示的了,现在我们来自定义Template:
<ListView ItemContainerStyle="{DynamicResource CustomListViewItemStyle}" Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Name="lvContent" AlternationCount="2">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="OS Name" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<hld:HighlightTextBlock HlContent="{Binding Path=OsName,Converter={StaticResource converterHlContent}}"></hld:HighlightTextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="File System" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<hld:HighlightTextBlock HlContent="{Binding Path=FileSystem,Converter={StaticResource converterHlContent}}"></hld:HighlightTextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Desktop" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<hld:HighlightTextBlock HlContent="{Binding Path=Desktop,Converter={StaticResource converterHlContent}}"></hld:HighlightTextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
可以看到,Template中使用了前面我们自定义的HighlightTextBlock控件,它们绑定的Path分别是OsName,FileSystem和Desktop,其实这都是string,而HlContent需要的是HighlightContent类型,所以我们还得指定一个转换器,转换器代码如下:
[ValueConversion(typeof(string), typeof(HighlightContent))]
public class HlContentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new HighlightContent {Content = (string)value};
} public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
杂七杂八
使用CsvHelper来读取CSV文件
这次我没有使用DBMS,其实DEMO项目能不用DBMS就不用了,否则部署困难,不利于问题分析。CsvHelper可以从github上获取,地址是:https://github.com/JoshClose/CsvHelper
它的帮助写得稍微有点潦草(个人感觉),我这里稍稍补充说明下:CsvHelper的思路就是把csv文件转为一个可枚举的集合,其中的一行转为集合中的一个对象,那么一列就对应到这个对象的一个属性,那么究竟哪一列转为那个属性呢?我们得告诉它,这就是“Map”,了解了这个之后看一下下面的代码,一切都清楚了。
public class LinuxInfo
{
public string OsName { get; set; }
public string FileSystem { get; set; }
public string Desktop { get; set; }
} public class LinuxMap : CsvClassMap<LinuxInfo>
{
public override void CreateMap()
{
Map(m => m.OsName).Index();
Map(m => m.FileSystem).Index();
Map(m => m.Desktop).Index();
}
}
上面代码是对象及Map定义。下面是执行读取和转换的操作。
TextReader tr = new StreamReader("linux.csv", Encoding.UTF8);
CsvReader csv = new CsvReader(tr);
csv.Configuration.RegisterClassMap<LinuxMap>();
csv.Configuration.HasHeaderRecord = false; //表示csv文件不带header行
_listData = csv.GetRecords<LinuxInfo>().ToList();
ListView的隔行背景样式
把ListView的AlternationCount属性设为2,并指定ItemContainerStyle="{DynamicResource CustomListViewItemStyle}"。Style这样定义:
<Style x:Key="CustomListViewItemStyle" TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="#DDEEFF"></Setter>
</Trigger>
</Style.Triggers>
</Style>
让TextBox获得焦点时全选文本
这个功能得在App.xml.cs中做一些全局处理:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e); //为了让TextBox能够在获得焦点的时候自动选中其中文本,特意添加此全局事件处理
EventManager.RegisterClassHandler(typeof(TextBox), UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(SelectivelyHandleMouseButton), true);
EventManager.RegisterClassHandler(typeof(TextBox), UIElement.GotKeyboardFocusEvent, new RoutedEventHandler(SelectAllText), true);
} private static void SelectivelyHandleMouseButton(object sender, MouseButtonEventArgs e)
{
var textbox = (sender as TextBox);
if (textbox != null && !textbox.IsKeyboardFocusWithin)
{
if (e.OriginalSource.GetType().Name == "TextBoxView")
{
e.Handled = true;
textbox.Focus();
}
}
} private static void SelectAllText(object sender, RoutedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox != null)
textBox.SelectAll();
}
完整代码下载
HighlightDispDemo.7z(Visual Studio 2010)
用WPF实现查找结果高亮显示的更多相关文章
- WPF中查找控件的扩展类
在wpf中查找控件要用到VisualTreeHelper类,但这个类并没有按照名字查找控件的方法,于是搜索网络,整理出下面这个类,感觉用起来很是方便. 贴出来,供大家参考. /// <summa ...
- 【WPF】查找父/子控件(元素、节点)
整理一下项目中常用的找控件功能,包括找父/子控件.找到所有同类型子控件(比如ListBox找到所有Item). using System; using System.Collections.Gener ...
- WPF FindName()查找命名注册的元素
一.查找xaml中命名注册的元素 <Button x:Name="btn1" Content="显示内容" HorizontalAlignment=&qu ...
- WPF中查找指定类型的父控件
/// <summary> /// 查找父控件 /// </summary> /// <typeparam name="T"></type ...
- 年度巨献-WPF项目开发过程中WPF小知识点汇总(原创+摘抄)
WPF中Style的使用 Styel在英文中解释为”样式“,在Web开发中,css为层叠样式表,自从.net3.0推出WPF以来,WPF也有样式一说,通过设置样式,使其WPF控件外观更加美化同时减少了 ...
- WPF查找父元素子元素
原文:WPF查找父元素子元素 /// <summary> /// WPF中查找元素的父元素 /// </summary> /// &l ...
- jQuery实现页内查找相关内容
当需要在页面中查找某个关键字时,一是可以通过浏览器的查找功能实现,二是可以通过前端脚本准确查找定位,本文介绍通过jQuery实现的页面内容查找定位的功能,并可扩展显示查找后的相关信息. 本文以查找车站 ...
- 八,WPF 命令
WPF命令模型 ICommand接口 WPF命令模型的核心是System.Windows.Input.ICommand接口,该接口定义了命令的工作原理,它包含了两个方法和一个事件: public in ...
- JS获取中文拼音首字母,并通过拼音首字母高速查找页面内的中文内容
实现效果: 图一: 图二: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGVzdGNzX2Ru/font/5a6L5L2T/fontsize/400/f ...
随机推荐
- C/C++中extern关键字解析
1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.此外extern也可用来进行链接指定. 也就是说extern ...
- freeCodeCamp:Seek and Destroy
金克斯的迫击炮! 实现一个摧毁(destroyer)函数,第一个参数是待摧毁的数组,其余的参数是待摧毁的值. 当你完成不了挑战的时候,记得开大招'Read-Search-Ask'. 这是一些对你有帮助 ...
- 大前端学习笔记整理【五】rem与px换算的计算方式
前言 这段时间的小项目中算是真正意义上使用了rem来进行移动端的页面布局,项目结束了我反思了一下之前的对于rem的使用...原来我以前对rem用法完全是在搞笑啊!!结合这次这个小项目,我觉得我也有必要 ...
- 如何彻底删除antlr-2.7.2.jar
1 找到Local\MyEclipse\MyEclipse 10\configuration\org.eclipse.osgi\bundles\12\1\.cp\data\1.3\lib,将antlr ...
- Java EE 和 Java Web
什么是 Java Web 应用程序? Java Web 应用程序会生成包含各种类型的标记语言(HTML 和 XML 等)和动态内容的交互式 Web 页.它通常由 Web 组件组成(如 JavaServ ...
- Spring aop 原始的工作原理的理解
理解完aop的名词解释,继续学习spring aop的工作原理. 首先明确aop到底是什么东西?又如何不违单一原则并实现交叉处理呢? 如果对它的认识只停留在面向切面编程,那就脏了.从oop(Objec ...
- select值的获取及修改
例子: <select id="a" name="a"> <options value="1">a</opti ...
- Java 第29章GUI
GUI入门 JDBC 连接数据库的过程 注册驱动(class ,forName) 创建连接 创建连接对象 执行SQL语句 statement对象的类型与作用 1.(layout :版面,布局) 2.( ...
- Python 7 —— 扩展与嵌入
Python 7 —— 扩展与嵌入 所谓扩展是指,在Python当中调用其他语言,由于Python的问题主要是效率,这里的扩展主要是指扩展C C++程序(重点) 所谓嵌入是指,在其他语言当中可以调用P ...
- C++的一个奇技淫巧
C++如何写一个函数,得到一个数组的长度呢? size_t GetArrayLength(int Array []) { return sizeof(Array)/sizeof(Array[0]); ...