绑定TextBlock.Inlines的数据

时间:2009-12-24 21:51:06

标签: wpf data-binding textblock inlines

我的WPF应用程序从后端服务接收消息流,我需要在UI中显示。这些消息差别很大,我想为每条消息提供不同的视觉布局(字符串格式,颜色,字体,图标,等等)。

我希望能够为每条消息创建一个内联(Run,TextBlock,Italic等),然后以某种方式将它们全部放在ObservableCollection<>中,并在我的TextBlock.Inlines上使用他的魔法WPF数据绑定在UI中。我找不到怎么做,这可能吗?

9 个答案:

答案 0 :(得分:13)

您可以将依赖属性添加到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);
        }
    }
}

答案 1 :(得分:10)

这是不可能的,因为TextBlock.Inlines属性不是依赖属性。只有依赖项属性才能成为数据绑定的目标。

根据您的确切布局要求,您可以使用ItemsControl执行此操作,并将ItemsPanel设置为WrapPanel并将其ItemsSource设置为您的收藏集。 (此处可能需要进行一些实验,因为Inline不是UIElement,因此默认渲染可能会使用ToString()而不是显示。)

或者,您可能需要构建一个新控件,例如MultipartTextBlock,具有可绑定的PartsSource属性和TextBlock作为其默认模板。设置PartsSource后,您的控件将附加CollectionChanged事件处理程序(直接或通过CollectionChangedEventManager),并在TextBlock.Inlines集合更改时从代码更新PartsSource集合。 / p>

在任何一种情况下,如果您的代码直接生成Inline元素,可能需要谨慎(因为Inline不能同时在两个地方使用)。您也可以考虑公开文本,字体等的抽象模型(即视图模型)并通过Inline创建实际的DataTemplate对象。这也可以提高可测试性,但显然会增加复杂性和工作量。

答案 2 :(得分:6)

在WPF的第4版中,您将能够绑定到Run对象,这可以解决您的问题。

我过去通过覆盖ItemsControl并将文本显示为ItemsControl中的项目来解决此问题。看看WPF博士在这类内容上做的一些教程:http://www.drwpf.com

答案 3 :(得分:4)

如果我正确地获得了您的要求,您可以手动检查即将发布的消息,并且可以为每条消息添加元素到TextBlock.Inlines属性。它不会采用任何DataBinding。 我用以下方法做到了这一点:

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()));
    }

答案 4 :(得分:3)

感谢Frank提供的解决方案。我不得不做一些小改动才能让它适合我。

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);
    }
}

答案 5 :(得分:3)

我意识到这个问题已经很久了,但我认为无论如何我都会分享另一种解决方案。它利用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 ,在这种情况下,您需要订阅集合更改。

答案 6 :(得分:1)

每个人都给出了很好的解决方案,但是我遇到了类似的问题,在寻找解决方案数小时后,我决定尝试直接绑定到默认内容。没有依赖属性。 对不起,我的英语已经过时了...呵呵呵

[ContentProperty("Inlines")]
public partial class WindowControl : UserControl
{
    public InlineCollection Inlines { get => txbTitle.Inlines; }
}

好吧,让我们在您的xaml文件中使用它...

<local:WindowControl>
    .:: Register Logbook : Connected User - <Run Text="{Binding ConnectedUser.Name}"/> ::.
</local:WindowControl>

瞧!

因为不需要绑定内联,所以您可以从另一个控件内容修改文本的一部分而无需绑定,此解决方案帮助我。

答案 7 :(得分:0)

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,并清除任何现有的上下文。

答案 8 :(得分:0)

Pavel Anhikouski的建议非常有效。这是MVVM中具有数据绑定的缺失部分。使用视图模型中的AddTrace属性将内容添加到窗口中的OutputBlock。 不需要窗口中的支持属性MyBindingPath。

ViewModel:

private string _addTrace;
public string AddTrace
{
  get => _addTrace;
  set
  {
    _addTrace = value;
    NotifyPropertyChanged();
  }
}

public void StartTrace()
{
  AddTrace = "1\n";
  AddTrace = "2\n";
  AddTrace = "3\n";
}

TraceWindow.xaml:

  <Grid>
    <ScrollViewer Name="Scroller" Margin="0" Background="#FF000128">
      <TextBlock Name="OutputBlock"  Foreground="White" FontFamily="Consolas" Padding="10"/>
    </ScrollViewer>
  </Grid>

TraceWindow.xaml.cs:

public TraceWindow(TraceWindowModel context)
{
  DataContext = context;
  InitializeComponent();

  //bind MyBindingPathProperty to AddTrace
  Binding binding = new Binding("AddTrace");
  binding.Source = context;
  this.SetBinding(MyBindingPathProperty, binding);
}

public static readonly DependencyProperty MyBindingPathProperty =
        DependencyProperty.Register("MyBindingPath", typeof(string), typeof(TraceWindow), new UIPropertyMetadata(null, OnPropertyChanged));



private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
  (sender as TraceWindow).OutputBlock.Inlines.Add(new Run(e.NewValue.ToString()));
}