דרך xaml מאוד קל לעצב טקסט ב- textblock ב-wpf
כמתואר כאן בטוב טעם
אבל איך עושים binding עבור זה?
ראיתי הרבה כתבות בנושא וכל אחד מציע את הזווית שלו הנה הניסיון שלי (הוא מוגבל כרגע לbold ן-italics אבל אפשר להרחיב את הרעיון, אשמח לקבל משוב:
<Window x:Class="HighlightSample.HighlighterTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HighlightSample"
mc:Ignorable="d"
Title="HighlighterTest"
Height="250" Width="250">
<Window.DataContext>
<local:HighlighterTextViewModel x:Name="vm"/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="PopulateButton" Command="{Binding PopulateResultsCommand}"
Content="Populate" Margin="5"/>
<ListBox ItemsSource="{Binding SearchResults}" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<local:HighlighterTextBlock
HighlightedText="{Binding SearchResult}"
TextWrapping="Wrap"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
using System;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Documents;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using PdfiumViewer.Demo.Annotations;
namespace HighlightSample
{
internal class HighlighterTextViewModel : ViewModelBase
{
private ObservableCollection<TextModel> _searchResults = new ObservableCollection<TextModel> { };
public ICommand PopulateResultsCommand { get => new RelayCommand(PopulateResults); }
public void PopulateResults()
{
for (int i = 0; i < 100000; i++)
{
SearchResults.Add(new TextModel());
}
}
public ObservableCollection<TextModel> SearchResults
{
get { return _searchResults; }
set
{
if (_searchResults != value)
{
_searchResults = value;
OnPropertyChanged(nameof(SearchResults));
}
}
}
}
internal class TextModel : ViewModelBase
{
private string _searchResult = "This is a <b>bold</b> and <i>italic</i> example.";
public string SearchResult
{
get { return _searchResult; }
set
{
if (_searchResult != value)
{
_searchResult = value;
OnPropertyChanged(nameof(SearchResult));
}
}
}
}
public class HighlighterTextBlock : TextBlock
{
public static readonly DependencyProperty HighlightedTextProperty =
DependencyProperty.Register("HighlightedText", typeof(string), typeof(HighlighterTextBlock), new PropertyMetadata(string.Empty, OnHighlightedTextChanged));
public string HighlightedText
{
get { return (string)GetValue(HighlightedTextProperty); }
set { SetValue(HighlightedTextProperty, value); }
}
private static void OnHighlightedTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as HighlighterTextBlock;
control?.UpdateText();
}
private void UpdateText()
{
Inlines.Clear();
if (string.IsNullOrEmpty(HighlightedText))
{
return;
}
string pattern = @"(<b>(.*?)</b>)|(<i>(.*?)</i>)"; // Example pattern for bold and italic tags
int lastPos = 0;
var matches = Regex.Matches(HighlightedText, pattern);
foreach (Match match in matches)
{
// Add text before the match
if (match.Index > lastPos)
{
Inlines.Add(new Run(HighlightedText.Substring(lastPos, match.Index - lastPos)));
}
// Add highlighted or formatted text
if (match.Groups[1].Success) // Bold tag
{
Inlines.Add(new Bold(new Run(match.Groups[2].Value)));
}
else if (match.Groups[3].Success) // Italic tag
{
Inlines.Add(new Italic(new Run(match.Groups[4].Value)));
}
lastPos = match.Index + match.Length;
}
// Add remaining text after the last match
if (lastPos < HighlightedText.Length)
{
Inlines.Add(new Run(HighlightedText.Substring(lastPos)));
}
}
}
public abstract class ICommandBase : ICommand
{
public event EventHandler CanExecuteChanged;
public virtual bool CanExecute(object parameter)
{
return true;
}
public abstract void Execute(object parameter);
protected void OnCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
public class RelayCommand : ICommandBase
{
private Action commandTask;
public RelayCommand(Action action)
{
commandTask = action;
}
public override void Execute(object parameter)
{
commandTask();
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string PropertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
[NotifyPropertyChangedInvocator]
protected virtual bool Set<T>(ref T field, T value, [CallerMemberName] string PropertyName = null)
{
if (Equals(field, value)) return false;
field = value;
OnPropertyChanged(PropertyName);
return true;
}
}
}