Introduction
A while ago, I spent considerable time scouring the net, looking for my perfect photo gallery. I tried the different solutions already available on the net, but for one reason or another, I was unable to fit them to my needs.
- I needed a photo gallery that "just works." All you need to do is plunk a couple of pictures in a folder and that's that.
- The gallery also needed to be aesthetically pleasing.
- The gallery should be easy to use.
I decided to give it a try myself. Within a weekend, I believe, I created something that fit all my needs. Hopefully it will fit yours, too.
Step 1
The first step is to create a "New Website..." project in Visual Studio .NET 2005. After the creation is complete, you will have a Default.aspx file and a web.config file. Create a folder PicStore in the root. This is the folder that will hold your galleries.
Inside the PicStore folder, create a Default sub-folder. We are creating this to denote the default set of galleries. This is for the future, when I plan to expand the application to include the galleries of my friends. They will reside in other folders, but the Default set of galleries will be loaded when the application is first browsed by the user.
Inside the Default folder, create your galleries. I suggest, for the sake of the article, creating Photogallery 1, Photogallery 2, etc. In each of these galleries, create a pics folder. This folder will hold your pictures.
Note:
- Only JPG file format is accepted.
- Photos have to be named in the pattern 001.JPG, 002.JPG, 003.JPG, ...
- If you want to include a title for any photo, rename it to the pattern 00X~Title_With_Underscores_For_Spaces.JPG, where X is the number of the photo.
The gallery has been designed to neatly show pictures that are a maximum of 750 pixels wide. You can, of course, put bigger or smaller pictures, but you then need to adjust the background accordingly. I would suggest to keep all the pictures of the same maximum width. You could use the free tool Irfanvie to batch format all your images to a particular size. There is an option in "Batch conversion> Advanced options" to set the long side of all images to a particular size. This fitted my requirements perfectly.
Optionally, you could also create a sibling folder thumbs to the pics folder, if you want the users to be able to see the thumbnails of your pictures instead of the image names in the navigation. I would suggest that the long side of your thumbnails should be limited to 100 pixels.
There are some images like arrow2.gif, bg.gif, etc. provided in my sample code. You could download these and put them in the appropriate folders in the project root, or alternatively, remove references to these from the code.
The final thing that we have to do is add the keys PicRootPath
and PicRootDefaultPath
to the web.config, as follows:
<appSettings>
<add key="PicRootPath" value="~/PICStore/"/>
<add key="PicRootDefaultPath" value="~/PicStore/Default/" />
</appSettings>
That was the preparation of the project. Now, we will add code for the gallery.
Step 2
Now, we create an App_Code folder in the project, if it doesn't exist already. Inside this, we will create a class ContentInfoLoader
. This class will be responsible for loading information about the galleries and the photos for our application. The constructor of our class will load the root path of the galleries and the default set of galleries. Following is the code for this:
Configuration rootWebConfig =
WebConfigurationManager.OpenWebConfiguration("~/");
if(0<rootWebConfig.AppSettings.Settings.Count)
{
KeyValueConfigurationElement picRootElement =
rootWebConfig.AppSettings.Settings["PicRootPath"];
if(null!=picRootElement)
{
_picRootPath=picRootElement.Value;
}
picRootElement = rootWebConfig.AppSettings.Settings["PicRootDefaultPath"];
if (null != picRootElement)
{
_picRootDefaultPath = picRootElement.Value;
}
}
The paths are stored in private variables which can be accessed via the readonly properties. Now we create a simple method to get the list of galleries available in our set. Following is the code for the same:
public string[] GetGalleryPaths(string picRootRealPath)
{
if (Directory.Exists(picRootRealPath))
{
return Directory.GetDirectories(picRootRealPath);
}
else
{
return null;
}
}
As you can see, the method takes the path for the root of the pictures (it could be the default one or any other) and it returns a string
array containing the gallery paths. Note that these are physical paths. We need to create another method to get a list of photos in each of the galleries currently selected. The following is the code that does this:
public string[] GetPhotoList(string galleryName, string picRootRealPath)
{
string galleryPath=picRootRealPath + "\\" + galleryName + "\\pics";
if (Directory.Exists(galleryPath))
{
return Directory.GetFiles(galleryPath,"*.JPG");
}
else
{
return null;
}
}
Note that this method takes the name of the gallery and the root path. It returns a list of photos with their physical paths.
Step 3
Now we create a master page to go with our gallery application page.
First, I would suggest deleting the Default.aspx file that was automatically created by the project. Next, right click on the project in the Solution Explorer and click on "Add New Item..." Choose "Master Page" and name it SuperMasterPage.master. Add the following JavaScript code to your master page, just after the body
tag:
<script type="text/javascript">
var offsetfromcursorX=12
var offsetfromcursorY=10
var offsetdivfrompointerX=10
var offsetdivfrompointerY=7
document.write('<div id="dhtmltooltip"></div>')
document.write('<img id="dhtmlpointer" ' +
'src="http://www.codeproject.com/SourceCode/images/arrow2.gif">')
var ie=document.all
var ns6=document.getElementById && !document.all
var enabletip=false
if (ie||ns6)
var tipobj=document.all? document.all["dhtmltooltip"] :
document.getElementById?
document.getElementById("dhtmltooltip") : ""
var pointerobj=document.all? document.all["dhtmlpointer"] :
document.getElementById?
document.getElementById("dhtmlpointer") : ""
function ietruebody()
{
return (document.compatMode && document.compatMode!="BackCompat")?
document.documentElement : document.body
}
function ddrivetip(thetext, thewidth, thecolor)
{
if (ns6||ie)
{
if (typeof thewidth!="undefined")
tipobj.style.width=thewidth+"px"
if (typeof thecolor!="undefined" && thecolor!="")
tipobj.style.backgroundColor=thecolor
tipobj.innerHTML=thetext
enabletip=true
return false
}
}
function positiontip(e)
{
if (enabletip)
{
var nondefaultpos=false
var curX=(ns6)?e.pageX : event.clientX+ietruebody().scrollLeft;
var curY=(ns6)?e.pageY : event.clientY+ietruebody().scrollTop;
var winwidth=ie&&!window.opera? ietruebody().clientWidth :
window.innerWidth-20
var winheight=ie&&!window.opera?
ietruebody().clientHeight : window.innerHeight-20
var rightedge=ie&&!window.opera?
winwidth-event.clientX-offsetfromcursorX :
winwidth-e.clientX-offsetfromcursorX
var bottomedge=ie&&!window.opera?
winheight-event.clientY-offsetfromcursorY :
winheight-e.clientY-offsetfromcursorY
var leftedge=(offsetfromcursorX<0)?
offsetfromcursorX*(-1) : -1000
if (rightedge<tipobj.offsetWidth)
{
tipobj.style.left=curX-tipobj.offsetWidth+"px"
nondefaultpos=true
}
else if (curX<leftedge)
tipobj.style.left="5px"
else
{
tipobj.style.left=curX+offsetfromcursorX-
offsetdivfrompointerX+"px"
pointerobj.style.left=curX+offsetfromcursorX+"px"
}
if (bottomedge<tipobj.offsetHeight)
{
tipobj.style.top=curY-tipobj.offsetHeight-
offsetfromcursorY+"px"
nondefaultpos=true
}
else
{
tipobj.style.top=curY+offsetfromcursorY+
offsetdivfrompointerY+"px"
pointerobj.style.top=curY+offsetfromcursorY+"px"
}
tipobj.style.visibility="visible"
if (!nondefaultpos)
pointerobj.style.visibility="visible"
else
pointerobj.style.visibility="hidden"
}
}
function hideddrivetip()
{
if (ns6||ie)
{
enabletip=false
tipobj.style.visibility="hidden"
pointerobj.style.visibility="hidden"
tipobj.style.left="-1000px"
tipobj.style.backgroundColor=''
tipobj.style.width=''
}
}
document.onmousemove=positiontip
</script>
This JavaScript code will be used to display a tooltip for showing the gallery names and thumbnails.
Note: Wherever you see "/SourceCode/" in the code, please replace it with whatever is the "virtual directory" in which you have created your application. If you have created it in the root of your machine website, remove the /SourceCode/ where necessary.
Step 4
Now we need to create our actual gallery display page so that it can use the class that we created above. Right click on your project and click on "Add New Item..." Choose a "Web Form" and make sure that the checkboxes "Place code in a separate file" and "Select master pages" are checked. Open the PicViewer.aspx source. You will see an asp:content
tag. Inside this tag, place the following code:
<table width="760px" cellpadding="1" cellspacing="0">
<tr>
<td colspan="5"><asp:PlaceHolder ID="galleryPlaceholder"
runat="server"></asp:PlaceHolder>
</td>
</tr>
<tr>
<td bgcolor="#494949" width="700px">
<asp:Label ID="lblGalleryName" runat="server"
CssClass="SubHeadingWhite">▼ How to use</asp:Label>
</td>
<td bgcolor="#494949"></td>
<td bgcolor="#494949"><asp:Label ID="lblPhotoName"
runat="server" CssClass="SubTitleWhite"></asp:Label></td>
</tr>
<tr>
<td align="center" colspan="5"><asp:Image ID="galleryImage"
runat="server" BorderStyle="Solid"
BorderWidth="5px" BorderColor="Black" />
</td>
</tr>
</table>
The above code places a placeholder called galleryPlaceholder
, to which we will add the gallery and photo navigation bars later. Apart from that, you can also see the label, which will show the title of the picture if any, or else shows the number of the picture.
When the PicViewer page is loaded, we load the navigation of the galleries. If this is the first time (no gallery is being displayed), we show the Howtouse.jpg instruction photo. If this is not the first time, i.e. the user has clicked on a gallery, we show the first picture. If the user has clicked on a picture, we show the picture and the title. Following is the code of the Page_Load
subroutine:
protected void Page_Load(object sender, EventArgs e)
{
ContentInfoLoader cil = new ContentInfoLoader();
string galleriesPath = "";
if (Session["PicRootPath"] != null &&
Session["PicRootPath"].ToString() != "")
{
galleriesPath = Session["PicRootPath"].ToString();
}
else
{
galleriesPath = cil.PicRootDefaultPath;
Session["PicRootPath"] = galleriesPath;
}
LoadGalleriesNav(galleriesPath,cil);
if (Request["gallery"] != "" && Request["gallery"] != null)
{
lblGalleryName.Text = Request["gallery"];
Int32 photoCount =
LoadPhotosNav(Request["gallery"], galleriesPath, cil);
string photoName;
if (Request["photo"] == "" || Request["photo"] == null)
{
photoName = cil.GetPhotoList(Request["gallery"],
Server.MapPath(galleriesPath))[0];
photoName = photoName.Substring(photoName.LastIndexOf("\\"));
}
else
{
photoName = Request["photo"];
}
char[] charSeparators = new char[] { '~', '.' };
if (photoName.Contains("~"))
{
string photoTitle = photoName.Split(charSeparators)[1];
lblPhotoName.Text = photoTitle.Replace("_", " ");
}
else
{
lblPhotoName.Text = photoName.Split(charSeparators)[0];
}
galleryImage.ImageUrl = galleriesPath +
Request["gallery"] + "/pics/" + photoName;
}
else
{
galleryImage.ImageUrl = galleriesPath + "Howtouse.jpg";
}
}
The navigation for the gallery and photos are basically unordered lists formatted using CSS. There are some major differences between the gallery list and the photos list, apart from the obvious size, shape and color:
- Gallery list, when we mouse-over, we see the name of the gallery.
- Photo list, when we mouse-over, we see the title if the thumbs directory doesn't exist, else we see the thumbnail and the title.
In both navigations, if a particular gallery is selected or a particular gallery and a photo is selected, those items are grayed out and cannot be selected. Apart from this, most of the code is quite simple and self-explanatory. Please let me know if further explanation is required. Following is the code:
public void LoadGalleriesNav(string galleriesPath, ContentInfoLoader cil)
{
string[] galleryPathList = new string[1];
if (galleriesPath != "")
{
galleryPathList =
cil.GetGalleryPaths(Server.MapPath(galleriesPath));
}
HtmlGenericControl blstGalleries = new HtmlGenericControl("ul");
blstGalleries.Attributes.Add("id", "navlist_a");
foreach (string galleryPath in galleryPathList)
{
string galleryName =
galleryPath.Substring(galleryPath.LastIndexOf("\\") + 1);
HtmlGenericControl galleryListItem = new HtmlGenericControl("li");
HtmlAnchor galleryAnchor = new HtmlAnchor();
if (Request["gallery"] != "" && Request["gallery"] != null)
{
if (Request["gallery"].ToUpper() != galleryName.ToUpper())
{
galleryAnchor.Attributes.Add("onMouseOver", "ddrivetip('" +
galleryName.ToUpper() + "', " +
galleryName.Length * 7 + ")");
galleryAnchor.Attributes.Add("onMouseOut", "hideddrivetip()");
galleryAnchor.HRef = "PicViewer.aspx?gallery=" + galleryName;
galleryAnchor.InnerHtml = "<em></em>";
}
else
{
galleryAnchor.Disabled = true;
galleryAnchor.InnerHtml = "<em style='border-top" +
":1em solid #696969'></em>";
}
}
else
{
galleryAnchor.Attributes.Add("onMouseOver", "ddrivetip('" +
galleryName.ToUpper() + "', " +
galleryName.Length * 7 + ")");
galleryAnchor.Attributes.Add("onMouseOut", "hideddrivetip()");
galleryAnchor.HRef = "PicViewer.aspx?gallery=" + galleryName;
galleryAnchor.InnerHtml = "<em></em>";
}
galleryListItem.Controls.Add(galleryAnchor);
blstGalleries.Controls.Add(galleryListItem);
}
galleryPlaceholder.Controls.Add(blstGalleries);
}
public Int32 LoadPhotosNav(string galleryName,
string galleriesPath, ContentInfoLoader cil)
{
string[] photoList = new string[1];
if (galleriesPath!= "")
{
photoList = cil.GetPhotoList(galleryName,
Server.MapPath(galleriesPath));
}
if (photoList != null && photoList.Length != 0)
{
HtmlGenericControl blstPhotos = new HtmlGenericControl("ul");
blstPhotos.Attributes.Add("id", "navlist_b");
foreach (string iPhotoName in photoList)
{
string photoName;
photoName =
iPhotoName.Substring(iPhotoName.LastIndexOf("\\") + 1);
HtmlGenericControl photoListItem = new HtmlGenericControl("li");
HtmlAnchor photoAnchor = new HtmlAnchor();
string originalPhotoName = photoName;
char[] charSeparators = new char[] { '~','.' };
string photoTitle = "";
if (photoName.Contains("~"))
{
photoTitle = photoName.Split(charSeparators)[1];
photoName = photoName.Split(charSeparators)[0] + ".JPG";
}
if (Request["photo"] != "" && Request["photo"] != null)
{
if (Request["photo"].ToUpper() != photoName.ToUpper())
{
if (Directory.Exists(Server.MapPath(galleriesPath +
galleryName + "/thumbs/")))
{
photoAnchor.Attributes.Add("onMouseOver",
"ddrivetip('<img src=\\'" +
ResolveUrl(galleriesPath) + galleryName +
"/thumbs/" + photoName +
"\\' /><br>" +
photoTitle.Replace("_", " ") + "', " +
photoTitle.Length + ")");
}
else
{
if (photoTitle != "")
{
photoAnchor.Attributes.Add("onMouseOver",
"ddrivetip('" + photoTitle.Replace("_",
" ") + "', " + photoTitle.Length + ")");
}
else
{
photoAnchor.Attributes.Add("onMouseOver",
"ddrivetip('" + photoName + "', " +
photoName.Length + ")");
}
}
photoAnchor.Attributes.Add("onMouseOut",
"hideddrivetip()");
photoAnchor.HRef = "PicViewer.aspx?gallery=" +
galleryName + "&photo=" + originalPhotoName;
photoAnchor.InnerHtml = "<em></em>";
}
else
{
photoAnchor.Disabled = true;
photoAnchor.InnerHtml = "<em style='border-top" +
":0.5em solid #696969'></em>";
}
}
else
{
if (Directory.Exists(Server.MapPath(galleriesPath +
galleryName + "/thumbs/")))
{
photoAnchor.Attributes.Add("onMouseOver",
"ddrivetip('<img src=\\'" +
ResolveUrl(galleriesPath) +
galleryName + "/thumbs/" + photoName +
"\\' /><br>" + photoTitle.Replace("_",
" ") + "', " + photoTitle.Length + ")");
}
else
{
if (photoTitle != "")
{
photoAnchor.Attributes.Add("onMouseOver",
"ddrivetip('" +
photoTitle.Replace("_", " ") + "', " +
photoTitle.Length + ")");
}
else
{
photoAnchor.Attributes.Add(
"onMouseOver", "ddrivetip('" +
photoName + "', " + photoName.Length + ")");
}
}
photoAnchor.Attributes.Add("onMouseOut", "hideddrivetip()");
photoAnchor.HRef = "PicViewer.aspx?gallery=" + galleryName +
"&photo=" + originalPhotoName;
photoAnchor.InnerHtml = "<em></em>";
}
photoListItem.Controls.Add(photoAnchor);
blstPhotos.Controls.Add(photoListItem);
}
galleryPlaceholder.Controls.Add(blstPhotos);
return photoList.Length;
}
else
{
return 0;
}
}
The application is now ready for testing.
How to Install the Demo/Source Code
Please note that you have to have .NET Framework 2.0 installed, and your website should be set to use ASP.NET 2.0 (IIS website properties).
- Create a virtual directory SourceCode on your IIS.
- Extract the contents of demo/sourcecode in the corresponding physical folder.
Known Issues
Among the known issues are the following:
- You cannot add more than 999 photos in a gallery.
- When the gallery is loaded, the navigation square for the first photo is not grayed out.
- You cannot provide a description of the photo, only a title.
- There is no user-friendly photo upload utility.
I am sure there are other things that need to be modified or some other functionality that needs to be added. Please let me know.
And Finally...
As I upgrade the application, I will also upgrade this article. So, watch this space. Feel free to send me your comments on this article. Please let me know whether the article was helpful to you or whether some improvement is needed.