Data binding the TextBlock.Inlines
Solution 1
This is not possible because the TextBlock.Inlines
property is not a dependency property. Only dependency properties can be the target of a data binding.
Depending on your exact layout requirements you may be able to do this using an ItemsControl
, with its ItemsPanel
set to a WrapPanel
and its ItemsSource
set to your collection. (Some experimentation may be required here because an Inline
is not a UIElement
, so its default rendering will probably be done using ToString()
rather than being displayed.)
Alternatively, you may need to build a new control, e.g. MultipartTextBlock
, with a bindable PartsSource
property and a TextBlock
as its default template. When the PartsSource
was set your control would attach a CollectionChanged
event handler (directly or via CollectionChangedEventManager), and update the TextBlock.Inlines
collection from code as the PartsSource
collection changed.
In either case, caution may be required if your code is generating Inline
elements directly (because an Inline
can't be used in two places at the same time). You may alternatively want to consider exposing an abstract model of text, font, etc. (i.e. a view model) and creating the actual Inline
objects via a DataTemplate
. This may also improve testability, but obviously adds complexity and effort.
Solution 2
You could add a Dependency Property to a TextBlock Subclass
public class BindableTextBlock : TextBlock
{
public ObservableCollection<Inline> InlineList
{
get { return (ObservableCollection<Inline>)GetValue(InlineListProperty); }
set { SetValue(InlineListProperty, value); }
}
public static readonly DependencyProperty InlineListProperty =
DependencyProperty.Register("InlineList",typeof(ObservableCollection<Inline>), typeof(BindableTextBlock), new UIPropertyMetadata(null, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
BindableTextBlock textBlock = sender as BindableTextBlock;
ObservableCollection<Inline> list = e.NewValue as ObservableCollection<Inline>;
list.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(textBlock.InlineCollectionChanged);
}
private void InlineCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
int idx = e.NewItems.Count -1;
Inline inline = e.NewItems[idx] as Inline;
this.Inlines.Add(inline);
}
}
}
Solution 3
This is an alternative solution which utilizes WPF behaviors/attached properties:
public static class TextBlockExtensions
{
public static IEnumerable<Inline> GetBindableInlines ( DependencyObject obj )
{
return (IEnumerable<Inline>) obj.GetValue ( BindableInlinesProperty );
}
public static void SetBindableInlines ( DependencyObject obj, IEnumerable<Inline> value )
{
obj.SetValue ( BindableInlinesProperty, value );
}
public static readonly DependencyProperty BindableInlinesProperty =
DependencyProperty.RegisterAttached ( "BindableInlines", typeof ( IEnumerable<Inline> ), typeof ( TextBlockExtensions ), new PropertyMetadata ( null, OnBindableInlinesChanged ) );
private static void OnBindableInlinesChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var Target = d as TextBlock;
if ( Target != null )
{
Target.Inlines.Clear ();
Target.Inlines.AddRange ( (System.Collections.IEnumerable) e.NewValue );
}
}
}
In your XAML, use it like this:
<TextBlock MyBehaviors:TextBlockExtensions.BindableInlines="{Binding Foo}" />
This saves you from having to inherit from TextBlock. It could just as well work using an ObservableCollection instead of IEnumerable, in that case you'd need to subscribe to collection changes.
Solution 4
Thanks Frank for your solution. I had to make a couple of minor changes to make it work for me.
public class BindableTextBlock : TextBlock
{
public ObservableCollection<Inline> InlineList
{
get { return (ObservableCollection<Inline>) GetValue(InlineListProperty); }
set { SetValue(InlineListProperty, value); }
}
public static readonly DependencyProperty InlineListProperty =
DependencyProperty.Register("InlineList", typeof (ObservableCollection<Inline>), typeof (BindableTextBlock), new UIPropertyMetadata(null, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
BindableTextBlock textBlock = (BindableTextBlock) sender;
textBlock.Inlines.Clear();
textBlock.Inlines.AddRange((ObservableCollection<Inline>) e.NewValue);
}
}
Solution 5
In version 4 of WPF you will be be able to bind to a Run object, which may solve your problem.
I have solved this problem in the past by overriding an ItemsControl and displaying the text as items in the ItemsControl. Look at some of the tutorials that Dr. WPF has done on this kind of stuff: http://www.drwpf.com
will
Updated on July 28, 2022Comments
-
will almost 2 years
My WPF App receives a stream of messages from a backend service that I need to display in the UI. These messages vary widely and I want to have different visual layout (string formats, colors, Fonts, icons, whatever etc.) for each message.
I was hoping to just be able to create an inline (Run, TextBlock, Italic etc) for each message then somehow put them all in a
ObservableCollection<>
and using he magic of WPF Data Binding on my TextBlock.Inlines in the UI. I couldn't find how to do this, is this possible? -
discorax over 13 yearsWhat does the XAML look like for your above example?
-
thumbmunkeys almost 11 yearsTextBlock is sealed on windows phone 8
-
LawMan almost 9 yearsThanks a million for this. I had to make a couple of minor changes to get it to work.
-
lelimacon over 7 yearsBetter add a
textBlock.Inlines.Clear()
before theAddRange
in order to reset the inlines when changed -
Admin over 4 yearsthen how do i use this? sry im new
-
El Bayames over 3 yearsOn my WindowModel I defined public ObservableCollection<Inline> ProcessTrackerInlines { get; set; } and binded it to TextBlockExtensions.BindableInlines="{Binding ProcessTrackerInlines, Mode=OneWay}". Added a method loadProcessTracker to the windowModel to fill the ProcessTrackerInlines and everything worked fine, but if I latter(by clicking a button) try to add an Inline, and call PropertyChanged . The new inline does not show on the control. I can Add the code if needed.
-
Defd over 2 yearsResult in InvalidOperation: "The calling thread cannot access this object because a different thread owns it". On the line: this.Inlines.Add.... so this is not a real solution
-
Trương Quốc Khánh over 2 years@Defd use dispatcher of main thread