How to display ObservableCollection <string> in UserControl
I am new to WPF and I have found some similar questions, but cannot understand the last part. I have a ViewModel with an ObservableCollection that contains error messages. I want to display them in a form and allow the user to select and copy all or part of the messages. (Previously, WinForm applications used RichTextBox for this, but I cannot figure out how to associate it with a collection in WPF.)
I got the view I got after the next xaml, but there is no built-in way to select and copy as I could with RichTextBox. Does anyone know what control I should use or is there a way to include / copy the contents of all text blocks or a way to bind it to a RichTextBox?
<Grid Margin="6"> <ScrollViewer VerticalScrollBarVisibility="Auto" Height="40" Grid.Column="0" Margin="6"> <ItemsControl ItemsSource="{Binding ErrorMessages}" > <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Mode=OneWay}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Grid> [Edit] @Andrey Shvydky - This will not hurt the comments. It took me a while to figure out the correct syntax (especially the / thing), but in the end I got the Flow Document syntax shown below. It looks right on the form and apparently supports all / copy selection first. But when I paste after selecting all / copies, nothing appears. Does anyone know why?
<Grid Margin="6"> <FlowDocumentScrollViewer> <FlowDocument > <Paragraph> <ItemsControl ItemsSource="{Binding ErrorMessages, Mode=OneWay}" /> <Run Text="{Binding /, Mode=OneWay}" /> </Paragraph> </FlowDocument> </FlowDocumentScrollViewer> </Grid> It may be useful to create a FlowDocument and display this document in a FlowDocumentReader . Try to start with this article: Workflow Overview .
Generation Example:
void ShowErrors(FlowDocumentReader reader, Exception[] errors) { FlowDocument doc = new FlowDocument(); foreach (var e in errors) { doc.Blocks.Add(new Paragraph(new Run(e.GetType().Name)) { Style = (Style)this.FindResource("header") }); doc.Blocks.Add(new Paragraph(new Run(e.Message)) { Style = (Style)this.FindResource("text") }); } reader.Document = doc; } In this example, I added some styles for the text in the flowdocument. PLease take a look at XAML:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style x:Key="header" TargetType="{x:Type Paragraph}"> <Setter Property="FontWeight" Value="Bold"/> </Style> <Style x:Key="text" TargetType="{x:Type Paragraph}"> <Setter Property="Margin" Value="30, 0, 0, 0"/> </Style> </Window.Resources> <FlowDocumentReader Name="reader"> </FlowDocumentReader> Result:

The simplest way:
Assuming your viewmodel implements INotifyPropertyChange, create an event handler for the ObservableCollection PropertyChanged event. Create a property that combines all the elements in the observed collection into one line. Whenever the observed collection changes, fire a notification event for your new property. Bind to this property
public class ViewModel : INotifyPropertyChange { public ViewModel() { MyStrings.CollectionChanged += ChangedCollection; } public ObservableCollection<string> MyStrings{get;set;} public void ChangedCollection(args,args) { base.PropertyChanged("MyAllerts"); } public string MyAllerts { get { string collated = ""; foreach(var allert in MyStrings) { collated += allert; collated += "\n"; } } } } I know that this code is fraught with errors (I wrote it in SO instead of VS), but it should give you some idea.
If you do not have a large number of messages, a simple converter can be viable:
<TextBox IsReadOnly="True"> <TextBox.Text> <Binding Path="Messages" Mode="OneWay"> <Binding.Converter> <vc:JoinStringsConverter /> </Binding.Converter> </Binding> </TextBox.Text> </TextBox> public class JoinStringsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var strings = value as IEnumerable<string>; return string.Join(Environment.NewLine, strings); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotSupportedException(); } } <Grid Margin="6"> <ScrollViewer VerticalScrollBarVisibility="Auto" Height="40" Grid.Column="0" Margin="6"> <ItemsControl ItemsSource="{Binding ErrorMessages}" > <ItemsControl.ItemTemplate> <DataTemplate> <TextBox Text="{Binding ViewModelMemberRepresentingYourErrorMessage}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Grid>