Страницы

Поиск по вопросам

понедельник, 6 января 2020 г.

TextBlock с подсветкой текста

#c_sharp #wpf #binding


Столкнулся с необходимостью выделения фрагмента текста в TextBlock, а именно определённых
ключевых слов по которым были отфильтрованы элементы ListBox , этот текстовый блок
собственно и содержащие

XAML, мой ваиант


                        
                            
                                 
                                    
                                        
                                        
                                            
                                                
                                                    
                                                        
                                                    
                                                
                                            
                                        
                                    
                        

                        
                    


Вариант XAML, рекомендованный VladD, не создаёт коллекцию HighlightRulesCollection
(хотя казалось бы даже отрабатывает конвертер)

Используется компонент написанный нашим соотечественником с открытым исходным Компонент

Описание компонента

Закомментированный код это старый TexBlock без выделения

Новый компонент HighlightTextBlock прекрасно выделяет текст если использовать статический
ресурс, как в примере, но когда я пытаюсь его привязать к текущему тексту он не может
найти это поле :( , я новенький в WPF помогите разобраться с 

HightlightedText="{Binding Path=title, Converter={StaticResource getFilter}}"


Изначально вопрос звучал как верно привязать это свойство к title ?
Мне дали совет по реструктуризации XAML, через ресурсы , но это не работает т.к.
не создаётся HighlightRulesCollection и выделение не работает.

В ходе обсуждения было высказано предположение что следует несколько модифицировать
сам компонент и поместить коллекцию HighlighRule (также) в визуальное дерево. Тогда
будет автоматически наследоваться DataContext и по идее будет работать привязка через
ElementName.

Подскажите как это можно реализовать? или может кто то внесёт правки в исходный код
компонента ?

Альтернативный вариант , подскажите, как реализовать такого рода функционал другими
методами ?

структура DataContext 

public ObservableCollection Procedures { set; get; }
public CollectionViewSource ProceduresView { set; get; } = new CollectionViewSource();

    ....

    Procedures = new ObservableCollection();

    ProceduresView.Filter += Procedures_Filter;
    ProceduresView.Source = Procedures;

    ....

public class Procedure : ObservableObject 
{
    ....
    public String title { get; set; }
    ....
}
....

// Просто фильтрация

void Procedures_Filter(object sender, FilterEventArgs e)
{
    Procedure procedure = (Procedure) e.Item;
    Boolean flag = false;
    if (!string.IsNullOrEmpty(filter))
    {
        Setting.Filter sfilter = new Setting.Filter();
        sfilter.type = "искать везде";
        sfilter.text = filter;
        ObservableCollection arr = new ObservableCollection();
        arr.Add(sfilter);
        if (Utils.AssignedProcedureFromFilter(procedure, arr)) flag = true;
    }
    else flag = true;
    e.Accepted = flag;
}


Видео с описанием проблемы 

Упрощённый проект эмитирующий мой функционал
    


Ответы

Ответ 1



Если вы пользуетесь MVVM, то ваш title должен быть открытым свойством в вашей VM. То есть, в DataContext'е. Если вы привязываетесь не к DataContext'у, то вы должны указать Source (если вы привязываетесь к какому-то другому объекту) или ElementName (если к свойству другого контрола). Обновление: Ваш случай, на самом деле, более серьёзный. DataContext у вас, судя по всему, правильный. Но ваше Binding-выражение находится внутри сеттера свойства HighlightRules, которое не является частью логического дерева (т. к. не доступно как Child-элемент вашего контрола). А элементы, которые не находятся внутри логического дерева, принимают участие в привязках лишь ограничено: в них нет ни наследования DataContext'а, ни доступа по имени через ElementName. В качестве решения должна сработать привязка к элементу через x:Reference. В моём (сильно порезанном) тестовом примере срабатывает HightlightedText="{Binding Path=DataContext.title, Source={x:Reference MainText}}". Но, если прямо заменить на это, срабатывает странная ошибка: 'Не удается вызвать MarkupExtension.ProvideValue из-за циклической зависимости. Свойства внутри MarkupExtension не могут ссылаться на объекты, ссылающиеся на результат MarkupExtension. Метод обхода ошибки нашёлся здесь: нужно положить ваш элемент в ресурсы. Получаем вот такое: Обновление. По идее, можно решить проблему более простым образом: поместить все VisualRule в логическое дерево. Это делается так: в классе HighlightTextBlock добавляем метод protected override IEnumerator LogicalChildren { get { var baseChildrenIter = base.LogicalChildren; while (baseChildrenIter.MoveNext()) yield return baseChildrenIter.Current; var rules = HighlightRules; if (rules != null) { foreach (var rule in rules) yield return rule; } } } Далее, в методе HighlightRulesChanged класса HighlightTextBlock дописываем: foreach (var rule in col) { rule.HighlightTextChanged += tb.Rule_HighlightTextChanged; // добавляем это: tb.AddLogicalChild(rule); } А также в соседнем HighlightRules_CollectionChanged добавляем case System.Collections.Specialized.NotifyCollectionChangedAction.Add: foreach (HighlightRule rule in e.NewItems) { // добавляем это: AddLogicalChild(rule); _ruleTasks.Add(rule, new TaskQueue(1)); SubscribeRuleNotifies(rule); BeginHighlight(rule); } break; case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: foreach (HighlightRule rule in e.OldItems) { // добавляем это: RemoveLogicalChild(rule); //rule.HightlightedText = string.Empty; _ruleTasks.Remove(rule); UnsubscribeRuleNotifies(rule); } break; case System.Collections.Specialized.NotifyCollectionChangedAction.Reset: foreach (HighlightRule rule in e.OldItems) { // добавляем это: RemoveLogicalChild(rule); //rule.HightlightedText = string.Empty; _ruleTasks.Remove(rule); UnsubscribeRuleNotifies(rule); } break; После этого можно будет привязываться так:

Ответ 2



Нашёл работающее решение своей задачи public class SearchHightlightTextBlock : TextBlock { public SearchHightlightTextBlock() : base() { } public String SearchText { get { return (String)GetValue(SearchTextProperty); } set { SetValue(SearchTextProperty, value); } } private static void OnDataChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { TextBlock tb = (TextBlock)source; if (tb.Text.Length == 0) return; string textUpper = tb.Text.ToUpper(); String toFind = ((String)e.NewValue).ToUpper(); int firstIndex = textUpper.IndexOf(toFind); String firstStr = ""; String foundStr = ""; if (firstIndex != -1) { firstStr = tb.Text.Substring(0, firstIndex); foundStr = tb.Text.Substring(firstIndex, toFind.Length); } String endStr = tb.Text.Substring(firstIndex + toFind.Length, tb.Text.Length - (firstIndex + toFind.Length)); tb.Inlines.Clear(); tb.FontSize = 16; var run = new Run(); run.Text = firstStr; tb.Inlines.Add(run); run = new Run(); run.Background = Brushes.Yellow; run.Text = foundStr; tb.Inlines.Add(run); run = new Run(); run.Text = endStr; tb.Inlines.Add(run); } public static readonly DependencyProperty SearchTextProperty = DependencyProperty.Register("SearchText", typeof(String), typeof(SearchHightlightTextBlock), new FrameworkPropertyMetadata(null, OnDataChanged)); } Использовать вот так Не совсем элегантное решение , но работает привязка и все дела :)

Комментариев нет:

Отправить комментарий