概述
我們經常會遇到這樣的需求:到數據庫里查找一些關鍵字,把帶這些關鍵字的記錄返回顯示在客戶端上。但如果僅僅是單純地把文本顯示出來,那很不直觀,用戶不能很輕易地看到他們想找的內容,所以通常我們還要做到“高亮顯示”。
如果是用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(0); Map(m => m.FileSystem).Index(1); Map(m => m.Desktop).Index(2); } }
上面代碼是對象及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)