Home > Mobile >  WPF How to find a specific control in an ItemsControl with data binding
WPF How to find a specific control in an ItemsControl with data binding

Time:05-20

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?

CodePudding user response:

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);
        }
     }
 }

CodePudding user response:

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.

  • Related