Professional, Software

Part 3: Image Search Result View

Next step after calling a webservice was to visualize the result of a search. To start with Basic, I wanted to get to basic grid (I have few ideas to use by Stack3dPanel in the future but wanted to start with Basics).Some of the functionality explained in this posts are…

  1. DataBinding
    1. ItemsControl
      1. Customize the panel for Items Control
      2. Data Templates
    2. Value Converters
    3. Data binding using Source
    4. INotifyPropertyChanged
    5. ObservableCollection
  2. Scrollbar
  3. Border

I wanted to get to screen shot below.

silverlight

To get started, I created few helper classes and I will use them later.

ImageSourceConverter

I needed to create a value converter that converts a URI into ImageSource (BitmapImage) so that I can use the results returned by query in databinding. Code here is pretty simple.

   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Windows.Media.Imaging;
   5: using System.Windows.Data;
   6:  
   7: namespace Search
   8: {
   9:     /// <summary>
  10:     /// Value converter that can convert Uri to ImageSource (BitmapImage)
  11:     /// </summary>
  12:     public class ImageSourceConverter : IValueConverter
  13:     {
  14:         #region Public Members
  15:  
  16:         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  17:         {
  18:             Uri source = new Uri((string)value, UriKind.Absolute);
  19:             return new BitmapImage(source);
  20:         }
  21:  
  22:         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  23:         {
  24:             return ((BitmapImage)value).UriSource;
  25:         }
  26:         #endregion
  27:     }
  28: }

SearchResultCollection

I needed to create a class that I can use as a source of Collection. I ended up doing this is because my actual source for databinding is collection of this collection class (2-D array)

   1: using System;
   2: using Search.SearchService;
   3: using System.Collections.ObjectModel;
   4:  
   5: namespace Search
   6: {
   7:     /// <summary>
   8:     /// Class to hold collection of results that can be databound
   9:     /// </summary>
  10:     public class SearchResultCollection : ObservableCollection<SearchService.Result>
  11:     {
  12:     }
  13: }

Tile

Eventually I needed to have to sources for databinding. One was the actual collection itself and I use datacontext to set the source of databinding. Then there are some settings (height/width of each tile or padding around the tile). I created this class so that I can use Source based databinding

   1: using System;
   2: using System.Windows;
   3: using System.ComponentModel;
   4:  
   5: namespace Search
   6: {
   7:     /// <summary>
   8:     /// Class that holds the tile settings. It implements INotifyPropertyChanged
   9:     /// so that it can be used as a source of databinding
  10:     /// </summary>
  11:     public class Tile : INotifyPropertyChanged
  12:     {
  13:  
  14:         #region Public Members
  15:         //implements INotifyPropertyChanged event
  16:         public event PropertyChangedEventHandler PropertyChanged;
  17:  
  18:         //represents the height of a tile
  19:         public double TileHeight
  20:         {
  21:             get { return _tileheight; }
  22:             set
  23:             {
  24:                 _tileheight = value;
  25:                 NotifyPropertyChanged("TileHeight");
  26:             }
  27:         }
  28:  
  29:         //represents width of a tile
  30:         public double TileWidth
  31:         {
  32:             get { return _tilewidth; }
  33:             set
  34:             {
  35:                 _tilewidth = value;
  36:                 NotifyPropertyChanged("TileWidth");
  37:             }
  38:         }
  39:  
  40:         //represents padding around a tile
  41:         public Thickness TilePadding
  42:         {
  43:             get { return _tilepadding; }
  44:             set
  45:             {
  46:                 _tilepadding = value;
  47:                 NotifyPropertyChanged("TilePadding");
  48:             }
  49:         }
  50:         #endregion
  51:  
  52:         #region Private Memebers
  53:  
  54:         private double _tileheight = 10;
  55:         private double _tilewidth = 10;
  56:         private Thickness _tilepadding = new Thickness(5);
  57:  
  58:         //fires Property Change event. 
  59:         private void NotifyPropertyChanged(String info)
  60:         {
  61:             if (PropertyChanged != null)
  62:             {
  63:                 PropertyChanged(this, new PropertyChangedEventArgs(info));
  64:             }
  65:         }
  66:         #endregion
  67:     }
  68: }
  69:  

Grid View

Xaml

Having all this classes, now lets start with a grid view. Here is the XAML that I ended up with…

   1: <UserControl x:Class="Search.ImageGridView"
   2:         xmlns="http://schemas.microsoft.com/client/2007" 
   3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:              xmlns:control="clr-namespace:Search;assembly=Search" >
   5:     <UserControl.Resources>
   6:         <control:ImageSourceConverter x:Key="converter"/>
   7:         <control:Tile x:Key="TileInfo"/>
   8:     </UserControl.Resources>
   9:     <Grid x:Name="LayoutRoot">
  10:         <ItemsControl Name ="outic" ItemsSource="{Binding}" VerticalAlignment="Top" >
  11:             <ItemsControl.ItemTemplate>
  12:                 <DataTemplate>
  13:                     <ItemsControl ItemsSource="{Binding}">
  14:                         <ItemsControl.ItemsPanel>
  15:                             <ItemsPanelTemplate>
  16:                                 <StackPanel Orientation="Horizontal"/>
  17:                             </ItemsPanelTemplate>
  18:                         </ItemsControl.ItemsPanel>
  19:                         <ItemsControl.ItemTemplate>
  20:                             <DataTemplate>
  21:                                     <Border 
  22:                                         BorderThickness="2,2,2,2" BorderBrush="#FF313131" 
  23:                                         CornerRadius="5,5,5,5" 
  24:                                         Height="{Binding TileHeight,Source={StaticResource TileInfo}}" 
  25:                                         Width="{Binding TileWidth,Source={StaticResource TileInfo}}" 
  26:                                         Margin="{Binding TilePadding,Source={StaticResource TileInfo}}" 
  27:                                         MouseEnter="Border_MouseEnter" 
  28:                                         MouseLeave="Border_MouseLeave" RenderTransformOrigin="1,1">
  29:                                         <Border.RenderTransform>
  30:                                             <TransformGroup>
  31:                                                 <ScaleTransform ScaleX="1" ScaleY="1"/>
  32:                                                 <SkewTransform AngleX="0" AngleY="0"/>
  33:                                                 <RotateTransform Angle="0"/>
  34:                                                 <TranslateTransform X="0" Y="0"/>
  35:                                             </TransformGroup>
  36:                                         </Border.RenderTransform>
  37:                                         <Border.Background>
  38:                                             <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  39:                                                 <GradientStop Color="#FFFFFFFF"/>
  40:                                                 <GradientStop Color="#FF71A7BB" Offset="1"/>
  41:                                             </LinearGradientBrush>
  42:                                         </Border.Background>
  43:                                         <Image 
  44:                                         Source="{Binding Image.ImageURL, Converter={StaticResource converter}}" 
  45:                                         ImageFailed="Image_ImageFailed"
  46:                                         Cursor="Hand" Opacity="0.7"
  47:                                         Margin="1,1,1,1"/>
  48:                                 </Border>
  49:                             </DataTemplate>
  50:                         </ItemsControl.ItemTemplate>
  51:                     </ItemsControl>
  52:                 </DataTemplate>
  53:             </ItemsControl.ItemTemplate>
  54:         </ItemsControl>
  55:     </Grid>
  56: </UserControl>

Here are some of the interesting points about this XAML…

  1. I did not use Grid panel, instead I used nested ItemsControl. so think of one item control shows a row of images. Then I use another ItemsControl to repeat these rows to get the grid look.
    1. To achieve this, I set the ItemsPanel property on the inner ItemsControl. I set the panel to be StackPanel (which is default) but I changed the orientation to be Horizontal (Vertical is default). This way I get a row of images
    2. Outer Items control repeats this inner items control (showing a row of images). This way I get a Grid of Images.
    3. Since the data context for this control is Collection of collection. Outer Items control sets collection instance in collection of collection to be the source for inner items control.
  2. Inside inner items control, I have a border that lays out each image (Image element inside).
    1. Border defines Height/Width/Padding for each tile.
    2. In the resources section, I have defined a Tile instance.
    3. Border height/width/margin are databound to TileHeight, TileWidth, TilePadding property of a Tile class.
    4. Border also defines the interactive behavior through Mouse Enter/leave events.
  3. Image element is contained within each of the border.
    1. Image element is bound to Uri returned by search service call
    2. Since Uri needs to be converted to ImageSource (BitmapImage), I use Value Converters of type ImageSourceConverter
    3. In the resources section, ImageSourceConverter is added.
    4. Image element also hooks up the ImageFailed event so that placeholder image can be shown in case, actual image can not be shown.
Code Behind

Now here is the code behind for the control.

   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Windows.Media.Imaging;
   5: using System.Windows.Media;
   6:  
   7: namespace Search
   8: {
   9:     public partial class ImageGridView : UserControl
  10:     {
  11:         #region Public Members
  12:  
  13:         //public constructor
  14:         public ImageGridView()
  15:         {
  16:             // Required to initialize variables
  17:             InitializeComponent();
  18:             _tileinfo = this.Resources["TileInfo"] as Tile;
  19:         }
  20:  
  21:         //height of each tile
  22:         public double TileHeight
  23:         {
  24:             get { return _tileinfo.TileHeight; }
  25:             set { _tileinfo.TileHeight = value; }
  26:         }
  27:  
  28:         //width of each tile
  29:         public double TileWidth
  30:         {
  31:             get { return _tileinfo.TileWidth; }
  32:             set { _tileinfo.TileWidth = value; }
  33:         }
  34:  
  35:         //padding around each tile applied as uniform thickness
  36:         public Double TilePadding
  37:         {
  38:             get { return _tileinfo.TilePadding.Top; }
  39:             set 
  40:             {
  41:                 //converts double value to uniform thickness value
  42:                 _tileinfo.TilePadding = new Thickness((double)value); 
  43:             }
  44:         }
  45:         #endregion
  46:  
  47:         #region Private Members
  48:         //handle to tile settings
  49:         private Tile _tileinfo;
  50:  
  51:         //if iamge fails to load then placeholder image is shown
  52:         private void Image_ImageFailed(object sender, ExceptionRoutedEventArgs e)
  53:         {
  54:             ((Image)sender).Source = new BitmapImage(new Uri("notfound.jpg", UriKind.Relative));
  55:         }
  56:  
  57:         //on mouse enter, increase the size of image and set opacity to 1
  58:         private void Border_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
  59:         {
  60:             Border b = (Border)sender;
  61:             b.Child.Opacity = 1;
  62:             TransformGroup tg = b.RenderTransform as TransformGroup;
  63:             ScaleTransform st = tg.Children[0] as ScaleTransform;
  64:             st.ScaleX = 1.1;
  65:             st.ScaleY = 1.1;
  66:         }
  67:  
  68:         //on mouse leave, reset the size of image and set opacity to 0
  69:         private void Border_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
  70:         {
  71:             Border b = (Border)sender;
  72:             b.Child.Opacity = 0.7;
  73:             TransformGroup tg = b.RenderTransform as TransformGroup;
  74:             ScaleTransform st = tg.Children[0] as ScaleTransform;
  75:             st.ScaleX = 1;
  76:             st.ScaleY = 1;
  77:         }
  78:         #endregion
  79:     }
  80: }

This is pretty straight forward too

  1. Control exposes following public APIs
    1. Constructor
    2. TileHeight/TileWidth so that consumer of this control can decide how big the tile should be
    3. TilePadding because setting height/width to be large has impact on things getting clipped so I also thought consumer should be allowed to set the padding. Padding property is interesting because it actually needs to be bound to Margin which is of type thickness but since I could not parse this property on my custom control, I use a hack. Even though Tile class exposes TilePadding to be of type thickness, I expose it as double. This way, I convert the double to thickness before setting it to Tile that was declared in the resource section and then I can use it in databinding without needing another value converter. Side effect is I assume uniform margin, which I did not think was a big problem for me for this scenario
  2. These are private members
    1. ImageFailed event handler, sets the source of the image that failed to place holder image
    2. Border’s mouse enter event handler, applies a scaletransform to itself so that image looks bigger when mouse is over it, it also sets the opacity to be 1 so that image appears clear
    3. Border’s mouse leave event handler, resets the scaletransform to 1 so that image goes back to original size and also sets the opacity to be 0.7.

Consumption of Grid Control

Xaml
   1: <UserControl x:Class="Search.Page"
   2:     xmlns="http://schemas.microsoft.com/client/2007" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   6:     xmlns:control="clr-namespace:Search;assembly=Search" 
   7:     mc:Ignorable="d" d:DesignHeight="500" d:DesignWidth="500">
   8:  
   9:     <UserControl.Resources>
  10:         <RadialGradientBrush x:Key="PageBackgroundBrush" 
  11:                              GradientOrigin="-0.0240000002086163,-0.0240000002086163">
  12:             <RadialGradientBrush.RelativeTransform>
  13:                 <TransformGroup>
  14:                     <ScaleTransform CenterX="0.5" CenterY="0.5" 
  15:                                     ScaleX="0.991" ScaleY="0.987"/>
  16:                     <SkewTransform CenterX="0.5" CenterY="0.5"/>
  17:                     <RotateTransform CenterX="0.5" CenterY="0.5"/>
  18:                     <TranslateTransform X="-0.001" Y="-0.005"/>
  19:                 </TransformGroup>
  20:             </RadialGradientBrush.RelativeTransform>
  21:             <GradientStop Color="#FFFFFFFF"/>
  22:             <GradientStop Color="#FFACD3E5" Offset="1"/>
  23:             <GradientStop Color="#FFF1F5F7" Offset="0.60000002384185791"/>
  24:             <GradientStop Color="#FFC8E7F7" Offset="0.29499998688697815"/>
  25:         </RadialGradientBrush>
  26:         <Style x:Key="ButtonStyle" TargetType="Button">
  27:             <Setter Property="Background"  Value="#FF007FFF"/>
  28:             <Setter Property="Foreground" Value="Black"/>
  29:             <Setter Property="FontFamily" Value="Fontin-Bold.ttf#Fontin"/>
  30:             <Setter Property="FontSize" Value="20"/>
  31:         </Style>
  32:         <LinearGradientBrush x:Key="TextBoxBackgroundBrush" 
  33:                             EndPoint="0.5,1" StartPoint="0.5,0">
  34:             <GradientStop Color="#FFFFFFFF" Offset="0.518"/>
  35:             <GradientStop Color="#FF9BDAFB" Offset="1"/>
  36:             <GradientStop Color="#FF9BDAFB"/>
  37:             <GradientStop Color="#FFFFFFFF" Offset="0.138"/>
  38:             <GradientStop Color="#FFFFFFFF" Offset="0.857"/>
  39:         </LinearGradientBrush>
  40:         <Storyboard x:Name="SearchProgressAnimation" RepeatBehavior="1x">
  41:             <DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" 
  42:                                    Storyboard.TargetProperty="(Panel.Background).(Brush.RelativeTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" 
  43:                                    BeginTime="00:00:00">
  44:                 <SplineDoubleKeyFrame KeyTime="00:00:01" Value="90.145"/>
  45:                 <SplineDoubleKeyFrame KeyTime="00:00:02" Value="180.143"/>
  46:                 <SplineDoubleKeyFrame KeyTime="00:00:03" Value="270.128"/>
  47:                 <SplineDoubleKeyFrame KeyTime="00:00:04" Value="359.946"/>
  48:             </DoubleAnimationUsingKeyFrames>
  49:             <PointAnimationUsingKeyFrames Storyboard.TargetName="ellipse" 
  50:                                   Storyboard.TargetProperty="(Shape.Fill).(LinearGradientBrush.StartPoint)" 
  51:                                   BeginTime="00:00:00">
  52:                 <SplinePointKeyFrame KeyTime="00:00:01" Value="0.716000020503998,0.439000010490417"/>
  53:                 <SplinePointKeyFrame KeyTime="00:00:02" Value="0.382999986410141,0.684000015258789"/>
  54:                 <SplinePointKeyFrame KeyTime="00:00:03" Value="0.0759999975562096,0.231000006198883"/>
  55:                 <SplinePointKeyFrame KeyTime="00:00:04" Value="0.726000010967255,0.395999997854233"/>
  56:             </PointAnimationUsingKeyFrames>
  57:             <PointAnimationUsingKeyFrames Storyboard.TargetName="ellipse" 
  58:                                   Storyboard.TargetProperty="(Shape.Fill).(LinearGradientBrush.EndPoint)" 
  59:                                   BeginTime="00:00:00">
  60:                 <SplinePointKeyFrame KeyTime="00:00:01" Value="0.0769999995827675,0.228000000119209"/>
  61:                 <SplinePointKeyFrame KeyTime="00:00:02" Value="0.409999996423721,-0.017000000923872"/>
  62:                 <SplinePointKeyFrame KeyTime="00:00:03" Value="0.717000007629395,0.435999989509583"/>
  63:                 <SplinePointKeyFrame KeyTime="00:00:04" Value="0.0670000016689301,0.270999997854233"/>
  64:             </PointAnimationUsingKeyFrames>
  65:         </Storyboard>
  66:         <Storyboard x:Name="MakeSearchResultVisible">
  67:             <DoubleAnimationUsingKeyFrames Storyboard.TargetName="gridscroll" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="00:00:00">
  68:                 <SplineDoubleKeyFrame x:Name="startopacity" KeyTime="00:00:00" Value="0"/>
  69:                 <SplineDoubleKeyFrame x:Name="endopacity" KeyTime="00:00:02" Value="1"/>
  70:             </DoubleAnimationUsingKeyFrames>
  71:         </Storyboard>
  72:     </UserControl.Resources>
  73:  
  74:     <Grid x:Name="LayoutRoot" Background="{StaticResource PageBackgroundBrush}" SizeChanged="LayoutRoot_SizeChanged">
  75:         <Grid.RowDefinitions>
  76:             <RowDefinition Height="0.091*"/>
  77:             <RowDefinition Height="50"/>
  78:             <RowDefinition Height="0.833*"/>
  79:         </Grid.RowDefinitions>
  80:         <Grid.ColumnDefinitions>
  81:             <ColumnDefinition Width="0.098*"/>
  82:             <ColumnDefinition Width="0.66*" MinWidth="300" x:Name="imagegridcolumn"/>
  83:             <ColumnDefinition Width="0.242*"/>
  84:         </Grid.ColumnDefinitions>
  85:         <Button  
  86:             Grid.Column="1" Grid.Row="1" Margin="0,10,10,10" 
  87:             MaxWidth="100"  
  88:             Content="Search" 
  89:             Name="btnSearch" 
  90:             HorizontalAlignment="Right" 
  91:             Style="{StaticResource ButtonStyle}"
  92:             Click="Button_Click"/>
  93:         <TextBox 
  94:             Grid.Column="1" Grid.Row="1" Margin="40,10,120,10" 
  95:             Text="Silverlight" 
  96:             x:Name="txtSearch" 
  97:             Background="{StaticResource TextBoxBackgroundBrush}" 
  98:             FontFamily="Fontin-Bold.ttf#Fontin" FontSize="20"/>
  99:         <Canvas Grid.Column="0" Grid.Row="1" Margin="30.1760005950928,5,0,-26" VerticalAlignment="Stretch">
 100:             <Ellipse HorizontalAlignment="Stretch" Margin="0,0,0,0" 
 101:              VerticalAlignment="Stretch" Stroke="#FF000000" 
 102:              StrokeThickness="4" Width="51" Height="48.682" x:Name="ellipse">
 103:                 <Ellipse.Fill>
 104:                     <LinearGradientBrush EndPoint="0.293000012636185,0.666999995708466" 
 105:                                  StartPoint="0.5,0">
 106:                         <GradientStop Color="#FF5B5B5B"/>
 107:                         <GradientStop Color="#FFFFFFFF" Offset="1"/>
 108:                     </LinearGradientBrush>
 109:                 </Ellipse.Fill>
 110:             </Ellipse>
 111:             <Path Height="28" Width="27" Canvas.Left="36.824" Canvas.Top="38.076" 
 112:           Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" 
 113:           StrokeThickness="4" Data="M39.823997,41.075745 L59.823997,62.075745"/>
 114:             <Path Height="22" HorizontalAlignment="Right" Margin="0,0,0,0" 
 115:           VerticalAlignment="Top" Width="21" Grid.Row="2" Fill="#FFFFFFFF"
 116:           Stretch="Fill" Stroke="#FF000000" StrokeThickness="7"
 117:           Data="M139.2532,209.79701 L125.25321,194.79701" 
 118:           Canvas.Top="45.374" Canvas.Left="43.402"/>
 119:         </Canvas>
 120: <ScrollViewer 
 121:     HorizontalScrollBarVisibility="Auto" 
 122:     VerticalScrollBarVisibility="Auto" 
 123:     Grid.Column="1" Grid.Row="2" 
 124:     Name="gridscroll" 
 125:     Opacity="0">
 126:     <control:ImageGridView 
 127:         Name ="imageGrid" 
 128:         VerticalAlignment="Top"  Margin="20,0,20,0"
 129:         TileHeight="100" TileWidth="100" TilePadding="10" />
 130: </ScrollViewer>
 131:     </Grid>
 132: </UserControl>

New Page.Xaml are above….

  1. Added a ScrollViewer so that I can get scrollbar if needed, but I have set the scrollbar visibility to Auto so that it does not show up if it is not needed
  2. Inside Scrollbar, there is ImageGrid control. I have set the TileHeight/Width to be 100.
  3. I have also added MakeSearchResultVisible storyboard in resources section so that I can fade in the grid view after the search service call is complete.
Code Behind
   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Windows.Input;
   5: using System.Windows.Media.Animation;
   6: using Search.SearchService;
   7: using System.Collections.ObjectModel;
   8: using System.ServiceModel;
   9: using System.Net;
  10:  
  11: namespace Search
  12: {
  13:     
  14:     public partial class Page : UserControl
  15:     {
  16:         #region Public Members
  17:         //public constructor
  18:         public Page()
  19:         {
  20:             InitializeComponent();
  21:             _searchresultcollectioncollection = new ObservableCollection<SearchResultCollection>();
  22:             _imagegridview = LayoutRoot.FindName("imageGrid") as ImageGridView;
  23:             _gridscroll = LayoutRoot.FindName("gridscroll") as ScrollViewer;
  24:             _imagegridview.DataContext = _searchresultcollectioncollection;
  25:             _progress = LayoutRoot.FindName("SearchProgressAnimation") as Storyboard;
  26:             _makesearchresultsvisible = LayoutRoot.FindName("MakeSearchResultVisible") as Storyboard;
  27:             _progress.Completed += new EventHandler(_progress_Completed);
  28:         }
  29:         #endregion
  30:  
  31:         #region Public Memebers
  32:         private SearchResultCollection _imagesearchresultcollection;
  33:         private ObservableCollection<SearchResultCollection> _searchresultcollectioncollection;
  34:         private Boolean _searchcompleted;
  35:  
  36:         private ImageGridView _imagegridview;
  37:         private ScrollViewer _gridscroll;
  38:  
  39:         private Storyboard _progress;
  40:         private Storyboard _makesearchresultsvisible;
  41:  
  42:         //Rearranges the size of source collection according to size
  43:         private void LayoutRoot_SizeChanged(object sender, SizeChangedEventArgs e)
  44:         {
  45:             int numberofcolumns = GetNumberofColumns();
  46:            CreateCollectionofCollections(_imagesearchresultcollection, numberofcolumns);
  47:         }
  48:  
  49:         //gets the number of columns depending on the size available and tileheight
  50:         private int GetNumberofColumns()
  51:         {
  52:             return (int)((imagegridcolumn.ActualWidth ) / (_imagegridview.TileHeight + 2 *_imagegridview.TilePadding));
  53:         }
  54:  
  55:         //need to do this because I wanted at least one cycle of animation to be complete
  56:         private void _progress_Completed(object sender, EventArgs e)
  57:         {
  58:             if (_searchcompleted == true)
  59:             {
  60:                 _progress.Stop();
  61:                 MakeSearchResultsVisible(0, 1);
  62:             }
  63:             else
  64:             {
  65:                 _progress.Begin();
  66:             }
  67:         }
  68:  
  69:         //starts the search
  70:         private void Button_Click(object sender, RoutedEventArgs e)
  71:         {
  72:             _searchcompleted = false;
  73:             //starts the animation during webservice call
  74:             _progress.Begin();
  75:             //makes the grid view invisible
  76:             _gridscroll.Opacity = 0;
  77:             //makes the service call
  78:             GetLiveData();
  79:         }
  80:  
  81:         //runs the animation when search is complete.
  82:         private void MakeSearchResultsVisible(int startopacity, int endopacity)
  83:         {
  84:             SplineDoubleKeyFrame temp;
  85:             temp = LayoutRoot.FindName("startopacity") as SplineDoubleKeyFrame;
  86:             temp.Value = startopacity;
  87:             temp = LayoutRoot.FindName("endopacity") as SplineDoubleKeyFrame;
  88:             temp.Value = endopacity;
  89:             _makesearchresultsvisible.Begin();
  90:         }
  91:  
  92:         //calls the webservice
  93:         private void GetLiveData()
  94:         {
  95:             try
  96:             {
  97:                 //Initialize web service
  98:                 string searchquery = txtSearch.Text;
  99:                 BasicHttpBinding binding = new BasicHttpBinding();
 100:                 EndpointAddress address = new EndpointAddress("http://soap.search.msn.com/webservices.asmx");
 101:                 MSNSearchPortTypeClient searchservice = new MSNSearchPortTypeClient(binding, address);
 102:                 SearchRequest searchrequest = new SearchRequest();
 103:                 SourceRequest[] sr = new SourceRequest[3];
 104:  
 105:                 sr[0] = new SourceRequest();
 106:                 sr[0].Source = SourceType.Image;
 107:                 sr[0].ResultFields = ResultFieldMask.Url | ResultFieldMask.Title | ResultFieldMask.Description | ResultFieldMask.Image;
 108:                 sr[0].Count = 50;
 109:  
 110:                 searchrequest.Query = searchquery;
 111:                 searchrequest.Requests = sr;
 112:                 searchrequest.AppID = "Enter your key here";
 113:                 searchrequest.CultureInfo = "en-US";
 114:                 searchservice.SearchCompleted += new EventHandler<Search.SearchService.SearchCompletedEventArgs>(searchservice_SearchCompleted);
 115:                 searchservice.SearchAsync(searchrequest);
 116:             }
 117:             catch (WebException webx)
 118:             {
 119:                 txtSearch.Text = webx.ToString();
 120:                 _searchcompleted = true;
 121:             }
 122:         }
 123:  
 124:         //when the search is completed, converts the search results to observation collection
 125:         private void searchservice_SearchCompleted(object sender, Search.SearchService.SearchCompletedEventArgs e)
 126:         {
 127:             Result[] _imageresults;
 128:  
 129:             if (e.Error != null)
 130:                 txtSearch.Text = e.Error.Message;
 131:             else
 132:             {
 133:                 _imagesearchresultcollection = new SearchResultCollection();
 134:                 _imageresults = e.Result.Responses[0].Results;
 135:                 for (int counter = 0; counter < _imageresults.Length; counter++)
 136:                 {
 137:                     if (_imageresults[counter].Image.ImageURL.Contains("jpg") || _imageresults[counter].Image.ImageURL.Contains("png"))
 138:                         _imagesearchresultcollection.Add(_imageresults[counter]);
 139:                 }
 140:                 int numberofcolumns = GetNumberofColumns();
 141:                 CreateCollectionofCollections(_imagesearchresultcollection, numberofcolumns);
 142:             }
 143:             _searchcompleted = true;
 144:         }
 145:  
 146:         //creates the source collection with specified number of columns. 
 147:         private void CreateCollectionofCollections(SearchResultCollection scollection, int columns)
 148:         {
 149:             int counter = 0;
 150:             if (scollection != null)
 151:             {
 152:                 _searchresultcollectioncollection.Clear();
 153:                 while (counter < scollection.Count)
 154:                 {
 155:                     SearchResultCollection searchresultcollection = new SearchResultCollection();
 156:                     for (int j = 0; j < columns; j++)
 157:                     {
 158:                         if (counter < scollection.Count)
 159:                         {
 160:                             searchresultcollection.Add(scollection[counter]);
 161:                             counter++;
 162:                         }
 163:                     }
 164:                     _searchresultcollectioncollection.Add(searchresultcollection);
 165:                 }
 166:             }
 167:         }
 168:         #endregion
 169:  
 170:     }
 171: }

Here are the few things about the code that might be interesting in order to understand the code…

  1. I use “_searchresultcollectioncollection” to represent the 2-D array that I need so that I can set it to be source of nested ItemsControl
  2. I recalculate that everytime search happens or size of the grid changes
  3. I think, this part could be made lot more tighter but I just stuck with it.
  4. I also start the animation to fade in the results once query is complete.

Here is the link to working project.

Advertisements
Standard

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s