I am having trouble getting Silverlight 2.0 to lay out text exactly how I want. I want text with line breaks and embedded links, with wrapping, like HTML text in a web page.
Here's the closest that I have come:
<UserControl x:Class="FlowPanelTest.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
Width="250" Height="300">
<Border BorderBrush="Black" BorderThickness="2" >
<Controls:WrapPanel>
<TextBlock x:Name="tb1" TextWrapping="Wrap">Short text. </TextBlock>
<TextBlock x:Name="tb2" TextWrapping="Wrap">A bit of text. </TextBlock>
<TextBlock x:Name="tb3" TextWrapping="Wrap">About half of a line of text.</TextBlock>
<TextBlock x:Name="tb4" TextWrapping="Wrap">More than half a line of longer text.</TextBlock>
<TextBlock x:Name="tb5" TextWrapping="Wrap">More than one line of text, so it will wrap onto the following line.</TextBlock>
</Controls:WrapPanel>
</Border>
</UserControl>
But the issue is that although the text blocks tb1 and tb2 will go onto the same line because there is room enough for them completely, tb3 onwards will not start on the same line as the previous block, even though it will wrap onto following lines.
I want each text block to start where the previous one ends, on the same line. I want to put click event handlers on some of the text. I also want paragraph breaks. Essentially I'm trying to work around the lack of FlowDocument and Hyperlink controls in Silverlight 2.0's subset of XAML.
To answer the questions posed in the answers:
Why not use runs for the non-clickable text? If I just use individual TextBlocks only on the clickable text, then those bits of text will still suffer from the wrapping problem illustrated above. And the TextBlock just before the link, and the TextBlock just after. Essentially all of it. It doesn't look like I have many opportunities for putting multiple runs in the same TextBlock.
Dividing the links from the other text with RegExs and loops is not the issue at all, the issue is display layout.
Why not put each word in an individual TextBlock in a WrapPanel Aside from being an ugly hack, this does not play at all well with linebreaks - the layout is incorrect.
It would also make the underline style of linked text into a broken line.
Here's an example with each word in its own TextBlock. Try running it, note that the linebreak isn't shown in the right place at all.
<UserControl x:Class="SilverlightApplication2.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
Width="300" Height="300">
<Controls:WrapPanel>
<TextBlock TextWrapping="Wrap">Short1 </TextBlock>
<TextBlock TextWrapping="Wrap">Longer1 </TextBlock>
<TextBlock TextWrapping="Wrap">Longerest1 </TextBlock>
<TextBlock TextWrapping="Wrap">
<Run>Break</Run>
<LineBreak></LineBreak>
</TextBlock>
<TextBlock TextWrapping="Wrap">Short2</TextBlock>
<TextBlock TextWrapping="Wrap">Longer2</TextBlock>
<TextBlock TextWrapping="Wrap">Longerest2</TextBlock>
<TextBlock TextWrapping="Wrap">Short3</TextBlock>
<TextBlock TextWrapping="Wrap">Longer3</TextBlock>
<TextBlock TextWrapping="Wrap">Longerest3</TextBlock>
</Controls:WrapPanel>
</UserControl>
What about The LinkLabelControl as here and here. It has the same problems as the approach above, since it's much the same. Try running the sample, and make the link text longer and longer until it wraps. Note that the link starts on a new line, which it shouldn't. Make the link text even longer, so that the link text is longer than a line. Note that it doesn't wrap at all, it cuts off. This control doesn't handle line breaks and paragraph breaks either.
Why not put the text all in runs, detect clicks on the containing TextBlock and work out which run was clicked
Runs do not have mouse events, but the containing TextBlock does. I can't find a way to check if the run is under the mouse (IsMouseOver is not present in SilverLight) or to find the bounding geometry of the run (no clip property).
There is VisualTreeHelper.FindElementsInHostCoordinates()
The code below uses VisualTreeHelper.FindElementsInHostCoordinates to get the controls under the click. The output lists the TextBlock but not the Run, since a Run is not a UiElement.
private void theText_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// get the elements under the click
UIElement uiElementSender = sender as UIElement;
Point clickPos = e.GetPosition(uiElementSender);
var UiElementsUnderClick = VisualTreeHelper.FindElementsInHostCoordinates(clickPos, uiElementSender);
// show the controls
string outputText = "";
foreach (var uiElement in UiElementsUnderClick)
{
outputText += uiElement.GetType().ToString() + "\n";
}
this.outText.Text = outputText;
}
Use an empty text block with a margin to space following content onto a following line
I'm still thinking about this one.
How do you calculate the right width for a line-breaking block to force following content onto the following line? Too short and the following content will still be on the same line, at the right. Too long and the "linebreak" will be on the following line, with content after it. You would have to resize the breaks when the control is resized.
Some of the code for this is:
TextBlock lineBreak = new TextBlock();
lineBreak.TextWrapping = TextWrapping.Wrap;
lineBreak.Text = " ";
// need adaptive width
lineBreak.Margin = new Thickness(0, 0, 200, 0);