Jun 21, 2011

Beginning WPF: Displaying an ObservableCollection<string>

One of the things I do all the time in Winform applications is show any errors that come back from the instruments I'm controlling. I typically just shows these in a rich text box, easy peasey. How hard could it be under WPF? Turns out pretty hard,  for me. More specifically a couple of days of head banging and some help from Stack Overflow and I haven't solved all the problems yet but I'm close. First it appears the vanilla rich text box in WPF doesn't support data binding. A good solution might be to find one that does, but since I'm learning WPF I thought I would try and get the native widgets to work.

The Versatile <TextBlock>

It looked to me like I could get the behavior I wanted with WPF's FlowDocument. The idea was to put a Paragraph in the  FlowDocument and wrap the FlowDocument in a FlowDocumentScrollViewer. Binding to the ViewModels' ObservableCollection<string> property named ReceivedData required an ItemsControl be placed inside the paragraph. The display of each string in the ItemsControl was done with a TextBlock as shown:

<ItemsControl ItemsSource="{Binding ReceivedData, Mode=OneWay}"

<TextBlock Text="Binding /,  Mode=OneWay"} />

imageThis approach worked fairly well except it didn't wrap long lines of text. Adding the TextWrapping="Wrap" attribute to the TextBlock  didn't  help. Eventually I discovered that the /, Mode = OneWay syntax on my TextBlock was causing the problem. The slash is XAML's method of explicitly telling the TextBlock that I want it to use the currently selected item not the collection itself.  Removing it helped with wrapping but then then the result of ReceivedData.ToString() was showing up in the paragraph along with each item. Somewhat ugly.  Experimentation revealed that using a DataTemplate solved all but one of my problems.

I created the following data template in the User.Resources section

  1. <DataTemplate x:Key="StringCollection">
  2.     <TextBlock TextWrapping="Wrap"
  3.                Text="{Binding Mode=OneWay}"
  4.                TextAlignment="Left" />
  5. </DataTemplate>

Then my FlowDocument section used it with the ItemsTemplate attribute as shown below:

  1. <FlowDocumentScrollViewer Style="{StaticResource myFlowDoc}"
  2.                           Grid.Row="4"
  3.                           Grid.Column="1"
  4.                           HorizontalAlignment="Left"
  5.                           Margin="0,6,12,0">
  6.     <FlowDocument>
  7.         <Paragraph LineHeight="12"
  8.                    TextIndent="-16">
  9.             <ItemsControl ItemsSource="{Binding ReceivedData, Mode=OneWay}"
  10.                           ItemTemplate="{StaticResource StringCollection}" />
  11.         </Paragraph>
  12.     </FlowDocument>
  13. </FlowDocumentScrollViewer>

I'm not sure why this works, but when done this way XAML seems to understand implicitly that I don't wasn't to display anything about the collection itself only about each item in the collection. The only issue remaining is figuring out how to copy and paste the contents. Right-clicking on the area of my form allows me to select all and then the Copy option is enabled (like shown in the figure above). Unfortunately, doing a copy doesn't appear to actually put anything except a new line character on the clipboard.

About Me

My photo
Tod Gentille (@todgentille) is now a Curriculum Director for Pluralsight. He's been programming professionally since well before you were born and was a software consultant for most of his career. He's also a father, husband, drummer, and windsurfer. He wants to be a guitar player but he just hasn't got the chops for it.