I would like to know if there is a way to change the color of the background of the text inside a TextBox
and not for the whole TextBox
.
Like when you highlight/select the text.
TextBox
doesn't support coloured text or rich text formatting in general. Depending on your scenario, you would have to go with a TextBlock
or RichtextBox
.
You can either handle the text elements directly:
<TextBlock>
<Run Text="This is some" />
<Run Text="red"
Background="Red" />
<Run Text="text." />
</TextBlock>
Or in case you need to find a particular text, handle the text pointers:
<TextBlock x:Name="ColouredTextBlock" />
private void OnLoaded(object sender, EventArgs e)
{
var text = "This is some red text.";
var highlightText = "red";
this.ColouredTextBlock.Text = text;
int highlightTextIndex = this.ColouredTextbox.Text.IndexOf(highlightText);
TextPointer textStartPointer = this.ColouredTextbox.ContentStart.DocumentStart.GetInsertionPosition(LogicalDirection.Forward);
var highlightTextRange = new TextRange(textStartPointer.GetPositionAtOffset(highlightTextIndex), textStartPointer.GetPositionAtOffset(highlightTextIndex + highlightText.Length));
highlightTextRange.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Red);
}
To make highlighting dynamic you can use a MultiBinding
to create the inline elements using a text-to-Inline converter. Since we can't bind directly to the TextBlock.Inlines
property, we use the TextBlock.Text
property as a dummy binding target:
HighlightInfo.cs
public struct HighlightInfo
{
/// <summary>
/// Set Range parameter: inclusive start index and exclusive end index
/// </summary>
/// <param name="highlightRange">inclusive start index and exclusive end index</param>
public HighlightInfo(Range highlightRange, Color foreground, Color background)
{
HighlightRange = highlightRange;
Foreground = foreground;
Background = background;
}
public System.Range HighlightRange { get; }
public int HighlightRangeLength => this.HighlightRange.End.Value - this.HighlightRange.Start.Value;
public Color Foreground { get; }
public Color Background { get; }
}
TextToInlineConverter.cs
public class TextToInlineConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string sourceText = values.OfType<string>().First();
if (string.IsNullOrWhiteSpace(sourceText))
{
return Binding.DoNothing;
}
TextBlock textBlock = values.OfType<TextBlock>().First();
IEnumerable<HighlightInfo> highlightInfos = values.OfType<IEnumerable<HighlightInfo>>().First();
List<HighlightInfo> sortedHighlightInfos = highlightInfos
.OrderBy(highlightInfo => highlightInfo.HighlightRange.Start.Value)
.ToList();
int highlightStartIndex = sortedHighlightInfos.FirstOrDefault().HighlightRange.Start.Value;
bool hasPreccedingNonHighlightText = highlightStartIndex > 0;
if (hasPreccedingNonHighlightText)
{
string preceedingText = sourceText.Substring(0, highlightStartIndex);
textBlock.Inlines.Add(preceedingText);
}
CreateHighlightTextElements(sourceText, textBlock, sortedHighlightInfos);
int highlightEndIndex = sortedHighlightInfos.LastOrDefault().HighlightRange.End.Value;
bool hasTrailingNonHighlightText = highlightEndIndex < sourceText.Length;
if (hasTrailingNonHighlightText)
{
string trailingText = sourceText.Substring(highlightEndIndex);
textBlock.Inlines.Add(trailingText);
}
// We are binding to the 'TextBlock.Text' property as a dummy target, so we don't want to set it.
// We have already modified the 'TextBlcok.Inlines' collection.
return Binding.DoNothing;
}
private void CreateHighlightTextElements(string sourceText, TextBlock textBlock, List<HighlightInfo> sortedHighlightInfos)
{
for (int index = 0; index < sortedHighlightInfos.Count; index++)
{
HighlightInfo highlightInfo = sortedHighlightInfos[index];
var highlightText = new Run(sourceText.Substring(highlightInfo.HighlightRange.Start.Value, highlightInfo.HighlightRangeLength))
{
Foreground = new SolidColorBrush(highlightInfo.Foreground),
Background = new SolidColorBrush(highlightInfo.Background),
};
textBlock.Inlines.Add(highlightText);
if (index + 1 < sortedHighlightInfos.Count)
{
HighlightInfo nextHighlightInfo = sortedHighlightInfos[index + 1];
var nonHighlightTextRangeLength = nextHighlightInfo.HighlightRange.Start.Value - highlightInfo.HighlightRange.End.Value;
bool hasEnclosedNonHighlightText = nonHighlightTextRangeLength > 0;
if (hasEnclosedNonHighlightText)
{
highlightText = new Run(sourceText.Substring(highlightInfo.HighlightRange.End.Value, nonHighlightTextRangeLength));
textBlock.Inlines.Add(highlightText);
}
}
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
MainWindow.xaml.cs
partial class MainWIndow : Window
{
public ObservableCollection<HighlightInfo> HighlightInfos { get; }
public string TextValue { get; }
public MainWindow()
{
InitializeComponent();
this.TextValue = "This is some red and orange and bluegreen text.";
this.HighlightInfos = new ObservableCollection<HighlightInfo>()
{
new HighlightInfo(new Range(
this.TextValue.IndexOf("red"),
this.TextValue.IndexOf("red") + "red".Length),
Colors.Black,
Colors.Red),
new HighlightInfo(new Range(
this.TextValue.IndexOf("orange"),
this.TextValue.IndexOf("orange") + "orange".Length),
Colors.Black,
Colors.Orange),
new HighlightInfo(new Range(
this.TextValue.IndexOf("blue"),
this.TextValue.IndexOf("blue") + "blue".Length),
Colors.Black,
Colors.Blue),
new HighlightInfo(new Range(
this.TextValue.IndexOf("green"),
this.TextValue.IndexOf("green") + "green".Length),
Colors.Black,
Colors.Green),
};
}
}
MainWindow.xaml
<Window>
<Window.Resources>
<local:TextToInlineConverter x:Key="TextToInlineConverter" />
</Window.Resources>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TextToInlineConverter}">
<Binding Path="TextValue" />
<Binding Path="HighlightInfos" />
<Binding RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Window>
In case you need to allow user input, you would have to use a RichTextBox
:
<RichTextBlock x:Name="ColouredRichTextBox" />
private void OnLoaded(object sender, EventArgs e)
{
var text = "This is some red text.";
var highlightText = "red";
this.ColouredRichTextBox.Document = new FlowDocument(new Paragraph(new Run(text)));
TextPointer textStartPointer = this.ColouredRichTextBox.Document.ContentStart.DocumentStart.GetInsertionPosition(LogicalDirection.Forward);
var highlightTextRange = new TextRange(textStartPointer.GetPositionAtOffset(highlightTextIndex), textStartPointer.GetPositionAtOffset(highlightTextIndex + highlightText.Length));
highlightTextRange .ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Red);
}