Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# Photo Album Viewer

0.00/5 (No votes)
1 Apr 2012 2  
A C# Windows application to store and retrieve photos from a SQL Server database.

Sample Image - AlbumViewer.jpg

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()
{
    // Get all albums, including photos, from the database
    ReadOnlyCollection<album> albums = Data.GetPhotoAlbums();

    // Now iterate through them and add to treeview
    foreach(Album album in albums)
    {
        TreeNode albumNode = new TreeNode(album.Name);
                
        // Add the album struct to the Tag for later
        // retrieval of info without database call
        albumNode.Tag = album;

        treeAlbums.Nodes.Add(albumNode);

        // Add each photo in album to treenode for the album
        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();

            // Use using here so SqlDataReader will be closed automatically
            using(SqlDataReader reader = cmd.ExecuteReader())
            {
                while(reader.Read())
                {
                    albums.Add(new Album()
                    {
                        Id = reader.GetInt32(0),
                        Name = reader.GetString(1),
                        Description = reader.GetString(2)
                    }
                    );
                }
            }
        }

        // Now get all the photos for each each album
        // This could be obtained by a single query with multiple
        // resultsets but for illustrative purposes it is broken
        // into two processes
        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>();
                // Use using here so SqlDataReader will be closed automatically
                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)
                            }
                        );
                    }
                }

                // Annoying because
                // albums[x].Photos = photos.AsReadOnly();
                // produces the error, Cannot modify the return value of xxx because it is not a variable
                // The error could be avoided by using class rather than struct
                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())
    {
        // Retrieve the Album to add photo(s) to
        Album album = (Album)treeAlbums.SelectedNode.Tag;

        // We allow multiple selections so loop through each one
        foreach(string file in openFileDialog1.FileNames)
        {
            // Create a new stream to load this photo into
            System.IO.FileStream stream = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read);
            // Create a buffer to hold the stream bytes
            byte[] buffer = new byte[stream.Length];
            // Read the bytes from this stream
            stream.Read(buffer, 0, (int)stream.Length);
            // Now we can close the stream
            stream.Close();

            Photo photo = new Photo()
            {
                // Extract out the name of the file an use it for the name
                // of the photo
                Name = System.IO.Path.GetFileNameWithoutExtension(file),
                Image = buffer
            };

            // Insert the image into the database and add it to the tree
            Data.AddPhoto(album.Id, photo);
            buffer = null;

            // Add the photo to the album node
            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();

                // Add the name parameter and set the value
            cmd.Parameters.AddWithValue("@name", photo.Name);
            // Add the description parameter and set the value
            cmd.Parameters.AddWithValue("@desc", photo.Description);
            // Add the image parameter and set the value
            cmd.Parameters.AddWithValue("@photo", photo.Image);
            // Add the album parameter and set the value
            cmd.Parameters.AddWithValue("@albumId", albumId);

            // Add the return value parameter
            SqlParameter param = cmd.Parameters.Add("RETURN_VALUE", SqlDbType.Int);
            param.Direction = ParameterDirection.ReturnValue;

            // Execute the insert
            cmd.ExecuteNonQuery();

            // Return value will be the index of the newly added photo
            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);

        // Draw photo to scale of picturebox
        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.  

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here