Introduction
This article is to demonstrate how to load images into a SQLite database and retrieve them for viewing. It is written in VS2010, C#, .NET4.0, and uses an ADO.NET provider
System.Data.SQLite
to connect to the SQLite database. And this all in a Windows XP environment.
Background
First of all, one has to obtain a few files and install them according to the rules:
SQLite ADO.NET provider: I installed the package into my "C:\" directory and chose not to register the DLL files,
due to only wanting to include the DLL files to my project.
Using the code
SQLite
First, I created a new database named ImageLib.s3db and added a table and required fields.
CREATE TABLE [ImageStore] (
[ImageStore_Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[ImageFile] NVARCHAR(20) NULL,
[ImageBlob] BLOB NULL
);
VS2010 - C# - .NET 4.0
Next, I created a VS2010 project named StoringImages, changed the default namespace, and added a few folders and files.
- folder: Database
- file: StoringImages.s3db
- Property: Copy to Output Directory => Copy Always
- folder: Model
- dBFunctions.cs
- dBHelper.cs
- Image.cs
- ImageHelper.cs
- file: System.Data.SQLite.dll
- Property: Copy to Output Directory => Copy Always
- file: SQLite.Interop.dll
- Property: Copy to Output Directory => Copy Always
- form: DisplayImages
- This is the startup form of the project
Both System.Data.SQLite.dll and SQLite.Interop.dll need to be placed just beneath the root (project) StoringImages.
This ensures that both files are installed into the same directory as the the project's "*.exe" file.
Solution Explorer
Model
Within the folder Model, there are a few classes, two for handling all database transactions and two for handling
image transactions. The two for handling database transactions, dBFunctions and dBHelper, I've used before in my previous article
C# & SQLite. So next, I'll be explaining how to use the remaining two classes, Image
and ImageHelper
.
The class Image
I'll be using as a custom made variable, which will be used to store the data of an imported image file, so it can be passed along between methods.
The class that will be doing all the hard work is ImageHelper
. Within this class, you'll find various methods for handling the Insert, Delete, and SaveAs of an
image. Insert
uses another method called LoadImage
which handles the binary reading of an image. Delete
is for the removal
of the data from the database. SaveAs
is for saving the image back to a directory of choice. After every transaction, a transaction state
is generated in the form of isSucces
. The view (form) DisplayImages
requires this state in order to or not to update itself.
ImageHelper - Assigning of references
I try never to use more references than needed, but sometimes forget to remove the ones VS2010 automatically adds to every new class.
using System;
using System.IO;
using System.Windows.Forms;
using System.Data;
using System.Data.SQLite;
ImageHelper - Declairation of variables
MaxImageSize
is used to declare the maximum number of bytes allowed when importing an image, which in this example is overridden in the LoadImage
method.
private dBHelper helper = null;
private string fileLocation = string.Empty;
private bool isSucces = false;
private int maxImageSize = 2097152;
dBHelper
is the class that handles transactions to the database. maxImageSize
is for the default maximum number of bytes allowed during upload.
isSucces
lets the view know that a transaction [Insert, Delete, SaveAs] was a success or not.
ImageHelper - Properties
private string FileLocation
{
get { return fileLocation; }
set
{
fileLocation = value;
}
}
ImageHelper - Method GetSucces
This method is used by the form DisplayImage
to find if a transaction [Insert, Delete, SaveAs] was a success or not.
public Boolean GetSucces()
{
return isSucces;
}
ImageHelper - Method LoadImage
First we ask the user for the selected image file location [path] so that we can use this in our FileStream
. Once the Filestream
is open, we read the image
as binary and store the acquired data in an instance of the Image
class, which we'll be sending to the caller of the method LoadImage
,
the InsertImage
method.
private Image LoadImage()
{
Image image = null;
OpenFileDialog dlg = new OpenFileDialog();
dlg.InitialDirectory = @"C:\\";
dlg.Title = "Select Image File";
dlg.Filter = "Image Files (*.jpg ; *.jpeg ; *.png ; *.gif ; *.tiff ; *.nef)
|*.jpg;*.jpeg;*.png;*.gif;*.tiff;*.nef";
dlg.ShowDialog();
this.FileLocation = dlg.FileName;
if (fileLocation == null || fileLocation == string.Empty)
return image;
if (FileLocation != string.Empty && fileLocation != null)
{
Cursor.Current = Cursors.WaitCursor;
FileInfo info = new FileInfo(FileLocation);
long fileSize = info.Length;
maxImageSize = (Int32)fileSize;
if (File.Exists(FileLocation))
{
using (FileStream stream = File.Open(FileLocation, FileMode.Open))
{
BinaryReader br = new BinaryReader(stream);
byte[] data = br.ReadBytes(maxImageSize);
image = new Image(dlg.SafeFileName, data, fileSize);
}
}
Cursor.Current = Cursors.Default;
}
return image;
}
ImageHelper - Method InsertImage
InsertImage
is called from the view (form) DisplayImages
via the NewPicture
method.
Once the insert is successfully completed, it will return the newly obtained image_id
back to the view.
As you'll notice, an instance of the class Image is used between the methods InsertImage
and LoadImage
.
public Int32 InsertImage()
{
DataRow dataRow = null;
isSucces = false;
Image image = LoadImage();
if (image == null) return 0;
if (image != null)
{
string connectionString = dBFunctions.ConnectionStringSQLite;
string commandText = "SELECT * FROM ImageStore WHERE 1=0";
helper = new dBHelper(connectionString);
{
if (helper.Load(commandText, "image_id") == true)
{
helper.DataSet.Tables[0].Rows.Add(
helper.DataSet.Tables[0].NewRow());
dataRow = helper.DataSet.Tables[0].Rows[0];
dataRow["imageFileName"] = image.FileName;
dataRow["imageBlob"] = image.ImageData;
dataRow["imageFileSizeBytes"] = image.FileSize;
try
{
if (helper.Save() == true)
{
isSucces = true;
}
else
{
isSucces = false;
MessageBox.Show("Error during Insertion");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
return Convert.ToInt32(dataRow[0].ToString());
}
ImageHelper - Method DeleteImage
DeleteImage
executes the removal of an image from the database. The method requires an integer, the row number of the dataset, given by the view (form)
DisplayImages
via the method DeletePicture
. And after processing, DeleteImage
returns the "state" back
to DeletePicture
.
public void DeleteImage(Int32 imageID)
{
isSucces = false;
string connectionString = dBFunctions.ConnectionStringSQLite;
string commandText = "SELECT * FROM ImageStore WHERE image_id=" + imageID;
helper = new dBHelper(connectionString);
{
if (helper.Load(commandText, "image_id") == true)
{
if (helper.DataSet.Tables[0].Rows.Count == 1)
{
helper.DataSet.Tables[0].Rows[0].Delete();
try
{
if (helper.Save() == true)
{
isSucces = true;
}
else
{
isSucces = false;
MessageBox.Show("Delete failed");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
}
ImageHelper - Method SaveAsImage
To top it all off, I've added a SaveAs
method. Save the binary data back to an image file, to an allocated directory of the user's choice.
Once again, we need to know which row of the dataset needs to be saved to file, thus our method requires an integer as parameter.
First, we set the local variables to the default values, a C# - .NET requirement and good standard programming practice.
Then we ask the user, via a SaveDialog, for the directory location and file name for the new image.
A dialog.Filter
range is set, that we allow, and a check is executed accordingly.
The binary data is retrieved from the database with the use of dBHelper
, once again using an instance of the Image
class. If dBHelper.Load
returns
the value "true
", the FileStream
is executed and writing the binary to image processed. To end the process the "state"
isSucces
is returned to the view (form) DisplayImages
.
public void SaveAsImage(Int32 imageID)
{
DataRow dataRow = null;
Image image = null;
isSucces = false;
SaveFileDialog dlg = new SaveFileDialog();
dlg.InitialDirectory = @"C:\\";
dlg.Title = "Save Image File";
dlg.Filter = "Tag Image File Format (*.tiff)|*.tiff";
dlg.Filter += "|Graphics Interchange Format (*.gif)|*.gif";
dlg.Filter += "|Portable Network Graphic Format (*.png)|*.png";
dlg.Filter += "|Joint Photographic Experts Group Format (*.jpg)|*.jpg";
dlg.Filter += "|Joint Photographic Experts Group Format (*.jpeg)|*.jpeg";
dlg.Filter += "|Bitmap Image File Format (*.bmp)|*.bmp";
dlg.Filter += "|Nikon Electronic Format (*.nef)|*.nef";
dlg.ShowDialog();
if (dlg.FileName != "")
{
Cursor.Current = Cursors.WaitCursor;
string defaultExt = ".png";
int pos = -1;
string[] ext = new string[7] {".tiff", ".gif", ".png",
".jpg", ".jpeg", ".bmp", ".nef"};
string extFound = string.Empty;
string filename = dlg.FileName.Trim();
for (int i = 0; i < ext.Length; i++)
{
pos = filename.IndexOf(ext[i], pos + 1);
if (pos > -1)
{
extFound = ext[i];
break;
}
}
if (extFound == string.Empty) filename = filename + defaultExt;
string connectionString = dBFunctions.ConnectionStringSQLite;
string commandText = "SELECT * FROM ImageStore WHERE image_id=" + imageID;
helper = new dBHelper(connectionString);
if (helper.Load(commandText, "") == true)
{
dataRow = helper.DataSet.Tables[0].Rows[0];
image = new Image(
(string)dataRow["imageFileName"],
(byte[])dataRow["imageBlob"],
(long)dataRow["imageFileSizeBytes"]
);
using (FileStream stream = new FileStream(filename, FileMode.Create))
{
BinaryWriter bw = new BinaryWriter(stream);
bw.Write(image.ImageData);
isSucces = true;
}
}
Cursor.Current = Cursors.Default;
}
if (isSucces)
{
MessageBox.Show("Save succesfull");
}
else
{
MessageBox.Show("Save failed");
}
}
View - (form) DisplayImages
The form contains a splitpanel
with a picture box on one side (left) + a label on the other side (right) of a DataGridView
.
It also contains a ContextMenuStrip
which is linked to the DataGridView
. The ContextMenuStrip
contains the three commands for this little project,
the commands being New
, Delete
, and SaveAs
.
The form itself contains a few extra methods for handling the commands, retrieving the data from the database, and filling up the DataGridView
. The filling up of the
DataGridView
is only executed at the start of the application and after every execution of a command if the command was a success.
Remark
I know that the class ImageHelper
and its methods need refactoring but I specially left it like this so that all its functionalities are contained; this makes it easier to read.
I hate reading articles about code and it's all over the place, jumping in and out methods to get a grip on things.
Points of Interest
Those who have read my previous article C# & SQLite will recognize the two
database classes for handling all database transactions.