Professional, Software

Part 4: Image Detail View

The next step in the search app was to show the the detail image. Here is what it looks like.

detailview

Building this was pretty straight forward. Few things to call out…

  1. One of the interesting things was the fact that I had switched my grid view to use the thumbnail images but I really wanted to show the full size images in the detail view. Full size images are not downloaded yet. Since Silverlight uses Browser to download, when I show the grid view all the images shown in grid view are already downloaded and cached in browser cache but the full size images are not downloaded. I played around with some animations to show while full image is downloaded but since downloadcompleted event does not exist on image, it was kind of tricky to know when to start animation. Finally the approach I went with was to show the thumbnail image (that is already downloaded) to begin with and then switch the fullsize image when it is already downloaded. It also gives “Deep Zoom” like effect where sometime you can see image crystallize when fullsize image replaces the thumbnail version.
  2. The only reason why I exposed the SearchResult property on this control was because I wanted to use databinding but also needed to know when the DataContext changes. Since in SL2 there are not DataContext change notifications, I exposed the property and set the datacontext myself.
  3. One of the data points that I wanted to display in detail view was link to actual image. I used HyperlinkButton control so that user can click on it and new browser window opens up to show the image. I used TargetName property exposed on this control. I also ended up building another IValueConverter so that I can convert string into Uri.
  4. I needed to make changes to ImageGridView since I needed to know which items in the grid was selected.
UriConverter
   1: using System;
   2: using System.Windows.Data;
   3: 
   4: namespace Search
   5: {
   6:     /// <summary>
   7:     /// Converts string into Uri
   8:     /// </summary>
   9:     public class UriConverter : IValueConverter
  10:     {
  11:         #region Public Members
  12: 
  13:         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  14:         {
  15:             return (new Uri((string)value, UriKind.RelativeOrAbsolute));
  16:         }
  17: 
  18:         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  19:         {
  20:             return ((Uri)value).ToString();
  21:         }
  22:         #endregion
  23:     }
  24: }

This class converts string returned by search service into Uri so that I can databind hyperlinkbutton’s NavigateUri property to it.

DetailImage

Here is the xaml that describes the detailimage control. It shows Size in bytes, height/width in pixels, link to image that opens in new browser window and actual image.

   1: <UserControl x:Class="Search.DetailImage"
   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:DesignWidth="300" d:DesignHeight="300"
   8:     >
   9:     <UserControl.Resources>
  10:         <control:UriConverter x:Key="uriconverter"/>
  11:         <control:ImageSourceConverter x:Key="imagesourceconverter"/>
  12:     </UserControl.Resources>
  13: 
  14:     <StackPanel x:Name="LayoutRoot">
  15:         <StackPanel Orientation="Horizontal">
  16:             <TextBlock Text="URL: " FontWeight="Bold" Margin="10,0,0,0"/>
  17:             <HyperlinkButton Content="Link to Image"
  18:                              FontSize="15"
  19:                              NavigateUri="{Binding Image.ImageURL,Converter={StaticResource uriconverter}}"
  20:                              TargetName="_blank"/>
  21:         </StackPanel>
  22:         <StackPanel Orientation="Horizontal">
  23:             <TextBlock Text="Width (px): " FontWeight="Bold" Margin="10,0,0,0"/>
  24:             <TextBlock x:Name="txtWidth" Text="{Binding Image.ImageWidth}"/>
  25:         </StackPanel>
  26:         <StackPanel Orientation="Horizontal">
  27:             <TextBlock Text="Height (px):" FontWeight="Bold" Margin="10,0,0,0"/>
  28:             <TextBlock x:Name="txtHeight" Text="{Binding Image.ImageHeight}"/>
  29:         </StackPanel>
  30:         <StackPanel Orientation="Horizontal">
  31:             <TextBlock Text="Size: (Bytes):" FontWeight="Bold" Margin="10,0,0,0"/>
  32:             <TextBlock x:Name="txtSize" Text="{Binding Image.ImageFileSize}"/>
  33:         </StackPanel>
  34:         <Border
  35:             BorderThickness="2,2,2,2" CornerRadius="5,5,5,5"
  36:             BorderBrush="#FF000000"
  37:             Margin="0,20,0,0" Visibility="Collapsed"
  38:             x:Name="fullborder">
  39: 
  40:
  41:         </Border>
  42:         <Border
  43:             BorderThickness="2,2,2,2" CornerRadius="5,5,5,5"
  44:             BorderBrush="#FF000000"
  45:             Margin="0,20,0,0" x:Name="imageborder">
  46: 
  47:             <Image
  48:                 Margin="20,20,20,20"  Stretch="Fill"
  49:                 Source="{Binding Image.ThumbnailURL , Converter={StaticResource imagesourceconverter}}"
  50:                 RenderTransformOrigin="0.5,0.5"
  51:                 x:Name="image"
  52:                 HorizontalAlignment="Stretch">
  53:                 <Image.RenderTransform>
  54:                     <TransformGroup>
  55:                         <ScaleTransform/>
  56:                         <SkewTransform/>
  57:                         <RotateTransform/>
  58:                         <TranslateTransform/>
  59:                     </TransformGroup>
  60:                 </Image.RenderTransform>
  61:             </Image>
  62:         </Border>
  63:     </StackPanel>
  64: </UserControl>

Here is the code behind for this user control.

   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Windows.Media.Imaging;
   5: using Search.SearchService;
   6: 
   7: namespace Search
   8: {
   9:     public partial class DetailImage : UserControl
  10:     {
  11:         #region Public Members
  12: 
  13:         public DetailImage()
  14:         {
  15:             InitializeComponent();
  16:             _fullimage = new System.Windows.Controls.Image();
  17:             _fullimage.Margin = new Thickness(20);
  18:             fullborder.Child = _fullimage;
  19:         }
  20: 
  21:         //sets the SearchResult. The reason why I had to expose this is because 
  22:         //I need to know when the data context is changing so that I can do necessary
  23:         //work to switch images from thumbnail to fullsize
  24:         public Result SearchResult
  25:         {
  26:             get
  27:             {
  28:                 return _searchresult;
  29:             }
  30:             set
  31:             {
  32:                 _searchresult = value;
  33:                 //set the datacontext for the control
  34:                 DataContext = value;
  35:                 imageborder.Visibility = Visibility.Visible;
  36:                 fullborder.Visibility = Visibility.Collapsed;
  37:                 BitmapImage bi = new BitmapImage(new Uri(_searchresult.Image.ImageURL, UriKind.Absolute));
  38:                 bi.DownloadProgress += new DownloadProgressEventHandler(bi_DownloadProgress);
  39:                 bi.ImageFailed += new ExceptionRoutedEventHandler(bi_ImageFailed);
  40:                 _fullimage.Source = bi;
  41:             }
  42:         }
  43: 
  44: 
  45:         #endregion
  46: 
  47:         #region Private Members
  48: 
  49:         //when the full image is downloaded 
  50:         //I switch the thumbnail image in detail view for full size
  51:         void bi_DownloadProgress(object sender, DownloadProgressEventArgs e)
  52:         {
  53:             if (e.Progress == 1)
  54:             {
  55:                 imageborder.Visibility = Visibility.Collapsed;
  56:                 fullborder.Visibility = Visibility.Visible;
  57:             }
  58:         }
  59: 
  60:         //If for some reason full size image does not download
  61:         //then stick with thumbnail
  62:         void bi_ImageFailed(object sender, ExceptionRoutedEventArgs e)
  63:         {
  64:             imageborder.Visibility = Visibility.Visible;
  65:             fullborder.Visibility = Visibility.Collapsed;
  66:         }
  67:
  68:         Search.SearchService.Result _searchresult;
  69:         System.Windows.Controls.Image _fullimage;
  70: 
  71:         #endregion
  72: 
  73:     }
  74: }

There are two borders in the XAML. One is to show the thumbnail image and other one is to show the full image. The only reason why I ended up doing that was because, in Beta1, I could not change the border’s child. When the fullsize image is downloaded completely. I make border that contains thumbnail collapased and make the border that contains fullsize image visible. I still see some flickers and should think about better way to do this.

ImageGridView

To make the Detail view work, I needed to know which image was selected in the grid view.

  1. I have exposed the SelectedImage property. I hook up LeftMouseDown/Up and MouseLeave events for each of the borders around thumbnails to know which element was clicked on
  2. I also decided to implement INotifiyPropertyChanged for this property so that consumer can know when the selection changed. I could have done this using some custom event (SelectedItemChanged) but this way some other control could directly databind to this change notification.

Here is the new XAML for ImageGridView

   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="imagesourceconverter"/>
   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"
  29:                                         RenderTransformOrigin="1,1"
  30:                                         MouseLeftButtonDown="Border_MouseLeftButtonDown"
  31:                                         MouseLeftButtonUp="Border_MouseLeftButtonUp">
  32:                                         <Border.RenderTransform>
  33:                                             <TransformGroup>
  34:                                                 <ScaleTransform ScaleX="1" ScaleY="1"/>
  35:                                                 <SkewTransform AngleX="0" AngleY="0"/>
  36:                                                 <RotateTransform Angle="0"/>
  37:                                                 <TranslateTransform X="0" Y="0"/>
  38:                                             </TransformGroup>
  39:                                         </Border.RenderTransform>
  40:                                         <Border.Background>
  41:                                             <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  42:                                                 <GradientStop Color="#FFFFFFFF"/>
  43:                                                 <GradientStop Color="#FF71A7BB" Offset="1"/>
  44:                                             </LinearGradientBrush>
  45:                                         </Border.Background>
  46:                                             <Image
  47:                                             Source="{Binding Image.ThumbnailURL , Converter={StaticResource imagesourceconverter}}"
  48:                                             ImageFailed="Image_ImageFailed"
  49:                                             Cursor="Hand" Opacity="0.7"
  50:                                             Margin="1,1,1,1"/>
  51:                                 </Border>
  52:                             </DataTemplate>
  53:                         </ItemsControl.ItemTemplate>
  54:                     </ItemsControl>
  55:                 </DataTemplate>
  56:             </ItemsControl.ItemTemplate>
  57:         </ItemsControl>
  58:     </Grid>
  59: </UserControl>

Here is the new code behind for ImageGridView

   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Windows.Media.Imaging;
   5: using System.Windows.Media;
   6: using System.ComponentModel;
   7: 
   8: namespace Search
   9: {
  10:     public partial class ImageGridView : UserControl, INotifyPropertyChanged
  11:     {
  12:         #region Public Members
  13: 
  14:         //implementation of INotifyPropertyChanged
  15:         public event PropertyChangedEventHandler PropertyChanged;
  16: 
  17:         //public constructor
  18:         public ImageGridView()
  19:         {
  20:             // Required to initialize variables
  21:             InitializeComponent();
  22:             _tileinfo = this.Resources["TileInfo"] as Tile;
  23:         }
  24: 
  25:         //height of each tile
  26:         public double TileHeight
  27:         {
  28:             get { return _tileinfo.TileHeight; }
  29:             set { _tileinfo.TileHeight = value; }
  30:         }
  31: 
  32:         //width of each tile
  33:         public double TileWidth
  34:         {
  35:             get { return _tileinfo.TileWidth; }
  36:             set { _tileinfo.TileWidth = value; }
  37:         }
  38: 
  39:         //padding around each tile applied as uniform thickness
  40:         public Double TilePadding
  41:         {
  42:             get { return _tileinfo.TilePadding.Top; }
  43:             set
  44:             {
  45:                 //converts double value to uniform thickness value
  46:                 _tileinfo.TilePadding = new Thickness((double)value);
  47:             }
  48:         }
  49: 
  50:         //selected image
  51:         public Image SelectedImage
  52:         {
  53:             get { return _selectedborder.Child as Image; }
  54:
  55:         }
  56:         #endregion
  57: 
  58:         #region Private Members
  59:         //handle to tile settings
  60:         private Tile _tileinfo;
  61: 
  62:         //tracks if the mouse button is down before handling mouse button up
  63:         private bool _leftmousebuttondown = false;
  64: 
  65:         //tracks the previously selected item
  66:         private Border _selectedborder = null;
  67: 
  68:         //if iamge fails to load then placeholder image is shown
  69:         private void Image_ImageFailed(object sender, ExceptionRoutedEventArgs e)
  70:         {
  71:             ((Image)sender).Source = new BitmapImage(new Uri("notfound.jpg", UriKind.Relative));
  72:         }
  73: 
  74:         //on mouse enter, increase the size of image and set opacity to 1
  75:         private void Border_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
  76:         {
  77:             //hover effect
  78:             Border b = (Border)sender;
  79:             b.Child.Opacity = 1;
  80:             TransformGroup tg = b.RenderTransform as TransformGroup;
  81:             ScaleTransform st = tg.Children[0] as ScaleTransform;
  82:             st.ScaleX = 1.1;
  83:             st.ScaleY = 1.1;
  84:         }
  85: 
  86:         //on mouse leave, reset the size of image and set opacity to 0
  87:         private void Border_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
  88:         {
  89:             //reset hover effect
  90:             Border b = (Border)sender;
  91:             b.Child.Opacity = 0.7;
  92:             TransformGroup tg = b.RenderTransform as TransformGroup;
  93:             ScaleTransform st = tg.Children[0] as ScaleTransform;
  94:             st.ScaleX = 1;
  95:             st.ScaleY = 1;
  96: 
  97:             //reset the left mouse button flag if set
  98:             _leftmousebuttondown = false;
  99: 
 100:         }
 101: 
 102:         //tracks if the left mouse is down over the border
 103:         private void Border_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
 104:         {
 105:             _leftmousebuttondown = true;
 106: 
 107:         }
 108: 
 109:         //handles as click and sets the visual cue for selected items. 
 110:         private void Border_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
 111:         {
 112:             if (_leftmousebuttondown == true)
 113:             {
 114:                 //reset the border of previously selected border
 115:                 if (_selectedborder != null)
 116:                 {
 117:                     _selectedborder.BorderThickness = new Thickness(2);
 118:                     _selectedborder.BorderBrush = new SolidColorBrush(Colors.Black);
 119:                 }
 120: 
 121:                 //set the new selected border
 122:                 _selectedborder = (Border)sender;
 123:                 _selectedborder.BorderThickness = new Thickness(4);
 124:                 _selectedborder.BorderBrush = new SolidColorBrush(Colors.Orange);
 125:                 NotifyPropertyChanged("SelectedImage");
 126:             }
 127:         }
 128: 
 129:         //Implementation of INotifyPropertyChanged, it is only used for selected item property
 130:         private void NotifyPropertyChanged(String info)
 131:         {
 132:             if (PropertyChanged != null)
 133:             {
 134:                 PropertyChanged(this, new PropertyChangedEventArgs(info));
 135:             }
 136:         }
 137: 
 138:         #endregion
 139: 
 140:     }
 141: }
Page

To consume this control in the application, I needed to add it in Page.xaml. I added following line of XAML to the root grid in Page.Xaml.

   1: <control:DetailImage Name="detailImage"
   2:                              Margin="10,50,10,10" VerticalAlignment="Top"
   3:                              Grid.Column="2" Grid.Row="2"
   4:                              d:LayoutOverrides="HorizontalAlignment, Height"
   5:                              Visibility="Collapsed"/>

Here are the changes to code

   1: public Page()
   2: {
   3:             InitializeComponent();
   4:             _searchresultcollectioncollection = new ObservableCollection<SearchResultCollection>();
   5:             _imagegridview = LayoutRoot.FindName("imageGrid") as ImageGridView;
   6:             _gridscroll = LayoutRoot.FindName("gridscroll") as ScrollViewer;
   7:             _imagegridview.DataContext = _searchresultcollectioncollection;
   8:             _progress = LayoutRoot.FindName("SearchProgressAnimation") as Storyboard;
   9:             _makesearchresultsvisible = LayoutRoot.FindName("MakeSearchResultVisible") as Storyboard;
  10:             _progress.Completed += new EventHandler(_progress_Completed);
  11: 
  12:             _detailimage = LayoutRoot.FindName("detailImage") as DetailImage;
  13:             _imagegridview.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_imagegridview_PropertyChanged);
  14:
  15: }
   1: //Property change for selected image to set the data context on detail image
   2: private void _imagegridview_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
   3: {
   4:     ImageGridView iamgegrid = sender as ImageGridView;
   5:     Search.SearchService.Result selectedresult = iamgegrid.SelectedImage.DataContext as Result;
   6:     _detailimage.SearchResult = selectedresult;
   7:     _detailimage.Visibility = Visibility.Visible;
   8: }

Here is the link to working project. 

Advertisements
Standard

5 thoughts on “Part 4: Image Detail View

  1. Hello, I think your site might be having browser compatibility issues.

    When I look at your blog in Safari, it looks fine but when opening in
    Internet Explorer, it has some overlapping. I just wanted to give you
    a quick heads up! Other then that, great blog!

  2. I almost never drop comments, however i did a few searching and wound up here Part 4: Image Detail View | Desperately
    Seeking Love of Sophie. And I actually do have a few questions for you if you tend not
    to mind. Could it be simply me or does it look like a few of the responses look like coming
    from brain dead visitors? 😛 And, if you are writing on additional social sites, I would like to keep
    up with everything new you have to post. Could you list of every one of
    your social sites like your Facebook page, twitter feed, or linkedin profile?

  3. Hello! This is my first visit to your blog! We are a collection of volunteers and starting a new initiative in a community in the same niche.
    Your blog provided us beneficial information to work on.

    You have done a outstanding job!

  4. I am now not certain the place you are getting your info, but good topic. I needs to spend a while finding out much more or working out more. Thanks for wonderful information I was looking for this info for my mission.

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