Sunday, November 17, 2013

WP8 - LongListSelector in Window Phone 8

In this article i will show you how to create Window 8 App Store(Metro) style dynamic Group Image Gallery using LongListSelector Control in Window Phone 8.

LongListSelector control provides a flexible way to display a collection of data in the form of a list or a grid.We can display data as grouped lists in LongListSelector Control. As the performance of LongListSelector is better than that of a ListBox and it also supports full data and UI virtualization.

In this example we display Images with names that are grouped by the first letter of the English name. When we click on a group header, a jump list with all letters of the alphabet appears, which the user can use to navigate to a particular group in the LongListSelector.

Lets create Window 8 Metro style group Image Gallery in Window Phone 8.

Step 1
Create a Window Phone Application and give the solution name as SolLongListSelector_WinPhone8.



Click on Image for better View

Note : Select a 4.5 Framework.

Step 2
Select Window Phone Platform,it is look like this



Click on Image for better View

Step 3
Create a New Folder in Solution and give folder name as Images,In that folder add images,it is look like this



Click on Image for better View

Step 4
Create a XML file in the solution and give the file name as ImageData.XML,it is look like this



Click on Image for better View

Step 5
The XML in the following example defines an XML document with a root node called Images. This root node contains one or more nodes called Image that include elements called English Name,Japanese Name and Image Path.
<?xml version="1.0" encoding="utf-8" ?>
<Images>

  <Image>
    <EnglishName>A</EnglishName>
    <JapaneseName>��</JapaneseName>
    <ImagePath>../Images/A.png</ImagePath>
  </Image>

  <Image>
    <EnglishName>Ao</EnglishName>
    <JapaneseName>�</JapaneseName>
    <ImagePath>../Images/Ao.png</ImagePath>
  </Image>

  <Image>
    <EnglishName>Aoba Yamashiro</EnglishName>
    <JapaneseName>山����</JapaneseName>
    <ImagePath>../Images/Aoba.png</ImagePath>
  </Image>

Continue........


</Images>

Note : Refer XML file from Solution.(Download Source Code and view XML file).

Step 6
Create a Image Entity class in the solution,it is look like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SolLongListSelector_WinPhone8
{
    public class ImageEntity
    {
        #region Property

        public String EnglishName
        {
            get;
            set;
        }

        public String JapaneseName
        {
            get;
            set;
        }

        public String ImagePath
        {
            get;
            set;
        }

        #endregion
    }
}

Step 7
Create a KeyGroup class in the solution,it is look like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Globalization;
using Microsoft.Phone.Globalization;

namespace SolLongListSelector_WinPhone8
{
    public class KeyGroup<T> : List<T>
    {
        /// <summary>
        /// The Key of this group.
        /// </summary>
        public string Key { get; private set; }

        /// <summary>
        /// Public constructor.
        /// </summary>
        /// <param name="Key">The key for this group.</param>
        public KeyGroup(string Key)
        {
            this.Key = Key;
        }

        /// <summary>
        /// Create a list of AlphaGroup<T> with keys set by a SortedLocaleGrouping.
        /// </summary>
        /// <param name="slg">The </param>
        /// <returns>Theitems source for a LongListSelector</returns>
        private static List<KeyGroup<T>> CreateGroups(SortedLocaleGrouping SLGObj)
        {
            try
            {
                List<KeyGroup<T>> ListKGObj = new List<KeyGroup<T>>();

                foreach (string key in SLGObj.GroupDisplayNames)
                {
                    ListKGObj.Add(new KeyGroup<T>(key));
                }

                return ListKGObj;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message); 
            }
        }

        /// <summary>
        /// Create a list of AlphaGroup<T> with keys set by a SortedLocaleGrouping.
        /// </summary>
        /// <param name="items">The items to place in the groups.</param>
        /// <param name="ci">The CultureInfo to group and sort by.</param>
        /// <param name="getKey">A delegate to get the key from an item.</param>
        /// <param name="sort">Will sort the data if true.</param>
        /// <returns>An items source for a LongListSelector</returns>
        public static List<KeyGroup<T>> CreateGroups(IEnumerable<T> Items, CultureInfo CIObj, Func<T,String> GetKey, bool Sort)
        {
            try
            {
                SortedLocaleGrouping SLGObj = new SortedLocaleGrouping(CIObj);

                List<KeyGroup<T>> ListKGObj = CreateGroups(SLGObj);

                foreach (T item in Items)
                {
                    int index = 0;
                    if (SLGObj.SupportsPhonetics)
                    {
                        //check if your database has yomi string for item
                        //if it does not, then do you want to generate Yomi or ask the user for this item.
                        //index = slg.GetGroupIndex(getKey(Yomiof(item)));
                    }
                    else
                    {
                        index = SLGObj.GetGroupIndex(GetKey(item));
                    }
                    if (index >= 0 && index < ListKGObj.Count)
                    {
                        ListKGObj[index].Add(item);
                    }
                }

                if (Sort)
                {
                    foreach (KeyGroup<T> group in ListKGObj)
                    {
                        group.Sort((c0, c1) => { return CIObj.CompareInfo.Compare(GetKey(c0), GetKey(c1)); });
                    }
                }

                return ListKGObj;
            }
            catch (Exception ex) 
            {
                throw new Exception(ex.Message); 
            }
        }

    }
}

KeyGroup is a helper class that is used to convert a flat list of data into a grouped list in which the entries are grouped by a key. In this example we use this class to convert a flat list of Images Data to a list of lists that is grouped by the first letter of the English name.

Step 8
Create an ImageView class in the solution for retrieving Data from XML document,it is look like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;

namespace SolLongListSelector_WinPhone8
{
    public static class ImageView
    {
        #region Method

        /// <summary>
        /// Group Image Data
        /// </summary>
        /// <returns>List</returns>
        public static Task<List<KeyGroup<ImageEntity>>> GetGroupImageData()
        {
            try
            {
                return Task.Run<List<KeyGroup<ImageEntity>>>(() =>
                {
                    // Load XML Document
                    XDocument XDoc = XDocument.Load("ImageData.xml");

                    // Get All data from XML Document
                    var Query = (from Q in XDoc.Descendants("Image")
                                 select new ImageEntity
                                 {
                                     EnglishName = Q.Element("EnglishName").Value,
                                     JapaneseName = Q.Element("JapaneseName").Value,
                                     ImagePath = Q.Element("ImagePath").Value
                                 }).ToList<ImageEntity>();

                    // Grouped Data
                    return KeyGroup<ImageEntity>.CreateGroups(Query, System.Threading.Thread.CurrentThread.CurrentUICulture, (ImageEntity IE) => { return IE.EnglishName; }, true);

                });
            }

            catch (Exception ex)
            {
                throw new Exception(ex.Message); 
            }
        }

        #endregion
    }
}

Step 9
In MainPage.XAML drag and drop a LongListSelector Control from Control Toolbox,it is look like this
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
   <phone:LongListSelector x:Name="LLSImageGallery" LayoutMode="Grid" GridCellSize="220,220"  IsGroupingEnabled="True" HideEmptyGroups="True" HorizontalAlignment="Stretch"  VerticalAlignment="Stretch"  VirtualizingStackPanel.VirtualizationMode="Recycling"/>

   

</Grid>

In this code, LayoutMode is set to Grid, which means that the Images Data are laid out in grid format.GridCellSize Indicates the size of each cell in the grid The IsGroupingEnabled is set to true to group the displayed items, and the HideEmptyGroups is set to true, which means that if a group does not have any items in it, it will not be displayed.The VirtualizingStackPanel.VirtualizationMode Attached Property is set Recycling,Which means the VirtualizingStackPanel reuses item containers instead of creating a new one each time.

Step 10
Add the following code to define a DataTemplate for display Images with Names in phone:PhoneApplicationPage.Resources,it is look like this
<phone:PhoneApplicationPage.Resources>
  <DataTemplate x:Key="LLSItemDataTemplate">
   <Grid>
    
    <Border Background="Transparent" Padding="5">
     <Image Source="{Binding ImagePath}" Stretch="Fill" HorizontalAlignment="Stretch"  VerticalAlignment="Center"></Image>
    </Border>
    
    <StackPanel VerticalAlignment="Bottom" Background="Black" Opacity="0.70" Width="Auto">
     <TextBlock Text="{Binding EnglishName}" Foreground="White" Height="30" FontWeight="SemiBold" HorizontalAlignment="Left" Margin="10,0,0,0"/>
     <TextBlock Text="{Binding JapaneseName}" Foreground="White" Height="30" FontWeight="SemiBold" HorizontalAlignment="Right" Margin="0,0,10,0"/>
    </StackPanel>
   
   </Grid>
  </DataTemplate>

</phone:PhoneApplicationPage.Resources>

Step 11
Add the following code to define DataTemplate for the group header in phone:PhoneApplicationPage.Resources,it is look like this.
<phone:PhoneApplicationPage.Resources>
  <DataTemplate x:Key="LLSHeaderDataTemplate">
   <Grid Background="Transparent">
    
    <Border Background="Red">
     <TextBlock Text="{Binding Key}" Foreground="White" FontSize="30" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Border>
    
   </Grid>
  </DataTemplate>
</phone:PhoneApplicationPage.Resources>

The DataTemplate will display the First Character of English Name(in our case Key Property in KeyGroup Class)

Step 12
Add the following code to define the jump list style in phone:PhoneApplicationPage.Resources.
<phone:PhoneApplicationPage.Resources>
  
  <phone:JumpListItemBackgroundConverter x:Key="BackgroundConverter"/>
  <phone:JumpListItemForegroundConverter x:Key="ForegroundConverter"/>
  <Style x:Key="LLSJumpListStyle" TargetType="phone:LongListSelector">
     <Setter Property="GridCellSize"  Value="113,113"/>
     <Setter Property="LayoutMode" Value="Grid" />
     <Setter Property="ItemTemplate">
     <Setter.Value>
     <DataTemplate>
      
     <Border Background="{Binding Converter={StaticResource BackgroundConverter}}" Width="113" Height="113" Margin="6" >
        <TextBlock Text="{Binding Key}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" FontSize="48" Padding="6" 
        Foreground="{Binding Converter={StaticResource ForegroundConverter}}" VerticalAlignment="Center"   UseLayoutRounding="False"/>
     </Border>
      
     </DataTemplate>
      </Setter.Value>
   </Setter>
  </Style>
  
 </phone:PhoneApplicationPage.Resources>

Finally  phone:PhoneApplicationPage.Resources look like this
<phone:PhoneApplicationPage.Resources>
  <DataTemplate x:Key="LLSItemDataTemplate">
   <Grid>
    
    <Border Background="Transparent" Padding="5">
     <Image Source="{Binding ImagePath}" Stretch="Fill" HorizontalAlignment="Stretch"  VerticalAlignment="Center"></Image>
    </Border>
    
    <StackPanel VerticalAlignment="Bottom" Background="Black" Opacity="0.70" Width="Auto">
     <TextBlock Text="{Binding EnglishName}" Foreground="White" Height="30" FontWeight="SemiBold" HorizontalAlignment="Left" Margin="10,0,0,0"/>
     <TextBlock Text="{Binding JapaneseName}" Foreground="White" Height="30" FontWeight="SemiBold" HorizontalAlignment="Right" Margin="0,0,10,0"/>
    </StackPanel>
   
   </Grid>
  </DataTemplate>
  
  <DataTemplate x:Key="LLSHeaderDataTemplate">
   <Grid Background="Transparent">
    
    <Border Background="Red">
     <TextBlock Text="{Binding Key}" Foreground="White" FontSize="30" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Border>
    
   </Grid>
  </DataTemplate>

  <phone:JumpListItemBackgroundConverter x:Key="BackgroundConverter"/>
  <phone:JumpListItemForegroundConverter x:Key="ForegroundConverter"/>
  <Style x:Key="LLSJumpListStyle" TargetType="phone:LongListSelector">
     <Setter Property="GridCellSize"  Value="113,113"/>
     <Setter Property="LayoutMode" Value="Grid" />
     <Setter Property="ItemTemplate">
     <Setter.Value>
     <DataTemplate>
      
     <Border Background="{Binding Converter={StaticResource BackgroundConverter}}" Width="113" Height="113" Margin="6" >
        <TextBlock Text="{Binding Key}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" FontSize="48" Padding="6" 
        Foreground="{Binding Converter={StaticResource ForegroundConverter}}" VerticalAlignment="Center"   UseLayoutRounding="False"/>
     </Border>
      
     </DataTemplate>
      </Setter.Value>
   </Setter>
  </Style>
  
 </phone:PhoneApplicationPage.Resources>

Step 13
Now apply this above templates and JumpListStyle in LongListSelector Control,it is look like this
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <phone:LongListSelector x:Name="LLSImageGallery" LayoutMode="Grid" GridCellSize="220,220"  IsGroupingEnabled="True" HideEmptyGroups="True"   HorizontalAlignment="Stretch"  VerticalAlignment="Stretch"  VirtualizingStackPanel.VirtualizationMode="Recycling" ItemsSource="{Binding}" ItemTemplate="{StaticResource LLSItemDataTemplate}" GroupHeaderTemplate="{StaticResource LLSHeaderDataTemplate}" JumpListStyle="{StaticResource LLSJumpListStyle}"/>

   

  </Grid>

Full XAML Code
<phone:PhoneApplicationPage
 x:Class="SolLongListSelector_WinPhone8.MainPage"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
 xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 mc:Ignorable="d"
 FontFamily="{StaticResource PhoneFontFamilyNormal}"
 FontSize="{StaticResource PhoneFontSizeNormal}"
 Foreground="{StaticResource PhoneForegroundBrush}"
 SupportedOrientations="Portrait" Orientation="Portrait"
 shell:SystemTray.IsVisible="True">
 
 <phone:PhoneApplicationPage.Resources>
  <DataTemplate x:Key="LLSItemDataTemplate">
   <Grid>
    
    <Border Background="Transparent" Padding="5">
     <Image Source="{Binding ImagePath}" Stretch="Fill" HorizontalAlignment="Stretch"  VerticalAlignment="Center"></Image>
    </Border>
    
    <StackPanel VerticalAlignment="Bottom" Background="Black" Opacity="0.70" Width="Auto">
     <TextBlock Text="{Binding EnglishName}" Foreground="White" Height="30" FontWeight="SemiBold" HorizontalAlignment="Left" Margin="10,0,0,0"/>
     <TextBlock Text="{Binding JapaneseName}" Foreground="White" Height="30" FontWeight="SemiBold" HorizontalAlignment="Right" Margin="0,0,10,0"/>
    </StackPanel>
   
   </Grid>
  </DataTemplate>
  
  <DataTemplate x:Key="LLSHeaderDataTemplate">
   <Grid Background="Transparent">
    
    <Border Background="Red">
     <TextBlock Text="{Binding Key}" Foreground="White" FontSize="30" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Border>
    
   </Grid>
  </DataTemplate>

  <phone:JumpListItemBackgroundConverter x:Key="BackgroundConverter"/>
  <phone:JumpListItemForegroundConverter x:Key="ForegroundConverter"/>
  <Style x:Key="LLSJumpListStyle" TargetType="phone:LongListSelector">
     <Setter Property="GridCellSize"  Value="113,113"/>
     <Setter Property="LayoutMode" Value="Grid" />
     <Setter Property="ItemTemplate">
     <Setter.Value>
     <DataTemplate>
      
     <Border Background="{Binding Converter={StaticResource BackgroundConverter}}" Width="113" Height="113" Margin="6" >
        <TextBlock Text="{Binding Key}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" FontSize="48" Padding="6" 
        Foreground="{Binding Converter={StaticResource ForegroundConverter}}" VerticalAlignment="Center"   UseLayoutRounding="False"/>
     </Border>
      
     </DataTemplate>
      </Setter.Value>
   </Setter>
  </Style>
  
 </phone:PhoneApplicationPage.Resources>

 <!--LayoutRoot is the root grid where all page content is placed-->
 <Grid x:Name="LayoutRoot" Background="Transparent">
  <Grid.RowDefinitions>
   <RowDefinition Height="Auto"/>
   <RowDefinition Height="*"/>
  </Grid.RowDefinitions>

  <!-- LOCALIZATION NOTE:
   To localize the displayed strings copy their values to appropriately named
   keys in the app's neutral language resource file (AppResources.resx) then
   replace the hard-coded text value between the attributes' quotation marks
   with the binding clause whose path points to that string name.

   For example:

    Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"

   This binding points to the template's string resource named "ApplicationTitle".

   Adding supported languages in the Project Properties tab will create a
   new resx file per language that can carry the translated values of your
   UI strings. The binding in these examples will cause the value of the
   attributes to be drawn from the .resx file that matches the
   CurrentUICulture of the app at run time.
   -->

  <!--TitlePanel contains the name of the application and page title-->
  <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
   <TextBlock Text="Image Gallery Using LongListSelector Control" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
  </StackPanel>

  <!--ContentPanel - place additional content here-->
  <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <phone:LongListSelector x:Name="LLSImageGallery" LayoutMode="Grid" GridCellSize="220,220"  IsGroupingEnabled="True" HideEmptyGroups="True"   HorizontalAlignment="Stretch"  VerticalAlignment="Stretch"  VirtualizingStackPanel.VirtualizationMode="Recycling" ItemsSource="{Binding}" ItemTemplate="{StaticResource LLSItemDataTemplate}" GroupHeaderTemplate="{StaticResource LLSHeaderDataTemplate}" JumpListStyle="{StaticResource LLSJumpListStyle}"/>

   

  </Grid>

  <!--Uncomment to see an alignment grid to help ensure your controls are
   aligned on common boundaries.  The image has a top margin of -32px to
   account for the System Tray. Set this to 0 (or remove the margin altogether)
   if the System Tray is hidden.

   Before shipping remove this XAML and the image itself.-->
  <!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" Grid.RowSpan="2" IsHitTestVisible="False" />-->
 </Grid>

</phone:PhoneApplicationPage>

Step 14
In Code Behind,On Page Load event Bind the data to LongListSelector Control,it is look like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using SolLongListSelector_WinPhone8.Resources;

namespace SolLongListSelector_WinPhone8
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor
        public MainPage()
        {
            InitializeComponent();

            // Sample code to localize the ApplicationBar
            //BuildLocalizedApplicationBar();

            this.Loaded += async (s, o) => {
                try
                {
                    List<KeyGroup<ImageEntity>> ListImageObj = await ImageView.GetGroupImageData();

                    if (ListImageObj != null)
                    {
                        if (ListImageObj.Count >= 1)
                        {
                            // Bind Data to the LongListSelector Control
                            LLSImageGallery.DataContext = ListImageObj;
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message); 
                }
            };
        }

        // Sample code for building a localized ApplicationBar
        //private void BuildLocalizedApplicationBar()
        //{
        //    // Set the page's ApplicationBar to a new instance of ApplicationBar.
        //    ApplicationBar = new ApplicationBar();

        //    // Create a new button and set the text value to the localized string from AppResources.
        //    ApplicationBarIconButton appBarButton = new ApplicationBarIconButton(new Uri("/Assets/AppBar/appbar.add.rest.png", UriKind.Relative));
        //    appBarButton.Text = AppResources.AppBarButtonText;
        //    ApplicationBar.Buttons.Add(appBarButton);

        //    // Create a new menu item with the localized string from AppResources.
        //    ApplicationBarMenuItem appBarMenuItem = new ApplicationBarMenuItem(AppResources.AppBarMenuItemText);
        //    ApplicationBar.MenuItems.Add(appBarMenuItem);
        //}
    }
}

Run the Project.

Output



Click on Image for better view

Click on a group header, a jump list with all letters of the alphabet appears



Click on Image for better view

Navigate to a particular group (Click on M group)



Click on Image for better view

Download
Download Source Code

8 comments: