我的WPF应用程序从后端服务接收消息流,我需要在UI中显示。这些消息差别很大,我想为每条消息提供不同的视觉布局(字符串格式,颜色,字体,图标,等等)。
我希望能够为每条消息创建一个内联(Run,TextBlock,Italic等),然后以某种方式将它们全部放在ObservableCollection<>
中,并在我的TextBlock.Inlines上使用他的魔法WPF数据绑定在UI中。我找不到怎么做,这可能吗?
答案 0 :(得分:13)
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()));
}