Creating Scrollable TextBlock for WP7.

There could be many scenarios when you'd need to be able to dipslay a text in your WP7 application which doesn't fit on the screen. The easiest way to approach to solve this would be to utilize the ScrollViewer control as a host for a TextBlock. Something like that:

 <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
      <ScrollViewer>
            <TextBlock x:Name="textBlock" TextWrapping="Wrap" />
      </ScrollViewer>
 </Grid>

You would think that we're done but as usual the "devil is in the details". If you assign a long text to the TextBlock you will see that part of this text could be truncated and an empty space would be displayed when scrolling to the end:

 The reason for this behavior is that any element that must be displayed beyond the area which is larger than 2048x2048 pixels would be clipped by the platform. I was told that this limitation had been dictated by a combination of hardware limitation and performance considerations. To workaround this limitation we'd need to break out the text into a separate blocks and create a TextBlock for each of this text blocks. So to make the life easier for myself and for you I created a custom control which I called ScrollableTextBlock which wraps this logic. So I created a custom control which derives from Control and implements Text property:

     public class ScrollableTextBlock : Control
    {
        private StackPanel stackPanel;
        
        public ScrollableTextBlock()
        {
            // Get the style from generic.xaml
            this.DefaultStyleKey = typeof(ScrollableTextBlock);           
        }
 
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register(
                "Text",
                typeof(string),
                typeof(ScrollableTextBlock),
                new PropertyMetadata("ScrollableTextBlock", OnTextPropertyChanged));        
 
        public string Text
        {
            get
            {
                return (string)GetValue(TextProperty);
            }
            set
            {
                SetValue(TextProperty, value);
            }
        }
 
        private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ScrollableTextBlock source = (ScrollableTextBlock)d;
            string value = (string)e.NewValue;
            source.ParseText(value);
        }

            }

And here's the ParseText method which is also a part of this control:

     private void ParseText(string value)
    {
         if (this.stackPanel == null)
         {
             return;
         }
         // Clear previous TextBlocks
         this.stackPanel.Children.Clear();
         // Calculate max char count
         int maxTexCount = this.GetMaxTextSize();
 
         if (value.Length < maxTexCount)
         {
             TextBlock textBlock = this.GetTextBlock();
             textBlock.Text = value;
             this.stackPanel.Children.Add(textBlock);
         }
         else
         {
             int n = value.Length / maxTexCount;
             int start = 0;
             // Add textblocks
             for (int i = 0; i < n; i++)
             {                    
                 TextBlock textBlock = this.GetTextBlock();
                 textBlock.Text = value.Substring(start, maxTexCount);
                 this.stackPanel.Children.Add(textBlock);
                 start = maxTexCount;
             }
 
             // Pickup the leftover text
             if (value.Length % maxTexCount > 0)
             {
                 TextBlock textBlock = this.GetTextBlock();
                 textBlock.Text = value.Substring(maxTexCount * n, value.Length - maxTexCount * n);
                 this.stackPanel.Children.Add(textBlock);                   
             }
         }
     }

In the code above I measure the text length and if it's greater that maxTextCount I break the text into a separate blocks and create a new TextBlock with this text. After the TextBlock is created I add it to the StackPanel. To make a picture complete here's how the control's style looks like:

    <Style TargetType="local:ScrollableTextBlock" >
        <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMedium}"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="Width" Value="200" />
        <Setter Property="Height" Value="70" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:ScrollableTextBlock">
                    <ScrollViewer x:Name="ScrollViewer" Foreground="{TemplateBinding Foreground}" 
                                  Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" 
                                  BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
                        <StackPanel Orientation="Vertical" x:Name="StackPanel" />
                    </ScrollViewer>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

As you can see, the StackPanel is located inside of the ScrollViewer control which provides the scrolling functionality.  And after we execute the test project the result is that the text is not truncated:

 

The test project and the ScrollableTextBlock control is available for your peruse.