C# 照片集查看器
一个用 C# 编写的 Windows 应用程序,用于从 SQL Server 数据库存储和检索照片。
C# 照片集查看器
此示例是一个 C# Windows 应用程序,演示了从数据库存储和检索图像。
尽管本文和代码已更新以支持 .NET 4.0 和 SQL Server 2012,但它以前是为 .NET 1.0 和 SQL Server 2000 构建的,因此所有方法和技术都应该在这些应用程序的早期版本中工作,只需稍作修改或无需修改。
数据库表和存储过程
下载文件包含创建此演示应用程序所需的数据库和存储过程的脚本。
初始加载
初始化此应用程序的主窗体后,首先需要获取任何现有的相册和照片,并将它们加载到 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);
}
}
}
这相当简单。 TreeNode 的 Tag 对象被设置为 struct,这样以后在选择项目时,就不需要额外的数据库调用来填充显示控件。
通过 Data.GetPhotoAlbums
方法获取,同样,这是一个直接的 ADO.NET 实现。
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();
}
第一步是初始化 SqlConnection
和 SqlCommand
对象。然后执行存储过程,并使用 SqlDataReader
迭代结果并创建一个要返回的相册集合。在此之后,照片以类似的方式添加到每个相册中。如前所述,这可以通过一个存储过程来完成,但是,为了说明该过程,它被分解为两种方法。
存储图像
选择“添加照片”上下文菜单项后,多选 FileOpenDialog
允许选择图像进行添加。对于每个文件,都会创建一个 System.IO.FileStream
并用于读入一个 byte
数组。然后,byte
数组被传递给一个方法,该方法将其用作存储过程的输入参数,以将其添加到数据库表的图像字段中。
最后一步是将图像添加到 treeview
。为此目的使用了名为 TreeItem
的帮助程序类。此类存储图像的描述和数据库索引 ID。此类也用于相册,因此一个 enum 定义了它的对象类型。创建此类的新实例后,它将被分配给 TreeNode
的 Tag
成员。然后将该节点添加到选定的相册节点。
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;
}
}
}
显示图像
AfterSelect 事件捕获图像或相册的选择。如果选择了图像,则检索所选节点的 Tag
成员,并将其转换为 TreeItem
。然后将此类的 Id 成员传递给存储过程以检索所需的图像。ExecuteScalar
方法的返回被转换为一个字节数组,然后将其读入 System.IO.MemoryStream
。然后使用此流对象创建 Bitmap
,然后将其用于分配给 picturebox 进行显示。
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;
}
}
结论
这是一个相对简单的相册查看器应用程序的实现。使用 .NET 和 C# 绝对使此应用程序非常容易制作。将图像存储在 SQL Server 数据库中会增加一些分发量,但也简化了应用程序,因为它只有一个源可以存储和检索图像。您也不必担心文件丢失或链接损坏。
更新
2012年4月1日 - 更新到 .NET 4.0、SQL Server 2012 并应用了过去 10 年的经验教训。