Mister Goodcat

Peter's home of all things life

Monday, 12/27/2010 8:48 AM
by Peter Kuhn
5 Comments

WP7 snippet: analyzing the hyperlink button style

Monday, 12/27/2010 8:48 AM by Peter Kuhn | 5 Comments

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

Comments (5) -

Hey, in addition to performance benefits, forcing text as the content was the only way to get the text to appear correctly (and as you may know, text is important for the Windows Phone visual design). To see what I mean, look closely at the behavior of HyperlinkButton on the desktop; when it turns into the underlined style, the text is doubled and thus appears bolder than the text around it.

Hallo Peter. Thank you for your reply. Yes, I'm aware of that problem. In my desktop projects, for text-only hyperlinks I usually change the control in a way similar to what Peter Brady described here:

peterbrady.net/.../quick-fix-for-silverlight-hyperlinkbutton-rendering

Maybe there would've been a better solution for the desktop version too (actually draw the underline as a line/rectangle?), but I see and understand why that decision has been made. Thanks again.

Hi Mr. Goodcat, the problem with the rectangle approach is that it doesn't scale to different text sizes; the height of the line and its offset from the text are relative to the font size (and for various reasons we could only change the template, not the underlying code for HyperlinkButton so we couldn't put that logic into the control itself).

Thank you for the explanation.

Thank you for posting this!

Pingbacks and trackbacks (2)+

Comments are closed