35

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

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

4

9 回答 9

15

您可以将依赖属性添加到 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);
        }
    }
}
于 2012-03-03T13:08:07.843 回答
13

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

根据您的确切布局要求,您可以使用 a 来执行此操作ItemsControl,并将其ItemsPanel设置为 aWrapPanel并将其ItemsSource设置为您的集合。(这里可能需要进行一些实验,因为 anInline不是 a UIElement,所以它的默认渲染可能会使用ToString()而不是显示来完成。)

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

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

于 2009-12-25T00:21:31.813 回答
12

这是利用 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工作,在这种情况下,您需要订阅集合更改。

于 2017-12-01T18:54:11.023 回答
6

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

于 2009-12-26T12:41:53.537 回答
6

感谢弗兰克的解决方案。我必须做一些小改动才能让它对我有用。

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);
    }
}
于 2015-05-28T19:01:06.243 回答
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()));
}
于 2009-12-25T03:55:27.603 回答
1

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

视图模型:

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()));
}
于 2019-12-11T10:06:27.633 回答
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,并清除任何现有上下文。

于 2013-01-06T23:48:20.267 回答
0

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

[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>

瞧!

这是因为它们绑定内联是不必要的,您可以在没有绑定的情况下从另一个控件内容修改文本的部分,这个解决方案帮助我。

于 2020-07-24T19:24:01.767 回答