The blog of dlaa.me

LB-SV-WHY [Three wacky uses for Silverlight 2's ListBox and ScrollViewer!]

In my Silverlight 2 ListBox/ScrollViewer FAQ I described some common scenarios and demonstrated how to implement them with sample XAML/code. In this post I demonstrate three UNcommon ListBox/ScrollViewer scenarios - again showing the XAML/code that makes them possible.

Because sometimes the power of a thing only becomes clear when it's being abused. :)

 

ListBox: Planets

When I saw Beatriz Costa's "PlanetsListBox" WPF demo of a fully functioning ListBox that looked like the solar system, I was stunned and knew I had to port it to Silverlight.

Planets shown smaller than actual size

So I downloaded the WPF project, created a new Silverlight application, copied over the implementations of the classes SolarSystem, SolarSystemObject, and ConvertOrbit, and went to work migrating the XAML. With one small exception, the code had no changes for Silverlight. As you'd expect, the XAML got a variety of minor tweaks because Silverlight is a subset of WPF and I was moving in the "wrong" direction. In particular, I made the following changes to the original implementation:

  • Switched to inline styles because Silverlight does not support implicit styles
  • Switched from a Trigger-based selection indicator (the yellow circle) to one based on the Silverlight parts model
  • Manually created Canvas bindings in PrepareContainerForItemOverride because Silverlight wasn't creating the attached property bindings in XAML
  • Manually created ToolTip contents/bindings in a custom IValueConverter because bindings in the ToolTip XAML were unexpectedly seeing a null DataContext
  • Changed the type of SolarSystemObject.Image from Uri to string and changed the image resource paths so that binding to Silverlight's Image.Source worked
  • Switched to Canvas.Top because Silverlight does not support Canvas.Bottom
  • Converted all images to JPEG format because GIF is not supported by Silverlight

 

ListBox: Slideshow

One day I was working on a perfectly sensible ListBox sample with images when my teammate Ted suggested making a simple Slideshow with ListBox. It's not the craziest idea he's ever had, so I decided to humor him.

It's not much of a list anymore...

The basic idea is simple: Populate the ListBox with images and then size it so only one of them shows at a time. You can use the dedicated Previous/Next Buttons to change slides - or click on the ListBox and press the usual keys (arrow Up/Down, Home/End, Page Up/Down). Because the Button states are updated in the SelectionChanged handler, they're kept in sync. Here's the complete implementation:

<UserControl x:Class="ListBoxSlideShow.Page"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
    <Grid HorizontalAlignment="Center" VerticalAlignment="Center">

        <!-- Column/row definitions -->
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- ListBox slide display -->
        <ListBox
            x:Name="Display" Grid.ColumnSpan="2" Width="484" Height="364"
            controls:ScrollViewer.HorizontalScrollBarVisibility="Hidden"
            controls:ScrollViewer.VerticalScrollBarVisibility="Hidden">

            <!-- Style to size all images to desired size -->
            <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <Setter Property="Width" Value="480"/>
                    <Setter Property="Height" Value="360"/>
                </Style>
            </ListBox.ItemContainerStyle>

            <!-- Slide images -->
            <!-- Note: Sample images are from C:\Windows\Web\Wallpaper\*
                       Update that path if necessary for your machine -->
            <Image Source="img10.jpg"/>
            <Image Source="img7.jpg"/>
            <Image Source="img8.jpg"/>
            <Image Source="img9.jpg"/>
        </ListBox>

        <!-- Previous/Next buttons -->
        <Button x:Name="Previous" Grid.Column="0" Grid.Row="1" Content="Previous"/>
        <Button x:Name="Next" Grid.Column="1" Grid.Row="1" Content="Next"/>
    </Grid>
</UserControl>
using System;
using System.Windows.Controls;

namespace ListBoxSlideShow
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();

            // Hook button event handlers to change slides
            Previous.Click += delegate { ChangeSlide(Display.SelectedIndex - 1); };
            Next.Click += delegate { ChangeSlide(Display.SelectedIndex + 1); };

            // Update button states if necessary
            Display.SelectionChanged += delegate
            {
                Previous.IsEnabled = (0 < Display.SelectedIndex);
                Next.IsEnabled = (Display.SelectedIndex < Display.Items.Count - 1);
            };

            // Change to first slide
            ChangeSlide(0);
        }

        private void ChangeSlide(int index)
        {
            // Select specified slide and bring it into view
            Display.SelectedIndex = Math.Max(0, Math.Min(Display.Items.Count - 1, index));
            Display.ScrollIntoView(Display.SelectedItem);
        }
    }
}

 

ScrollViewer: Marquee

On a discussion list one day, someone innocently asked for a way to scroll text. I immediately thought: <MARQUEE>!

Welcome back to Web 1.0!

Again, the idea is simple: Put arbitrary content inside a ScrollViewer, hide its ScrollBars, and adjust its HorizontalOffset on a timer. I chose to have the content bounce at each end, but you could just as easily snap it back to the beginning. Here's the complete implementation:

<UserControl x:Class="ScrollViewerMarquee.Page"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>

        <!-- Marquee-like ScrollViewer -->
        <ScrollViewer x:Name="Marquee" Width="200"
                      VerticalAlignment="Center"
                      HorizontalScrollBarVisibility="Hidden"
                      VerticalScrollBarVisibility="Disabled">

            <!-- Arbitrary nested content -->
            <TextBlock Text=" This message bounces! "
                       FontFamily="Arial Black" FontSize="40"/>
        </ScrollViewer>
    </Grid>
</UserControl>
using System;
using System.Windows.Controls;
using System.Windows.Threading;

namespace ScrollViewerMarquee
{
    public partial class Page : UserControl
    {
        // Timer and direction variables
        private DispatcherTimer _timer;
        private int _delta = 1;

        public Page()
        {
            InitializeComponent();

            // Create timer to tick every 10ms
            _timer = new DispatcherTimer
            {
                Interval = TimeSpan.FromMilliseconds(10)
            };

            // Attach a Tick handler to do the scrolling
            _timer.Tick += delegate
            {
                // Adjust the horizontal offset
                Marquee.ScrollToHorizontalOffset(Marquee.HorizontalOffset + _delta);

                // Flip direction if necessary
                if ((0 < Marquee.ViewportWidth) &&  // (NOOP if not visible yet)
                    ((Marquee.ScrollableWidth <= Marquee.HorizontalOffset) ||
                     (Marquee.HorizontalOffset <= 0)))
                {
                    _delta = -_delta;
                }
            };

            // Start the timer
            _timer.Start();
        }
    }
}

 

I've made the complete source code I used to build these samples available as an attachment to this post for anyone to play around with. If you come up with your own wacky use for ListBox or ScrollViewer, please let me know. It's always interesting to see the clever, creative ways people end up using stuff like this!

[LB-SV-WHY.zip]