
C# Photo Album Viewer
This sample is a C# Windows application that uses a SQL Server 2000 database to store and retrieve images. Images are organized under albums in a treeview and displayed in a picturebox when selected. An static control is used to display a description of the album or image. Context menus on the treeview allow the user to add, rename, and delete images or albums. Descriptions can also be edited by selecting a context menu item.
The Database Tables
CREATE TABLE [dbo].[Albums] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[name] [varchar] (20) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[desc] [varchar] (200) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Photos] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[desc] [varchar] (200) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[album_id] [int] NOT NULL ,
[photo] [image] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
The Stored Procedures
CREATE PROC sp_GetPhotoAlbums AS
SELECT Albums.[id] AS AlbumID, Albums.[name] AS Album,
Albums.[desc] AS Album_Desc, Photos.[id] AS PhotoID,
Photos.[name] AS Photo, Photos.photo, Photos.[desc] AS Photo_Desc
FROM Albums INNER JOIN Photos ON Albums.[id] = Photos.album_id
ORDER BY Albums.[id]
GO
CREATE PROCEDURE sp_InsertPhoto
@name AS VARCHAR(50),
@image AS IMAGE,
@album AS INT
AS
INSERT INTO Photos ([name], photo, album_id)
VALUES (@name, @image, @album)
RETURN @@identity
GO
CREATE PROCEDURE sp_NewAlbum
@name AS VARCHAR(20)
AS
INSERT INTO Albums ([name])
VALUES (@name)
RETURN @@identity
GO
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, System.EventArgs e)
{
if( DialogResult.OK == FileOpenDlg.ShowDialog() )
{
TreeItem item = (TreeItem)treeAlbum.SelectedNode.Tag;
foreach( string file in FileOpenDlg.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();
string strName = System.IO.Path.GetFileNameWithoutExtension(file);
InsertImage(ref buffer, strName, item.Id);
buffer = null;
}
treeAlbum.SelectedNode = treeAlbum.SelectedNode.LastNode;
}
}
private void InsertImage(ref byte[] buffer, string strName, int nAlbum)
{
try
{
SqlCommand cmd = new SqlCommand("sp_InsertPhoto", sqlConn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter param = cmd.Parameters.Add("RETURN_VALUE", SqlDbType.Int);
param.Direction = ParameterDirection.ReturnValue;
cmd.Parameters.Add("@name", SqlDbType.VarChar).Value = strName;
cmd.Parameters.Add("@image", SqlDbType.Image).Value = buffer;
cmd.Parameters.Add("@album", SqlDbType.Int).Value = nAlbum;
cmd.ExecuteNonQuery();
int nID = (int)cmd.Parameters["RETURN_VALUE"].Value;
TreeNode node = new TreeNode(strName);
node.Tag = new TreeItem(ItemType.Photo, nID, "Enter description");
nID = treeAlbum.SelectedNode.Index;
treeAlbum.Nodes[nID].Nodes.Add(node);
}
catch(Exception e)
{
MessageBox.Show(e.Message);
}
}
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 OnAfterSelect(object sender,
System.Windows.Forms.TreeViewEventArgs e)
{
try
{
if( true == btnUpdate.Visible )
{
btnUpdate.Visible = false;
txtDesc.Visible = false;
lblDesc.Visible = true;
}
TreeItem item = (TreeItem)e.Node.Tag;
if( ItemType.Album == item.Type )
{
lblDesc.Text = item.Desc; pictureBox.Image = null; return;
}
string strCmd = String.Format("SELECT photo FROM Photos WHERE id = {0}",
item.Id);
SqlCommand cmd = new SqlCommand(strCmd, sqlConn);
lblDesc.Text = item.Desc;
byte[] b = (byte[])cmd.ExecuteScalar();
if(b.Length > 0)
{
System.IO.MemoryStream stream = new System.IO.MemoryStream(b, true);
stream.Write(b, 0, b.Length);
Bitmap bmp = new Bitmap(stream);
if( bmp.Width > 500 && bmp.Height > 300)
{
Bitmap bmp1 = new Bitmap(bmp, new Size(500,300));
pictureBox.Image = bmp1;
}
else
pictureBox.Image = bmp;
stream.Close();
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
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.
One area in which this application can be improved is the scaling of the displayed images. Unfortunately I have not had the time to add this. Despite this limitation I felt the application sufficient to share with others for their benefit.