One of the fresh new add-ons for the official Q3 2011 release of RadControls for Windows Phone is RadSlideView. The control provides smooth and slick user experience allowing for navigating a sequence of content items slide-by-slide. The control is also fully aware of orientation changes and the items are resized and re-arranged accordingly to the current orientation.

In this post I want to demonstrate how easy it is to build a simple picture-browser application based on our RadDataBoundListBox and RadSlideView controls.

 

Download Demo

What’s more interesting in the demo:

Simulating Wrap Layout with RadDataBoundListBox
Building a custom control to be used for image holder
Using RadSlideView with SlideViewPictureSource 

App architecture

It is a very simple application with two pages – the main one that displays the list box with all the picture thumbnails. The second page contains a slide view instance and it is navigated to when a thumbnail image is tapped. Additional Tilt effect is used on each thumbnail to emphasize the user action. Embedded images are used on purpose, so that the application does not use your internet connection to download data. Still, the demo will work with any absolute or relative Uri. 

Building the Wrap Layout

Currently our list box supports stack layout only. Our plans are to extend the control with a virtualized wrap layout that will have the same performance as the stack one and will allow you implement scenarios (like the one in this blog) for your apps. Although this feature is not yet available, we can easily simulate a wrap layout using stack layout with three inner images in each listbox item.

Here is how our view model looks like:

public class WrapViewModel : BaseModel
{
    private Uri image1;
    private Uri image2;
    private Uri image3;
 
    public int ItemIndex
    {
        get;
        set;
    }
 
    public Uri Image1
    {
        get
        {
            return this.image1;
        }
        set
        {
            this.image1 = value;
            this.OnPropertyChanged("Image1");
        }
    }
 
    public Uri Image2
    {
        get
        {
            return this.image2;
        }
        set
        {
            this.image2 = value;
            this.OnPropertyChanged("Image2");
        }
    }
 
    public Uri Image3
    {
        get
        {
            return this.image3;
        }
        set
        {
            this.image3 = value;
            this.OnPropertyChanged("Image3");
        }
    }
}

We have three Uri properties per item and each property will be bound to an Image control. The item index property is used to tell the index of this item in the owning source collection. This index will be used to determine the index of the contained images. For example:

int image1Index = this.ItemIndex * 3;
int image2Index = this.ItemIndex * 3 + 1;

Now that we have the view model we need to setup the item template in the listbox. It will consist of a grid layout with three columns and three custom ImageLoaderControl instances within each column.

Creating ImageLoaderControl

The control’s idea is to have two visual states – Loading and Loaded, indicating that an image is about to be displayed. Here is the control’s XAML:

<Style TargetType="local:ImageLoaderControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:ImageLoaderControl">
                <Grid Margin="6">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Loading">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="loadingContent">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="image">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Loaded">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="loadingContent">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="image">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                         
                    <Border Background="{StaticResource PhoneChromeBrush}" x:Name="loadingContent" Visibility="Collapsed">
                        <TextBlock Text="Loading..." HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Border>
                    <Image Source="{TemplateBinding Source}" Stretch="UniformToFill" x:Name="image" Visibility="Collapsed"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The “Loading” state sets the “Loading…” text block’s visibility to Visible and the “Loaded” one hides the text block and displays the actual image. Of course this is a very simple scenario and you may implement more complex and visually appealing states to meet the requirements of you application.

And the code behind looks like: 

public class ImageLoaderControl : Control
{
    public static readonly DependencyProperty SourceProperty =
        DependencyProperty.Register("Source", typeof(ImageSource), typeof(ImageLoaderControl), new PropertyMetadata(null, OnSourceChanged));
 
    public static readonly DependencyProperty RowIndexProperty =
        DependencyProperty.Register("RowIndex", typeof(int), typeof(ImageLoaderControl), new PropertyMetadata(-1));
 
    public static readonly DependencyProperty ColumnIndexProperty =
        DependencyProperty.Register("Column", typeof(int), typeof(ImageLoaderControl), new PropertyMetadata(-1));
 
    private Image image;
    private Border loadingContent;
 
    public ImageLoaderControl()
    {
        this.DefaultStyleKey = typeof(ImageLoaderControl);
 
        InteractionEffectManager.AllowedTypes.Add(typeof(ImageLoaderControl));
    }
 
    public int RowIndex
    {
        get
        {
            return (int)this.GetValue(RowIndexProperty);
        }
        set
        {
            this.SetValue(RowIndexProperty, value);
        }
    }
 
    public int ColumnIndex
    {
        get
        {
            return (int)this.GetValue(ColumnIndexProperty);
        }
        set
        {
            this.SetValue(ColumnIndexProperty, value);
        }
    }
 
    public ImageSource Source
    {
        get
        {
            return this.GetValue(SourceProperty) as ImageSource;
        }
        set
        {
            this.SetValue(SourceProperty, value);
        }
    }
 
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
 
        this.image = this.GetTemplateChild("image") as Image;
        this.loadingContent = this.GetTemplateChild("loadingContent") as Border;
        VisualStateManager.GoToState(this, "Loading", false);
    }
 
    private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ImageLoaderControl control = d as ImageLoaderControl;
        VisualStateManager.GoToState(control, "Loaded", false);
    }
}

We have a Source property, used to specify the source of the inner Image instance, a RowIndex property to tell the index of the container RadDataBoundListBoxItem and a ColumnIndex to specify the column (0, 1, 2) the control is located at. Upon a change in the Source property we enter the “Loaded” visual state, which hides the text block and displays the image. You may perform more complex logic here to determine when the “Loaded” state actually occurs by handling the ImageOpened event of the image.

The XAML of the main page is quite simple – it uses the image loader to setup its item itemplate:

<Grid x:Name="LayoutRoot" Background="Transparent" Margin="18,0,0,0" telerikCore:InteractionEffectManager.IsInteractionEnabled="True">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Text="images" FontSize="{StaticResource PhoneFontSizeExtraExtraLarge}" Margin="12,24,0,48"/>
        <telerikPrimitives:RadDataBoundListBox x:Name="listBox" ItemsSource="{Binding Items}" Grid.Row="1" ItemTap="OnImageTap">
            <telerikPrimitives:RadDataBoundListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="148"/>
                            <ColumnDefinition Width="148"/>
                            <ColumnDefinition Width="148"/>
                        </Grid.ColumnDefinitions>
                        <StackPanel>
                            <local:ImageLoaderControl Source="{Binding Image1}" RowIndex="{Binding ItemIndex}" ColumnIndex="0" Height="148"/>
                            <TextBlock Text="{Binding Image1Description}" Style="{StaticResource itemText}"/>
                        </StackPanel>
                        <StackPanel Grid.Column="1">
                            <local:ImageLoaderControl Source="{Binding Image2}" RowIndex="{Binding ItemIndex}" ColumnIndex="1" Height="148"/>
                            <TextBlock Text="{Binding Image2Description}" Style="{StaticResource itemText}"/>
                        </StackPanel>
                        <StackPanel Grid.Column="2">
                            <local:ImageLoaderControl Source="{Binding Image3}" RowIndex="{Binding ItemIndex}" ColumnIndex="2" Height="148"/>
                            <TextBlock Text="{Binding Image3Description}" Style="{StaticResource itemText}"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </telerikPrimitives:RadDataBoundListBox.ItemTemplate>
        </telerikPrimitives:RadDataBoundListBox>
    </Grid>

Now we have the listbox with a simulate wrap layout and the fancy ImageLoaderControl to represent each image. Let’s see how to navigate to the slide view page upon a tap over an image:

private void OnImageTap(object sender, Telerik.Windows.Controls.ListBoxItemTapEventArgs e)
{
    Image tappedImage = e.OriginalSource as Image;
    if (tappedImage == null)
    {
        return;
    }
 
    ImageLoaderControl ownerControl = ElementTreeHelper.FindVisualAncestor<ImageLoaderControl>(tappedImage);
    if (ownerControl != null)
    {
        MainViewModel.Instance.NavigatoToImage(ownerControl.RowIndex, ownerControl.ColumnIndex);
    }
}
 
public void NavigatoToImage(int rowIndex, int colIndex)
{
    int imageIndex = rowIndex * 3 + colIndex;
    (Application.Current.RootVisual as PhoneApplicationFrame).Navigate(new Uri("/SlideViewPage.xaml?index=" + imageIndex, UriKind.RelativeOrAbsolute));
}

We handle the ItemTap event of the listbox, find the owning ImageLoader control and navigate to the slide view page passing the currently selected index as a query string. And finally let’s see how the slide view page is setup.

Setting up the Slide View

The control’s usage is simple and straightforward:

<Grid x:Name="LayoutRoot" Background="Transparent">
    <telerikPrimitives:RadSlideView x:Name="slideView" Margin="-6">
        <telerikPrimitives:RadSlideView.DataSource>
            <telerikPrimitives:SlideViewPictureSource ItemsSource="{Binding Images}"/>
        </telerikPrimitives:RadSlideView.DataSource>
    </telerikPrimitives:RadSlideView>
</Grid>

We specify the special SlideViewPictureSource as its DataSource. The data source itself will provide a default item template, so no need to specify our own one except we want a more complex one rather than a simple Image. We bind the data source to the Images property of the main view model, which is actually a collection with the URIs of all the loaded images. Currently the picture data source does not support an ImageSource instance directly but it will when the control exits its BETA state and you will have even better support for images. We also have plans to add an arbitrary option to load images directly from the IsolatedStorage, based on specified folder(s).

And the page’s code behind:

public partial class SlideViewPage : PhoneApplicationPage
{
    public SlideViewPage()
    {
        InitializeComponent();
 
        // update selected index after layout is updated
        // this is a bug in the BETA of RadSlideView
        this.LayoutUpdated += this.SlideViewPage_LayoutUpdated;
    }
 
    void SlideViewPage_LayoutUpdated(object sender, EventArgs e)
    {
        this.LayoutUpdated -= this.SlideViewPage_LayoutUpdated;
        this.slideView.SelectedIndex = Int32.Parse(this.NavigationContext.QueryString["index"]);
    }
}

Due to a bug that is currently present, the SelectedIndex property cannot be set before the control is added to the visual scene and its containers are created. That is why we handle the LayoutUpdated event of the page and specify the currently selected index of the control. The index is passed as a parameter in the page’s query string. Once the Service Pack of Q3 is out you will be able to bind the selected index to a property of the main view model.

Well, these are most of the more interesting parts of the demo app. I really hope that you will find it useful and learn some new tips and tricks from it. Do not hesitate to let me know for any suggestions or questions you may have.

Download Demo


About the Author

Georgi Atanasov


Related Posts

Comments

Comments are disabled in preview mode.