Thursday, January 22, 2009

Simulating internal links in a Silverlight ScrollViewer

One of the unsung heroes of HTML is the ability to use internal links within a document for navigation within a page.

In HTML, this is achieved by placing name tags (e.g. <A Name=”top>…</A>) as invisible placeholders throughout the document, and then referencing them from internal hyperlinks (e.g. <A HREF=”#target”>Back to top</a>) wherever you want the navigation to appear.

In Silverlight, it’s common to use a ScrollViewer control to provide a scrolling viewport over some other set of controls, but there’s no analogue to the internal hyperlink to allow us to easily scroll that viewport. It’s easy enough to use HyperlinkButton controls to provide the navigation, but how to scroll the ScrollViewer?

My solution is a simple one – provide an extension method that commands a ScrollViewer to position itself so that a specific control is visible. The click event handler for the HyperlinkButton can then easily simulate internal links and position the ScrollViewer.

 

In the following Xaml source snippet I’ve used HeaderedItemsControls to contain my sections:

 

<!-- Navigation -->

<StackPanel Orientation="Horizontal" >

<TextBlock Text="Jump to" />

<HyperlinkButton x:Name="Section1Link" Content="First Section" Click=" Section1Link _Click" />

<HyperlinkButton x:Name="Section2Link" Content="Second Section" Click="Section2Link_Click" />

      …

</StackPanel>

 

<!-- Viewport -->

<ScrollViewer x:Name="Scroller”>

<StackPanel>

<HeaderedItemsControl x:Name="Section1">

</HeaderedItemsControl>

<HeaderedItemsControl x:Name="Section2">

</HeaderedItemsControl>

            …

</StackPanel>

</ScrollViewer>

 

By making sure the “target” HeaderedItemsControls have names, the click method for the HyperlinkButton is as simple as you could wish for:

 

/// <summary>

/// Handles the Click event of the NamesSectionLink control.

/// </summary>

/// <param name="sender">The source of the event.</param>

/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>

private void Section1Link_Click(object sender, RoutedEventArgs e)

{

DetailsScroller.PositionOnControl(Section1);

}

 

And finally, the extension method itself.

 

/// <summary>

/// Extension methods for ScrollViewer controls

/// </summary>

public static class ScrollViewerExtensions

{

/// <summary>

/// Extension method that positions the scroll viewer to show the specified control

/// </summary>

/// <param name="scrollViewer">The scroll viewer.</param>

/// <param name="targetControl">The target control.</param>

public static void PositionOnControl(this ScrollViewer scrollViewer, UIElement targetControl)

{

var transform = targetControl.TransformToVisual(scrollViewer);

            var offset = transform.Transform(new Point(0, 0));

            scrollViewer.ScrollToVerticalOffset(offset.Y);

}       
}

 

The only magic is in the transformation used to convert the origin coordinates of the target into a scroll offset for the ScrollViewer.

Enjoy!

No comments: