How to highlight text in the TextBlock with Inlines?

Emon Haque 3,176 Reputation points
2021-05-19T21:53:23.19+00:00

For the normal ListBox, that would've otherwise used DisplayMemberPath if I hadn't used the HiBlock, a subclass of TextBlock, it works:

98006-test.gif

and those normal ListBox uses the HiTemplate:

class HiTemplate : DataTemplate  
{  
    public HiTemplate(string boundPorpertyName, EditText querySource) {  
        var block = new FrameworkElementFactory(typeof(HiBlock));  
        block.SetBinding(HiBlock.TextProperty, new Binding(boundPorpertyName));  
        block.SetBinding(HiBlock.QueryProperty, new Binding("Text") { Source = querySource });  
        VisualTree = block;  
    }  
}  

and I've set the ItemTemplate for the ListBox, for example in Edit Plot view, this way:

plotList = new ListBox() {  
    BorderBrush = Brushes.LightGray,  
    BorderThickness = new Thickness(0, 0, 1, 0),  
    ItemTemplate = new HiTemplate(nameof(Plot.Name), search),  
    ItemContainerStyle = App.editableItemContainerStyle  
};   

Now, in the Edit Lease or Rent view, it doesn't work. Both of those ListBoxes uses this template:

class LeaseTemplate : DataTemplate  
{  
    public LeaseTemplate(EditText querySource, bool isSpaceBold = true) {  
        var block = new FrameworkElementFactory(typeof(HiBlock));  
        var space = new FrameworkElementFactory(typeof(Run));  
        var colon = new FrameworkElementFactory(typeof(Run));  
        var tenant = new FrameworkElementFactory(typeof(Run));  
        space.SetBinding(Run.TextProperty, new Binding(nameof(Lease.SpaceName)));  
        if(isSpaceBold) space.SetValue(Run.FontWeightProperty, FontWeights.Bold);  
        colon.SetValue(Run.TextProperty, " : ");  
        tenant.SetBinding(Run.TextProperty, new Binding(nameof(Lease.TenantName)));  
        tenant.SetValue(Run.FontStyleProperty, FontStyles.Italic);  
        block.SetBinding(HiBlock.QueryProperty, new Binding("Text") { Source = querySource });  
        block.AppendChild(space);  
        block.AppendChild(colon);  
        block.AppendChild(tenant);  
        VisualTree = block;  
    }  
}  

and in the Rent view I've set the template this way:

leaseList = new ListBox() {  
    Margin = new Thickness(0, 5, 0, 5),  
    BorderThickness = new Thickness(0, 1, 0, 1),  
    BorderBrush = Brushes.WhiteSmoke,  
    SelectionMode = SelectionMode.Extended,  
    ItemTemplate = new LeaseTemplate(search, false),  
    ItemContainerStyle = App.editableItemContainerStyle,  
    GroupStyle = {  
        new GroupStyle() {  
            ContainerStyle = new Style(typeof(GroupItem)) {  
                Setters = {  
                    new Setter(GroupItem.TemplateProperty, new GroupedLeaseTemplate())  
                }  
            }  
        }  
    }  
};  

Here's the HiBlock:

class HiBlock : TextBlock  
{  
    public string Query {  
        get { return (string)GetValue(QueryProperty); }  
        set { SetValue(QueryProperty, value); }  
    }  

    public static readonly DependencyProperty QueryProperty =  
        DependencyProperty.Register("Query", typeof(string), typeof(HiBlock), new PropertyMetadata(null, onQueryChanged));  

    static void onQueryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {  
        var block = d as HiBlock;  
        var query = block.Query;  
        var text = block.Text;  
        List<Inline> inlines = new();  
        if (string.IsNullOrWhiteSpace(text)) {  
            foreach (Run line in block.Inlines) {  
                inlines.Add(new Run() { Text = line.Text});  
            }  
        }  
        block.Inlines.Clear();  
        if (!string.IsNullOrWhiteSpace(text)) {  
            if (string.IsNullOrWhiteSpace(query)) block.Inlines.Add(text);  
            else {  
                int index = text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
                if (index < 0) block.Inlines.Add(text);  
                else {  
                    int startIndex = 0;  
                    for (  
                        int i = text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
                        i > -1;  
                        i = text.IndexOf(query, i + 1, StringComparison.OrdinalIgnoreCase)) {  
                        block.Inlines.Add(text.Substring(startIndex, i - startIndex));  
                        block.Inlines.Add(new Run(text.Substring(i, query.Length)) { Background = Brushes.LightBlue });  
                        startIndex = i + query.Length;  
                    }  
                    if (startIndex < text.Length) block.Inlines.Add(text.Substring(startIndex));  
                }  
            }  
        }  
        else {  
            if (string.IsNullOrWhiteSpace(query)) {  
                foreach (var line in inlines)  
                    block.Inlines.Add(line);  
            }  
            else {  
                foreach (Run line in inlines) {  
                    int index = line.Text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
                    if (index < 0) block.Inlines.Add(line.Text);  
                    else {  
                        int startIndex = 0;  
                        for (  
                            int i = line.Text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
                            i > -1;  
                            i = line.Text.IndexOf(query, i + 1, StringComparison.OrdinalIgnoreCase)) {  
                            block.Inlines.Add(line.Text.Substring(startIndex, i - startIndex));  
                            block.Inlines.Add(new Run(line.Text.Substring(i, query.Length)) { Background = Brushes.LightBlue });  
                            startIndex = i + query.Length;  
                        }  
                        if (startIndex < text.Length) block.Inlines.Add(line.Text.Substring(startIndex));  
                    }  
                }  
            }  
        }  
    }  
}  

How to make it work with the LeaseTemplate?

Developer technologies | Windows Presentation Foundation
0 comments No comments
{count} votes

Accepted answer
  1. Emon Haque 3,176 Reputation points
    2021-06-13T10:53:15.22+00:00

    I got away with the approach of adding both HiBlock and TextBlocks, instead of Run, in the DatatTemplate to solve the issue in other views BUT the problem of wrapping text in these Ledger views brought me back to this problem and with this in onQueryChanged the HiBlock actually works with multiple Run:

    static void onQueryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {              
        var block = d as HiBlock;  
        if (block.Visibility == Visibility.Collapsed) return;  
        var query = block.Query;  
        var text = block.Text;           
        if (!string.IsNullOrWhiteSpace(text)) {  
            block.Inlines.Clear();  
            if (string.IsNullOrWhiteSpace(query)) block.Inlines.Add(text);  
            else {  
                int index = text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
                if (index < 0) block.Inlines.Add(text);  
                else {  
                    int startIndex = 0;  
                    for (  
                        int i = text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
                        i > -1;  
                        i = text.IndexOf(query, i + 1, StringComparison.OrdinalIgnoreCase)) {  
                        block.Inlines.Add(text.Substring(startIndex, i - startIndex));  
                        block.Inlines.Add(new Run(text.Substring(i, query.Length)) { Background = Brushes.LightBlue });  
                        startIndex = i + query.Length;  
                    }  
                    if (startIndex < text.Length) block.Inlines.Add(text.Substring(startIndex));  
                }  
            }  
        }  
        else {  
            List<Run> inlines = new(block.Inlines.Count);  
            foreach (Run line in block.Inlines) {  
                inlines.Add(new Run() {  
                    Text = line.Text,  
                    FontStyle = line.FontStyle,  
                    Foreground = line.Foreground,  
                    FontWeight = line.FontWeight  
                });  
            }  
            block.Inlines.Clear();  
            foreach (Run item in inlines) {  
                if (string.IsNullOrWhiteSpace(query)) block.Inlines.Add(item);  
                else {  
                    int index = item.Text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
                    if (index < 0) block.Inlines.Add(item);  
                    else {  
                        int startIndex = 0;  
                        for (  
                            int i = item.Text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
                            i > -1;  
                            i = item.Text.IndexOf(query, i + 1, StringComparison.OrdinalIgnoreCase)) {  
                            block.Inlines.Add(new Run(item.Text.Substring(startIndex, i - startIndex)) {  
                                FontStyle = item.FontStyle,  
                                Foreground = item.Foreground,  
                                FontWeight = item.FontWeight  
                            });  
                            block.Inlines.Add(new Run(item.Text.Substring(i, query.Length)) {  
                                Background = Brushes.LightBlue,  
                                FontStyle = item.FontStyle,  
                                Foreground = item.Foreground,  
                                FontWeight = item.FontWeight  
                            });  
                            startIndex = i + query.Length;  
                        }  
                        if (startIndex < item.Text.Length) block.Inlines.Add(new Run(item.Text.Substring(startIndex)) {  
                            Background = Brushes.LightBlue,  
                            FontStyle = item.FontStyle,  
                            Foreground = item.Foreground,  
                            FontWeight = item.FontWeight  
                        });  
                    }  
                }  
            }  
        }  
    }  
    

    105143-test.gif

    and it retains the formatting of Text.

    0 comments No comments

3 additional answers

Sort by: Most helpful
  1. DaisyTian-1203 11,646 Reputation points
    2021-05-21T08:24:02.003+00:00

    I added three Run into your HiBlock, as below shown,

      <TextBox Width="120" Height="30" Name="txt"></TextBox>  
            <local:HiBlock Query="{Binding ElementName=txt,Path=Text,Mode=TwoWay}" Width="120" Height="30">  
                <Run>abc</Run>  
                <Run>def</Run>  
                <Run>hij</Run>  
            </local:HiBlock>  
    

    Run the project, a strange problem occures. b and c disappear.
    98554-3.gif

    Debuged your HiBlock and found the text is null after running var text = block.Text; TextBlock.Text can't give the value of Run in TextBlock. Then I add text += line.Text; to your onQueryChanged as below, it problem disappears.

     if (string.IsNullOrWhiteSpace(text))  
                {  
                    foreach (Run line in block.Inlines)  
                    {  
                        inlines.Add(new Run() { Text = line.Text });  
                        text += line.Text;  
                    }  
                }  
    

    Could you add text += line.Text; to your code to check if is work for your ListBox with Run ?


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

  2. Emon Haque 3,176 Reputation points
    2021-05-21T13:31:39.667+00:00

    Yes the text remains "" If I use Run and that's why I've tried by adding the last else block in onQueryChanged. Now, if I do text += line.Text, I don't need the last half of the onQueryChanged so it becomes this:

    static void onQueryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {  
        var block = d as HiBlock;  
        var query = block.Query;  
        var text = block.Text;  
        if (string.IsNullOrWhiteSpace(text)) {  
            foreach (Run line in block.Inlines)   
                text += line.Text;  
        }  
        block.Inlines.Clear();  
        if (string.IsNullOrWhiteSpace(query)) block.Inlines.Add(text);  
        else {  
            int index = text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
            if (index < 0) block.Inlines.Add(text);  
            else {  
                int startIndex = 0;  
                for (  
                    int i = text.IndexOf(query, StringComparison.OrdinalIgnoreCase);  
                    i > -1;  
                    i = text.IndexOf(query, i + 1, StringComparison.OrdinalIgnoreCase)) {  
                    block.Inlines.Add(text.Substring(startIndex, i - startIndex));  
                    block.Inlines.Add(new Run(text.Substring(i, query.Length)) { Background = Brushes.LightBlue });  
                    startIndex = i + query.Length;  
                }  
                if (startIndex < text.Length) block.Inlines.Add(text.Substring(startIndex));  
            }  
        }  
    }  
    

    BUT it doesn't work:

    98623-test.gif

    Did you try with bound Text in Run? I also tried by commenting out the rest of the code after block.Inlines.Clear(); and expected to see nothing in the list box BUT that call, apparently, doesn't have any effect when Run is used with bound Text:

    98617-test2.gif


  3. Emon Haque 3,176 Reputation points
    2021-05-24T06:08:01.727+00:00

    Here's the code for EditText:

    class EditText : FrameworkElement
    {
        TextBlock hintBlock, errorBlock, lengthBlock;
        TextBox inputBox;
        Separator separator;
        Path leftIcon, invalidIcon, validIcon;
        Grid container, infoContainer;
        TranslateTransform translateHint;
        ScaleTransform scaleHint;
        DoubleAnimation translateHintAnim, scaleHintAnim;
        ColorAnimation brushAnim;
        SolidColorBrush brush;
        bool isHintMoved;
    
        public string Icon { get; set; }
        public string Hint { get; set; }
        public bool IsRequired { get; set; }
        public bool IsMultiline { get; set; }
        public int MaxLength { get; set; }
        public bool IsTrimBottomRequested { get; set; }
    
        public EditText() {
            Margin = new Thickness(5);
            brush = new SolidColorBrush(Colors.SkyBlue);
            initializeControls();
            container = new Grid {
                Margin = new Thickness(0, 5, 0, 0),
                RowDefinitions = {
                    new RowDefinition(),
                    new RowDefinition(){Height = GridLength.Auto},
                    new RowDefinition(){Height = GridLength.Auto}
                },
                ColumnDefinitions = {
                    new ColumnDefinition(){Width = GridLength.Auto},
                    new ColumnDefinition(),
                    new ColumnDefinition(){Width = GridLength.Auto}
                },
                Children = { leftIcon, inputBox, hintBlock, invalidIcon, validIcon, separator, infoContainer }
            };
            AddVisualChild(container);
            initializeAnimations();
            inputBox.TextChanged += onInputTextChanged;
            Loaded += onLoaded;
        }
    
        void onLoaded(object sender, RoutedEventArgs e) {
            hintBlock.Text = Hint;
            if (Icon != null) {
                leftIcon.Data = Geometry.Parse(Icon);
                leftIcon.Visibility = Visibility.Visible;
            }
            if (IsRequired) {
                Grid.SetColumnSpan(inputBox, 1);
                errorBlock.Visibility = Visibility.Visible;
                invalidIcon.Visibility = Visibility.Visible;
            }
            if (MaxLength > 0) {
                inputBox.MaxLength = MaxLength;
                lengthBlock.Text = "0/" + MaxLength;
                lengthBlock.Visibility = Visibility.Visible;
            }
            if (IsMultiline) {
                inputBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
                inputBox.Resources.Add(typeof(ScrollBar), App.multilineTextScrollStyle);
                inputBox.AcceptsReturn = true;
                inputBox.TextWrapping = TextWrapping.Wrap;
                inputBox.VerticalAlignment = VerticalAlignment.Top;
    
                leftIcon.VerticalAlignment = VerticalAlignment.Top;
                invalidIcon.VerticalAlignment = VerticalAlignment.Top;
                validIcon.VerticalAlignment = VerticalAlignment.Top;
                hintBlock.VerticalAlignment = VerticalAlignment.Top;
            }
    
            inputBox.SetBinding(TextBox.TextProperty, new Binding() {
                Path = new PropertyPath(nameof(Text)),
                RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(EditText), 1),
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            });
            errorBlock.SetBinding(TextBlock.TextProperty, new Binding() {
                Path = new PropertyPath(nameof(Error)),
                RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(EditText), 1)
            });
    
            if (IsTrimBottomRequested) {
                infoContainer.Visibility = Visibility.Collapsed;
            }
        }
        void initializeControls() {
            translateHint = new TranslateTransform();
            scaleHint = new ScaleTransform();
    
            hintBlock = new TextBlock {
                Padding = new Thickness(5, 0, 5, 0),
                Foreground = Brushes.Gray,
                Background = Brushes.White,
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Center,
                RenderTransform = new TransformGroup {
                    Children = { scaleHint, translateHint }
                }
            };
            inputBox = new TextBox {
                BorderThickness = new Thickness(0),
                Margin = new Thickness(5, 0, 5, 0),
                VerticalAlignment = VerticalAlignment.Bottom
            };
            separator = new Separator { Background = brush };
            leftIcon = new Path {
                Fill = brush,
                Width = 18,
                Height = 18,
                Stretch = Stretch.Uniform,
                HorizontalAlignment = HorizontalAlignment.Left,
                Visibility = Visibility.Collapsed
            };
            invalidIcon = new Path {
                Data = Geometry.Parse(Icons.Info),
                Fill = Brushes.Coral,
                Width = 16,
                Height = 16,
                Stretch = Stretch.Uniform,
                Visibility = Visibility.Collapsed,
                HorizontalAlignment = HorizontalAlignment.Right
            };
            validIcon = new Path {
                Data = Geometry.Parse(Icons.Checked),
                Fill = Brushes.Green,
                Width = 16,
                Height = 16,
                Stretch = Stretch.Uniform,
                Visibility = Visibility.Collapsed
            };
    
            Grid.SetRow(separator, 1);
            Grid.SetColumnSpan(separator, 3);
            Grid.SetColumn(inputBox, 1);
            Grid.SetColumnSpan(inputBox, 2);
            Grid.SetColumn(hintBlock, 1);
            Grid.SetColumn(invalidIcon, 2);
            Grid.SetColumn(validIcon, 2);
    
            errorBlock = new TextBlock {
                Foreground = Brushes.Gray,
                Margin = new Thickness(5, 0, 0, 0),
                TextWrapping = TextWrapping.Wrap,
                Visibility = Visibility.Hidden,
                FontSize = 11,
                FontStyle = FontStyles.Italic
            };
            lengthBlock = new TextBlock() {
                Foreground = Brushes.Gray,
                Visibility = Visibility.Collapsed,
                HorizontalAlignment = HorizontalAlignment.Right,
                FontSize = 11,
                FontStyle = FontStyles.Italic
            };
            Grid.SetColumn(lengthBlock, 1);
            infoContainer = new Grid() {
                ColumnDefinitions = {
                    new ColumnDefinition(),
                    new ColumnDefinition(){Width = GridLength.Auto}
                },
                Children = { errorBlock, lengthBlock }
            };
            Grid.SetRow(infoContainer, 2);
            Grid.SetColumnSpan(infoContainer, 3);
        }
        void initializeAnimations() {
            var duration = TimeSpan.FromMilliseconds(250);
            var ease = new CubicEase { EasingMode = EasingMode.EaseInOut };
            scaleHintAnim = new DoubleAnimation {
                Duration = duration,
                EasingFunction = ease
            };
            translateHintAnim = new DoubleAnimation {
                Duration = duration,
                EasingFunction = ease
            };
            brushAnim = new ColorAnimation {
                Duration = duration,
                EasingFunction = ease
            };
        }
        void onInputTextChanged(object sender, TextChangedEventArgs e) {
            lengthBlock.Text = inputBox.Text.Length + "/" + MaxLength;
        }
        void animateHint() {
            translateHint.BeginAnimation(TranslateTransform.YProperty, translateHintAnim);
            scaleHint.BeginAnimation(ScaleTransform.ScaleYProperty, scaleHintAnim);
            scaleHint.BeginAnimation(ScaleTransform.ScaleXProperty, scaleHintAnim);
        }
        void moveHintUp() {
            isHintMoved = true;
            scaleHintAnim.To = 0.9;
            translateHintAnim.To = -15;
            animateHint();
        }
        void moveHintDown() {
            isHintMoved = false;
            scaleHintAnim.To = 1;
            translateHintAnim.To = 0;
            animateHint();
        }
    
        #region Overrides
        protected override void OnMouseEnter(MouseEventArgs e) {
            if (string.IsNullOrWhiteSpace(inputBox.Text)) {
                inputBox.Focus();
                brushAnim.To = Colors.CornflowerBlue;
                brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);
            }
        }
        protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) {
            if (inputBox.Text.Length == 0 && !isHintMoved) moveHintUp();
            brushAnim.To = Colors.CornflowerBlue;
            brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);
        }
        protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e) {
            if (string.IsNullOrWhiteSpace(Text) && isHintMoved) moveHintDown();
            brushAnim.To = Colors.SkyBlue;
            brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);
        }
        protected override Size MeasureOverride(Size availableSize) {
            if (IsMultiline) {
                hintBlock.Measure(availableSize);
                separator.Measure(availableSize);
                leftIcon.Measure(availableSize);
                double height = 0d;
                if (double.IsInfinity(availableSize.Height)) height = 100;
                else {
                    height = availableSize.Height - hintBlock.DesiredSize.Height - separator.DesiredSize.Height - Margin.Top;
                    height = height > 0 ? height : 0;
                }
                inputBox.Height = height;
            }
            container.Width = availableSize.Width;
            container.Measure(availableSize);
            return container.DesiredSize;
        }
        protected override Size ArrangeOverride(Size finalSize) {
            container.Arrange(new Rect(container.DesiredSize));
            return finalSize;
        }
        protected override Visual GetVisualChild(int index) => container;
        protected override int VisualChildrenCount => 1;
        #endregion
    
        #region DependencyProperty
        public string Text {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
    
        public string Error {
            get { return (string)GetValue(ErrorProperty); }
            set { SetValue(ErrorProperty, value); }
        }
    
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(EditText), new FrameworkPropertyMetadata() {
                DefaultValue = null,
                BindsTwoWayByDefault = true,
                PropertyChangedCallback = onTextChanged
            });
    
        static void onTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            var o = d as EditText;
            if (e.NewValue == null && o.Error == string.Empty ||
                e.NewValue == null && o.Error == null)
                o.OnPreviewLostKeyboardFocus(null);
            else {
                o.moveHintUp();
            }
        }
    
        public static readonly DependencyProperty ErrorProperty =
            DependencyProperty.Register("Error", typeof(string), typeof(EditText), new PropertyMetadata(null, onErrorChanged));
    
        static void onErrorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            var o = d as EditText;
            var err = e.NewValue == null ? null : e.NewValue.ToString();
            if (err == string.Empty) {
                o.invalidIcon.Visibility = Visibility.Hidden;
                o.validIcon.Visibility = Visibility.Visible;
                o.errorBlock.Visibility = Visibility.Hidden;
            }
            else {
                o.invalidIcon.Visibility = Visibility.Visible;
                o.validIcon.Visibility = Visibility.Hidden;
                o.errorBlock.Visibility = Visibility.Visible;
            }
        }
        #endregion      
    }
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.