Header Ads

Windows Phone: Dynamic Tile Design Generation

Today, I shall be demonstrating the generation of a tile base (simple button) design dynamically in Windows Phone platform.
At many times when the structure of system is very much consistent, then generally the choice will always be to generalize the code at maximum and that's where dynamic coding is generally done. Today's post is also somewhat of such scenario, when developers prefer to generalize their application design. I will be showing today that how can we generate the famous tile base design of Windows Phone platform dynamically. Following are some prerequisites before you proceed any further in this tutorial. 

Prerequisites: 

1) Knowledge about Windows Phone platform

You can download the complete source code or you can follow step by step discussion below. The sample code is in Microsoft Visual Studio 2013 and Windows Phone SDK 8 has been targeted.

Download Now!

If you choose to follow step by step discussion then do not get worry about the application styling as I am not posting the code for application styling.

Now lets begin our coding.

1) Create a Windows Phone (silverlight) application with target of SDK 8 in visual studio 2013 and name it "DynamicTileDesign".

2) In your "MainPage.xaml" file, replace the existing code with the following:
 
<phone:PhoneApplicationPage  
   x:Class="DynamicTileDesign.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">  
   <!--LayoutRoot is the root grid where all page content is placed-->  
   <Grid x:Name="LayoutRoot" Background="#FFC11E1E">  
     <Grid.RowDefinitions>  
       <RowDefinition Height="Auto"/>  
       <RowDefinition Height="*"/>  
     </Grid.RowDefinitions>  
     <!--TitlePanel contains the name of the application and page title-->  
     <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">  
       <TextBlock Text="Dynamic Design" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>  
       <TextBlock Text="Tile" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>  
     </StackPanel>  
     <!--ContentPanel - place additional content here-->  
     <Grid x:Name="ContentPanel" Grid.Row="1">  
       <ListBox x:Name="lstTile"  
             HorizontalAlignment="Center"  
             ItemsSource="{Binding}" >  
         <ListBox.ItemTemplate>  
           <DataTemplate>  
             <StackPanel Orientation="Vertical">  
               <Grid>  
                 <Grid.ColumnDefinitions>  
                   <ColumnDefinition Width="*"/>  
                   <ColumnDefinition Width="*"/>  
                 </Grid.ColumnDefinitions>  
                 <Button Grid.Column="0"   
                     Content="{Binding TileObjA.TileName}"   
                     VerticalAlignment="Center"   
                     HorizontalAlignment="Center"   
                     Width="202"  
                     Height="202"   
                     Background="{Binding TileObjA.Color}"   
                     Foreground="Black"  
                     BorderBrush="Black"/>  
                 <Button Grid.Column="1"   
                     x:Name="btnTileB"  
                     Content="{Binding TileObjB.TileName}"   
                     VerticalAlignment="Center"  
                     HorizontalAlignment="Center"  
                     Width="202"  
                     Height="202"  
                     Background="{Binding TileObjB.Color}"  
                     Foreground="Black"  
                     BorderBrush="Black"/>  
               </Grid>  
             </StackPanel>  
           </DataTemplate>  
         </ListBox.ItemTemplate>  
       </ListBox>  
     </Grid>  
   </Grid>  
 </phone:PhoneApplicationPage>  

The above list box template follows a design in such way that for a single element in a list box there are two tiles. Of course a little coding effort is needed to handle the generation for odd number of tiles.


This is a simple list box base template. If you face any styling related issues then remove the styling classes that are not linked in your project.

3) Now create a new folder under "DynamicTileDesign" project and name it "Objects". This folder will contain our models or object classes. 

4) Now create two model classes and name them "ElementObj.cs" & "TileObj.cs" under "Objects" folder. Replace the code in each of the file as follow:


namespace DynamicTileDesign.Objects  
 {  
   using System;  
   using System.Collections.Generic;  
   using System.Linq;  
   using System.Text;  
   using System.Threading.Tasks;  
   /// <summary>  
   /// Tile object class.  
   /// </summary>  
   public class TileObj  
   {  
     #region Default Constructor method.  
     /// <summary>  
     /// Initializes a new instance of the <see cref="TileObj" /> class.  
     /// </summary>  
     /// <param name="tileName">Title name parameter</param>  
     /// <param name="imgFilenamePath">Image filename path parameter</param>  
     /// <param name="color">Background color parameter</param>   
     public TileObj(string tileName, string imgFilenamePath, string color)  
     {  
       try  
       {  
         // Settings.  
         this.TileName = tileName;  
         this.ImgFilenamePath = imgFilenamePath;  
         this.Color = color;  
       }  
       catch (Exception ex)  
       {  
         // Info.  
         throw ex;  
       }  
     }  
     #endregion  
     #region Public / Protected Properties  
     /// <summary>  
     /// Gets or sets Name property.  
     /// </summary>  
     public string TileName { get; set; }  
     /// <summary>  
     /// Gets or sets image filename path property.  
     /// </summary>  
     public string ImgFilenamePath { get; set; }  
     /// <summary>  
     /// Gets or sets background path property.  
     /// </summary>  
     public string Color { get; set; }  
     #endregion  
   }  
 }  

"TileObj.cs" class contains the core properties of a particular tile. This class will be used in the "ElementObj.cs" class so that we can access/bind per tile properties to a single list box element.


namespace DynamicTileDesign.Objects  
 {  
   using System;  
   using System.Collections.Generic;  
   using System.Linq;  
   using System.Text;  
   using System.Threading.Tasks;  
   /// <summary>  
   /// Element object class.  
   /// </summary>  
   public class ElementObj  
   {  
     #region Default Constructor method.  
     /// <summary>  
     /// Initializes a new instance of the <see cref="ElementObj" /> class.  
     /// </summary>  
     /// <param name="tileObjA">Title object A parameter</param>  
     /// <param name="tileObjB">Title object B parameter</param>  
     public ElementObj(TileObj tileObjA, TileObj tileObjB)  
     {  
       try  
       {  
         // Settings.  
         this.TileObjA = tileObjA;  
         this.TileObjB = tileObjB;  
       }  
       catch (Exception ex)  
       {  
         // Info.  
         throw ex;  
       }  
     }  
     #endregion  
     #region Public / Protected Properties  
     /// <summary>  
     /// Gets or sets tile A property.  
     /// </summary>  
     public TileObj TileObjA { get; set; }  
     /// <summary>  
     /// Gets or sets tile B property.  
     /// </summary>  
     public TileObj TileObjB { get; set; }  
     #endregion  
   }  
 }  

"ElementObj.cs" class contains two tiles objects of "TileObj.cs" class type as its property. "ElementObj.cs" class will be the binding item source for our list box in "MainPage.xaml" page.


5) Now replace the code in "MainPage.xaml.cs" file with following:


public partial class MainPage : PhoneApplicationPage  
   {  
     #region Private property.  
     /// <summary>  
     /// Tile elements list list.  
     /// </summary>  
     private ObservableCollection<ElementObj> myTiles = new ObservableCollection<ElementObj>();  
     #endregion  

     #region Default Constructor  
     /// <summary>  
     /// Initializes a new instance of the <see cref="MainPage" /> class..  
     /// </summary>  
     public MainPage()  
     {  
       try  
       {  
         // Initialization.  
         this.InitializeComponent();  
         TileObj objA = new TileObj("Button 1", string.Empty, "#FF000CFF");  
         TileObj objB = new TileObj("Button 2", string.Empty, "#FFFFFF00");  
         TileObj objC = new TileObj("Button 3", string.Empty, "#FF33D633");  
         TileObj objD = new TileObj("Button 4", string.Empty, "#FFB833D6");  
         ElementObj e1 = new ElementObj(objA, objB);  
         ElementObj e2 = new ElementObj(objC, objD); 
 
         // Settings.  
         this.myTiles.Add(e1);  
         this.myTiles.Add(e2);  
         this.lstTile.ItemsSource = this.myTiles;  
       }  
       catch (Exception ex)  
       {  
         // Info.  
         MessageBox.Show(ex.Message);  
       }  
     }  
     #endregion  
   }  

In the above code we have simply created a ObservableCollection list called myTiles of type "ElementObj", then we have created four "TileObj" type objects and add two elements into our list and bind the list to our list box.

6) Build & Execute the project and you will see something like this:


At this point our tiles are even i.e. 4 which make things simple. So, lets make things a bit complicated by let say adding 3 tiles instead. Let see how we can achieve this.

7) Do the following modification in "MainPage.xaml.cs" file: In "Private property" region, add the following property i.e.


     /// <summary>  
     /// Total tiles property.  
     /// </summary>  
     private int totalTiles = 3;  

In "Default Constructor" region, replace the code with following i.e.
 
     try  
       {  
         // Initialization.  
         this.InitializeComponent();  
         TileObj objA = new TileObj("Button 1", string.Empty, "#FF000CFF");  
         TileObj objB = new TileObj("Button 2", string.Empty, "#FFFFFF00");  
         TileObj objC = new TileObj("Button 3", string.Empty, "#FF33D633");
  
         ElementObj e1 = new ElementObj(objA, objB);  
         ElementObj e2 = new ElementObj(objC, null); 
 
         // Settings.  
         this.myTiles.Add(e1);  
         this.myTiles.Add(e2);  
         this.lstTile.ItemsSource = this.myTiles;  
         // Loading.  
         this.Loaded += this.MainPage_Loaded;  
       }  
       catch (Exception ex)  
       {  
         // Info.  
         MessageBox.Show(ex.Message);  
       }  

Here what we have done is simply remove one tile object and add a new line of code i.e.


this.Loaded += this.MainPage_Loaded;

The above code is a simple event which will invoke when our page is loaded completely. Now, add "MainPage_Loaded(...)"& "FindVisualChild(...)" methods i.e.


     #region Main page loaded event.  
     /// <summary>  
     /// Main page loaded event.  
     /// </summary>  
     /// <param name="sender">Sender parameter</param>  
     /// <param name="e">Event parameter</param>  
     private void MainPage_Loaded(object sender, RoutedEventArgs e)  
     {  
       try  
       {  
         // Verification.  
         if ((this.totalTiles % 2) != 0)  
         {  
           // Setting.  
           ListBoxItem item = (ListBoxItem)this.lstTile.ItemContainerGenerator.ContainerFromIndex(this.lstTile.Items.Count - 1);  
           Button btnInfo = this.FindVisualChild<Button>(item, "btnTileB");  
           // Verification.  
           if (btnInfo != null)  
           {  
             // Settings.  
             btnInfo.Visibility = Visibility.Collapsed;  
           }  
         }  
       }  
       catch (Exception ex)  
       {  
         // Info.  
         MessageBox.Show(ex.Message);  
       }  
     }  
     #endregion  
     #region Find visual child method.  
     /// <summary>  
     /// Find visual child method.  
     /// </summary>  
     /// <typeparam name="T">Template type.</typeparam>  
     /// <param name="obj">Object parameter.</param>  
     /// <param name="objname">Object name parameter.</param>  
     /// <returns>Returns - visual child</returns>  
     private T FindVisualChild<T>(DependencyObject obj, string objname) where T : DependencyObject  
     {  
       try  
       {  
         // Initialization  
         string controlObjName = string.Empty;
  
         // Verify if the object contains name property.  
         Type tyype = obj.GetType(); 
 
         // Verification.  
         if (tyype.GetProperty("Name") != null)  
         {  
           // Settings.  
           PropertyInfo prop = tyype.GetProperty("Name");  
           controlObjName = prop.GetValue((object)obj, null).ToString();  
         }  
         else  
         {  
           // Info.  
           return null;  
         }  

         // Verify if specify object name matches to the require control name.  
         if (obj is T && objname.ToString().ToLower().Equals(controlObjName.ToString().ToLower()))  
         {  
           // Info.  
           return obj as T;  
         }  

         // Check for children  
         int childrenCount = VisualTreeHelper.GetChildrenCount(obj);  

         // Verification.  
         if (childrenCount < 1)  
         {  
           // Info.  
           return null;  
         }  

         // First check all the children  
         for (int i = 0; i <= childrenCount - 1; i++)  
         {  
           // Initialization.  
           DependencyObject child = VisualTreeHelper.GetChild(obj, i);  
           // Processing.  
           if (child is T && objname.ToString().ToLower().Equals(controlObjName.ToString().ToLower()))  
           {  
             // Info.  
             return child as T;  
           }  
         }  

         // Then check the childrens children  
         for (int i = 0; i <= childrenCount - 1; i++)  
         {  
           // Initialization.  
           string checkobjname = objname;  
           // Recursion.  
           DependencyObject child = this.FindVisualChild<T>(VisualTreeHelper.GetChild(obj, i), objname);  
           if (child != null && child is T && objname.ToString().ToLower() == checkobjname.ToString().ToLower())  
           {  
             // Info.  
             return child as T;  
           }  
         }  
       }  
       catch (Exception ex)  
       {  
         // Info.  
         MessageBox.Show(ex.Message);  
       }  
       // Info.  
       return null;  
     }  
     #endregion  

In "MainPage_Loaded(...)" method we have simply check out total number of the tiles if they are not even then we shall simply hide the last tile as it will be an empty rectangle anyway, so, for that we need to access our list box tile element as a visual object rather than model object, you can check out my previous post in understanding the visual object here. "FindVisualChild(...)" method will find out our designated visual child. The trick here for odd cases is simple i.e. hide the last tile.

7) Now build & execute the project and you will see following:


That's about it!!!

Enjoy coding!!!

2 comments: