(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

The Silverlight BlockText Control

(6 votes)
Thomas Kirchmair
>
Thomas Kirchmair
Joined Jan 27, 2009
Articles:   2
Comments:   0
More Articles
11 comments   /   posted on Apr 09, 2009
Categories:   Controls

This article is compatible with the latest version of Silverlight.


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

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.

 private void UpdateWords()
 {
      if( _myLayoutRoot != null )
      {
          // ** get all the words of my Text
          string[] arWords = Text.Split( new string[] { " " }, StringSplitOptions.RemoveEmptyEntries );
  
          // ** Clear my Content
          _myGrids.Clear();
          _myLayoutRoot.Children.Clear();
          _myTextBlocks.Clear();
  
          // ** Create the new Grid
          Grid grMaster = CreateNewGridAndAppend();
  
          // ** now create the TextBlocks with all my settings,
          // ** so I get a correct measure of the width
          foreach( string strWord in arWords )
          {
              // ** create the TextBlock and append it to
              // ** my Grid an my List of TextBlocks
              TextBlock txtWord = CreateNewTextBlock( strWord );
              grMaster.Children.Add( txtWord );
              _myTextBlocks.Add( txtWord );
          }
      }
  }

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.

 public void UpdateStackPanel()
 {
     if(( _myLayoutRoot != null ) && (_myGrids.Count > 0) )
     {
          double dbWidth = _myLayoutRoot.ActualWidth;
          int nLineCounter = 0;
          double dbLineLength = (double)0.0;
          int nWordCounter = 0;
          int nWordLineCounter = 0;
  
          // ** walk through all my words
          while( nWordCounter < _myTextBlocks.Count )
          {
               // ** get the TextBlock
               TextBlock txtText = _myTextBlocks[ nWordCounter ];
   
               // ** get the Grid for the Line
               Grid grGrid = ( nLineCounter < _myGrids.Count ) ? _myGrids[ nLineCounter ] : CreateNewGridAndAppend();
   
               // ** now get the row and col
               // ** the first word in the line is always fix placed
               if( nWordLineCounter == 0 )
               {
                   // ** Set the Parent
                   SetNewParent( txtText, grGrid );
   
                   // ** set col, row and width
                   grGrid.ColumnDefinitions[ 0 ].Width = new GridLength( txtText.ActualWidth, GridUnitType.Pixel );
                   Grid.SetColumn( txtText, 0 );
                   Grid.SetRow( txtText, 0 );
   
                   // ** increase the counters
                   nWordCounter++;
                   nWordLineCounter++;
                   dbLineLength = txtText.ActualWidth;
               }
               else
               {
                   // ** calculate the position, where the TextBlock has to be entered
                   int nSpaceColIndex = ( nWordLineCounter * 2 ) - 1;
                   int nWordColIndex = ( nWordLineCounter * 2 );
   
                   // ** it is not the first word in the line, so check if it fits into this line
                   if( ( this.ActualWidth - dbLineLength - this.MinSpaceWidth ) > txtText.ActualWidth )
                   {
                       ColumnDefinition colDefSpace = null, colDefWord = null;
   
                       // ** do I have the needed Col's
                       if( grGrid.ColumnDefinitions.Count <= nSpaceColIndex )
                       {
                           colDefSpace = new ColumnDefinition();
                           grGrid.ColumnDefinitions.Add( colDefSpace );
                       }
                       else
                       {
                           colDefSpace = grGrid.ColumnDefinitions[ nSpaceColIndex ];
                       }
  
                       // ** set the Width to Star, so the space get's calculated automatically
                       colDefSpace.Width = new GridLength( (double)1.0, GridUnitType.Star );
                       colDefSpace.MinWidth = this.MinSpaceWidth;
  
                      // ** do I have the needed Col's
                       if( grGrid.ColumnDefinitions.Count <= nWordColIndex )
                       {
                           colDefWord = new ColumnDefinition();
                           grGrid.ColumnDefinitions.Add( colDefWord );
                       }
                       else
                       {
                           colDefWord = grGrid.ColumnDefinitions[ nWordColIndex ];
                       }
   
                       // ** Set the width of the Word Column
                       colDefWord.Width = new GridLength( txtText.ActualWidth, GridUnitType.Pixel );
  
                       // ** Set the Parent of the TextBlock
                       SetNewParent( txtText, grGrid );
   
                       // ** set the position of the word
                       Grid.SetColumn( txtText, nWordColIndex );
                       Grid.SetRow( txtText, 0 );
   
                       // ** increase the counters
                       nWordLineCounter++;
                       nWordCounter++;
                       dbLineLength += this.MinSpaceWidth + txtText.ActualWidth;
                   }
                   else
                   {
                       // ** if there is a new line needed, so cut the old if
                       // ** there are too much columns and set the horalign of the
                       // ** last word to right
                       while( grGrid.ColumnDefinitions.Count > ( nWordColIndex - 1 ) )
                       {
                           grGrid.ColumnDefinitions.RemoveAt( nWordColIndex - 1 );
                       }
   
                       // ** set the grid to stretch !! If this was a last line sometimes the 
                       // ** stretch were changed to left
                       grGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
   
                       // ** the first word in this line is left oriented, the last one right
                       ( (TextBlock)( grGrid.Children[ 0 ] ) ).HorizontalAlignment = HorizontalAlignment.Left;
                       ( (TextBlock)( grGrid.Children[ grGrid.Children.Count-1 ] ) ).HorizontalAlignment = HorizontalAlignment.Right;
   
                       // ** prepare for next line
                       nWordLineCounter = 0;
                       nLineCounter++;
                       dbLineLength = (double)0.0;
                   }
               }
           }
   
           // ** the last grid ist leftoriented
           _myGrids[ nLineCounter ].HorizontalAlignment = HorizontalAlignment.Left;
   
           // ** remove unused grids
           while( _myLayoutRoot.Children.Count > ( nLineCounter + 1 ) )
           {
               _myLayoutRoot.Children.RemoveAt( ( nLineCounter + 1 ) );
               _myGrids.RemoveAt( ( nLineCounter + 1 ) );
           }
       }
   }

 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


Subscribe

Comments

  • -_-

    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 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

  • -_-

    RE: The Silverlight BlockText Control


    posted by LawBot on Mar 21, 2010 18:44

    Hallo Thomas,

    ich habe Probleme, den ZIP-Download zu öffnen. Erhalte eine Fehlermeldung, der Ordner sei leer. Hast Du eine Idee?

    Beste Grüße,

    M.

     

  • -_-

    RE: The Silverlight BlockText Control


    posted by Thomas Kirchmair on Mar 22, 2010 12:17

    Hallo M.,

    Danke für Deine Information. Die ContentCompression vom IIS ist nun ausgeschalten, dadurch wird das zip-File richtig übertragen. Falls Du Fragen hast, Du kannst mich unter kir.at kontaktieren.

    Grüße, Tom

    --------------

    For those who had problems downloading the sample zip-file: My IIS is now running with deactivated ContentCompression. So the zip will be downloaded correctly.

    Greetings, Tom

  • -_-

    RE: The Silverlight BlockText Control


    posted by Todd on May 18, 2010 10:05
    Your source link is no longer working.
  • -_-

    RE: The Silverlight BlockText Control


    posted by Abhilash on Nov 25, 2010 09:54

    Hi,

    Thanks for the solution.
    I'm getting an error as "Object reference not set to an instance of an object" during design time, whenever I try to add more than one <kir:BlockText> tag in my markup. I'm using Silverlight 4. I'm placing all <kir:BlockText> inside a stackpanel like:

    <

     

     

    StackPanel Background="White" Width="750">
    <kir:BlockText x:Name="txtBlockPrivacyPolicy"></kir:BlockText>
    <kir:BlockText x:Name="txtBlockTermsAndCondtions"></kir:BlockText>
    </StackPanel>

     

     

     

     Thanks.

  • -_-

    RE: The Silverlight BlockText Control


    posted by Tom on Nov 26, 2010 00:59

    Hi,

    thanks for using my code and reporting this issue. As a workaround, insert in BlockText.cs at the beginning of the function "public void UpdateAll()", at line 187 the following code:

                if (DesignerProperties.IsInDesignTool == true)
                    return;

    then it works. I will update the online project the next days.

    Maybe You could send a link, where I can see the BlockTextControl life in action
    inside Your project.

    Greetings, Tom

  • -_-

    RE: The Silverlight BlockText Control


    posted by Shohan on Feb 22, 2011 12:01

    Hi,

    Your BlockText control works fine, but wanted to know how to implement the Style property in the BlockText control which is similar to the TextBlock control.

    Thanks

Add Comment

Login to comment:
  *      *