Thursday, May 27, 2010

WPF TextBlock TextWrapping hell: a solution

In a project I was working on I encountered a situation where I wanted a WPF text-block to wrap, a naive person would think that simply stating TextWrapping="Wrap" would do the trick, Microsoft would tell you - that would make too much sense.

Hence - the problem, how would you cause your textblock to not greedily increase in width and destroy your layout?

Problem better described here, here and here.

A solution for you, assuming this would be the troubled textblock XAML:
<TextBlock TextWrapping="Wrap" Text="bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla "/>

Wrap in a canvas and binding to its actual width/height should do the trick. Just make sure the canvas plays nice with its parent alignment scheme. Why a canvas? Because a canvas does not query its children to determine its size. It allows an inverse layout planning as shown below:

<Canvas VerticalAlignment="Stretch" HorizontAlalignment="Stretch" x:Name="canvasHelper" Margin="30, 30, 30, 30" MinWidth="30">
<TextBlock TextWrapping="Wrap" MaxWidth="{Binding ActualWidth, ElementName=canvasHelper}" MaxHeight="{Binding ActualHeight, ElementName=canvasHelper}" Text="bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla "></Canvas>


Cheers!

6 comments:

  1. Hi, couldn't get this to work.
    Perhaps this works differently in different versions of WPF? I'm running .NET 3.5

    Some of the problem I have is that I would want the container to grow vertically when the textblock is forced to grow by the wrapping...

    ReplyDelete
  2. It works for me, in either .net 4, .net 3.5, or 3.5sp1. I suggest you copy some of your own sample code into an MSDN forum and let the MVP-s there track what hasn't worked for you

    ReplyDelete
  3. Text wrapping is the process of taking a new line when your text reaches the specified Width.

    How on earth is the renderer expected to know where you want your wrapping to start when you haven't even specified a Width for your TextBlock?

    This can be done by explicitly setting the TextBlock.Width property eg:

    <TextBlock Width="50" .... />

    or be set by the containing panel.

    If you don't constrain the width or your containing panel is setting your TextBlock's Width to "Auto" then the TextBlock will continue to expand horizontally to contain the text.

    With a constrained Width and an unconstrained Height then the text will wrap when it exceeds the Width.

    ReplyDelete
  4. Anonymous, you are missing the point. TextBlock is constrained by logic of its parent containers - however it behaves unexpectadly unless you dynamically hook up that width to the width from above.

    A programmer would have expected a textblock without width specified but with "wrapping", to not claim width based on a one line measurement of its text, but only accept text from upper tree logic and then render with what it gets.

    The logic microsoft use here is inverted to most other UI environments.

    You appear angry too... perhaps it is just your way of expressing yourself

    ReplyDelete
  5. Great post. This helped solve an issue I had. Thanks for the help.

    ReplyDelete
  6. Hi, i was searching a solution for a long time. I found some solution on stackexchange with dockpanel, grid, binding on Parent size... None of them worked. The solution was the Canvas, thanks a lot!

    ReplyDelete