Remove the last entered character in combobox - c#

I am here with another problem.
I have setup my comboBox such that it accepts only those characters which matches with the name of any items in the comboBoxItems.
Now here I am stuck with a problem. Please have a look at my code then I will explain you the problem :
private void myComboBox_KeyUp(object sender, KeyEventArgs e)
{
// Get the textbox part of the combobox
TextBox textBox = cbEffectOn.Template.FindName("PART_EditableTextBox", cbEffectOn) as TextBox;
// holds the list of combobox items as strings
List<String> items = new List<String>();
// indicates whether the new character added should be removed
bool shouldRemoveLastChar = true;
for (int i = 0; i < cbEffectOn.Items.Count; i++)
{
items.Add(cbEffectOn.Items.GetItemAt(i).ToString());
}
for (int i = 0; i < items.Count; i++)
{
// legal character input
if (textBox.Text != "" && items.ElementAt(i).StartsWith(textBox.Text))
{
shouldRemoveLastChar = false;
break;
}
}
// illegal character input
if (textBox.Text != "" && shouldRemoveLastChar)
{
textBox.Text = textBox.Text.Remove(textBox.Text.Length - 1);
textBox.CaretIndex = textBox.Text.Length;
}
}
In the last if condition I am removing the last character from the combobox. But user can use arrow keys or mouse to change the position of the cursor and enter the text at the middle of the text.
So if by entering a character at the middle of the text if the text becomes invalid I mean if it does not match the Items in the ComboBox then I should remove the last entered character. Can anybody suggest me how to get the last inserted character and remove it?
Update :
string OldValue = "";
private void myComboBox_KeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = cbEffectOn.Template.FindName("PART_EditableTextBox", cbEffectOn) as TextBox;
List<String> items = new List<String>();
for (int i = 0; i < cbEffectOn.Items.Count; i++)
{
items.Add(cbEffectOn.Items.GetItemAt(i).ToString());
}
OldValue = textBox.Text;
bool shouldReplaceWithOldValue = true;
string NewValue = textBox.Text.Insert(textBox.CaretIndex,e.Key.ToString()).Remove(textBox.CaretIndex + 1,textBox.Text.Length - textBox.CaretIndex);
for (int i = 0; i < items.Count; i++)
{
// legal character input
if (NewValue != "" && items.ElementAt(i).StartsWith(NewValue, StringComparison.InvariantCultureIgnoreCase))
{
shouldReplaceWithOldValue = false;
break;
}
}
//// illegal character input
if (NewValue != "" && shouldReplaceWithOldValue)
{
e.Handled = true;
}
}
Here I have tried to move all the code in KeyDown event to solve the above problem. This code works just fine but have 1 problem.
If I have any item named Birds & Animals then After typing Birds and a space I cannot type &.
I know what is the problem but don't know the solution.
The Problem is : To type & I have to press shift key and then press the 7 key. But both are sent as different keys.
Solutions that I think about :
1) I should move my code to KeyUp event. But here the problem of long press and fast typing will arise.
2) I think I should replace e.Key with something. But don't know what.

I'm not sure if this is what you're trying to do but I feel like you're trying to do what we typically see in visual studio Intellisense fitlering out results as we type.
Instead of removing the keystrokes, you should be using the validation mechanisms that WPF provides. Here's a sample of how this could work.
Scenarios covered:
Input matches a combox item completely: TypedInput & SelectedItem both show full match.
Input matches some element partially: TypedInput shortlists the popup list. The binding shows the matching text while SelectedItem remains null.
Input doesn't match any item in list either from start or at some
random point: user is visually given feedback (with possibility to
add additional feedback information) with typical red outline. The
TypedInput remains at last valid entry, SelectedItem may or may
not be null depending on if the last TypedInput matched any item or not.
Full Code:
MainWindow.xaml
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:Sample"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Source={x:Static l:MainWindowViewModel.CurrentInstance}}">
<StackPanel>
<TextBlock>
<Run Text="Typed valid text" />
<Run Text="{Binding TypedText}"/>
</TextBlock>
<TextBlock>
<Run Text="Valid SelectedItem" />
<Run Text="{Binding SelectedItem}"/>
</TextBlock>
<ComboBox ItemsSource="{Binding FilteredItems}" IsEditable="True" IsTextSearchEnabled="False" SelectedItem="{Binding SelectedItem}">
<ComboBox.Text>
<Binding Path="TypedText" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<l:ContainsValidationRule />
</Binding.ValidationRules>
</Binding>
</ComboBox.Text>
</ComboBox>
</StackPanel>
</Window>
MainWindow.xaml.cs
namespace Sample
{
public partial class MainWindow { public MainWindow() { InitializeComponent(); } }
}
ContainsValidationRule.cs -- Meat of the solution
namespace Sample
{
using System.Globalization;
using System.Linq;
using System.Windows.Controls;
public class ContainsValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var result = MainWindowViewModel.CurrentInstance.Items.Any(x => x.ToLower(cultureInfo).Contains((value as string).ToLower(cultureInfo)));
return new ValidationResult(result, "No Reason");
}
}
}
MainWindowViewModel - Supporting ViewModel Singleton
namespace Sample
{
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
public sealed class MainWindowViewModel : INotifyPropertyChanged
{
private string _typedText;
private string _selectedItem;
private static readonly MainWindowViewModel Instance = new MainWindowViewModel();
private MainWindowViewModel()
{
Items = new[] { "Apples", "Apples Green", "Bananas", "Bananas & Oranges", "Oranges", "Grapes" };
}
public static MainWindowViewModel CurrentInstance { get { return Instance; } }
public string SelectedItem
{
get { return _selectedItem; }
set
{
if (value == _selectedItem) return;
_selectedItem = value;
OnPropertyChanged();
}
}
public string TypedText
{
get { return _typedText; }
set
{
if (value == _typedText) return;
_typedText = value;
OnPropertyChanged();
OnPropertyChanged("FilteredItems");
}
}
public IEnumerable<string> Items { get; private set; }
public IEnumerable<string> FilteredItems
{
get
{
return Items == null || TypedText == null ? Items : Items.Where(x => x.ToLowerInvariant().Contains(TypedText.ToLowerInvariant()));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Instead of KeyUp event, subscribe to TextChanged event on your ComboBox Textbox. In event handler you can get the offset where the change has occured. You can use your validation logic inside the hanlder and delete the character at the offset if it makes Text invalid.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
TextBox textBox = cbEffectOn.Template.FindName("PART_EditableTextBox", cbEffectOn) as TextBox;
textBox.TextChanged += new TextChangedEventHandler(textBox_TextChanged);
}
void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
int index = e.Changes.First().Offset;
}

Have you considered using a string variable to hold the last legal text value in the text box portion of the combo box?
Initially, this string would be empty, as the user has not typed anything yet, then as each KeyUp event is handled, if an invalid character is input, then the previous string value is used to replace the text of the text box; otherwise the previous string value is now updated with the new complete string; awaiting anymore input by the user.

Related

C# WPF MVVM Binding commands to hyperlinks in custom text block control

I have a collection of items with a string property. That string property contains text which includes 6 digit numbers in various places like so:
this string 123456 is an example of a set of links 884555 to the following numbers
401177
155879
998552
I want to turn those 6 digit numbers into hyperlinks that when clicked will run a command on the ViewModel passing themselves as parameters. For example if I click 401177 I want to run HyperlinkCommand on the VM with the string parameter "401177". I still want to keep the formatting of the original text.
I figured the best way to do it would be with a custom control based on TextBlock. Below is the rough structure of my view, the UserControl is bound to the ViewModel, I use a ContentControl to bind to a collection of items with the property "detail", and that is templated with the custom text block bound to the "detail" property of my items.
<UserControl.DataContext>
<VM:HdViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate x:Key="DetailTemplate">
<StackPanel Margin="30,15">
<helpers:CustomTextBlock FormattedText="{Binding detail}"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl Content="{Binding ItemListing}" ContentTemplate="{StaticResource DetailTemplate}" />
</Grid>
I used the code from this question and edited it slightly to generate the following custom control:
public class CustomTextBlock : TextBlock
{
static Regex _regex = new Regex(#"[0-9]{6}", RegexOptions.Compiled);
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached("FormattedText", typeof(string), typeof(CustomTextBlock), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{ return (string)textBlock.GetValue(FormattedTextProperty); }
static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is TextBlock textBlock)) return;
var formattedText = (string)e.NewValue ?? string.Empty;
string fullText =
$"<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{formattedText}</Span>";
textBlock.Inlines.Clear();
using (var xmlReader1 = XmlReader.Create(new StringReader(fullText)))
{
try
{
var result = (Span)XamlReader.Load(xmlReader1);
RecognizeHyperlinks(result);
textBlock.Inlines.Add(result);
}
catch
{
formattedText = System.Security.SecurityElement.Escape(formattedText);
fullText =
$"<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{formattedText}</Span>";
using (var xmlReader2 = XmlReader.Create(new StringReader(fullText)))
{
try
{
dynamic result = (Span)XamlReader.Load(xmlReader2);
textBlock.Inlines.Add(result);
}
catch
{
//ignored
}
}
}
}
}
static void RecognizeHyperlinks(Inline originalInline)
{
if (!(originalInline is Span span)) return;
var replacements = new Dictionary<Inline, List<Inline>>();
var startInlines = new List<Inline>(span.Inlines);
foreach (Inline i in startInlines)
{
switch (i)
{
case Hyperlink _:
continue;
case Run run:
{
if (!_regex.IsMatch(run.Text)) continue;
var newLines = GetHyperlinks(run);
replacements.Add(run, newLines);
break;
}
default:
RecognizeHyperlinks(i);
break;
}
}
if (!replacements.Any()) return;
var currentInlines = new List<Inline>(span.Inlines);
span.Inlines.Clear();
foreach (Inline i in currentInlines)
{
if (replacements.ContainsKey(i)) span.Inlines.AddRange(replacements[i]);
else span.Inlines.Add(i);
}
}
static List<Inline> GetHyperlinks(Run run)
{
var result = new List<Inline>();
var currentText = run.Text;
do
{
if (!_regex.IsMatch(currentText))
{
if (!string.IsNullOrEmpty(currentText)) result.Add(new Run(currentText));
break;
}
var match = _regex.Match(currentText);
if (match.Index > 0)
{
result.Add(new Run(currentText.Substring(0, match.Index)));
}
var hyperLink = new Hyperlink();
hyperLink.Command = ;
hyperLink.CommandParameter = match.Value;
hyperLink.Inlines.Add(match.Value);
result.Add(hyperLink);
currentText = currentText.Substring(match.Index + match.Length);
} while (true);
return result;
}
}
This is showing the links properly, however I dont know how to bind to the command on my ViewModel. I tested the command and the parameter using a button previously, and the binding was
Command="{Binding DataContext.HyperlinkCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
So what I am hoping is that I can convert this XAML into C# and attach it to hyperLink.Command = in my custom control. I can't figure out how to access the DataContext of the UserControl that the CustomTextBlock will be placed in.
I am not under any illusion that what I am doing is the best or right way of doing things so I welcome any suggestions
This is an interesting challenge, which I have solved with new code - coming at the problem in a slightly different way:
The code can be found here:
https://github.com/deanchalk/InlineNumberLinkControl

How can I calculate what the Text of my WPF TextBox will be following these events?

I want to restrict this TextBox to only take positive integers up to and including Int32.MaxValue:
<TextBox Name="CurrentPageNumber"
PreviewTextInput="CurrentPageNumber_PreviewTextInput"
DataObject.Pasting="CurrentPageNumber_Pasting" />
I have the following events:
private void CurrentPageNumber_Pasting(object sender, DataObjectPastingEventArgs e)
{
if (!e.DataObject.GetDataPresent(typeof(String)))
e.CancelCommand();
String text = (String)e.DataObject.GetData(typeof(String));
if (!IsPositiveInteger(text))
e.CancelCommand();
}
private bool IsPositiveInteger(String text)
{
if (text.Length <= 0 || ((int)text[0] == 48 && text.Length != 1)) // Restricts empty strings and numbers with preceding 0's except for 0 itself.
return false;
for (int i = 0; i < text.Length; i++)
{
var c = (int)text[i];
if (c < 48 || c > 57) // Check that all characters are between 0 and 9.
return false;
}
int result;
return Int32.TryParse(text, out result);
}
This is not good enough, because someone can have the equivalent integer value of Int32.MaxValue (2147483647) in the text box already, and then add another 1 to the right of it. How could it be possible to use my IsPositiveInteger method by predicting the outcome of the Text following the event's operation?
This is not the same as preventing the change to the text, but it's the customary way of telling a user "you can't put that text here". I recommend doing it this way because this is the way the framework supports, so it's easiest, and usually what the framework supports is what people are most accustomed to seeing in user interfaces. What we're accustomed to, we call "intuitive".
When the user changes the text in that textbox, the ValidationRule gets invoked. If it returns a false ValidationResult, the user gets a red error border, the bound property is not updated, and the tooltip tells him what he did wrong.
If you really want to stick with your original idea, you're in for a lot of work, as you found out. I don't think the ROI justifies the effort, but when I was younger I once wrote a binary adder in Perl, so I'm in no position to throw any stones.
The conventional way to do this type of thing in WPF is using a ValidationRule on the Binding. You don't have a Binding because you don't have a viewmodel. This is a nearly incomprehensible heresy and it chills me to the very marrow, but we'll work with it. We'll add a property to your codebehind just to have something to bind to.
public int NonNegativeIntValue { get; set; }
If you want to be able to update the textbox by setting that property in your codebehind, you'll have to go the INotifyPropertyChanged route. Better yet, use a viewmodel; every time you do anything without a viewmodel in WPF, it turns out you'd have been better off with one. The framework is like a traffic cop; acknowledge its authority and it'll be easier to get along with.
And then we'll write a ValidationRule:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Controls;
namespace ValidationRules
{
public class PositiveIntegerRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int result;
if (value.GetType() == typeof(String) && Int32.TryParse((String)value, out result) && result > 0)
return new ValidationResult(true, null);
return new ValidationResult(false, "Value must be a positive integer");
}
}
}
And finally we'll fix your TextBox:
<TextBox
Name="CurrentPageNumber"
xmlns:vr="clr-namespace:ValidationRules"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"
>
<TextBox.Text>
<Binding
Path="NonNegativeIntValue"
RelativeSource="{RelativeSource AncestorType=Window}"
>
<Binding.ValidationRules>
<vr:PositiveIntegerRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
I'm making one assumption here: That your codebehind class inherits from Window. If it's UserControl, you'll have to change RelativeSource AncestorType=Window in the binding to RelativeSource AncestorType=UserControl -- whatever's right.
Another way to do it other than Ed's answer is like this, this way will ensure the UI always has a usable, visible value displayed, but it is not as clean and so I recommend people go with validation rules as the other answer has (its more WPF-like). Also, I had an error with my IsPositiveInteger method; originally I wanted 0 but now I realize since I am paging data I want 1 as the beginning page.
XAML:
<TextBox Name="CurrentPageNumber" TextChanged="CurrentPageNumber_TextChanged"/>
C#:
private bool IsPositive32BitInteger(String text)
{
if (text.Length <= 0 || (int)text[0] == 48) // Restricts empty strings and numbers with preceding 0's or 0 itself.
return false;
for (int i = 0; i < text.Length; i++)
{
var c = (int)text[i];
if (c < 48 || c > 57) // Check that all characters are between 0 and 9.
return false;
}
int result;
return Int32.TryParse(text, out result);
}
private void CurrentPageNumber_TextChanged(object sender, TextChangedEventArgs e)
{
var textBox = (TextBox)sender;
if (textBox.Text.Length <= 0)
{
textBox.Text = "1";
return; // Set text to "1" as default and return. The event will fire again since we set the Text property.
}
if (textBox.Text.StartsWith("0"))
{
textBox.Text = textBox.Text.TrimStart(new char[] { '0' });
return; // Trim the text and return. The event will fire again since we set the Text property.
}
if (!IsPositive32BitInteger(textBox.Text)) {
textBox.Text = "1";
return; // Set text to "1" as default and return. The event will fire again since we set the Text property.
}
// At this point the value is forced into the state you want and you can do other stuff.
}

Two ListPicker, one TextBox. Result not displayed. Master-Detail?

I have two ListPickers originating from one TextBox.
private void textBox3_GotFocus(object sender, RoutedEventArgs e)
{
string a = "Domestic";
string b = Convert.ToString(textBox2.Text);
string c = "Foreign";
if (b == a)
{
NavigationService.Navigate(new Uri("/ListPickerExchanges1.xaml", UriKind.Relative));
}
else if (b == c)
{
NavigationService.Navigate(new Uri("/ListPickerExchanges2.xaml", UriKind.Relative));
}
Both ListPickers load and give you the option to choose something from the list. But only one ListPicker will displays the selection back into TextBox3 , and it is always the one under the second Else If (b == c) condition.
The ListPickerExchanges1 under the first condition will not display the selection back into textbox3.
But if i change the code under the second Else If condition to navigate to Exchanges1 instead of Exchanges2, then the Exchanges1 listpicker displays back the selection into textbox 3, and Exchanges2 does not.
Which means, Everything under the second condition works, and does not under the first condition.
Here is the code behind ListpickerExchanges1, which is intended to display the selection back into the textbox.
public partial class ListPickerExchanges1 : PhoneApplicationPage
{
public ListPickerExchanges1()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(ListPickerExchanges1_Loaded);
}
private void ListPickerExchanges1_Loaded(object sender, RoutedEventArgse)
{
ListBoxExchanges.ItemsSource = Page2.ListExchanges;
}
public void Selection(object sender, System.Windows.Input.GestureEventArgs e)
{
ListBox myselecteditem4 = (ListBox)sender;
ItemsModeling4 item4 = (ItemsModeling4)myselecteditem4.SelectedItem;
if (item4 != null)
{
NavigationService.Navigate(new Uri("/Broker.xaml?name4=" + item4.Exchanges, UriKind.RelativeOrAbsolute));
}
}
..............................................................................................................................................................
Update: Additional Code behind Navigation and Selection
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
string name4 = "";
NavigationContext.QueryString.TryGetValue("name4", out name4);
textBox3.Text = string.Format("{0}", name4);
}
..............................................................................................................................................................
public Page2()
{
InitializeComponent();
ListExchanges = new ObservableCollection<ItemsModeling4>();
this.Loaded += new RoutedEventHandler(Page44_Loaded);
}
..............................................................................................................................................................
private void Page44_Loaded(object sender, RoutedEventArgs e)
{
if (ListExchanges.Count == 0)
{
string[] arrangeExchanges = new string[] { "Option1", "Option2", "Option3", "Option4" };
foreach (string item4 in arrangeExchanges)
{
ListExchanges.Add(new ItemsModeling4 { Exchanges = item4 });
}
}
}
public static ObservableCollection<ItemsModeling4> ListExchanges { get; private set; }
..............................................................................................................................................................
public class ItemsModeling4
{
public string Exchanges { get; set; }
}
..............................................................................................................................................................
Xaml
<ListBox x:Name="ListBoxExchanges" Background="Transparent"
Tap="Selection">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Height="Auto" FontFamily="Segoe WP Semibold" FontSize="50" HorizontalAlignment="Left" Margin="12,0,0,0"
Text="{Binding Exchanges}" VerticalAlignment="Top" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What am i doing wrong here?
It Seems like a Master - Detail binding issue. I just don't see where or how to fix it.
The system doesn't give any errors, and I've checked everything with the debugger and everything seems fine.
The listpickers work independently and i don't see where the code might intervene and stop the one from working. It all seems good to me.
help
But if i change the code under the second Else If condition to navigate to Exchanges1 instead of Exchanges2, then the Exchanges1 listpicker displays back the selection into textbox 3, and Exchanges2 does not.
Are you sure that you are comparing culturaly correct strings? Try this compare instead for both of them.
if (String.Equals(a, b, InvariantCultureIgnoreCase))
See StringComparison Enumeration (System) and Best Practices for Using Strings in the .NET Framework for more info.
Otherwise strings can be tricky with spaces. Try
String.Equals(a.Trim(), b.Trim());
to remove any spaces the user may be adding \r\n maybe?
Frankly since the code knows about the choices it may be better to simply provide a ComboBox with the selectable choices for the user instead of using a TextBox.

Delete input if date is invalid : XAML datepicker

I am working with datepicker control in silverlight. I want to delete input if date is invalid.
I have tried following code :
XAML:
<sdk:DatePicker Height="23" x:Name="datePicker1" Width="179"
DateValidationError="datePicker1_DateValidationError" />
C#:
private void datePicker1_DateValidationError(object sender, DatePickerDateValidationErrorEventArgs e)
{
try
{
if (datePicker1.SelectedDate != null && (Regex.IsMatch(datePicker1.SelectedDate.ToString(), #"^([1-9]|0[1-9]|1[0-2])[- / .]([1-9]|0[1-9]|1[0-9]|2[0-9]|3[0-1])[- / .](1[9][0-9][0-9]|2[0][0-9][0-9])$")))
{
//WORKING FINE
}
else
{
MessageBox.Show("Please enter a valid date");
DatePicker myElement = ((FrameworkElement)System.Windows.Application.Current.RootVisual).FindName("datePicker1") as DatePicker; // search for control named "datePicker1"
myElement.Text = String.Empty;
}
}
catch
{
}
}
But myElement returns null and I can not delete invalid input.
Here's some code I used to do a similar thing:
Code behind:
private void DatePicker_DateValidationError(object sender, DatePickerDateValidationErrorEventArgs e)
{
var MyDatePicker = sender as DatePicker;
DatePickerTextBox dateBox = MyDatePicker.GetVisualDescendants().OfType<DatePickerTextBox>().FirstOrDefault();
if (dateBox != null)
{
dateBox.Text = String.Empty;
}
// clear the SelectedDate property too, otherwise you'll be left with the last valid one
MyDatePicker.SelectedDate = null;
}
Xaml:
<sdk:DatePicker DateValidationError="DatePicker_DateValidationError"/>
I have a number of pickers all on the same form, and all sharing this DateValidationError handler, hence not finding the picker by name.
No need to search for datePicker1 if you have set x:Name already on it. You can access directly it in code behind:
datePicker1.Text = String.Empty;
Moreover, if it's part of some template which is restricting you from accessing it, it is already passed in you method as sender object. You can get it like this:
DatePicker myElement = (DatePicker)sender;
myElement.Text = String.Empty;

WPF TextBlock highlight certain parts based on search condition

I have TextBlock that has Inlines dynamicly added to it (basically bunch of Run objects that are either italic or bold).
In my application I have search function.
I want to be able to highlight TextBlock's text that is in being searched for.
By highlighting I mean changing certain parts of TextBlock text's color (keeping in mind that it may highlight several different Run objects at a time).
I have tried this example http://blogs.microsoft.co.il/blogs/tamir/archive/2008/05/12/search-and-highlight-any-text-on-wpf-rendered-page.aspx
But it seams very unstable :(
Is there easy way to solve this problem?
This question is similar to How to display search results in a WPF items control with highlighted query terms
In answer to that question, I came up with an approach that uses an IValueConverter. The converter takes a text snippet, formats it into valid XAML markup, and uses a XamlReader to instantiate the markup into framework objects.
The full explanation is rather long, so I've posted it to my blog: Highlighting Query Terms in a WPF TextBlock
I took dthrasers answer and took out the need for an XML parser. He does a great job explaining each of the pieces in his blog, However this didn't require me to add any extra libraries, here's how I did it.
Step one, make a converter class:
class StringToXamlConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string input = value as string;
if (input != null)
{
var textBlock = new TextBlock();
textBlock.TextWrapping = TextWrapping.Wrap;
string escapedXml = SecurityElement.Escape(input);
while (escapedXml.IndexOf("|~S~|") != -1) {
//up to |~S~| is normal
textBlock.Inlines.Add(new Run(escapedXml.Substring(0, escapedXml.IndexOf("|~S~|"))));
//between |~S~| and |~E~| is highlighted
textBlock.Inlines.Add(new Run(escapedXml.Substring(escapedXml.IndexOf("|~S~|") + 5,
escapedXml.IndexOf("|~E~|") - (escapedXml.IndexOf("|~S~|") + 5)))
{ FontWeight = FontWeights.Bold, Background= Brushes.Yellow });
//the rest of the string (after the |~E~|)
escapedXml = escapedXml.Substring(escapedXml.IndexOf("|~E~|") + 5);
}
if (escapedXml.Length > 0)
{
textBlock.Inlines.Add(new Run(escapedXml));
}
return textBlock;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("This converter cannot be used in two-way binding.");
}
}
Step two:
Instead of a TextBlock use a ContentBlock. Pass in the string (you would of used for your textBlock) to the content block, like so:
<ContentControl Margin="7,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{Binding Description, Converter={StaticResource CONVERTERS_StringToXaml}, Mode=OneTime}">
</ContentControl>
Step three:
Make sure the text you pass includes |~S~| before and |~E~| after the text part you want to be highlighted. For example in this string "my text |~S~|is|~E~| good" the is will be highlighted in yellow.
Notes:
You can change the style in the run to determine what and how your text is highlighted
Make sure you add your Converter class to your namespace and resources. This might also require a rebuild to get working.
Differences to other solutions
easier to reuse -> attached behavior instead of custom control
MVVM friendly -> no code behind
works BOTH ways! -> Changing the term to be highlighted OR the text, both updates the highlight in the textblock. The other solutions i checked had the problem, that changing the text does not reapply the highlighting. Only changing the highlighted term/search text worked.
How to use
IMPORTANT: do NOT use the regular Text="blabla" property of the TextBlock anymore. Instead bind your text to HighlightTermBehavior.Text="blabla".
Add the attached properties to your TextBlock like that
<TextBlock local:HighlightTermBehavior.TermToBeHighlighted="{Binding MyTerm}"
local:HighlightTermBehavior.Text="{Binding MyText}" />
or hardcoded
<TextBlock local:HighlightTermBehavior.TermToBeHighlighted="highlight this"
local:HighlightTermBehavior.Text="bla highlight this bla" />
Add this class
To change the kind of highlighting, just change these Methods:
AddPartToTextBlock() for non highlighted text
AddHighlightedPartToTextBlock() for the highlighted text.
At the moment highlighted is FontWeights.ExtraBold and non highlighted text is FontWeights.Light.
probably hard to read without an IDE, sorry.
public static class HighlightTermBehavior
{
public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
"Text",
typeof(string),
typeof(HighlightTermBehavior),
new FrameworkPropertyMetadata("", OnTextChanged));
public static string GetText(FrameworkElement frameworkElement) => (string) frameworkElement.GetValue(TextProperty);
public static void SetText(FrameworkElement frameworkElement, string value) => frameworkElement.SetValue(TextProperty, value);
public static readonly DependencyProperty TermToBeHighlightedProperty = DependencyProperty.RegisterAttached(
"TermToBeHighlighted",
typeof(string),
typeof(HighlightTermBehavior),
new FrameworkPropertyMetadata("", OnTextChanged));
public static string GetTermToBeHighlighted(FrameworkElement frameworkElement)
{
return (string) frameworkElement.GetValue(TermToBeHighlightedProperty);
}
public static void SetTermToBeHighlighted(FrameworkElement frameworkElement, string value)
{
frameworkElement.SetValue(TermToBeHighlightedProperty, value);
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBlock textBlock)
SetTextBlockTextAndHighlightTerm(textBlock, GetText(textBlock), GetTermToBeHighlighted(textBlock));
}
private static void SetTextBlockTextAndHighlightTerm(TextBlock textBlock, string text, string termToBeHighlighted)
{
textBlock.Text = string.Empty;
if (TextIsEmpty(text))
return;
if (TextIsNotContainingTermToBeHighlighted(text, termToBeHighlighted))
{
AddPartToTextBlock(textBlock, text);
return;
}
var textParts = SplitTextIntoTermAndNotTermParts(text, termToBeHighlighted);
foreach (var textPart in textParts)
AddPartToTextBlockAndHighlightIfNecessary(textBlock, termToBeHighlighted, textPart);
}
private static bool TextIsEmpty(string text)
{
return text.Length == 0;
}
private static bool TextIsNotContainingTermToBeHighlighted(string text, string termToBeHighlighted)
{
return text.Contains(termToBeHighlighted, StringComparison.Ordinal) == false;
}
private static void AddPartToTextBlockAndHighlightIfNecessary(TextBlock textBlock, string termToBeHighlighted, string textPart)
{
if (textPart == termToBeHighlighted)
AddHighlightedPartToTextBlock(textBlock, textPart);
else
AddPartToTextBlock(textBlock, textPart);
}
private static void AddPartToTextBlock(TextBlock textBlock, string part)
{
textBlock.Inlines.Add(new Run {Text = part, FontWeight = FontWeights.Light});
}
private static void AddHighlightedPartToTextBlock(TextBlock textBlock, string part)
{
textBlock.Inlines.Add(new Run {Text = part, FontWeight = FontWeights.ExtraBold});
}
public static List<string> SplitTextIntoTermAndNotTermParts(string text, string term)
{
if (text.IsNullOrEmpty())
return new List<string>() {string.Empty};
return Regex.Split(text, $#"({Regex.Escape(term)})")
.Where(p => p != string.Empty)
.ToList();
}
}
By strange coincidence, I have recently written an article that solves the very same problem. It is a custom control that has the same properties as a TextBlock (so you can swap is out for a TextBlock wherever you need it), and it has an extra Property that you can bind to called HighLightText, and wherever the value of HighLightText is found in the main Text property (case insensitive), it is highlighted.
It was a fairly straight-forward control to create, and you can find the full code as a solution here:
SearchMatchTextblock(GitHub)
Here is what I came up with by building off of the exisiting TextBlock and adding a new dependency property named SearchText:
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 = tb.Text.Substring(0, firstIndex);
String foundStr = tb.Text.Substring(firstIndex, toFind.Length);
String endStr = tb.Text.Substring(firstIndex + toFind.Length,
tb.Text.Length - (firstIndex + toFind.Length));
tb.Inlines.Clear();
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));
}
And in your view, this:
<view:SearchHightlightTextBlock SearchText="{Binding TextPropertyContainingTextToSearch}"
Text="{Binding YourTextProperty}"/>
Here I present another Approach for highlighting text. I had a use case where I needed to decorate a bunch of C# Code in WPF, however I did not want to use textBlock.Inlines.Add type of syntax, instead I wanted to generate the highlighting XAML on the fly and then dynamically add it to a Canvas or some other container in WPF.
So suppose you want to colorize the following piece of code and also highlight a part of it:
public static void TestLoop(int count)
{
for(int i=0;i<count;i++)
Console.WriteLine(i);
}
Suppose the above code is found in a file called Test.txt .
Suppose you want to colorize all the C# keywords (public, static, void etc..) and simple types(int, string) in Blue, and Console.WriteLine highlight in yellow.
Step 0. Create a new WPF Application and include some sample code similar to above in a file called Test.txt
Step 1. Create a Code Highlighter class:
using System.IO;
using System.Text;
public enum HighLightType
{
Type = 0,
Keyword = 1,
CustomTerm = 2
}
public class CodeHighlighter
{
public static string[] KeyWords = { "public", "static", "void", "return", "while", "for", "if" };
public static string[] Types = { "string", "int", "double", "long" };
private string FormatCodeInXaml(string code, bool withLineBreak)
{
string[] mapAr = { "<","<" , //Replace less than sign
">",">" }; //Replace greater than sign
StringBuilder sb = new StringBuilder();
using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
{
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
line = line.Replace("\t", "    "); //Replace tabs
line = line.Replace(" ", " "); //Replace spaces
for (int i = 0; i < mapAr.Length; i += 2)
line = line.Replace(mapAr[i], mapAr[i + 1]);
if (withLineBreak)
sb.AppendLine(line + "<LineBreak/>"); //Replace line breaks
else
sb.AppendLine(line);
}
}
return sb.ToString();
}
private string BuildForegroundTag(string highlightText, string color)
{
return "<Span Foreground=\"" + color + "\">" + highlightText + "</Span>";
}
private string BuildBackgroundTag(string highlightText, string color)
{
return "<Span Background=\"" + color + "\">" + highlightText + "</Span>";
}
private string HighlightTerm(HighLightType type, string term, string line)
{
if (term == string.Empty)
return line;
string keywordColor = "Blue";
string typeColor = "Blue";
string statementColor = "Yellow";
if (type == HighLightType.Type)
return line.Replace(term, BuildForegroundTag(term, typeColor));
if (type == HighLightType.Keyword)
return line.Replace(term, BuildForegroundTag(term, keywordColor));
if (type == HighLightType.CustomTerm)
return line.Replace(term, BuildBackgroundTag(term, statementColor));
return line;
}
public string ApplyHighlights(string code, string customTerm)
{
code = FormatCodeInXaml(code, true);
customTerm = FormatCodeInXaml(customTerm, false).Trim();
StringBuilder sb = new StringBuilder();
using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
{
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
line = HighlightTerm(HighLightType.CustomTerm, customTerm, line);
foreach (string keyWord in KeyWords)
line = HighlightTerm(HighLightType.Keyword, keyWord, line);
foreach (string type in Types)
line = HighlightTerm(HighLightType.Type, type, line);
sb.AppendLine(line);
}
}
return sb.ToString();
}
}
Step 2. Add a Canvas XAML tag to your MainWindow.xaml
<Window x:Class="TestCodeVisualizer.MainWindow"
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:TestCodeVisualizer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="canvas" />
</Window>
Step 3. In Your WPF Application add the following code: (make sure that test.txt is in the correct location) :
using System.Text;
using System.IO;
using System.Windows;
using System.Windows.Markup;
namespace TestCodeVisualizer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
string testText = File.ReadAllText("Test.txt");
FrameworkElement fe = GenerateHighlightedTextBlock(testText, "Console.WriteLine");
this.canvas.Children.Add(fe);
}
private FrameworkElement GenerateHighlightedTextBlock(string code, string term)
{
CodeHighlighter ch = new CodeHighlighter();
string uc = "<UserControl xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>[CONTENT]</UserControl>";
string content = "<TextBlock>" + ch.ApplyHighlights(code, term) + "</TextBlock>";
uc = uc.Replace("[CONTENT]", content);
FrameworkElement fe = XamlReader.Load(new System.IO.MemoryStream(Encoding.UTF8.GetBytes(uc))) as FrameworkElement;
return fe;
}
}
}
I had a similar problem - trying to implement a text search over a load of presenters that basically represent a report. The report was originally written into a string and we were leveraging FlowDocumentViewer's built in ctrl-F - it's not very good and has some wierd options but was sufficient.
If you just want something like that you can do the following:
<FlowDocumentScrollViewer>
<FlowDocument>
<Paragraph FontFamily="Lucida Console" FontSize="12">
<Run Text="{Binding Content, Mode=OneWay}"/>
</Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
We decided to go for a rewrite as the report is kept in sync with the rest of the program and basically every edit changes it, having to recreate the entire report everytime means that this is quite slow. We wanted to improve this by moving to a update-the-bits-you-need-to model but needed to have view model (rather than just a string) to be able to do that in a sane way! We wanted to preserve the searching functionality before swapping out the report however and go one better and have highlighting of the 'current' search position in one colour and other search hits in another.
Here's a simplified version of my solution; a class that derives from TextBlock that adds a dependency property of Type HighlightingInformation. I've not included the namespace and usings as they are sensitive.
public class HighlightingTextBlock : TextBlock
{
public static readonly DependencyProperty HighlightingProperty =
DependencyProperty.Register("Highlighting", typeof (HighlightingInformation), typeof (HighlightingTextBlock));
public HighlightingInformation Highlighting
{
get { return (HighlightingInformation)GetValue(HighlightingProperty); }
set { SetValue(HighlightingProperty, value); }
}
public HighlightingTextBlock()
{
AddValueChangedCallBackTo(HighlightingProperty, UpdateText);
}
private void AddValueChangedCallBackTo(DependencyProperty property, Action updateAction)
{
var descriptor = DescriptorFor(property);
descriptor.AddValueChanged(this, (src, args) => updateAction());
}
private DependencyPropertyDescriptor DescriptorFor(DependencyProperty property)
{
return DependencyPropertyDescriptor.FromProperty(property, GetType());
}
private void UpdateText()
{
var highlighting = Highlighting;
if (highlighting == null)
return;
highlighting.SetUpdateMethod(UpdateText);
var runs = highlighting.Runs;
Inlines.Clear();
Inlines.AddRange(runs);
}
}
The type this class can be bound to uses the update method when it's text and list of highlights are changed to update the list of Runs. The highlights themselves look something like this:
public class Highlight
{
private readonly int _length;
private readonly Brush _colour;
public int Start { get; private set; }
public Highlight(int start, int length,Brush colour)
{
Start = start;
_length = length;
_colour = colour;
}
private string TextFrom(string currentText)
{
return currentText.Substring(Start, _length);
}
public Run RunFrom(string currentText)
{
return new Run(TextFrom(currentText)){Background = _colour};
}
}
To produce the correct collection of highlights is a seperate problem, which I basically solved by treating the collection of presenters as a Tree that you recursively search for content - leaf nodes are those that have content and other nodes just have children. If you search depth-first you get the order you'd expect. You can then basically write a wrapper around the list of results to keep track of the position. Im not going to post all the code for this - my response here it is to document how you can make wpf do multi-coloured highlighting in MVP style.
I haven't used INotifyPropertyChanged or CollectionChanged here as we didn't need the changes to be multi-cast (eg one presenter has multiple views). Initially I tried to do that by adding an event changed notification for Text and one for a list (which you also have to manually subscribe to the INotifyCollectionChanged event on). I had concerns about memory leaks from the event subcriptions however and the fact that the updates for the text and the highlights didn't come at the same time made it problematic.
The one drawback of this approach is that people shouldn't bind to the Text property of this control. In the real version I have added some checking + exception throwing to stop people from doing this but ommitted it from the example for clarity's sake!
Ended up writing following code
At moment has few bugs, but solves the problem
if (Main.IsFullTextSearch)
{
for (int i = 0; i < runs.Count; i++)
{
if (runs[i] is Run)
{
Run originalRun = (Run)runs[i];
if (Main.SearchCondition != null && originalRun.Text.ToLower()
.Contains(Main.SearchCondition.ToLower()))
{
int pos = originalRun.Text.ToLower()
.IndexOf(Main.SearchCondition.ToLower());
if (pos > 0)
{
Run preRun = CloneRun(originalRun);
Run postRun = CloneRun(originalRun);
preRun.Text = originalRun.Text.Substring(0, pos);
postRun.Text = originalRun.Text
.Substring(pos + Main.SearchCondition.Length);
runs.Insert(i - 1 < 0 ? 0 : i - 1, preRun);
runs.Insert(i + 1, new Run(" "));
runs.Insert(i + 2, postRun);
originalRun.Text = originalRun.Text
.Substring(pos, Main.SearchCondition.Length);
SolidColorBrush brush = new SolidColorBrush(Colors.Yellow);
originalRun.Background = brush;
i += 3;
}
}
}
}
}
If you are handling ContainerContentChanging for your ListViewBase, you can take the following approach: TextBlock highlighting for WinRT/ContainerContentChanging
Please note that this code is for Windows RT. The WPF syntax will be slightly different. Also note that if you are using binding to populate the TextBlock.Text property, the text generated by my approach will be overwritten. I use ContainerContentChanging to populate target fields because of radically-increased performance and improvements in memory usage, vs. normal binding. I use binding only to manage the source data, not the data view.
The following highlight search method takes your TextBlock and search term then returns your block with this term or words which contain this term highlighted purple.
private TextBlock HighlightSearch(TextBlock textBlock, string searchTerm)
{
string[] words = textBlock.Text.Split(' ');
textBlock.Text = string.Empty;
foreach (string word in words)
{
if (!string.IsNullOrEmpty(searchTerm) &&
word.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0)
{
textBlock.Inlines.Add(new Run($"{word} ") { Foreground = Brushes.Purple, FontWeight = FontWeights.DemiBold });
}
else
{
textBlock.Inlines.Add($"{word} ");
}
}
return textBlock;
}
`
The requirement I had was highlighting must be fully style-able beyond just a few pre-defined options:
public partial class HighlightTextBlock : UserControl
{
public HighlightTextBlock()
{
InitializeComponent();
}
public static readonly DependencyProperty TextBlockStyleProperty = DependencyProperty.Register(
nameof(TextBlockStyle), typeof(Style), typeof(HighlightTextBlock), new PropertyMetadata(default(Style)));
public Style TextBlockStyle
{
get { return (Style)GetValue(TextBlockStyleProperty); }
set { SetValue(TextBlockStyleProperty, value); }
}
public static readonly DependencyProperty HighlightTextElementStyleProperty = DependencyProperty.Register(
nameof(HighlightTextElementStyle), typeof(Style), typeof(HighlightTextBlock), new PropertyMetadata(default(Style)));
public Style HighlightTextElementStyle
{
get { return (Style)GetValue(HighlightTextElementStyleProperty); }
set { SetValue(HighlightTextElementStyleProperty, value); }
}
public static readonly DependencyProperty NormalTextElementStyleProperty = DependencyProperty.Register(
nameof(NormalTextElementStyle), typeof(Style), typeof(HighlightTextBlock), new PropertyMetadata(default(Style)));
public Style NormalTextElementStyle
{
get { return (Style)GetValue(NormalTextElementStyleProperty); }
set { SetValue(NormalTextElementStyleProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
nameof(Text), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(default(string), PropertyChangedCallback));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty HighlightProperty = DependencyProperty.Register(
nameof(Highlight), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(default(string), PropertyChangedCallback));
public string Highlight
{
get { return (string)GetValue(HighlightProperty); }
set { SetValue(HighlightProperty, value); }
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.EnsureType<HighlightTextBlock>().update();
}
private void update()
{
var highlightLength = this.Highlight?.Length ?? 0;
if (highlightLength > 0)
{
var highlightOffset = this.Text?.IndexOf(this.Highlight, StringComparison.InvariantCultureIgnoreCase) ?? -1;
if (highlightOffset > -1)
{
PrefixRun.Text = this.Text.Substring(0, highlightOffset);
HighlightRun.Text = this.Text.Substring(highlightOffset, highlightLength);
SuffixRun.Text = this.Text.Substring(highlightOffset + highlightLength);
return;
}
}
PrefixRun.Text = this.Text;
HighlightRun.Text = null;
SuffixRun.Text = null;
}
}
Mind PropertyChangedCallback used by HighlightProperty and TextProperty.
XAML:
<UserControl x:Class="Example.HighlightTextBlock"
x:Name="self"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
<Grid>
<TextBlock DataContext="{Binding ElementName=self}" Style="{Binding TextBlockStyle}">
<!-- NOTE: TO avoid whitespaces when rendering Inlines, avoid them in markup (e.g. between Run tags)-->
<TextBlock.Inlines><Run
x:Name="PrefixRun" x:FieldModifier="private" Style="{Binding NormalTextElementStyle}"/><Run
x:Name="HighlightRun" x:FieldModifier="private" Style="{Binding HighlightTextElementStyle}"/><Run
x:Name="SuffixRun" x:FieldModifier="private" Style="{Binding NormalTextElementStyle}"/></TextBlock.Inlines>
</TextBlock>
</Grid>
</UserControl>
DataTemplate:
<DataTemplate x:Key="ExampleDataTemplate">
<DataTemplate.Resources>
<Style x:Key="HighlightTextElementStyle" TargetType="{x:Type Inline}">
<Setter Property="Foreground" Value="DarkGray"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="TextDecorations" Value="Underline"/>
</Style>
<Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="TextAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<controls1:HighlightTextBlock Text="{Binding ExampleText}"
Highlight="{Binding ExampleHighlight}"
TextBlockStyle="{StaticResource TextBlockStyle}"
HighlightTextElementStyle="{StaticResource HighlightTextElementStyle}"/>
</DataTemplate>

Categories

Resources