Skip Navigation LinksHome / Articles / View Article

The Silverlight BlockText Control

+ Add to SilverlightShow Favorites
5 comments   /   posted by Thomas Kirchmair on Apr 09, 2009
(5 votes)
Categories: Products , Controls , Demos , Tutorials

Introduction

Yes, I was searching for months if there is any possibility to simple justify block text! And I didn’t find anything – except lots of posts like: “Why didn’t they implement it?”!

So I waited for Silverlight 3 Beta 1 to appear, and I began to hope and pray, but: Once again – there was nothing like block text. I was very disappointed. How should I display long text on the screen without clear formatting borders on both sides - and I don’t want to mix Silverlight and HTML again, either. I need this for building a web application composed of 100% Silverlight, and I don’t feel like doing the text-rendering and position-calculation by myself.

And as an aside some day I got the idea to justify the left and the right side of my text, as shown in the picture:

BlockText

Demo

The Demo and the sample project are both build with Silverlight 3 Beta 1. If you need the functionality in Silverlight 2 it is possible to convert the project.

Online Demo
Sample Project

Idea

The main idea is to let Silverlight’s Grid do the work. So at the beginning I split the given string into words and draw them hidden into my temporary grid, so I get the ActualWidth of each word.

 1: private void UpdateWords()
 2:  {
 3:      if( _myLayoutRoot != null )
 4:      {
 5:          // ** get all the words of my Text
 6:          string[] arWords = Text.Split( new string[] { " " }, StringSplitOptions.RemoveEmptyEntries );
 7:  
 8:          // ** Clear my Content
 9:          _myGrids.Clear();
 10:          _myLayoutRoot.Children.Clear();
 11:          _myTextBlocks.Clear();
 12:  
 13:          // ** Create the new Grid
 14:          Grid grMaster = CreateNewGridAndAppend();
 15:  
 16:          // ** now create the TextBlocks with all my settings,
 17:          // ** so I get a correct measure of the width
 18:          foreach( string strWord in arWords )
 19:          {
 20:              // ** create the TextBlock and append it to
 21:              // ** my Grid an my List of TextBlocks
 22:              TextBlock txtWord = CreateNewTextBlock( strWord );
 23:              grMaster.Children.Add( txtWord );
 24:              _myTextBlocks.Add( txtWord );
 25:          }
 26:      }
 27:  }

With that I create a Silverlight grid for each line of text, and two columns for each word. The column which contains the Silverlight-TextBlock for the word has exactly the length of the word, and the second column is used for the space between the words. This second column has a minimum width which is given through a definable property of the BlockText-Control, and the star-operator for auto-sizing. So the grid takes all the free space between the words and divides them into equal parts. So I have a fully automated calculation of the space-width between the words.

The next parts are surprisingly simple. The first word on the left side has set the HorizontalAligment-Property to left-aligned and the last word on the right side has set its HorizontalAligment-Property to right-aligned. That’s it!

The grids for each line themselves are stacked vertically inside a StackPanel.

To optimize the performance in case of size changing of the BlockText-Control, I reuse the existing child controls. So I do not create them all new each time, I try to add needed columns to existing grids, remove unused ones, reposition the TextBlocks inside my grids and change the parents of the TextBlocks - in case a TextBlock moves from one line to another.

 1: public void UpdateStackPanel()
 2:  {
 3:      if(( _myLayoutRoot != null ) && (_myGrids.Count > 0) )
 4:      {
 5:          double dbWidth = _myLayoutRoot.ActualWidth;
 6:          int nLineCounter = 0;
 7:          double dbLineLength = (double)0.0;
 8:          int nWordCounter = 0;
 9:          int nWordLineCounter = 0;
 10:  
 11:          // ** walk through all my words
 12:          while( nWordCounter < _myTextBlocks.Count )
 13:          {
 14:              // ** get the TextBlock
 15:              TextBlock txtText = _myTextBlocks[ nWordCounter ];
 16:  
 17:              // ** get the Grid for the Line
 18:              Grid grGrid = ( nLineCounter < _myGrids.Count ) ? _myGrids[ nLineCounter ] : CreateNewGridAndAppend();
 19:  
 20:              // ** now get the row and col
 21:              // ** the first word in the line is always fix placed
 22:              if( nWordLineCounter == 0 )
 23:              {
 24:                  // ** Set the Parent
 25:                  SetNewParent( txtText, grGrid );
 26:  
 27:                  // ** set col, row and width
 28:                  grGrid.ColumnDefinitions[ 0 ].Width = new GridLength( txtText.ActualWidth, GridUnitType.Pixel );
 29:                  Grid.SetColumn( txtText, 0 );
 30:                  Grid.SetRow( txtText, 0 );
 31:  
 32:                  // ** increase the counters
 33:                  nWordCounter++;
 34:                  nWordLineCounter++;
 35:                  dbLineLength = txtText.ActualWidth;
 36:              }
 37:              else
 38:              {
 39:                  // ** calculate the position, where the TextBlock has to be entered
 40:                  int nSpaceColIndex = ( nWordLineCounter * 2 ) - 1;
 41:                  int nWordColIndex = ( nWordLineCounter * 2 );
 42:  
 43:                  // ** it is not the first word in the line, so check if it fits into this line
 44:                  if( ( this.ActualWidth - dbLineLength - this.MinSpaceWidth ) > txtText.ActualWidth )
 45:                  {
 46:                      ColumnDefinition colDefSpace = null, colDefWord = null;
 47:  
 48:                      // ** do I have the needed Col's
 49:                      if( grGrid.ColumnDefinitions.Count <= nSpaceColIndex )
 50:                      {
 51:                          colDefSpace = new ColumnDefinition();
 52:                          grGrid.ColumnDefinitions.Add( colDefSpace );
 53:                      }
 54:                      else
 55:                      {
 56:                          colDefSpace = grGrid.ColumnDefinitions[ nSpaceColIndex ];
 57:                      }
 58:  
 59:                      // ** set the Width to Star, so the space get's calculated automatically
 60:                      colDefSpace.Width = new GridLength( (double)1.0, GridUnitType.Star );
 61:                      colDefSpace.MinWidth = this.MinSpaceWidth;
 62:  
 63:                      // ** do I have the needed Col's
 64:                      if( grGrid.ColumnDefinitions.Count <= nWordColIndex )
 65:                      {
 66:                          colDefWord = new ColumnDefinition();
 67:                          grGrid.ColumnDefinitions.Add( colDefWord );
 68:                      }
 69:                      else
 70:                      {
 71:                          colDefWord = grGrid.ColumnDefinitions[ nWordColIndex ];
 72:                      }
 73:  
 74:                      // ** Set the width of the Word Column
 75:                      colDefWord.Width = new GridLength( txtText.ActualWidth, GridUnitType.Pixel );
 76:  
 77:                      // ** Set the Parent of the TextBlock
 78:                      SetNewParent( txtText, grGrid );
 79:  
 80:                      // ** set the position of the word
 81:                      Grid.SetColumn( txtText, nWordColIndex );
 82:                      Grid.SetRow( txtText, 0 );
 83:  
 84:                      // ** increase the counters
 85:                      nWordLineCounter++;
 86:                      nWordCounter++;
 87:                      dbLineLength += this.MinSpaceWidth + txtText.ActualWidth;
 88:                  }
 89:                  else
 90:                  {
 91:                      // ** if there is a new line needed, so cut the old if
 92:                      // ** there are too much columns and set the horalign of the
 93:                      // ** last word to right
 94:                      while( grGrid.ColumnDefinitions.Count > ( nWordColIndex - 1 ) )
 95:                      {
 96:                          grGrid.ColumnDefinitions.RemoveAt( nWordColIndex - 1 );
 97:                      }
 98:  
 99:                      // ** set the grid to stretch !! If this was a last line sometimes the 
 100:                      // ** stretch were changed to left
 101:                      grGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
 102:  
 103:                      // ** the first word in this line is left oriented, the last one right
 104:                      ( (TextBlock)( grGrid.Children[ 0 ] ) ).HorizontalAlignment = HorizontalAlignment.Left;
 105:                      ( (TextBlock)( grGrid.Children[ grGrid.Children.Count-1 ] ) ).HorizontalAlignment = HorizontalAlignment.Right;
 106:  
 107:                      // ** prepare for next line
 108:                      nWordLineCounter = 0;
 109:                      nLineCounter++;
 110:                      dbLineLength = (double)0.0;
 111:                  }
 112:              }
 113:          }
 114:  
 115:          // ** the last grid ist leftoriented
 116:          _myGrids[ nLineCounter ].HorizontalAlignment = HorizontalAlignment.Left;
 117:  
 118:          // ** remove unused grids
 119:          while( _myLayoutRoot.Children.Count > ( nLineCounter + 1 ) )
 120:          {
 121:              _myLayoutRoot.Children.RemoveAt( ( nLineCounter + 1 ) );
 122:              _myGrids.RemoveAt( ( nLineCounter + 1 ) );
 123:          }
 124:      }
 125:  }

 

Conclusion

I hope I could show how to block justify text. The sample project is written for one paragraph a time. So if you want to display sequential paragraphs, create a StackPanel, split the text at the paragraph- or line-breaks and display your paragraphs by one BlockText-Control for each.

 

Thomas Kirchmair
kir.at

Share


Comments

Comments RSS RSS
  • RE: The Silverlight BlockText Control  

    posted by Nair on Apr 10, 2009 10:17

    Very good solution. Still wondering why MS couldn't implement the feature.

    Thanks

  • RE: The Silverlight BlockText Control  

    posted by Ruurd Boeke on Apr 10, 2009 22:17

    Very innovative, however, I'm not sure if this solution is not somewhat to heavy. I would sooner have liked a custom panel for this kind of layout that does all the measuring and layout. That would be many times faster than having soo many instances of grid behind the scenes

    RJ

  • RE: The Silverlight BlockText Control  

    posted by marko on Apr 15, 2009 14:09
    i hope that this will be fixed in final sl3
  • RE: The Silverlight BlockText Control  

    posted by Sachin Mukhija on Aug 28, 2009 08:10
    In my project I have added all the controls at runtime but when I add the blocktext control to statckpanel and then stackpanel to canvas, I get the error Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED)), Can u please specify the solution for this
  • RE: The Silverlight BlockText Control  

    posted by Thomas Kirchmair on Sep 01, 2009 12:12

    Hi Sachin,

    thank you for your interest on my BlockText-Control. I tried to include 3 BlockText-Controls into one StackPanel and inserted the StackPanel into a Canvas. I didn't get the error in Silveright 3 Release, but I mentioned that the layout update is different the time the control is inside a canvas. This results in the different layout-cycles of the canvas and that the HorizontalAlignment="Stretch" does not work for controls inside the canvas (which is default in all other situations).
    Please download the sample again, because I recently updated the sample project and online demo to Silverlight 3 Release. Then specify a width in pixels for each BlockText-Control inside your Canvas in xaml, so that the layout-system is able to determine the width for the first calculations.

    If you need further info please provide your contact,
    Tom Kirchmair

Add Comment

 
 

   
  
  
   
Please add 8 and 2 and type the answer here: