Связывание данных с TextBlock.Inlines
My WPF App получает поток сообщений из бэкэнд-сервиса, который мне нужно отображать в пользовательском интерфейсе. Эти сообщения сильно различаются, и я хочу иметь разные визуальные макеты (строковые форматы, цвета, шрифты, значки и т.д.) Для каждого сообщения.
Я надеялся просто создать встроенный (Run, TextBlock, Italic и т.д.) для каждого сообщения, а затем как-то поместить их все в ObservableCollection<>
и используя магию привязки данных WPF на моем текстовом блоке. UI. Я не мог найти, как это сделать, возможно ли это?
Ответы
Ответ 1
Это невозможно, так как свойство TextBlock.Inlines
не является свойством зависимостей. Только свойства зависимостей могут быть объектом привязки данных.
В зависимости от ваших точных требований к макету вы можете сделать это, используя ItemsControl
, с его ItemsPanel
, установленным в WrapPanel
и его ItemsSource
, установленным в вашу коллекцию. (Здесь может потребоваться некоторое экспериментирование, потому что Inline
не является UIElement
, поэтому его рендеринг по умолчанию, вероятно, будет выполнен с использованием ToString()
вместо отображения.)
В качестве альтернативы вам может понадобиться создать новый элемент управления, например. MultipartTextBlock
, с привязываемым свойством PartsSource
и TextBlock
в качестве шаблона по умолчанию. Когда был установлен PartsSource
, ваш элемент управления будет прикреплять обработчик события CollectionChanged
(напрямую или через CollectionChangedEventManager) и обновлять коллекцию TextBlock.Inlines
от кода при изменении коллекции PartsSource
.
В любом случае может потребоваться предостережение, если ваш код генерирует непосредственно элементы Inline
(поскольку Inline
нельзя использовать одновременно в двух местах). Вы также можете рассмотреть возможность подвергнуть абстрактной модели текста, шрифта и т.д. (Т. Е. Модели представления) и создать фактические объекты Inline
с помощью DataTemplate
. Это также может улучшить тестируемость, но, очевидно, добавляет сложности и усилий.
Ответ 2
Вы можете добавить свойство зависимостей в подкласс TextBlock
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);
}
}
}
Ответ 3
В версии 4 WPF вы сможете привязать объект Run, который может решить вашу проблему.
Я решил эту проблему в прошлом, переопределив ItemControl и отображая текст как элементы в ItemsControl. Посмотрите на некоторые из уроков, которые доктор WPF сделал на таких вещах: http://www.drwpf.com
Ответ 4
Если я правильно получаю ваше требование, вы можете вручную проверить поступающие сообщения и для каждого сообщения вы можете добавить элемент в свойство TextBlock.Inlines. Это не займет никакой привязки данных.
Я сделал это с помощью следующего:
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()));
}
Ответ 5
Спасибо Фрэнк за ваше решение. Мне пришлось сделать пару небольших изменений, чтобы это сработало для меня.
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);
}
}
Ответ 6
Я понимаю, что этот вопрос очень старый, но я все равно решил поделиться альтернативным решением. Он использует поведения WPF/вложенные свойства:
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 );
}
}
}
В вашем XAML используйте его так:
<TextBlock MyBehaviors:TextBlockExtensions.BindableInlines="{Binding Foo}" />
Это избавляет вас от необходимости наследовать от TextBlock. С таким же успехом это может работать с использованием ObservableCollection вместо IEnumerable, в этом случае вам нужно будет подписаться на изменения коллекции.
Ответ 7
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
''' <summary>
''' Adds the items to the inlines
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
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
Я думаю, вам может понадобиться дополнительный код в обработчике PropertyChanged, поэтому для инициализации textBlock.Inlines, если связанная коллекция уже имеет контент, и очистить любой существующий контекст.