XNA: Sophisticated primitives The XML content importer and Windows Phone 7

WP7 snippet: analyzing the hyperlink button style

Published on Monday, December 27, 2010 7:48:00 AM UTC in Programming

Today someone in the Silverlight Forums had the problem that the default style for the hyperlink button in Windows Phone 7 does not wrap the link text. Used to the hyperlink button of Silverlight 4, he tried something like this:

<HyperlinkButton>
    <HyperlinkButton.Content>
        <TextBlock Text="Testing text wrapping"
                   TextWrapping="Wrap" />
    </HyperlinkButton.Content>
</HyperlinkButton>

In the desktop version of Silverlight, this is totally valid. On the phone however, you simply won't see anything (but don't receive any errors either). Why is that?

Optimized for performance

On Windows Phone 7, some details are wired differently. Often the default templates for controls are heavily simplified due to the lower performance provided on the devices. I recall a statement from someone official (unfortunately from memory as I don't remember where it was) that sacrifices have been made especially in those situations where we have a most prevalent use of certain controls. In this case, the common scenario for a hyperlink is to show a text link, so consequently the default template on WP7 is tailored to text use. That means that those 95% of the developers for Windows Phone 7 that use hyperlinks with text content benefit from an increased performance. Those however who want to use different content or change the default behavior have to go the extra mile, as the flexibility of the desktop version is missing. Fortunately, it's not that hard to put that back in.

A text-wrapping template

The easiest way to edit the default templates still is to use Expression Blend to make a copy of it and work from there. When you do that you'll see that the template really only has a text block to show the content. That is why using another UI element as content doesn't work for the hyperlink button on WP7. If you only want to make the text wrap, it's sufficient to change the TextWrapping property on that text block:

<Style x:Key="HyperlinkButtonWrappingStyle"
        TargetType="HyperlinkButton">
    <Setter Property="Foreground"
            Value="{StaticResource PhoneForegroundBrush}" />
    <Setter Property="Background"
            Value="Transparent" />
    <Setter Property="FontSize"
            Value="{StaticResource PhoneFontSizeMedium}" />
    <Setter Property="Padding"
            Value="0" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="HyperlinkButton">
                <Border Background="Transparent">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="MouseOver" />
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <DoubleAnimation Duration="0"
                                                        To="0.5"
                                                        Storyboard.TargetProperty="Opacity"
                                                        Storyboard.TargetName="TextElement" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                    Storyboard.TargetName="TextElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{StaticResource PhoneDisabledBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border Background="{TemplateBinding Background}"
                            Margin="{StaticResource PhoneHorizontalMargin}"
                            Padding="{TemplateBinding Padding}">
                        <TextBlock x:Name="TextElement"
                                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                    Text="{TemplateBinding Content}"
                                    TextDecorations="Underline"
                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                    TextWrapping="Wrap" />
                    </Border>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

It wraps!

image

Arbitrary content

But what if you really want to use a UI element as content of the hyperlink button? Then you have to edit the template to use a content presenter instead of the text block. Here is a style you can use as a basis for that. It uses a content presenter that binds some properties using template binding, including the content. It behaves like the normal hyperlink button in that the opacity of the content is altered when the button is pressed to give the user some visual feedback. In addition, I've added a disabled overlay that uses the PhoneDisabledBrush, a system resource brush on the phone devices.

<Style x:Key="HyperlinkButtonContentStyle"
        TargetType="HyperlinkButton">
    <Setter Property="Foreground"
            Value="{StaticResource PhoneForegroundBrush}" />
    <Setter Property="Background"
            Value="Transparent" />
    <Setter Property="FontSize"
            Value="{StaticResource PhoneFontSizeMedium}" />
    <Setter Property="Padding"
            Value="0" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="HyperlinkButton">
                <Border Background="Transparent">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="MouseOver" />
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <DoubleAnimation Duration="0"
                                                        To="0.5"
                                                        Storyboard.TargetProperty="Opacity"
                                                        Storyboard.TargetName="ContentElement" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill"
                                                                    Storyboard.TargetName="DisabledElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{StaticResource PhoneDisabledBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border Background="{TemplateBinding Background}"
                            Margin="{StaticResource PhoneHorizontalMargin}"
                            Padding="{TemplateBinding Padding}">
                        <Grid Height="31"
                                Margin="0">
                            <ContentPresenter x:Name="ContentElement"
                                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                Content="{TemplateBinding Content}"
                                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                            <Rectangle x:Name="DisabledElement" />
                        </Grid>
                    </Border>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This style can be used as follows in your code:

<StackPanel Orientation="Horizontal">
    <TextBlock Text="A rectangle hyperlink:" />
    <HyperlinkButton Style="{StaticResource HyperlinkButtonContentStyle}">
        <Rectangle Width="20"
                    Height="20"
                    Fill="Red" />
    </HyperlinkButton>
</StackPanel>

There you go. A hyperlink button on Windows Phone 7 that uses non-text content:

image

Tags: Silverlight · Windows Phone 7