Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Filmstrip or CatalogView Photo Gallery Sample

4.88/5 (53 votes)
4 Nov 2005CPOL10 min read 1   7K  
A photo images gallery browsing template - CatalogView style.

Sample Image

Introduction

This article demonstrates a photo gallery template, similar to the Windows XP filmstrip folder view with Slideshow features and "on-fly" thumbnail image generation during run-time. As an option there is a demonstration of how to use the basic XMLHttpRequest to get the required data from a server through an HttpHandler. A demo is located here.

Background

There are many photo galleries available throughout the web, but to my surprise, I wasn't able to find what I needed exactly, for a small project. Most of the photo albums show a thumbnail gallery similar to the "Icon" view, which usually takes a lot of screen space when you have a number of images. Anyway, my client had asked to show images as a thumbnail "strip" with scrolling through them and having large images with some details below, and it gave me a reason to start developing this. And in my opinion, this type of a gallery suits well, for example, to show a catalog of items, etc.

The main idea here is to show small images from the gallery in one row with scrolling back and forth, and put one large image below, something similar to the WinXP folder “Filmview”, or similar to a catalog view. When a user clicks on a small image, he gets a large image and some details about the product, item or photo, that is pulled from a database or a data file. (It is a different story to link data with images.)

Using the code

The page has the following controls: a Datalist with HyperLink items to show the folders list and navigate between galleries. It binds an array of folders that have photos/images. The name for the main photo folder is set in the Web.config file:

XML
<add key="photofolder" value="photo"/>

When the page is loaded, the Datalist is filled with subdirectory names from the main photo folder, and each DataItem has links to the proper folders. A subfolder name is added to TargetUrl as a parameter. For example, a URL to switch our page to the desired folder looks like PhotoGallery.aspx?G="foldername". If there is no parameter, the page will load the first gallery and the first image from it, using the GetPhotos() method.

So, we have our folder list from the main photo directory. What is next? We want to show only portions, for instance, 10 images, and indicate that there is more to show. I used a Repeater control for the reason that it is a simple list control, and I only need to have images listed within. The main challenge was to create “pages” for the Repeater control, but searching the Internet I found an article by Harrison Enholm. The main idea is to bind my array of addresses of images to the PagedDataSource first, set PageSize, PageNumber etc., and then, bind the Repeater to it. Simple, but effective.

Now, we can bind the data and use the ViewState to keep track of pages, etc. If there is only one page, the navigation control could be hidden or disabled, depending on client preference. I put some navigation control here like "Go to Page: #", "Go to last/first page" etc.

The GetPhotos() is the main method to get the desired images from a directory, based on the page parameters.

This method will iterate through the chosen directory (depending on the parameter ?G=""), and using the System.IO.Directory.GetFiles method, we will get a string array of image names. Having the names, we can use them as the source for image controls, link address for the large image etc. So then, we will use this array as the datasource for our Repeater control. At the same time, we will fill the DropDownList dlPages with page numbers, which we will use to jump to any page directly. This option is good when you have more than 10 pages, for example.

Within the Repeater, each item is a <img> with a src attribute:

ASP.NET
<a><img id=iPhoto runat=server 
  src='<%# "ThumbGen.ashx?path="+Container.DataItem%>' 
  title='<%# Container.DataItem %>' 
  width="69" height="90" style="cursor:hand" 
  class=photoover>
</a>
C#
private void GetPhotos()
{
    string photodir = 
      System.Configuration.ConfigurationSettings.AppSettings["photofolder"];
      //or get from config file

    string curGallery="";
    // use convention G is parameters to switch between galleries
    if (Request["G"]!=null)
    {
        curGallery=Request["G"]; //find where we are
    }
    else
    {
        curGallery=
          System.IO.Directory.GetDirectories(Server.MapPath(photodir))[0];
        //if no parameters show first directory

        string parent=(string) ViewState["parent"];
        curGallery=curGallery.Substring(parent.Length);
    }
    // try get all files in folder
    try
    {
        //get thumbnails and photos arrays in current directory
        string[] photos = System.IO.Directory.GetFiles(
                 Server.MapPath(photodir+"/"+curGallery),"*.jpg");
        // this array used if we have separate files as
        // thumbnail images. Convention here to add _thumb to file name
        
        //string[] thumb=System.IO.Directory.GetFiles(
        //   Server.MapPath("photo/"+curGallery),"*_thumb*");
        //get directory info itself
        int i=System.IO.Directory.GetParent(
              Server.MapPath(photodir)).FullName.Length;
        //
        //get files names/ relative path
        //just get actual files names for thumbnails and photos
        //just removing extra path, leaving only catalog and file name
        for (int ix=0; ix<photos.Length; ix++)
        {
            photos[ix]=photos[ix].Substring(i+1);
        }
        //ArrayList gallery=new ArrayList(photos);
        
        //find how many photos and how many pages to create pages
        int num=photos.Length;//it is number of photos
        //int num=thumb.Length; old usage
        lblPage.InnerText=num.ToString();
        if (num>9) imgbNext.Visible=true;
    
        //here we are using paged datasource
        PagedDataSource objPds = new PagedDataSource();
        //
        objPds.DataSource = photos; //we will show thumbnails
        
        // Indicate that the data should be paged
        objPds.AllowPaging = true;

        // Set the number of items you wish to display per page
        objPds.PageSize = 10;
        //fixed page size, other option 
        //to control through drop down box.

        objPds.CurrentPageIndex = CurrentPage;
        ViewState["Pages"]=objPds.PageCount;
        //fill pages values for dropdown list
        //do it only first time
        if (!Page.IsPostBack)
        {
            dlPages.Items.Clear();
            for (int ip=1;ip<=objPds.PageCount;ip++)
            {
                dlPages.Items.Add(ip.ToString());
            }
        }
        else
        {
            //list already there, so just set the right page
            //dlPages.SelectedIndex=CurrentPage;
        }

        lblPage.InnerText=" Page "+(CurrentPage+1).ToString() + 
                          " of "+objPds.PageCount.ToString();
        //control visibility of next/prev buttons
        imgbPrev.Enabled = !objPds.IsFirstPage;
        //imgbPrev.Visible=!objPds.IsFirstPage;
        imgbNext.Enabled = !objPds.IsLastPage;
        //imgbNext.Visible = !objPds.IsLastPage;

        repPhotoG.DataSource=objPds;
        //
        //DIV1.InnerText=thumb.
        //gallery.Add()
        repPhotoG.DataBind();
        //we also will use item data bind events 
        //to add some links to thumbnails

    }
    catch (Exception ex)
    {
        //do something with error
    }

    //imgLarge.Attributes["onfilterchange"]="fade()";
}

Initially, I created separate thumbnail images with some distinctive names and then parsed file names to select only thumbnails, but not large images from the directory, and bound the list with my Repeater template. However, logically, it seems better to generate them on the fly, to make life easer for the user. So, I used System.Drawing classes and the method getThumbnail to get small images. Now the image src attribute looks like “src=<% ''ThumbGen.ashx?path="+Container.DataItem %>”. ThumbGen.ashx is an HttpHandler, handling the creation of images on the fly. It is a fairly simple handler, its task is to create a small image and return a byte stream back using the image path as the input parameter. Some details are shown below:

C#
public class ThumbGen : IHttpHandler
{
    public void ProcessRequest (HttpContext context)
    {    
        if (context.Request["path"]!=null)
        //we have a pth try process image
        {
        
            string img=(string)context.Request["path"];
            //here is path for application dir
            string path1=context.Server.MapPath(context.Request.ApplicationPath);
            path1=context.Server.MapPath(img);
            //img=path1+"\\"+img; //full path to image
            
            img=path1;
            System.Drawing.Image.GetThumbnailImageAbort myCallback =
            new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback);
            
            //get bitmap from file
            Bitmap bitmap = new Bitmap(img);
            
            // process image, create thumbnail.
            // Try use to reduce size, instead of GetThubmnail
            ImageFormat imgformat=bitmap.RawFormat;
            //we will use fixed size for picture, in our case 69x90
            //but easy to add parameters such as width and calculate height
            /////////////////this code borrowed from Rick Strahl,
            //                        http://west-wind.com/weblog
            //works better than NET method 
            //GetThumbnail() when need transparent gif
            //Bitmap bmpOut=new Bitmap(70,90);
            //Graphics g=Graphics.FromImage(bmpOut);
            //g.InterpolationMode=System.Drawing.Drawing2D.
            //        InterpolationMode.HighQualityBicubic;
            //g.FillRectangle(Brushes.White,0,0,69,90);
            //g.DrawImage(bitmap,0,0,69,90);
            ///////////////////////////////////////////////////

            // ALternatively you can use 
            System.Drawing.Image imgOut=
              bitmap.GetThumbnailImage(70, 90, myCallback, IntPtr.Zero);
            bitmap.Dispose();
            
            //but seems creating Black background for transparent GIF's
            //////////////////return our image back to stream
            context.Response.ContentType="image/jpeg";
            imgOut.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            //bmpOut.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            //bmpOut.Dispose();
            imgOut.Dispose();
        }

I left commented code, that I used as well, so you could try different ways to generate thumbnails. I used a standard method available in the .NET framework. Another approach creates a white background first and then loads the bitmap on it. At the same time I use hard coded thumbnail image sizes, but again it could be passed as optional parameters to the handler or be calculated based on the image orientation (landscape or portrait). It is possible, for example, to add more parameters, like size, width or height for the thumbnail image etc. In my case, landscape images will look distorted. Finally each small image will be generated "on-the-fly" and returned back to the page.

In addition, there are some Microsoft filters for thumbnails and large photos. All thumbnail photos are shown in gray and becomes colored on mouse hover. Large images have a fade effect when they are changed. To avoid "flickering", the page transition effect is applied to the whole page, but it be might more appropriate to apply the transition to the thumbnail only. (Place to experiment.)

After we have got the thumbnail gallery, we still need to decide how to show large images. For this I am using the ItemDataBound event. Here we can find images within the Repeater, and then using string manipulation, we can convert absolute paths to relative (for example, instead of c:\\foldername\filename just /<virtualfolder>/<filename>) and add a JavaScript function OnClick for each image to show the large image. Relative image path names are passed to the JavaScript function:

C#
img.Attributes["onclick"]="showImg('"+IMGpath(img.Src)+"')";

Here is the JavaScript function:

JavaScript
function showImg(imgName) { 
  imgOn = ("" + imgName);
  document.imgLarge.src = imgOn;
  hdr.innerHTML=imgName; document.imgLarge.filters[0].Apply();
  document.imgLarge.filters[0].Play();
}

So, when the user clicks on a small image, the large image imgLarge's src attribute will be changed, and the full size photo will be shown.

At the same time, I decided to check the size of the first image and use this info to change the size of my large image to have a proportional picture. Initially, I had all my images in all the folders the same size, that was good just for me, but in real life, images could be of different sizes and orientations. In my case they should be the same size within one folder only. May be it is not the best way so far, somehow it is better to resize each image on the client side before assigning it for a large one.

For this purpose (to find the size of images), I used a Bitmap created from the first photo to get the width- to-height ratio and change the size of the large image proportionally.

C#
private void repPhotoG_ItemDataBound(object sender, 
        System.Web.UI.WebControls.RepeaterItemEventArgs e)
{
    //finding repeater item Image
    System.Web.UI.HtmlControls.HtmlImage img= 
       (System.Web.UI.HtmlControls.HtmlImage)e.Item.FindControl("iPhoto");
    //create a relative path for image and add onclick function
    img.Src=img.Src.Replace('\\', '/');
    img.Attributes["onclick"]="showImg('"+IMGpath(img.Src)+"')";
    
    //some visual effects using MS filters
    img.Attributes["onmouseover"]="this.className='thumb'";
    img.Attributes["title"]=img.Src;
    img.Attributes["onmouseout"]="this.className='photoover'";
    //here we are trying to get size of images 
    //within directiry to set a more proportional
    //thumbnail. The rule to have all images within folder the same size.
    if (e.Item.ItemIndex==0)
    {
        imgLarge.ImageUrl=IMGpath(img.Src); //show first photo in folder
        string path=Server.MapPath(imgLarge.ImageUrl);
        Bitmap newimg = new Bitmap(path);
        double ratio= (double)newimg.Width/(double)newimg.Height;
        lblName.InnerText="size:"+newimg.Width.ToString() 
                          +"-"+newimg.Height.ToString();
        lblName.InnerText=lblName.InnerText+"Resolution:"+
                          newimg.VerticalResolution.ToString();
        imgLarge.Style["Width"]=(imgLarge.Width.Value*ratio).ToString();
        newimg.Dispose();
    }
}

That's it. To use this gallery in real life just specify the main folder name, put PhotGallery.aspx on the web server, copy the bin folder, and create a subfolder with images, and don't forget to correct the Web.config file (add config keys and the HttpHandler section). The application will take care about others.

Updates (October 24,2005):

A slideshow feature is added. Again, perhaps there are a few different ways to create a slideshow. My intention was not to use any postback and do everything on client side. The idea behind slideshows is to have a list of images on the client side and then dynamically change our image.src property based on the current choice.

For this purpose, I put a hidden HtmlListBox on the page to keep a list of all image info within one folder. Pretty much simple. At the time when we are binding our Repeater on the server side, we bind the ListBox with image paths. When the page is returned to the client we will have the full list of images that we will play in the slideshow. After that we only need to add some scripting to control playback/stop and slideshow appearance. There is a set of different Java functions responsible for the slideshow. All the slideshow functionality is based on JavaScript/DHTML (file gallery.js).

How it works: clicking on Slideshow-Launch will activate the slideshow and will show the "slide control box" on the top of the image. (Previous/Next/Pause/ Full Size/Stop) For "Fullsize" functionality, we are using DTHML so we can change the layout and the position of elements. So when the user will click on the "FullSize" button, he will get an image on the middle (the position will be calculated using the client screen size).

To stop the slide show, for simplicity sake, I just used a regular ASP.NET button "postback", which will refresh a page and reload the gallery again. Otherwise we need to have a function opposite to "Full-Size" and return everything back. Yet another option could be a separate "full screen" window.

JavaScript
////////////////
//start slideshow, using list of images from list box
function SlideShow()
{
    document.imgLarge.filters[0].Duration=2.5;
    document.imgLarge.filters[0].Apply();
    //alert(document.getElementById("listImg").options[i].text);
    document.getElementById('hdr').innerText=
       document.getElementById("listImg").options[i].text;
    document.imgLarge.src=document.getElementById("listImg").options[i].text;
    document.imgLarge.filters[0].Play();
    if (i>=((document.getElementById("listImg").length)-1))
    {
        i=0;
    }
    else
    {
        i=i+1;
    }
}

"AJAX" way to create a slideshow:

Just to explore the AJAX technology the new option is added to HttmlHandler. If we will use the parameter "Dir'" (will look "ThumbGen.asxh?Dir=___"), which means the directory name where we want to run the "slideshow", the handler will return to the response stream XML document with all image names. We will use this to make a remote callback from the client side through an XtmlHTTP request and get the required list of images. The result we have is an array, but please note that we will convert the return stream to get an XML presentation of the data. This will give you more options to work with documents on client side, especially when you have data from a database. For example if we have a DataSet, we can use the method .GetXML() to get the XML format. Otherwise we simply make a call to ThumbGen.ashx with the required parameters and in return will get a "list" with photo names. Then we fill Arrays on the client side. That's it, we have a source for the slideshow and will use the same script as before to run it.

I tried this way just to see for myself how we can use the XmlHtml object and HtmlHandler. In a similar way we can request data from a database. On a page you could also find a hidden dropdown listbox, just for example, and try how we can fill it with data using a request. I am not using this for any other purpose. Tip: If we will not specify for example the width of an image, it will be taken proportional to the height automatically.

I believe there is a lot of room for improvement, but this just one template to build a gallery and a different example of Javascripts/DHTML, and the remote callback technology.

JavaScript
function ProcessResponse()
{
    if(req.readyState == 4)
    {
        if(req.status == 200) //analog "OK",
        {
        //here we are processing result,////////////////////////
        //
        var p=req.responseXML.documentElement;
        
        ///alert(req.responseXML); -testing purpose
        var ddlTest = document.getElementById("test");
        //var limg=document.getElementById("listImg");
        
        for (var count = ddlTest.options.length-1;count >-1; count--)
        {
            ddlTest.options[count] = null;
        }
        var photos = p.getElementsByTagName('photos');
        var text;
        //here we are trying to create array in memory

        //this example just to show how we can 
        //fill selection box from callback result.
        //After callback we got XMl document-table 
        //and we can use it to fill table/select/list etc.
        var listItem;
        var l=photos.length;
        for (var count = 0; count < photos.length; count++)
        {
            text = (photos[count].textContent ||
            photos[count].innerText || photos[count].text);
            listItem = new Option(text, text, false, false);
            ddlTest.options[ddlTest.length] = listItem;
        }
    }
    else
    {
        alert("Error retrieving data!" );
    }

    ////// this script responsible to fill Album array
    // by data returned from client in our case
    // it is a images pathes.
    
    for (i = 0; i<l; i++)
    {
        text2 = (photos[i].textContent ||
        photos[i].innerText || photos[i].text);
        Album[i]= new Image();
        Album[i].src = text2;
    }
    //now we have array with images. call slide show
    slide=0;
    //call slide show and show menu on top of image
    AjaxSlideShow();
    SlideMenu();

}
function AjaxSlideShow()
{
    StopSlide();

    if (slide>=Album.length)
    {
        slide=0;
    }

    document.imgLarge.filters[0].Duration=2.5;
    document.imgLarge.filters[0].Apply();
    document.imgLarge.src=Album[slide].src;
    document.imgLarge.filters[0].Play();
    var tm=document.getElementById("slidetime").value*1000
    oInterval=window.setTimeout("AjaxSlideShow()",tm);
    
    //nr=nr+1;
    slide++;
}

Points of Interest

I am thinking of extending the handler by adding some methods to get images from a database and tie the images with a data file or a database. I am still looking to improve thumbnail loading to avoid "flickering" and get a more smooth appearance. I also left a link to the "Slideshow" function: to play all the files from the directory - that is to be implemented. Also I noticed that filters are not working in the first place when a page is just loaded and starts working after the next click. Also I want to try converting this to a User Control with some external properties. There is always a way to add something, to improve. That's why I like programming, it is endless.

History

  • August 2005 - Basic project.
  • October 2005 - Rewritten for usability.
  • October 24, 2005- Slideshow feature added.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)