-1

I have an ItemsControl which is bound to a list:

<ItemsControl x:Name="icFiles" ItemsSource="{Binding Path=files}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <CheckBox Content="" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
                <TextBlock x:Name="ThisTextBlock" Text="{Binding FileName}" />
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
private readonly List<FileModel> files = new();

icFiles.ItemsSource = files;

I want to highlight certain text in the TextBlock in the ItemsControl. For this, I thought about using a TextPointer:

string? highlightText = "blue";

int highlightTextIndex = ThisTextBlock.Text.IndexOf(highlightText);
if(highlightTextIndex >= 0)
{
    TextPointer textStartPointer = ThisTextBlock.ContentStart.DocumentStart.GetInsertionPosition(LogicalDirection.Forward);
    TextRange? highlightTextRange = new TextRange(textStartPointer.GetPositionAtOffset(highlightTextIndex), textStartPointer.GetPositionAtOffset(highlightTextIndex + highlightText.Length));
                highlightTextRange.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Blue);
    }
}

How do I find this ThisTextBlock?

  • 1
    As a note, when you assign `icFiles.ItemsSource = files;` that will replace the ItemsSource Binding declared in XAML and hence make that Binding redundant. You could simply remove it from XAML. Also note that `{Binding Path=files}` requires that `files` is a public property of the current DataContext object. Your `files` is not a public property, but a private field. – Clemens May 19 '22 at 14:56

2 Answers2

1

You need to access the item container's content template (which is the item's DataTemplate).

In case of the ItemsControl, you can use the following example to obtain a named element from the DataTemplate:


for (int itemIndex = 0; itemIndex < this.ItemsControl.Items.Count; itemIndex++)
{
  var itemContainer = this.ItemsControl.ItemContainerGenerator.ContainerFromIndex(itemIndex) as ContentPresenter;
  var textBlock = itemContainer.ContentTemplate.FindName("ThisTextBlock", itemContainer) as TextBlock;

  HighlightText(textBlock);
}

A simple implementation that searches an element in the visual tree can be found at How to: Microsoft Docs: How to: Find DataTemplate-Generated Elements. You can copy and use the example's helper method FindVisualChild to search for elements by type rather than by name. The method is part of an example that shows how to get the content of the DataTemplate in case you use a ListBox or ListView.

In case you didn't modified the ListBoxItem template or don't expect it to change, you can use this simplified and faster version (to find named elements):

for (int itemIndex = 0; itemIndex < this.ListBox.Items.Count; itemIndex++)
{
  var listBoxItemContainer = this.ListBox.ItemContainerGenerator.ContainerFromIndex(itemIndex) as ListBoxItem;
  var templateRootBorder = VisualTreeHelper.GetChild(listBoxItemContainer, 0) as Border;
  var contentHost = templateRootBorder.Child as ContentPresenter;

  var textBlock = contentHost.ContentTemplate.FindName("TD", contentHost) as TextBlock;
}

Except for special use cases, it is highly recommended to use the ListBox instead of the ItemsControl. ListBox and ListView are both an extended ItemsControl. They both provide scrolling and a significantly improved performance.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thanks! That helps a lot! I use a `ItemsControl` because I don't want to be able to select single items. Can you tell me, how this would work, if the `Textblock` would be inside a `ContentControl` `ControlTemplate` under `Window.Resources`? – GanzWeitAußen May 20 '22 at 06:04
  • From a performance perspective it would be better to remove the highlight behavior from the ListBox by overriding the ListBoxItem template. The performance gain when using a ListBox is really huge, especially when you expect to display many items or changes of the source collection or the collection's items. – BionicCode May 20 '22 at 06:21
  • There is nothing to find inside a template that is defined inside the resources. You would have to wait until the template was applied e.g. by the ContentPresenter. – BionicCode May 20 '22 at 06:22
  • I have one small problem. Items which have highlighted text do not update when the bound content is changed. Do you happen to know why that is? Do I have to remove the highlight first? – GanzWeitAußen May 20 '22 at 06:45
  • You are modifying the TextBlock directly. This will remove the data binding from the Text property. Instead you should bind the source text to the TextBlock.Inlines property. Then attach a converter to the binding that creates the inline elements from the source text: create one or more `Run` elements for the non-highlight text and a `Run` for every highlight text span. You can set Background and Foreground on the `Run` to give it its highlight appearance. – BionicCode May 20 '22 at 06:55
  • You can refer to [this answer](https://stackoverflow.com/a/72262122/3141792) to find an example that binds TextBlock.Text to a string to highlight its text. – BionicCode May 20 '22 at 08:40
0

First of all, you need to delete the Binding from code behind.

You can do this using Loaded event as follows:

 <ItemsControl x:Name="icFiles" ItemsSource="{Binding Path=files}">
     <ItemsControl.ItemTemplate>
         <DataTemplate>
             <StackPanel Orientation="Horizontal">
                 <CheckBox Content="" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
                 <TextBlock Loaded="ThisTextBlock_OnLoaded" x:Name="ThisTextBlock" Text="{Binding FileName}" />
             </StackPanel>
         </DataTemplate>
     </ItemsControl.ItemTemplate>
 </ItemsControl>    
  

 private void ThisTextBlock_OnLoaded(object sender, RoutedEventArgs e)
 {
    if (sender is TextBlock tb)
    {
        string? highlightText = "blue";
        int highlightTextIndex = tb.Text.IndexOf(highlightText);
        if (highlightTextIndex >= 0)
        {
            TextPointer textStartPointer = tb.ContentStart.DocumentStart.GetInsertionPosition(LogicalDirection.Forward);
            TextRange? highlightTextRange = new TextRange(textStartPointer.GetPositionAtOffset(highlightTextIndex), textStartPointer.GetPositionAtOffset(highlightTextIndex + highlightText.Length));
            highlightTextRange.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Blue);
        }
     }
 }
Ohad Cohen
  • 597
  • 4
  • 9
  • Thank you very much for the Answer! How could I implement it that the text to be highlighted is variable. So when I change it, the highlight update themselves? Can I just refresh the `ItemsControl` or something? – GanzWeitAußen May 20 '22 at 05:45