Подтвердить что ты не робот

Связывание данных с TextBlock.Inlines

My WPF App получает поток сообщений из бэкэнд-сервиса, который мне нужно отображать в пользовательском интерфейсе. Эти сообщения сильно различаются, и я хочу иметь разные визуальные макеты (строковые форматы, цвета, шрифты, значки и т.д.) Для каждого сообщения.

Я надеялся просто создать встроенный (Run, TextBlock, Italic и т.д.) для каждого сообщения, а затем как-то поместить их все в ObservableCollection<> и используя магию привязки данных WPF на моем текстовом блоке. UI. Я не мог найти, как это сделать, возможно ли это?

4b9b3361

Ответ 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, если связанная коллекция уже имеет контент, и очистить любой существующий контекст.