XAML Convert textblock text to Inlines - c#

I want to set this kind of text on TextBlock in a UWP project :
"<Bold>" + variable + "</Bold>"
But set it to Text value do not consider <Bold> tags.
So i searched how to do and the only one answer is "creat Inlines and add it to your textBlock".
but i don't want to do it on my View Model.
So i'm looking for a converter to replace my text attribute by a inlines collection to set on my textBlock.
I found some example (https://social.msdn.microsoft.com/Forums/en-US/1a1af975-e186-4167-b1c9-cc86afcdd93a/how-to-show-text-in-textblock-as-rich-text-format?forum=wpf), but not working on universal Windows apps (UWP).
I tried this but i have an error (unable to cast Binding to string):
<TextBlock x:Name="newsArticleSections"
Tag="{Binding RelativeSource={RelativeSource Self}, Mode=OneWay, Converter={StaticResource TextToRunConverter}, ConverterParameter={Binding ArticleSections}}"/>
And this is my converter :
public object Convert(object value, Type targetType, object parameter, string language)
{
TextBlock textblock = value as TextBlock;
textblock.ClearValue(TextBlock.TextProperty);
Run run = new Run();
run.Text = (string)parameter;
textblock.Inlines.Add(run);
return null;
}
It's just the ways that i had explored, but with no result for the moment.
Does someone has another idea ?

#devuxer answer was a good idea, but only for WPF project.
So i used it to make UWP solution and it works :
Create a Formatter class :
public class TextBlockFormatter
{
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(TextBlockFormatter),
new PropertyMetadata(null, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Clear current textBlock
TextBlock textBlock = d as TextBlock;
textBlock.ClearValue(TextBlock.TextProperty);
textBlock.Inlines.Clear();
// Create new formatted text
string formattedText = (string)e.NewValue ?? string.Empty;
string #namespace = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
formattedText = $#"<Span xml:space=""preserve"" xmlns=""{#namespace}"">{formattedText}</Span>";
// Inject to inlines
var result = (Span)XamlReader.Load(formattedText);
textBlock.Inlines.Add(result);
}
}
And add this reference to your XAML file :
xmlns:helpers="using:MyProject.Helpers"
To use the formatter, simply add a TextBlock and declare your binding on FormattedText, like this :
<TextBlock x:Name="textBlock" helpers:TextBlockFormatter.FormattedText="{Binding Content}">

I've been using the following solution for WPF projects (not UWP), so I'm not sure if it will work for you, but feel free to give it a try.
You begin by putting the following into a class file within, say, a Helpers folder inside your project:
public class Formatter
{
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(Formatter),
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);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null) return;
const string #namespace = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
var formattedText = (string)e.NewValue ?? string.Empty;
formattedText = $#"<Span xml:space=""preserve"" xmlns=""{#namespace}"">{formattedText}</Span>";
textBlock.Inlines.Clear();
using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
{
var result = (Span)XamlReader.Load(xmlReader);
textBlock.Inlines.Add(result);
}
}
}
Then, in your XAML file, reference the namespace, like so:
xmlns:helpers="clr-namespace:MyProject.Helpers"
To use the formatter, simply add a TextBlock and declare your binding on FormattedText (instead of Text), like this:
<TextBlock helpers:Formatter.FormattedText="{Binding Content}" />

I use something like this:
XAML
<TextBlock Name="TB" Text="Bold Text " FontWeight="Bold" />
At the Controller:
TB.Inlines.Add(new Run { Text = "New append text with diferent FontWeigth on the same TextBlock", FontWeight = FontWeights.Normal } );
Output will be:
Bold Text New append text with diferent FontWeigth on the same TextBlock

I hope my contribution is still useful, or may be useful to other reaching this question. I've updated the Formatter class from #Geoffrey, so that specific characters in the textblock are turning bold. I having a list with people and a search query at the top. The query part in name of the person is turning bold.
Query: ic
Result: Rick
public class TextBlockFormatter
{
const string #namespace = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(TextBlockFormatter),
new PropertyMetadata(null, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = (TextBlock)d;
if (textBlock.DataContext == null)
{
textBlock.DataContextChanged += TextBlock_DataContextChanged;
return;
}
var query = (string)e.NewValue ?? string.Empty;
HighlightSearch(textBlock, query);
}
private static void HighlightSearch(TextBlock textBlock, string value)
{
var name = ((Person)textBlock.DataContext).Name;
var query = value.ToUpper();
var indexOfSearch = name.ToUpper().IndexOf(query, 0);
if (indexOfSearch < 0) return;
// add normal text first
var firstText = name.Substring(0, indexOfSearch).Replace("&", "&");
var first = $#"<Span xml:space=""preserve"" xmlns=""{#namespace}"">{firstText}</Span>";
var firstResult = (Span)XamlReader.Load(first);
// add the bold text
var boldText = name.Substring(indexOfSearch, query.Length).Replace("&", "&");
var bold = $#"<Bold xml:space=""preserve"" xmlns=""{#namespace}"">{boldText}</Bold>";
var boldResult = (Bold)XamlReader.Load(bold);
// add the rest of the text
var restStartIndex = indexOfSearch + query.Length;
var restText = name.Substring(restStartIndex, name.Length - restStartIndex).Replace("&", "&");
var rest = $#"<Span xml:space=""preserve"" xmlns=""{#namespace}"">{restText}</Span>";
var restResult = (Span)XamlReader.Load(rest);
// Clear current textBlock
textBlock.ClearValue(TextBlock.TextProperty);
textBlock.Inlines.Clear();
// Inject to inlines
textBlock.Inlines.Add(firstResult);
textBlock.Inlines.Add(boldResult);
textBlock.Inlines.Add(restResult);
}
private static void TextBlock_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
var block = (TextBlock)sender;
if (block.DataContext == null) return;
block.DataContextChanged -= TextBlock_DataContextChanged;
var query = (string)sender.GetValue(FormattedTextProperty);
HighlightSearch(block, query);
}
}
Use in XAML:
<TextBlock Text="{Binding Name}" helpers:TextBlockFormatter.FormattedText="{Binding ElementName=queryTextBox, Path=Text}" />
I had some issues, For example my datasource was not set when the FormattedTextPropertyChanged was called. Also you need to take care of escaping the text. I made myself easy by using the Replace function.

Related

Data Template Button created in XamlReader don't firing events

I'm trying to create a ListView Page that works for every object in my app where I can show all the objects received by parameter from a List<object> where I don't know which object the page will have to handle, I also receive the fields to show as parameters in a List <string, string> where the first string is the type of the field (header/subheader/image) and for that, I had to use XAML Reader so I could create the data bindings in the code behind.
Here's my code:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var param = (Dictionary<string, object>)e.Parameter;
_ItemsList = (List<object>)param["itemsList"];
_Fields = (Dictionary<string, string>)param["fields"];
_DetailsPage = (Type)param["detailPage"];
EditCommand = new UniversalListViewCommand();
ObjListView.ItemsSource = UniversalAutoSuggestBox.ItemsSource = _ItemsList;
MainStackPanel.DataContext = this;
ObjListView.ItemTemplate = NormalItemTemplate = (DataTemplate)XamlReader.Load(GenerateDataTemplate("NormalItemTemplate"));
SelectedItemTemplate = (DataTemplate)XamlReader.Load(GenerateDataTemplate("SelectedItemTemplate"));
}
public string GenerateDataTemplate(string param)
{
StringBuilder sbTemp = new();
//Data Template definition
if (param == "NormalItemTemplate") sbTemp.Append("<DataTemplate x:Key=\"NormalItemTemplate\" ");
if (param == "SelectedItemTemplate") sbTemp.Append("<DataTemplate x:Key=\"SelectedItemTemplate\" ");
//sbTemp.Append("x:Class = \"xizPrototipo_0_2.Pages.UniversalListViewPage\" ");
sbTemp.Append(#"xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" ");
sbTemp.Append(#"xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">");
//Grid definition
sbTemp.Append("<Grid MinWidth=\"350\" Margin=\"5\">");
//Grid row definition
sbTemp.Append("<Grid.RowDefinitions>");
sbTemp.Append("<RowDefinition Height = \"*\" />");
sbTemp.Append("<RowDefinition Height = \"*\" />");
sbTemp.Append("<RowDefinition Height = \"*\" />");
sbTemp.Append("</Grid.RowDefinitions>");
//Grid column definition
sbTemp.Append("<Grid.ColumnDefinitions>");
sbTemp.Append("<ColumnDefinition Width = \"Auto\"/>");
sbTemp.Append("<ColumnDefinition Width = \"*\" />");
sbTemp.Append("</Grid.ColumnDefinitions>");
//Ellipse definition
sbTemp.Append("<Ellipse Grid.Row=\"0\" Grid.RowSpan=\"2\" Width = \"32\" Height = \"32\" Margin = \"6\" VerticalAlignment = \"Center\" HorizontalAlignment = \"Center\">");
sbTemp.Append("<Ellipse.Fill> <ImageBrush x:Name =\"LogoImage\" ImageSource = \"{Binding " + _Fields["Image"] + "}\" /> </Ellipse.Fill> ");
sbTemp.Append("</Ellipse>");
//Header Text Block Definition
sbTemp.Append("<TextBlock Grid.Row = \"0\" Grid.Column = \"1\" Text = \"{Binding " + _Fields["Header"] + "}\" Style = \"{ThemeResource BaseTextBlockStyle}\" Margin = \"12,6,0,0\"/> ");
//Subheader Text Block Definition
sbTemp.Append("<TextBlock Grid.Row=\"1\" Grid.Column = \"1\" Text = \"{Binding " + _Fields["Subheader"] + "}\" Style = \"{ThemeResource BodyTextBlockStyle}\" Margin = \"12,0,0,6\" /> ");
//Button (case Selected)
if (param == "SelectedItemTemplate")
{
sbTemp.Append("<StackPanel Grid.Row=\"2\" Grid.Column=\"1\" Orientation =\"Horizontal\" HorizontalAlignment =\"Right\" Spacing = \"5\" > ");
sbTemp.Append("<Button x:Name=\"EditButton\" Content=\"Editar\" Click=\"AlterButton_Click\" />");
sbTemp.Append("</StackPanel>");
}
//Grid end
sbTemp.Append("</Grid>");
//DataTemplate end
sbTemp.Append("</DataTemplate>");
return sbTemp.ToString();
}
With this method I could get the outcome that I wanted but when I click the button created in the following line it don't fires any event:
sbTemp.Append("<Button x:Name=\"EditButton\" Content=\"Editar\" Click=\"AlterButton_Click\" />");
I also tried using commands, Data Template selector and still I couldn't do anything with that button.
Does anyone know how can I put that button to work?
(Yes, I have the event on the button created and a function to change the Data Template to show the button.)
Data Template Button created in XamlReader don't firing events
I am afraid it will not respond to the click event handler created from the string xaml. Because the click event registration process will be saved in the xxpage.g.cs file during pre-compilation. However, if you load the data template from the string runtime. It will not register for this event.
So the better way is binding command for button.
For example
public MainPage()
{
this.InitializeComponent();
DataTemplate itmeTemplate = (DataTemplate)XamlReader.Load(
#"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
<Button Command='{Binding BtnClickCommand}' Content='Editor' />
</DataTemplate>");
TestListView.ItemTemplate = itmeTemplate;
NumbersList = new ObservableCollection<Item>();
for (int i = 0; i < 50; i++)
{
var item = new Item ();
NumbersList.Add(item);
}
TestListView.ItemsSource = NumbersList;
}
public ObservableCollection<Item> NumbersList { get; set; }
Model
public class Item : INotifyPropertyChanged
{
public ICommand BtnClickCommand
{
get
{
return new RelayCommand(() =>
{
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Xaml Code
<Grid x:Name="RootGrid">
<ListView
x:Name="TestListView"
ItemsSource="{x:Bind NumbersList}"
Visibility="Visible">
</ListView>
</Grid>
Update 1
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
DataTemplate itmeTemplate = (DataTemplate)XamlReader.Load(
#"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
<Button Command='{Binding DataContext.BtnClickCommand, ElementName=TestListView}' Content='Editor' />
</DataTemplate>");
TestListView.ItemTemplate = itmeTemplate;
NumbersList = new ObservableCollection<Item>();
for (int i = 0; i < 50; i++)
{
var item = new Item ();
NumbersList.Add(item);
}
TestListView.ItemsSource = NumbersList;
}
public ICommand BtnClickCommand
{
get
{
return new RelayCommand(async () =>
{
});
}
}

c# textbox shows object name instead of value

I have a form written in c#, with various drop down lists, but I'm having problems with a listbox on the form. I need to populate a textbox with the values selected from the listbox when I double click on them. I've got the click event working, but the textbox will only populate with the object name, not the value from the listbox
i.e.
'System.Windows.Controls.SelectedItemCollection'
instead of the actual value.
Here is the entire code block I'm working on:
I should have just done this at the start - here is the complete code block I'm working on:
else if (theValue.FieldName.Equals("UIPathList", StringComparison.OrdinalIgnoreCase) == true)
{
int nRow = 14;
Button theUIPathOptionsButton = new Button();
TextBox theOldValueTextBox = AddLabelAndOldValue(theHelper, nRow, theValue);
theOldValueTextBox.Text = theValue.OldValue.Replace(",", "," + Environment.NewLine);
theUIPathOuterStackPanel = new StackPanel
{
Visibility = Visibility.Visible,
Orientation = Orientation.Vertical,
Background = new SolidColorBrush(Colors.White),
ClipToBounds = true,
};
theUIPathOptionsInnerStackPanel = new StackPanel
{
Visibility = Visibility.Visible,
Orientation = Orientation.Horizontal,
Background = new SolidColorBrush(Colors.White)
};
theUIPathOuterStackPanel.ClipToBounds = true;
TextBox theNewTextBox = new TextBox
{
TabIndex = nRow,
TextWrapping = TextWrapping.Wrap,
AcceptsReturn = true,
};
theNewTextBox.Clear();
theNewTextBox.MouseDoubleClick += MultiLineChildDatapointList_HandleMouseDoubleClick;
theNewTextBox.Focusable = true;
theNewTextBox.HorizontalAlignment = HorizontalAlignment.Stretch;
theNewTextBox.Width = 365;
theNewTextBox.PreviewKeyDown += theGetMetadataHelper.Preview_KeyDown_IsMultilineText;
theNewTextBox.Tag = theValue;
ListBox theUIPathOptionslistBox = new ListBox();
theUIPathOptionslistBox.Items.Add("RuntimeDefaults");
theUIPathOptionslistBox.Items.Add("CommonSettings");
theUIPathOptionslistBox.Items.Add(InputDatapointManager.CONST_CHANGE_RECORD_CHANGES_CLEAR_VALUE);
theUIPathOptionslistBox.TabIndex = nRow;
theUIPathOptionslistBox.SelectionMode = SelectionMode.Multiple;
theUIPathOptionslistBox.ClipToBounds = true;
theUIPathOptionslistBox.Focusable = true;
theUIPathOptionslistBox.Visibility = Visibility.Hidden;
theUIPathOptionslistBox.Height = 34;
theUIPathOptionsInnerStackPanel.Children.Add(theNewTextBox);
theUIPathOptionsInnerStackPanel.Children.Add(theUIPathOptionsButton);
theUIPathOuterStackPanel.Children.Add(theUIPathOptionsInnerStackPanel);
theUIPathOuterStackPanel.Children.Add(theUIPathOptionslistBox);
void button1_click(object sender, EventArgs e)
{
theUIPathOptionslistBox.Visibility = Visibility.Visible;
}
void button1_doubleclick(object sender, EventArgs e)
{
theNewTextBox.Text = theUIPathOptionslistBox.SelectedItem.ToString();
}
theUIPathOptionsButton.Click += button1_click;
theUIPathOptionslistBox.MouseDoubleClick += button1_doubleclick;
Grid.SetColumn(theUIPathOuterStackPanel, 4);
Grid.SetRow(theUIPathOuterStackPanel, nRow);
theDataGrid.Children.Add(theUIPathOuterStackPanel);
theEditControlList.Add(theNewTextBox);
}
This was (possibly) already answered here : Getting value of selected item in list box as string
string myItem = listBox1.GetItemText(listBox1.SelectedItem);
Then you just have to add your item to the textbox :
textBox1.Text = myItem;
If you don't want to create a new string variable, then this one is working too :
textBox1.Text = listBox1.SelectedItem.ToString();
ListBox, Item is a collection of objects, not strings, so you must let it know how to convert it to string, otherwise it will use its defualt .ToString() function that obviously the object currently in your items not giving the desired result.
Imagine items are oftype following class:
class SomeClass
{
public int Id;
public string Name;
}
You may do one of these three:
1.set the DisplayMember of your ListBox to Name
2.add override method to your class so that it overrides its .ToString() and return its Name property:
class SomeClass
{
public int Id;
public string Name;
public override string ToString()
{
return Name;
}
}
3.Just cast it to its real type and get the property you want:
SomeClass selected = (SomeClass)ListBox.SelectedItem;
TextBox1.Text = selected.Name;
This is because the TextBox will use the ToString() method on what it is bound to, which by default will return the class name.
You solutions are to either override the ToString() method of the class to return the value you want or set the text property of the TextBox to the text value you want rather then the object.
The solution I finally got to was as follows:
void theUIPathOptionslistBox_SelectedIndexChanged(object sender,
SelectionChangedEventArgs e)
{
theNewTextBox.Clear();
foreach (object selectedItem in theUIPathOptionslistBox.SelectedItems)
{
theNewTextBox.AppendText(selectedItem.ToString() + Environment.NewLine);
}
}
theUIPathOptionslistBox.SelectionChanged +=
theUIPathOptionslistBox_SelectedIndexChanged;

Binding source doesn't change when setting the binding from code behind file

So I have this program where I mainly do all my bindings by using x:Bind but I have a control page where I need to generate a lot of Slider to be able to manipulate the data I stored in an ObservableCollection<T>
I really need to generate those sliders from my code because I need around 100 of them and it would really mess up my xmal file to create them by hand...
This is how I create the bindings in my code. They also work when I load them the first time. But they don't change the source data when I move the slider around.
private void CreateGrid()
{
for (var parameterNumberIndex = 1; parameterNumberIndex < 97; parameterNumberIndex++)
{
var paraName = new TextBlock()
{
Name = $"Parameter{parameterNumberIndex}",
FontSize = 25,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(0, 0, 0, 10)
};
var slider = new Slider()
{
Name = $"ValueSlider{parameterNumberIndex}",
Width = 200,
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center
};
var value = new TextBox()
{
Name = $"ValueBox{parameterNumberIndex}",
Margin = new Thickness(10, 5, 0, 0),
FontSize = 20,
VerticalAlignment = VerticalAlignment.Top
};
var row = new RowDefinition { Height = new GridLength(50, GridUnitType.Pixel) };
SettingsGrid.RowDefinitions.Add(row);
var nameBinding = new Binding
{
Source = ViewModel.BlockCollection.NamesBlock.Names[parameterNumberIndex].NameString,
Mode = BindingMode.OneWay
};
var valueBinding = new Binding()
{
Source = ViewModel.BlockCollection.ParameterBlock.Parameters[parameterNumberIndex].ParameterValue,
Mode = BindingMode.TwoWay
};
var minBinding = new Binding()
{
Source = ViewModel.BlockCollection.MinMaxBlock.AllValues[parameterNumberIndex].MinValue,
Mode = BindingMode.OneWay
};
var maxBinding = new Binding()
{
Source = ViewModel.BlockCollection.MinMaxBlock.AllValues[parameterNumberIndex].MaxValue,
Mode = BindingMode.OneWay
};
var textBinding = new Binding()
{
Path = new PropertyPath("Value"),
Source = slider,
Mode = BindingMode.TwoWay
};
BindingOperations.SetBinding(paraName, TextBlock.TextProperty, nameBinding);
BindingOperations.SetBinding(slider, Slider.MinimumProperty, minBinding);
BindingOperations.SetBinding(slider, Slider.MaximumProperty, maxBinding);
BindingOperations.SetBinding(slider, Slider.ValueProperty, valueBinding);
BindingOperations.SetBinding(value, TextBox.TextProperty, textBinding);
SettingsGrid.Children.Add(paraName);
SettingsGrid.Children.Add(slider);
SettingsGrid.Children.Add(value);
Grid.SetColumn(paraName, 0);
Grid.SetColumn(slider, 1);
Grid.SetColumn(value, 2);
Grid.SetRow(paraName, parameterNumberIndex - 1);
Grid.SetRow(slider, parameterNumberIndex - 1);
Grid.SetRow(value, parameterNumberIndex - 1);
}
}
The Source = ... are always ObservableCollection<T> depending on which type I need them in.
The slider source is a collection of uint.
The BindableBase is my implementation of INotifyPropertyChanged.
public class ParameterBlock : BindableBase
{
public ParameterBlock()
{
this.Parameters = new ObservableRangeCollection<ParameterBlockValue>();
}
public ObservableRangeCollection<ParameterBlockValue> Parameters
{
get => _parameters;
set
{
_parameters = value;
OnPropertyChanged();
}
}
private ObservableRangeCollection<ParameterBlockValue> _parameters;
}
public class ParameterBlockValue : BindableBase
{
private uint _parameterValue;
public uint ParameterValue
{
get => _parameterValue;
set
{
_parameterValue = value;
OnPropertyChanged();
}
}
public ParameterBlockValue(uint parameter)
{
this.ParameterValue = parameter;
}
public override string ToString()
{
return $"{this.ParameterValue} {Environment.NewLine}";
}
}
This is nearly the last step I need to fix before I'm more or less done with this project and I don't want to get stuck here :/
I don't know if the wpf tag is wrong. But everytime I look for xaml references I stumble over the wpf stuff and it's somewhat usable. So if it's wrong just tell me and I remove it.
To explain a bit more... In the finished product it will depend on what data I recieve to decide if there will be a slider or a textbox.
Just a small example picture from another question I had yesterday:
I see you are adding the controls in a Grid, generating the rows manually. While this could certainly work, it would be much better to use a list control and create the items within:
<ItemsControl ItemsSource="{x:Bind Data}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- your Sliders, etc. -->
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl>
If the data might vary between multiple layouts, you may implement a DataTemplateSelector class, override the SelectTemplateCore(object item, DependencyObject container) (this method specifically, there is another one with the same name but different number of parameters which is not called) and decide on the right template for each item. You can create multiple DataTemplates as resources with x:Key and then reference them from your DataTemplateSelector. See this blog post for an example and documentation for more details.

WPF C# - Get inline formated bold Text from TextBlock

I'm adding some TextBlock elements to Border elements in a StackPanel.
I'm adding and formating the text of the TextBlock by adding Inlines.
When clicked, I want to get the formated text of the TextBlock.Here is my code.
public void addText()
{
TextBlock myText = new TextBlock();
myText.Inlines.Add(new Bold(new Run("Hello ")));
myText.Inlines.Add("World!");
Border myBorder = new Border();
myBorder.Child = myText;
myBorder.MouseDown += new MouseButtonEventHandler(Border_Clicked);
myStackPanel.Children.Add(myBorder);
}
private void Border_Clicked(object sender, MouseButtonEventArgs e)
{
//Border senderBox = (Border)sender;
//TextBlock senderText = (TextBlock)senderBox.Child;
//Bold inline = (Bold) senderText.Inlines.ElementAt(0);
// How to Output "Hello "?
}
Border_Clicked should output "Hello ". As you can see I'm able to get to the bolded Text but how can I ouput it?
#Helen, There is a way to get the Text from the TextPointer using TextRange. Try this code
void myBorder_MouseDown(object sender, MouseButtonEventArgs e)
{
var senderBox = (Border)sender;
var senderText = (TextBlock)senderBox.Child;
var inline = (Bold)senderText.Inlines.ElementAt(0);
var textRange = new TextRange(inline.ContentStart, inline.ContentEnd);
Console.WriteLine(textRange.Text);
}
Is the problem to get text out of Bold element?
private void Border_Clicked(object sender, MouseButtonEventArgs e)
{
var border = (Border)sender;
var textBlock = (TextBlock)border.Child;
var bold = (Bold)textBlock.Inlines.ElementAt(0);
// How to Output "Hello "?
// try
var output = ((Run)bold).Text;
// or rather (because Bold is a wrapper)
var output = ((Run)bold.Inlines[0]).Text;
}
If you can add inline like this
myText.Inlines.Add(new Run("Bold text") { FontWeight = FontWeight.Bold });
then it's
var run = (Run)textBlock.Inlines[0];
var output = run.Text;
It is not possible to control Font characteristics in a MessageBox. I think you should consider to create a "custom MessageBox". Something like this:
<Window x:Class="WpfApplication1.CustomMessageBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
SizeToContent="WidthAndHeight" MaxWidth="400">
<Grid x:Name="GridContent" Margin="10">
</Grid>
</Window>
So in its constructor you could send your Bold:
private Bold _text;
public CustomMessageBox(Bold formattedText)
{
_text = formattedText;
GridContent.Child = _text;
}
Using:
private void Border_Clicked(object sender, MouseButtonEventArgs e)
{
Border senderBox = (Border)sender;
TextBlock senderText = (TextBlock)senderBox.Child;
Bold inline = (Bold) senderText.Inlines.ElementAt(0);
var customMsgBox = new CustomMessageBox(inline);
customMsgBox.ShowModal();
}
Now, if you are not sure it will be always a Bold object, I suggest you to save your formatted text in XML and load it after. Take a look at this: showing formatted text

Get the lines of the TextBlock according to the TextWrapping property?

I have a TextBlock in WPF application.
The (Text, Width, Height, TextWrapping, FontSize, FontWeight, FontFamily) properties of this TextBlock is dynamic (entered by the user at the runtime).
Every time the user changes one of the previous properties, the Content property of the TextBlock is changed at the runtime. (everything is ok until here)
Now, I need to get the lines of that TextBlock according to the previously specified properties.
That means I need the lines that TextWrapping algorithms will result.
In other words, I need each line in a separated string or I need one string with Scape Sequence \n.
Any Idea to do that?
I would have been surprised if there is no public way of doing that (although one never knows, especially with WPF). And indeed looks like TextPointer class is our friend, so here is a solution based on the TextBlock.ContentStart, TextPointer.GetLineStartPosition and TextPointer.GetOffsetToPosition:
public static class TextUtils
{
public static IEnumerable<string> GetLines(this TextBlock source)
{
var text = source.Text;
int offset = 0;
TextPointer lineStart = source.ContentStart.GetPositionAtOffset(1, LogicalDirection.Forward);
do
{
TextPointer lineEnd = lineStart != null ? lineStart.GetLineStartPosition(1) : null;
int length = lineEnd != null ? lineStart.GetOffsetToPosition(lineEnd) : text.Length - offset;
yield return text.Substring(offset, length);
offset += length;
lineStart = lineEnd;
}
while (lineStart != null);
}
}
There is not much to explain here
Get the start position of the line, subtract the start position of the previous line to get the length of the line text and here we are.
The only tricky (or non obvious) part is the need to offset the ContentStart by one since by design The TextPointer returned by this property always has its LogicalDirection set to Backward., so we need to get the pointer for the same(!?) position, but with LogicalDirection set to Forward, whatever all that means.
With FormattedText class, the formatted text can be first created and its size evaluated, so you know the space it takes in a first step,
If it's too long, it's up to you to split in separate lines.
Then in a second step, it could be drawn.
Everything could happen on the DrawingContext object in the following method :
protected override void OnRender(System.Windows.Media.DrawingContext dc)
Here is the CustomControl solution :
[ContentProperty("Text")]
public class TextBlockLineSplitter : FrameworkElement
{
public FontWeight FontWeight
{
get { return (FontWeight)GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
public static readonly DependencyProperty FontWeightProperty =
DependencyProperty.Register("FontWeight", typeof(FontWeight), typeof(TextBlockLineSplitter), new PropertyMetadata(FontWeight.FromOpenTypeWeight(400)));
public double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
public static readonly DependencyProperty FontSizeProperty =
DependencyProperty.Register("FontSize", typeof(double), typeof(TextBlockLineSplitter), new PropertyMetadata(10.0));
public String FontFamily
{
get { return (String)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register("FontFamily", typeof(String), typeof(TextBlockLineSplitter), new PropertyMetadata("Arial"));
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(TextBlockLineSplitter), new PropertyMetadata(null));
public double Interline
{
get { return (double)GetValue(InterlineProperty); }
set { SetValue(InterlineProperty, value); }
}
public static readonly DependencyProperty InterlineProperty =
DependencyProperty.Register("Interline", typeof(double), typeof(TextBlockLineSplitter), new PropertyMetadata(3.0));
public List<String> Lines
{
get { return (List<String>)GetValue(LinesProperty); }
set { SetValue(LinesProperty, value); }
}
public static readonly DependencyProperty LinesProperty =
DependencyProperty.Register("Lines", typeof(List<String>), typeof(TextBlockLineSplitter), new PropertyMetadata(new List<String>()));
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
Lines.Clear();
if (!String.IsNullOrWhiteSpace(Text))
{
string remainingText = Text;
string textToDisplay = Text;
double availableWidth = ActualWidth;
Point drawingPoint = new Point();
// put clip for preventing writing out the textblock
drawingContext.PushClip(new RectangleGeometry(new Rect(new Point(0, 0), new Point(ActualWidth, ActualHeight))));
FormattedText formattedText = null;
// have an initial guess :
formattedText = new FormattedText(textToDisplay,
Thread.CurrentThread.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(FontFamily),
FontSize,
Brushes.Black);
double estimatedNumberOfCharInLines = textToDisplay.Length * availableWidth / formattedText.Width;
while (!String.IsNullOrEmpty(remainingText))
{
// Add 15%
double currentEstimatedNumberOfCharInLines = Math.Min(remainingText.Length, estimatedNumberOfCharInLines * 1.15);
do
{
textToDisplay = remainingText.Substring(0, (int)(currentEstimatedNumberOfCharInLines));
formattedText = new FormattedText(textToDisplay,
Thread.CurrentThread.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(FontFamily),
FontSize,
Brushes.Black);
currentEstimatedNumberOfCharInLines -= 1;
} while (formattedText.Width > availableWidth);
Lines.Add(textToDisplay);
System.Diagnostics.Debug.WriteLine(textToDisplay);
System.Diagnostics.Debug.WriteLine(remainingText.Length);
drawingContext.DrawText(formattedText, drawingPoint);
if (remainingText.Length > textToDisplay.Length)
remainingText = remainingText.Substring(textToDisplay.Length);
else
remainingText = String.Empty;
drawingPoint.Y += formattedText.Height + Interline;
}
foreach (var line in Lines)
{
System.Diagnostics.Debug.WriteLine(line);
}
}
}
}
Usage of that control (border is here to show effective clipping) :
<Border BorderThickness="1" BorderBrush="Red" Height="200" VerticalAlignment="Top">
<local:TextBlockLineSplitter>Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do. Once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, "and what is the use of a book," thought Alice, ...</local:TextBlockLineSplitter>
</Border>
If it is not a problem you can use reflection on the TextBlock control (it of course knows how the string is wrapped). If you are not using MVVM, I guess it is suitable for you.
First of all I created a minimal window for testing my solution:
<Window x:Class="WpfApplication1.MainWindow" Name="win"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="600" Width="600">
<StackPanel>
<TextBlock Name="txt" Text="Lorem ipsum dolor sit amet, consectetur adipisci elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua." Margin="20"
TextWrapping="Wrap" />
<Button Click="OnCalculateClick" Content="Calculate ROWS" Margin="5" />
<TextBox Name="Result" Height="100" />
</StackPanel>
</Window>
Now let's see the most important part of the code-behind:
private void OnCalculateClick(object sender, EventArgs args)
{
int start = 0;
int length = 0;
List<string> tokens = new List<string>();
foreach (object lineMetrics in GetLineMetrics(txt))
{
length = GetLength(lineMetrics);
tokens.Add(txt.Text.Substring(start, length));
start += length;
}
Result.Text = String.Join(Environment.NewLine, tokens);
}
private int GetLength(object lineMetrics)
{
PropertyInfo propertyInfo = lineMetrics.GetType().GetProperty("Length", BindingFlags.Instance
| BindingFlags.NonPublic);
return (int)propertyInfo.GetValue(lineMetrics, null);
}
private IEnumerable GetLineMetrics(TextBlock textBlock)
{
ArrayList metrics = new ArrayList();
FieldInfo fieldInfo = typeof(TextBlock).GetField("_firstLine", BindingFlags.Instance
| BindingFlags.NonPublic);
metrics.Add(fieldInfo.GetValue(textBlock));
fieldInfo = typeof(TextBlock).GetField("_subsequentLines", BindingFlags.Instance
| BindingFlags.NonPublic);
object nextLines = fieldInfo.GetValue(textBlock);
if (nextLines != null)
{
metrics.AddRange((ICollection)nextLines);
}
return metrics;
}
The GetLineMetrics method retrieves a collection of LineMetrics (an internal object, so I cannot use it directly). This object has a property called "Length" which has the information that you need. So the GetLength method just read this property's value.
Lines are stored in the list named tokens and showed by using the TextBox control (just to have an immediate feedback).
I hope my sample can help you in your task.

Categories