WPF Listbox Highlights Part of ListBoxItem

I have a TextBox and a ListBox. The user can search for ListBox items from a TextBox.

ListBox bound to CollectionViewSource.

CollectionViewSource has a filter event handler that filters items based on the text that the user types into the TextBox.

My requirement is to highlight the user-entered text in the TextBlock of the ListBoxItem elements.

I was thinking about breaking a TextBlock into several Runs objects and changing the Background property for the Run objects that need to be selected.

I think this is not possible with DataTemplates.

Is there an easy way to do this?

Thanks!

+3
source share
2 answers

UPDATE: I have elaborated on this question in this blog post .

I don't think there is an easy way to do this, but here is how I could solve this problem:

  • Define a presentation model for the items in the list. It should include properties for expanding the text and determining how much of the text should be highlighted (start and end indexes, mainly).
  • When the user enters text in the search field, browse the view models and check for matches in the text. If a match is found, set the indexes accordingly. If no match is found, set the indices to -1 or something else doesn't match.
  • In your opinion, set Background from TextBlock to indexes. Use the converter to convert indexes to GradientBrush , bright yellow (or any other) between two indexes.

Here, as I think, you can find out the sizes of the selected parts of the TextBlock :

  • Get a TextPointer using the TextBlock.ContentStart property.
  • Scroll to the top of the selection by calling TextPointer.GetPositionAtOffset(indexOfStart) using LogicalDirection.Forwards .
  • Move to the end of the selection by calling TextPointer.GetPositionAtOffset(indexOfStart) using LogicalDirection.Backwards .
  • Call TextPointer.GetCharacterRect to get the Rectangle constraint of the selected content.

Honestly, I'm not sure if the last bit is working. I have to try this for myself, and I can do it for a blog post.

EDIT : I had time to try this for myself. This definitely works, although with a few changes in my logic above. Below is the code that demonstrates. Here is a screenshot:

Screenshot http://img219.imageshack.us/img219/2969/searchx.png

Window1.xaml:

 <Window x:Class="TextSearch.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1"> <StackPanel> <TextBox x:Name="_searchTextBox"/> <Grid> <Path Fill="Yellow" Stroke="Black" StrokeThickness="0"> <Path.Data> <RectangleGeometry x:Name="_rectangleGeometry"/> </Path.Data> </Path> <TextBlock x:Name="_textBlock">Some sample text that you can search through by typing in the above TextBox.</TextBlock> </Grid> </StackPanel> </Window> 

Window1.xaml.cs:

 using System.Windows; using System.Windows.Controls; using System.Windows.Documents; namespace TextSearch { public partial class Window1 : Window { public Window1() { InitializeComponent(); _searchTextBox.TextChanged += _searchTextBox_TextChanged; } void _searchTextBox_TextChanged(object sender, TextChangedEventArgs e) { var searchText = _searchTextBox.Text; var index = _textBlock.Text.IndexOf(searchText); if (index == -1) { _rectangleGeometry.Rect = Rect.Empty; } else { var textPointer = _textBlock.ContentStart; textPointer = textPointer.GetPositionAtOffset(index + 1, LogicalDirection.Forward); var leftRectangle = textPointer.GetCharacterRect(LogicalDirection.Forward); textPointer = textPointer.GetPositionAtOffset(searchText.Length, LogicalDirection.Backward); var rightRectangle = textPointer.GetCharacterRect(LogicalDirection.Forward); _rectangleGeometry.Rect = new Rect(leftRectangle.TopLeft, rightRectangle.BottomRight); } } } } 

I think the code is pretty clear. Obviously, you will need to extend the concept to your specific scenario. You can use the Background TextBlock property in combination with a DrawingBrush or GradientBrush instead of having a separate Path .

+9
source

Filtering in a list of WPF list. The following code example works for me.

 <Window x:Class="Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <Grid> <TextBox Name="txtSearch" Height="21" Margin="63,12,12,0" VerticalAlignment="Top"></TextBox> <ListBox Name="listItems" ItemsSource="{Binding}" Margin="22,0,0,44" Height="179" VerticalAlignment="Bottom" /> </Grid> 

Vb.net Code

data binding to your list

 Dim _dtable As New DataTable("tblItems") _dtable.Columns.Add("Id", GetType(Integer)) _dtable.Columns.Add("Name", GetType(String)) _dtable.Columns.Add("Price", GetType(Double)) ' Add any initialization after the InitializeComponent() call. For i = 100 To 110 _dtable.Rows.Add(i, "Item " & i, 15.0) Next Private Sub Window1_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded listItems.DataContext = _dtable.DefaultView End Sub Private Sub txtSearch_TextChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.TextChangedEventArgs) Handles txtSearch.TextChanged 

_dtable.DefaultView.RowFilter = "Name like" and txtSearch.Text and "% '"

 End Sub 

Sample Code Works for me

0
source

All Articles