Datos vinculantes del TextBlock.Inlines

Mi aplicación WPF recibe una secuencia de mensajes de un servicio de back-end que necesito mostrar en la UI. Estos mensajes varían mucho y deseo tener un diseño visual diferente (formatos de cadena, colores, fonts, íconos, lo que sea, etc.) para cada mensaje.

Esperaba solo poder crear un en línea (Ejecutar, TextBlock, Cursiva, etc.) para cada mensaje y, de alguna manera, ponerlos en un ObservableCollection y usar la magia de WPF Data Binding en mi TextBlock.Inlines en la UI. No pude encontrar cómo hacer esto, ¿es esto posible?

Esto no es posible porque la propiedad TextBlock.Inlines no es una propiedad de dependencia. Solo las propiedades de dependencia pueden ser el objective de un enlace de datos.

Dependiendo de los requisitos exactos de su diseño, puede hacer esto usando un ItemsControl , con su ItemsPanel establecido en WrapPanel y su ItemsSource configurado en su colección. (Es posible que se requiera algo de experimentación aquí porque un Inline no es un UIElement , por lo que su renderización predeterminada probablemente se hará usando ToString() lugar de mostrarse).

Alternativamente, puede necesitar construir un nuevo control, por ejemplo, MultipartTextBlock , con una propiedad PartsSource y un TextBlock como su plantilla predeterminada. Cuando se configuró PartsSource , su control adjuntaría un manejador de eventos CollectionChanged (directamente o mediante CollectionChangedEventManager) y actualizaría la colección TextBlock.Inlines del código a medida que PartsSource colección de PartsSource .

En cualquier caso, puede ser necesario tener precaución si su código está generando elementos en Inline directamente (porque un Inline no se puede usar en dos lugares al mismo tiempo). Alternativamente, puede querer exponer un modelo abstracto de texto, fuente, etc. (es decir, un modelo de vista) y crear los objetos en Inline reales a través de una DataTemplate . Esto también puede mejorar la capacidad de prueba, pero obviamente agrega complejidad y esfuerzo.

Podría agregar una Propiedad de dependencia a una Subclase de TextBlock

 public class BindableTextBlock : TextBlock { public ObservableCollection InlineList { get { return (ObservableCollection)GetValue(InlineListProperty); } set { SetValue(InlineListProperty, value); } } public static readonly DependencyProperty InlineListProperty = DependencyProperty.Register("InlineList",typeof(ObservableCollection), typeof(BindableTextBlock), new UIPropertyMetadata(null, OnPropertyChanged)); private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { BindableTextBlock textBlock = sender as BindableTextBlock; ObservableCollection list = e.NewValue as ObservableCollection; 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); } } } 

En la versión 4 de WPF podrá enlazar a un objeto Run, que puede resolver su problema.

He resuelto este problema en el pasado anulando un ItemsControl y mostrando el texto como elementos en ItemsControl. Mira algunos de los tutoriales que el Dr. WPF ha hecho sobre este tipo de cosas: http://www.drwpf.com

Si recibo su requerimiento correctamente, puede verificar manualmente los próximos mensajes y para cada mensaje puede agregar un elemento a la propiedad TextBlock.Inlines. No tomará ningún enlace de datos. He hecho esto con lo siguiente:

 public string MyBindingPath { get { return (string)GetValue(MyBindingPathProperty); } set { SetValue(MyBindingPathProperty, value); } } // Using a DependencyProperty as the backing store for MyBindingPath. This enables animation, styling, binding, etc... public static readonly DependencyProperty MyBindingPathProperty = DependencyProperty.Register("MyBindingPath", typeof(string), typeof(Window2), new UIPropertyMetadata(null, OnPropertyChanged)); private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { (sender as Window2).textBlock.Inlines.Add(new Run(e.NewValue.ToString())); } 

Gracias Frank por tu solución. Tuve que hacer un par de cambios menores para que funcione para mí.

 public class BindableTextBlock : TextBlock { public ObservableCollection InlineList { get { return (ObservableCollection) GetValue(InlineListProperty); } set { SetValue(InlineListProperty, value); } } public static readonly DependencyProperty InlineListProperty = DependencyProperty.Register("InlineList", typeof (ObservableCollection), 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) e.NewValue); } } 

Me doy cuenta de que esta pregunta es muy antigua, pero de todos modos pensé en compartir una solución alternativa. Utiliza comportamientos de WPF / propiedades adjuntas:

 public static class TextBlockExtensions { public static IEnumerable GetBindableInlines ( DependencyObject obj ) { return (IEnumerable) obj.GetValue ( BindableInlinesProperty ); } public static void SetBindableInlines ( DependencyObject obj, IEnumerable value ) { obj.SetValue ( BindableInlinesProperty, value ); } public static readonly DependencyProperty BindableInlinesProperty = DependencyProperty.RegisterAttached ( "BindableInlines", typeof ( IEnumerable ), 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 ); } } } 

En tu XAML, utilízalo así:

  

Esto le ahorra tener que heredar de TextBlock. También podría funcionar usando un ObservableCollection en lugar de IEnumerable , en ese caso necesitaría suscribirse a los cambios de colección.

 Imports System.Collections.ObjectModel Imports System.Collections.Specialized Public Class BindableTextBlock Inherits TextBlock Public Property InlineList As ObservableCollection(Of Inline) Get Return GetValue(InlineListProperty) End Get Set(ByVal value As ObservableCollection(Of Inline)) SetValue(InlineListProperty, value) End Set End Property Public Shared ReadOnly InlineListProperty As DependencyProperty = _ DependencyProperty.Register("InlineList", _ GetType(ObservableCollection(Of Inline)), GetType(BindableTextBlock), _ New UIPropertyMetadata(Nothing, AddressOf OnInlineListPropertyChanged)) Private Shared Sub OnInlineListPropertyChanged(sender As DependencyObject, e As DependencyPropertyChangedEventArgs) Dim textBlock As BindableTextBlock = TryCast(sender, BindableTextBlock) Dim list As ObservableCollection(Of Inline) = TryCast(e.NewValue, ObservableCollection(Of Inline)) If textBlock IsNot Nothing Then If list IsNot Nothing Then ' Add in the event handler for collection changed AddHandler list.CollectionChanged, AddressOf textBlock.InlineCollectionChanged textBlock.Inlines.Clear() textBlock.Inlines.AddRange(list) Else textBlock.Inlines.Clear() End If End If End Sub '''  ''' Adds the items to the inlines '''  '''  '''  '''  Private Sub InlineCollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs) Select Case e.Action Case NotifyCollectionChangedAction.Add Me.Inlines.AddRange(e.NewItems) Case NotifyCollectionChangedAction.Reset Me.Inlines.Clear() Case NotifyCollectionChangedAction.Remove For Each Line As Inline In e.OldItems If Me.Inlines.Contains(Line) Then Me.Inlines.Remove(Line) End If Next End Select End Sub End Class 

Creo que es posible que necesite algún código adicional en el controlador PropertyChanged, para inicializar textBlock.Inlines si la colección enlazada ya tiene contenido y para borrar cualquier contexto existente.