要显示“动态”数据,一种简单的方法就是使用ItemsControl
(例如,WrapPanel
作为项目模板。)
现在我希望我的应用程序,一个充满运行的富文本框是理想的。 - 运行的(数字和数据)取决于我的viewmodel中的可观察集合。如果我使用WrapPanel
而不是RichTextBox
,则itemscontrol代码将如下所示:
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
现在我尝试在usercontrol中使用richtextbox,用户控件的xaml如下所示:
<UserControl x:Class="testit.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:testit"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:DispData}">
<TextBlock>
<Run Text="{Binding Text}"></Run>
</TextBlock>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<RichTextBox IsReadOnly="True" IsDocumentEnabled="True" VerticalScrollBarVisibility="Auto">
<FlowDocument>
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Paragraph IsItemsHost="True">
</Paragraph>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</FlowDocument>
</RichTextBox>
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</UserControl>
绑定到usercontrol的datacontext的viewmodel是:
namespace testit
{
class ViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<DispData> _data =
new ObservableCollection<DispData>();
public ReadOnlyObservableCollection<DispData> Data { get; private set; }
public ViewModel() {
Data = new ReadOnlyObservableCollection<DispData>(_data);
_data.Add(new DispData("hello"));
_data.Add(new DispData("world"));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
这会给RichTextBox
带来很多错误(第一个错误是ItemsControl
无法放在那里,而另一个错误是Paragraph
没有IsItemsHost
我要强调的是,如果我注释掉richtextbox xaml,包装面板的xaml确实有用:所以它不是绑定或任何错误。
RichTextBox
甚至可以与ItemsControl
一起使用 - 如果没有,我将如何以MVVM方式填充文本框的内容?
答案 0 :(得分:3)
您应该查看this article如何编写与FlowDocument
或RichTextBox
兼容的自己的项目控件。可以在此location
下载控件后,请更新GenerateContent()
中ItemsContent
方法中的if-else条件,如下所示,以添加对Paragraph
和Inlines
的支持。
private void GenerateContent(DataTemplate itemsPanel, DataTemplate itemTemplate, IEnumerable itemsSource)
{
....
if (panel is Section)
((Section)panel).Blocks.Add(Helpers.ConvertToBlock(data, element));
else if (panel is TableRowGroup)
((TableRowGroup)panel).Rows.Add((TableRow)element);
else if (panel is Paragraph && element is Inline)
((Paragraph)panel).Inlines.Add((Inline)element);
else
throw new Exception(String.Format("Don't know how to add an instance of {0} to an instance of {1}", element.GetType(), panel.GetType()));
并将您的XAML更新为:
<RichTextBox IsReadOnly="True" IsDocumentEnabled="True" VerticalScrollBarVisibility="Auto">
<FlowDocument>
<flowdoc:ItemsContent ItemsSource ItemsSource="{Binding Data}">
<flowdoc:ItemsContent.ItemsPanel>
<DataTemplate>
<flowdoc:Fragment>
<Paragraph flowdoc:Attached.IsItemsHost="True" />
</flowdoc:Fragment>
</DataTemplate>
</flowdoc:ItemsContent.ItemsPanel>
<flowdoc:ItemsContent.ItemTemplate>
<DataTemplate>
<flowdoc:Fragment>
<flowdoc:BindableRun BoundText="{Binding Text}" />
</flowdoc:Fragment>
</DataTemplate>
</flowdoc:ItemsContent.ItemTemplate>
</flowdoc:ItemsContent>
</FlowDocument>
</RichTextBox>
编辑 - 1 正如@ ed-plunkett建议的那样,在这里共享相关代码(如果外部链接不起作用)
为了能够在item-template中使用Run
(类似于标签或文本块);您需要扩展Run
以添加可绑定属性。
public class BindableRun : Run
{
public static readonly DependencyProperty BoundTextProperty = DependencyProperty.Register("BoundText", typeof(string), typeof(BindableRun), new PropertyMetadata(OnBoundTextChanged));
public BindableRun()
{
Helpers.FixupDataContext(this);
}
private static void OnBoundTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Run)d).Text = (string)e.NewValue;
}
public String BoundText
{
get { return (string)GetValue(BoundTextProperty); }
set { SetValue(BoundTextProperty, value); }
}
}
接下来,您需要能够将容器控件标记为项目主机;这可以通过定义附加属性来完成。
public class Attached
{
private static readonly DependencyProperty IsItemsHostProperty = DependencyProperty.RegisterAttached("IsItemsHost", typeof(bool), typeof(Attached), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.NotDataBindable, OnIsItemsHostChanged));
private static readonly DependencyProperty ItemsHostProperty = DependencyProperty.RegisterAttached("ItemsHost", typeof(FrameworkContentElement), typeof(Attached), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.NotDataBindable));
public static bool GetIsItemsHost(DependencyObject target)
{
return (bool)target.GetValue(IsItemsHostProperty);
}
public static void SetIsItemsHost(DependencyObject target, bool value)
{
target.SetValue(IsItemsHostProperty, value);
}
private static void SetItemsHost(FrameworkContentElement element)
{
FrameworkContentElement parent = element;
while (parent.Parent != null)
parent = (FrameworkContentElement)parent.Parent;
parent.SetValue(ItemsHostProperty, element);
}
public static FrameworkContentElement GetItemsHost(DependencyObject dp)
{
return (FrameworkContentElement)dp.GetValue(ItemsHostProperty);
}
private static void OnIsItemsHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
FrameworkContentElement element = (FrameworkContentElement)d;
if (element.IsInitialized)
SetItemsHost(element);
else
element.Initialized += ItemsHost_Initialized;
}
}
private static void ItemsHost_Initialized(object sender, EventArgs e)
{
FrameworkContentElement element = (FrameworkContentElement)sender;
element.Initialized -= ItemsHost_Initialized;
SetItemsHost(element);
}
}
可用于在FrameworkContentElement
内嵌入DataTemplate
的片段控件。
[ContentProperty("Content")]
public class Fragment : FrameworkElement
{
private static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(FrameworkContentElement), typeof(Fragment));
public FrameworkContentElement Content
{
get
{
return (FrameworkContentElement)GetValue(ContentProperty);
}
set
{
SetValue(ContentProperty, value);
}
}
}
而且,最后这些项目控制着自己:这是重大举措:
public class ItemsContent : Section
{
private static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ItemsContent), new PropertyMetadata(OnItemsSourceChanged));
private static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(ItemsContent), new PropertyMetadata(OnItemTemplateChanged));
private static readonly DependencyProperty ItemsPanelProperty = DependencyProperty.Register("ItemsPanel", typeof(DataTemplate), typeof(ItemsContent), new PropertyMetadata(OnItemsPanelChanged));
public ItemsContent()
{
Helpers.FixupDataContext(this);
Loaded += ItemsContent_Loaded;
}
private void ItemsContent_Loaded(object sender, RoutedEventArgs e)
{
GenerateContent(ItemsPanel, ItemTemplate, ItemsSource);
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public DataTemplate ItemsPanel
{
get { return (DataTemplate)GetValue(ItemsPanelProperty); }
set { SetValue(ItemsPanelProperty, value); }
}
private void GenerateContent(DataTemplate itemsPanel, DataTemplate itemTemplate, IEnumerable itemsSource)
{
Blocks.Clear();
if (itemTemplate != null && itemsSource != null)
{
FrameworkContentElement panel = null;
foreach (object data in itemsSource)
{
if (panel == null)
{
if (itemsPanel == null)
panel = this;
else
{
FrameworkContentElement p = Helpers.LoadDataTemplate(itemsPanel);
if (!(p is Block))
throw new Exception("ItemsPanel must be a block element");
Blocks.Add((Block)p);
panel = Attached.GetItemsHost(p);
if (panel == null)
throw new Exception("ItemsHost not found. Did you forget to specify Attached.IsItemsHost?");
}
}
FrameworkContentElement element = Helpers.LoadDataTemplate(itemTemplate);
element.DataContext = data;
Helpers.UnFixupDataContext(element);
if (panel is Section)
((Section)panel).Blocks.Add(Helpers.ConvertToBlock(data, element));
else if (panel is TableRowGroup)
((TableRowGroup)panel).Rows.Add((TableRow)element);
else if (panel is Paragraph && element is Inline)
((Paragraph)panel).Inlines.Add((Inline)element);
else
throw new Exception(String.Format("Don't know how to add an instance of {0} to an instance of {1}", element.GetType(), panel.GetType()));
}
}
}
private void GenerateContent()
{
GenerateContent(ItemsPanel, ItemTemplate, ItemsSource);
}
private void OnItemsSourceChanged(IEnumerable newValue)
{
if (IsLoaded)
GenerateContent(ItemsPanel, ItemTemplate, newValue);
}
private void OnItemTemplateChanged(DataTemplate newValue)
{
if (IsLoaded)
GenerateContent(ItemsPanel, newValue, ItemsSource);
}
private void OnItemsPanelChanged(DataTemplate newValue)
{
if (IsLoaded)
GenerateContent(newValue, ItemTemplate, ItemsSource);
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ItemsContent)d).OnItemsSourceChanged((IEnumerable)e.NewValue);
}
private static void OnItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ItemsContent)d).OnItemTemplateChanged((DataTemplate)e.NewValue);
}
private static void OnItemsPanelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ItemsContent)d).OnItemsPanelChanged((DataTemplate)e.NewValue);
}
}
静态辅助方法:
internal static class Helpers
{
/// <summary>
/// If you use a bindable flow document element more than once, you may encounter a "Collection was modified" exception.
/// The error occurs when the binding is updated because of a change to an inherited dependency property. The most common scenario
/// is when the inherited DataContext changes. It appears that an inherited properly like DataContext is propagated to its descendants.
/// When the enumeration of descendants gets to a BindableXXX, the dependency properties of that element change according to the new
/// DataContext, which change the (non-dependency) properties. However, for some reason, changing the flow content invalidates the
/// enumeration and raises an exception.
/// To work around this, one can either DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=FrameworkElement}}"
/// in code. This is clumsy, so every derived type calls this function instead (which performs the same thing).
/// See http://code.logos.com/blog/2008/01/data_binding_in_a_flowdocument.html
/// </summary>
/// <param name="element"></param>
public static void FixupDataContext(FrameworkContentElement element)
{
Binding b = new Binding(FrameworkContentElement.DataContextProperty.Name);
// another approach (if this one has problems) is to bind to an ancestor by ElementName
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(FrameworkElement), 1);
element.SetBinding(FrameworkContentElement.DataContextProperty, b);
}
private static bool InternalUnFixupDataContext(DependencyObject dp)
{
// only consider those elements for which we've called FixupDataContext(): they all belong to this namespace
if (dp is FrameworkContentElement && dp.GetType().Namespace == typeof(Helpers).Namespace)
{
Binding binding = BindingOperations.GetBinding(dp, FrameworkContentElement.DataContextProperty);
if (binding != null
&& binding.Path != null && binding.Path.Path == FrameworkContentElement.DataContextProperty.Name
&& binding.RelativeSource != null && binding.RelativeSource.Mode == RelativeSourceMode.FindAncestor && binding.RelativeSource.AncestorType == typeof(FrameworkElement) && binding.RelativeSource.AncestorLevel == 1)
{
BindingOperations.ClearBinding(dp, FrameworkContentElement.DataContextProperty);
return true;
}
}
// as soon as we have disconnected a binding, return. Don't continue the enumeration, since the collection may have changed
foreach (object child in LogicalTreeHelper.GetChildren(dp))
if (child is DependencyObject)
if (InternalUnFixupDataContext((DependencyObject)child))
return true;
return false;
}
public static void UnFixupDataContext(DependencyObject dp)
{
while (InternalUnFixupDataContext(dp))
;
}
/// <summary>
/// Convert "data" to a flow document block object. If data is already a block, the return value is data recast.
/// </summary>
/// <param name="dataContext">only used when bindable content needs to be created</param>
/// <param name="data"></param>
/// <returns></returns>
public static Block ConvertToBlock(object dataContext, object data)
{
if (data is Block)
return (Block)data;
else if (data is Inline)
return new Paragraph((Inline)data);
else if (data is BindingBase)
{
BindableRun run = new BindableRun();
if (dataContext is BindingBase)
run.SetBinding(BindableRun.DataContextProperty, (BindingBase)dataContext);
else
run.DataContext = dataContext;
run.SetBinding(BindableRun.BoundTextProperty, (BindingBase)data);
return new Paragraph(run);
}
else
{
Run run = new Run();
run.Text = (data == null) ? String.Empty : data.ToString();
return new Paragraph(run);
}
}
public static FrameworkContentElement LoadDataTemplate(DataTemplate dataTemplate)
{
object content = dataTemplate.LoadContent();
if (content is Fragment)
return (FrameworkContentElement)((Fragment)content).Content;
else if (content is TextBlock)
{
InlineCollection inlines = ((TextBlock)content).Inlines;
if (inlines.Count == 1)
return inlines.FirstInline;
else
{
Paragraph paragraph = new Paragraph();
// we can't use an enumerator, since adding an inline removes it from its collection
while (inlines.FirstInline != null)
paragraph.Inlines.Add(inlines.FirstInline);
return paragraph;
}
}
else
throw new Exception("Data template needs to contain a <Fragment> or <TextBlock>");
}
}