|
Overview
It's common for modern databases to contain large binary objects,
more commonly referred to as BLOB's (Binary Large Objects). BLOBs are typically
graphical images such as photos contained in .bmp, .jpg or .tif
files. Although the database is
fully capable of storing BLOB data, the potential size of these objects means that
they must be accessed and managed differently than standard text and numeric data
types.
SQL Server used three different data types for BLOB
storage:
-
Text - The Text data type can accommodate up to 2GB
of non-Unicode text data.
|
-
nText - The nText data type can accommodate up to
1GB of Unicode text data.
|
-
Image - The Image data type can store up to 2GB of
binary data, which also enables it to store standard text data.
|
The following example shows how to retrieve Image data from the
SQL Server database Northwind and displaying it in the Windows Form Control
PictureBox. If you are using a DataSet, you can retrieve an employee's name
and photo by using a simple command such as SELECT name, photo FROM employees
WHERE id = 1.
Binding to BLOB Fields
The Windows Forms data binding works fine with BLOB fields (that is, images), but not
in the default way. If you try to bind the Employees.Photo column to the Image
property of a PictureBox, you get an exception.
The exception originates from a clear incompatibility between the desired type
(System.Drawing.Image) and the contents of the Photo field, which is
resolved to System.Byte[]. To make things even more interesting, the
Northwind's Employees pictures need some work to become really usable.
The net effect of all these issues is that you need more than a simple conversion to
successfully bind to images. Nevertheless, the Format event lets you easily
accomplish the tasks. So your code would look something like this:
Binding bdPhoto = new Binding("Image",_dataSet,"Employees.Photo");
bdPhoto.Format += new ConvertEventHandler(this.PictureFormat);
Photo.DataBindings.Add(bdPhoto);
The event handler does the job of transforming an array of bytes into a Bitmap
object that can be safely assigned to the Image property of a
PictureBox control.
private void PictureFormat(object sender,
ConvertEventArgs e)
{
// e.Value is the original value
Byte[] img = (Byte[])e.Value;
MemoryStream ms = new MemoryStream();
int offset = 78;
ms.Write(img, offset, img.Length - offset);
Bitmap bmp = new Bitmap(ms);
ms.Close();
// Writes the new value back
e.Value = bmp;
}
The array of bytes read from the SQL Server table is first
copied into a MemoryStream object. This step is necessary because in .NET
you can't create graphic objects directly from an array of bytes. Instead,
wrapping the same bytes into a stream object meets the expectations of at least one
constructor of the Bitmap class. Normally, the database BLOB field contains
only the image itself. Unfortunately, this is not the case with Northwind, in which
images are prefixed with a 78-byte header. So, to create a valid object, you
must skip those bytes and pass the excerpt to the Bitmap's constructor. Generally
speaking, this is a practical demonstration of how you can perform any kind of task
prior to binding data. When a reasonable match between the source and target types
has been reached, you replace the current content of the Value property. After
that, the method returns.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;
using System.Data;
using System.Data.SqlClient;
namespace Akadia.SqlBlob
{
// Load Photo (Image) from a SQL Server
Database into a Picture Box
public class SqlBlob : System.Windows.Forms.Form
{
private System.Windows.Forms.Button
ButtonNext;
private System.Windows.Forms.Button
ButtonPrev;
private System.Windows.Forms.Label
Label4;
private System.Windows.Forms.PictureBox
Photo;
private System.Windows.Forms.Label
Label5;
private System.Windows.Forms.Label
Label3;
private System.Windows.Forms.Label
Label2;
private System.Windows.Forms.StatusBar
AppStatusBar;
private System.Windows.Forms.TextBox
hired;
private System.Windows.Forms.TextBox
position;
private System.Windows.Forms.TextBox
lastName;
private System.Windows.Forms.TextBox
firstName;
private System.ComponentModel.Container
components = null;
//
Fields
private BindingManagerBase
_bmbEmployees;
private DataSet _dataSet;
//
Constructor
public SqlBlob()
{
InitializeComponent();
}
.....
.....
// Load Data from
the Database into the Dataset
// (Runs when Form is loaded)
private void
SqlBlob_Load(object sender, System.EventArgs e)
{
_dataSet = new
DataSet();
// Fetch data
SqlDataAdapter
da;
da = new
SqlDataAdapter("SELECT * FROM Employees",
"SERVER=xeon;DATABASE=northwind;UID=sa;PASSWORD=manager");
da.Fill(_dataSet,"Employees");
// Set bindings for textbox controls
BindControls();
// Refresh the UI
EnableControls(true);
DisplayRecordPosition();
}
// Bind data bound
controls to binding expression
private void BindControls()
{
firstName.DataBindings.Add(new Binding("Text",_dataSet,"Employees.FirstName"));
lastName.DataBindings.Add(new Binding("Text",_dataSet,"Employees.LastName"));
position.DataBindings.Add(new Binding("Text",_dataSet,"Employees.Title"));
// The Format event lets you easily format columns
// before
displaying it in the control
Binding
bdHireDate = new Binding("Text",_dataSet,"Employees.HireDate");
bdHireDate.Format
+= new ConvertEventHandler(this.HireDateFormat);
hired.DataBindings.Add(bdHireDate);
Binding bdPhoto =
new Binding("Image",_dataSet,"Employees.Photo");
bdPhoto.Format +=
new ConvertEventHandler(this.PictureFormat);
Photo.DataBindings.Add(bdPhoto);
// Store the instance of the BindingContext for the
// control bound
to the Employees table in the given DataSet.
// We use these
for the Row Position Management.
_bmbEmployees =
this.BindingContext[_dataSet,"Employees"];
}
// Update the
status bar to reflect the current record position
private void EnableControls(bool
activate)
{
firstName.Enabled
= activate;
lastName.Enabled
= activate;
position.Enabled
= activate;
hired.Enabled =
activate;
ButtonPrev.Enabled = activate;
ButtonNext.Enabled = activate;
}
// Update the
status bar to reflect the current record position
private void
DisplayRecordPosition()
{
AppStatusBar.Text
= "Currently on Record # " +
(_bmbEmployees.Position + 1).ToString();
}
// Move to the
previous record
private void ButtonPrev_Click(object
sender, System.EventArgs e)
{
_bmbEmployees.Position -= 1;
DisplayRecordPosition();
}
// Move to the next
record
private void ButtonNext_Click(object
sender, System.EventArgs e)
{
_bmbEmployees.Position += 1;
DisplayRecordPosition();
}
// Convert the
image bits into a Bitmap object that
// can be assigned to a PictureBox
control
private void PictureFormat(object sender, ConvertEventArgs e)
{
// e.Value is the original value
Byte[] img =
(Byte[])e.Value;
// Normally, the database BLOB field contains only the image itself.
// Unfortunately,
this is not the case with Northwind, in which images
// are prefixed
with a 78-byte header. So, to create a valid object,
// you must skip
those bytes.
MemoryStream ms =
new MemoryStream();
int offset =
78;
ms.Write(img,
offset, img.Length - offset);
Bitmap bmp = new
Bitmap(ms);
ms.Close();
// Writes the new value back
e.Value =
bmp;
}
// Format the hire
date into a more convenient output format
private void HireDateFormat(object sender, ConvertEventArgs e)
{
DateTime dt =
(DateTime) e.Value;
e.Value =
dt.ToString("dd.MM.yyyy");
}
[STAThread]
static void Main()
{
Application.Run(new SqlBlob());
}
}
}
|