C# Photo Album Viewer
This sample is a C# Windows application to demonstrate storing and retrieving of images from a database.
Although, this article and code has been updated to support .NET 4.0 and SQL Server 2012 it was previously build for .NET 1.0 and SQL Server 2000 so all methods and techniques should work in previous versions of those applications with little or no modifications.
The Database Tables and Stored Procedures
The download file contains the script to create the database and stored procedures necessary for this demo application.
Initial Load
After the main form for this application is initialized the first thing necessary is to obtain any existing Albums and Photos and load them in the TreeView.
private void LoadAlbums()
{
ReadOnlyCollection<album> albums = Data.GetPhotoAlbums();
foreach(Album album in albums)
{
TreeNode albumNode = new TreeNode(album.Name);
albumNode.Tag = album;
treeAlbums.Nodes.Add(albumNode);
foreach(Photo photo in album.Photos)
{
TreeNode photoNode = new TreeNode(photo.Name);
photoNode.Tag = photo;
albumNode.Nodes.Add(photoNode);
}
}
}
This is fairly straight forward. The TreeNode Tag object is being set to the struct so that later when selecting and item there is no additional database calls necessary to populate the display controls.
Obtaining the via the Data.GetPhotoAlbums
method is, again, a straight forward ADO.NET implementation.
public static ReadOnlyCollection<album> GetPhotoAlbums()
{
List<album> albums = new List<album>();
using(SqlConnection conn = new SqlConnection(ConnectionString))
{
using(SqlCommand cmd = new SqlCommand("GetAlbums", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
conn.Open();
using(SqlDataReader reader = cmd.ExecuteReader())
{
while(reader.Read())
{
albums.Add(new Album()
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Description = reader.GetString(2)
}
);
}
}
}
using(SqlCommand cmd = new SqlCommand("GetPhotosByAlbum", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add("@albumId", SqlDbType.Int);
for(int x = 0; x < albums.Count; x++)
{
cmd.Parameters["@albumId"].Value = albums[x].Id;
List<photo> photos = new List<photo>();
using(SqlDataReader reader = cmd.ExecuteReader())
{
while(reader.Read())
{
photos.Add(new Photo()
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Description = reader.GetString(2),
Image = (byte[])reader.GetValue(3)
}
);
}
}
Album temp = albums[x];
temp.Photos = photos.AsReadOnly();
albums[x] = temp;
}
}
}
return albums.AsReadOnly();
}
The first step is to initialize the SqlConnection
and SqlCommand
objects. Then execute the store procedure and use a SqlDataReader
to iterate through the results and create a collection of Albums to return. After this the Photos are added to each Album in a similar manner. As noted, this could be done with one store procedure, however, to illustrate the process it has been broken in to two methods.
Storing the images
After selecting the Add Photo context menu item a multi select FileOpenDialog
allows images to be selected for addition. For each file a System.IO.FileStream
is created and used to read into a byte
array. The byte
array is then passed to a method which uses it as an input parameter for a stored procedure to add it to an image field of the database table.
The last step is to add the image to the treeview
. A helper class call TreeItem
is used for this purpose. This class stores the description and database index id of the image. This class is also used for albums so an enum defines the type of object it is. After a new instance of this class is created it is assigned to the Tag
member of the TreeNode
. The node is then added to the selected album node.
private void OnAddPhoto(object sender, EventArgs e)
{
if(DialogResult.OK == openFileDialog1.ShowDialog())
{
Album album = (Album)treeAlbums.SelectedNode.Tag;
foreach(string file in openFileDialog1.FileNames)
{
System.IO.FileStream stream = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read);
byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, (int)stream.Length);
stream.Close();
Photo photo = new Photo()
{
Name = System.IO.Path.GetFileNameWithoutExtension(file),
Image = buffer
};
Data.AddPhoto(album.Id, photo);
buffer = null;
TreeNode node = treeAlbums.SelectedNode.Nodes.Add(photo.Name);
node.Tag = photo;
}
}
}
public static void AddPhoto(int albumId, Photo photo)
{
using(SqlConnection conn = new SqlConnection(ConnectionString))
{
using(SqlCommand cmd = new SqlCommand("InsertPhoto", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
conn.Open();
cmd.Parameters.AddWithValue("@name", photo.Name);
cmd.Parameters.AddWithValue("@desc", photo.Description);
cmd.Parameters.AddWithValue("@photo", photo.Image);
cmd.Parameters.AddWithValue("@albumId", albumId);
SqlParameter param = cmd.Parameters.Add("RETURN_VALUE", SqlDbType.Int);
param.Direction = ParameterDirection.ReturnValue;
cmd.ExecuteNonQuery();
photo.Id = (int)cmd.Parameters["RETURN_VALUE"].Value;
}
}
}
Display the image
The AfterSelect event captures the selection of an image or album. If an images is selected the Tag
member of the selected node is retrieved and cast into a TreeItem
. The Id member of this class is then passed to a stored procedure to retrieve the desired image. The return of the ExecuteScalar
method is cast into a byte array which is then read into a System.IO.MemoryStream
. This stream object is then used to create a Bitmap
which is then used assigned to the picturebox for display.
private void AfterSelect(object sender, TreeViewEventArgs e)
{
DisplayPanel.Visible = true;
if(treeAlbums.SelectedNode.Tag is Album)
{
Album album = (Album)treeAlbums.SelectedNode.Tag;
DisplayName.Text = album.Name;
DisplayDescription.Text = album.Description;
pictureBox.Image = null;
}
else if(treeAlbums.SelectedNode.Tag is Photo)
{
Photo photo = (Photo)treeAlbums.SelectedNode.Tag;
DisplayName.Text = photo.Name;
DisplayDescription.Text = photo.Description;
System.IO.MemoryStream stream = new System.IO.MemoryStream(photo.Image, true);
stream.Write(photo.Image, 0, photo.Image.Length);
DrawToScale(new Bitmap(stream));
}
else
{
DisplayPanel.Visible = false;
}
}
Conclusion
This is a relatively simple implementation of a photo album viewer application. Using .NET and C# definitely made this application very easy to make. Storing the images in a SQL Server database adds a bit to the distribution but also simplifies the application by having one source to store and retrieve images from. You also don't have to worry about missing files or broken links.
Updates
4/1/2012 - Updated to .NET 4.0, SQL Server 2012 and apply lessons learned learned for last 10 years.