Silverlight TextBlock Text Attribute vs Content - silverlight

When would I use the Text attribute of the <TextBlock> and when should I put my text in the content of the <TextBlock>?
<TextBlock Text="Example Text" />
vs.
<TextBlock>Example Text</TextBlock>

The former can be bound, whilst the latter is particularly useful when combining Runs:
<TextBlock Text="{Binding SomeProperty}"/>
<TextBlock>
<Run>You have </Run>
<Run Text="{Binding Count}"/>
<Run>items.</Run>
</TextBlock>

The use of the Text property has become common as a result of previous versions of the Xaml parser but the placing the text as content is more natural especially if you have a background in HTML.
The fact the many TextBlocks either have simple short chunks of literal text in or are bound. Would tip the balance IMO to using the Text property. In addition any globalisation that may come along latter may end with those literals being replaced by bindings as well.

Related

Set/change text weight within the Text property of a TextBlock

I would like to be able to change the weight of text (e.g. change from Normal to Bold and back again) within the Text property string of a TextBlock (presumably using some control character set). Is this even possible?
TextBLock.Text creates a single Run, you set custom Inlines instead:
<TextBlock>
Text with <Bold>bold</Bold> within.
<TextBlock>
Obviously it no longer uses the Text property.
Are you talking about something like this?
<TextBlock>
<Run Text="Hey it's Normal Text"/>
<Run Text="Hey it's Bold Text" FontWeight="Bold"/>
<Run Text="Hey it's Colored Text" Foreground="Green"/>
</TextBlock>

Is it possible to conditionally display some text using the StringFormat attribute?

I have a nullable field in the database, called Generation. It specifies things like "Jr.", "II" and so on. I want a mean of conditionally specifying the generation of the client, if it isn't null, otherwise not displaying it at all. I thought that the following would work:
<TextBlock Text="{Binding LastName}" />
<TextBlock Text="{Binding Generation, StringFormat= {0}}" />
<TextBlock Text=", " />
However I'm getting an error saying that "0 is not supported in a Windows Presentation Foundation (WPF) project". The field value Generation is a varchar field. Can I do what I want with the StringFormat attribute of the TextBlock class, or do I need to use a converter?
You need to write this as:
<TextBlock Text="{Binding Generation, StringFormat={}{0}}" />
This is due to the nature of using the Markup Extension, which gives special meaning to {}. By adding this at the beginning (when you have no text before your first format specifier), it has the effect of "escaping" the string format specification for you, similar to how # is used to handle string literals in C#.

How can I make a formatted TextBlock width data binding localizable?

In my WPF application, I would like to display something that looks like this:
User Bob has logged off at 22:17.
Where "Bob" and "22:17" are data-bound values.
The obvious way to do this would be to use a StackPanel with multiple TextBlock children, some of them data bound:
<StackPanel Orientation="Horizontal">
<TextBlock Text="The user"/>
<TextBlock Text="{Binding Path=Username}" TextBlock.FontWeight="Bold" />
<TextBlock Text="has logged off at"/>
<TextBlock Text="{Binding Path=LogoffTime}" TextBlock.FontWeight="Bold" />
</StackPanel/>
This works, but it's ugly. The program is supposed to be localized to different languages, and having separate strings for "The user" and "has logged off at" is a recipie for localization disaster.
Ideally, I would like to do something like this:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}The user <Bold>{0}</Bold> has logged off at <Bold>{1}</Bold>">
<Binding Path="Username" />
<Binding Path="LogoffTime" />
</MultiBinding>
</TextBlock>
So the translator would see a complete sentence The user <Bold>{0}</Bold> has logged off at <Bold>{1}</Bold>. But that doesn't work, of course.
This has to be a common problem, what's the right solution for this?
The issue I see is that you want a single String yet with a different UI presence across the String.
One option could be to dismiss the need for bold and simply place the single String within the Resources.resx file. That String will then be referenced in the property which the TextBlock is bound to, returning the values as needed; {0} and {1} where applicable.
Another option could be to returning a set of Run values to be held within the TextBlock. You can not bind to a Run out of the box in 3.5 however I believe you can in 4.
<TextBlock>
<Run Text="The user "/><Run FontWeight="Bold" Text="{Binding User}"/><Run Text="has logged at "/><Run FontWeight="Bold" Text="{Binding LogoffTime}"/>
</TextBlock>
The last option can be found here and involves creating a more dynamic approach to the Run concept, allowing you to bind your values and then tie them back to a String.
I've never tried to do something like this before, but if I had to I would probably try and use a Converter that takes the MultiBinding and breaks it up and returns a StackPanel of the pieces
For example, the binding would be something like:
<Label>
<Label.Content>
<MultiBinding Converter={StaticResource TextWithBoldParametersConverter}>
<Binding Source="The user {0} has logged off at {1}" />
<Binding Path="Username" />
<Binding Path="LogoffTime" />
</MultiBinding>
</Label.Content>
</Label>
And the Converter would do something like
public class TextWithBoldParametersConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Create a StackPanel to hold the content
// Set StackPanel's Orientation to Horizontal
// Take values[0] and split it by the {X} tags
// Go through array of values parts and create a TextBlock object for each part
// If the part is an {X} piece, use values[X+1] for Text and make TextBlock bold
// Add TextBlock to StackPanel
// return StackPanel
}
}
There's unlikely to be a single solution to this problem because there are so many different ways it can manifest itself, and there are so many different ways to solve it.
If you're Microsoft, the solution is to put all of your templates in a resource dictionary, create a project that can use Expression Blend to present them, and then have your translators work with Blend (and probably someone who can help them use it) to translate the text in the resource dictionary, reordering the elements in the template where there are idiomatic differences in word order. This is of course the most expensive solution, but it has the advantage of getting formatting problems (like someone forgot that French text occupies about 20% more space than English text) solved at the same time that the UI is being translated. It also has the advantage that it handles every presentation of text in the UI, not just the presentations that are created by stacking together text blocks.
If you're really only going to need to fix stacks of text blocks, you can create a simple XML representation of marked-up text, and use XSLT to create your XAML from a file in that format. For instance, something like:
<div id="LogoutTime" class="StackPanel">
The user
<strong><span class="User">Bob</span><strong>
logged out at
<strong><span class="Time">22:17</span></strong>
.
</div>
By an amazing coincidence, that markup format is one that can also be viewed in a web browser, so it can be edited with a really wide range of tools and proofread without using, say, Expression Blend. And it's relatively straightforward to translate back into XAML:
<xsl:template match="div[#class='StackPanel']">
<DataTemplate x:Key="{#id}">
<StackPanel Orientation="Horizontal">
<xsl:apply-templates select="node()"/>
</StackPanel>
</DataTemplate>
</xsl:template>
<xsl:template match="div/text()">
<TextBlock Text="{.}"/>
</xsl:template>
<xsl:template match="strong/span">
<TextBlock FontWeight="Bold">
<xsl:attribute name="Text">
<xsl:text>{Binding </xsl:text>
<xsl:value-of select="#class"/>
<xsl:text>}</xsl:text>
</xsl:attribute>
</TextBlock>
</xsl:template>

Superscript / subscript in hyperlink in WPF

I'm trying to make a Hyperlink that contains text with super- and/or subscripts. I've found two ways to do this, and both of them suck.
Solution #1: use Typography.Variants. This gives a terrific superscript... for some fonts.
<StackPanel>
<TextBlock FontFamily="Palatino Linotype" FontSize="30">
<Hyperlink>R<Run Typography.Variants="Superscript">2</Run></Hyperlink>
(Palatino Linotype)
</TextBlock>
<TextBlock FontFamily="Segoe UI" FontSize="30">
<Hyperlink>R<Run Typography.Variants="Superscript">2</Run></Hyperlink>
(Segoe UI)
</TextBlock>
</StackPanel>
(source: excastle.com)
Looks beautiful in Palatino Linotype; but for fonts that don't support variants, it's simply ignored, no emulation is done, and the text is full-size, at-baseline, 100% normal. I would prefer to allow my end-users to select the font they want to use, and still have super/subscripts work.
Solution #2: use BaselineAlignment. This raises or lowers the text appropriately, though unlike solution #1, I have to decrease the font size manually. Still, it's effective for all fonts. The problem is the Hyperlink's underline.
<TextBlock FontSize="30" FontFamily="Palatino Linotype">
<Hyperlink>
R<Run BaselineAlignment="Superscript" FontSize="12pt">2</Run>
</Hyperlink>
</TextBlock>
The underline is raised and lowered along with the text, which looks pretty wretched. I'd rather have a continuous, unbroken underline under the whole Hyperlink. (And before anyone suggests a Border, I'd also like the Hyperlink to be able to word-wrap, with all of the words underlined, including the first row.)
Is there any way to make superscript and subscript work in WPF, in any font, without looking laughably bad when underlined?
If the hyperlink isn't going to wrap to more than one line, then embedding another TextBlock can work:
<TextBlock FontSize="30" FontFamily="Palatino Linotype">
<Hyperlink>
<TextBlock>
R<Run BaselineAlignment="Superscript" FontSize="12pt">2</Run>
</TextBlock>
</Hyperlink>
</TextBlock>
This will give a solid hyperlink under the Hyperlink's child, which means an unbroken hyperlink:
However, if the embedded TextBlock needs to wrap to multiple lines, you'll only get one underline under the entire wrapped paragraph, rather than underlining each line of text:
(source: excastle.com)
If you can put a TextBlock only around a short bit of content that needs superscripts -- e.g., around just the R^2 in the above example -- and leave the rest of the text parented to the hyperlink, then you get underlining as normal. But sometimes that's not practical, so it's something to watch out for.
You can use the superscript unicode characters (e.g. http://www.fileformat.info/info/unicode/char/b2/index.htm)
Like this:
<TextBlock FontSize="30" FontFamily="Segoe UI">
<Hyperlink>
Apply R² Calculation
</Hyperlink>
</TextBlock>
Result:
Obviously this will not work unless what you are super scripting actually has a unicode superscript character.

WPF: How to embed button inside text flow (wrap text around button)?

I'd like an advice to the following problem: I want to embed a Button into a text flow, but when I embed a Button and Label (or TextBlock) into the WrapPanel, I get the first figure:
alt text http://sklad.tomaskafka.com/files/wpf-wrappanel-problem.png
I think that one of solutions could be FlowDocument, but I feel that this is far too heavy for a control simple like this (which could be used in several hundred instances). Do you have some other ideas about how to implement this? Thank you!
EDIT:
One solution could be the following (I didn't know it was possible to put more stuff into TextBlock), but I would lose the ability to bind (which I need):
<TextBlock TextWrapping="Wrap">
<Span>
<Button x:Name="MyButton" Command="{Binding Path=MyCommand}" Content="+" />
<Run x:Name="MyLabel" Text="{Binding Path=Subject}" />
<!--
Problem: binding makes following error:
A 'Binding' cannot be set on the 'Text' property of type 'Run'.
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
-->
</Span>
</TextBlock>
To bind to Run.Text, checkout the BindableRun class by Fortes. Simple to implement, I use it all over my projects.
I found that implementing BindableRun correctly is pretty tricky - and almost all other available implementations will cause an exception from wpf layouting engine when the bound content changes from null to something non-null - see this problem, keyword "Collection was modified; enumeration operation may not execute."
Corrrect implementation from Microsoft is here - it shows how tricky this really is.
Solution: BindableRun class + the following markup:
<TextBlock>
<Button x:Name="MyButton" Command="{Binding Path=MyCommand}" Content="+" />
<common:BindableRun x:Name="Subject" BindableText="{Binding Path=Subject}"/>
</TextBlock>
Funny thing it works on the designer of a UserControl...
In that case, using the Property Change of your control to set the value to the Run is enough. I mean, if you had something like:
<TextBlock>
<Run Text="{Binding ElementName=thisCtrl, Path=Description}" />
</TextBlock>
Then just name the run, and on your property change handler of your UserControl DependencyProperty get/set the value.

Resources