第一节处理BLOBs(很大的二进制数据块),在Access中存储图片

现在的数据库应用程序不仅仅只需要处理文本或数字数据。例如,基于Interner/Intranet或多媒体的应用开发,就需要频繁的显示数据库中的文字以及图片。在这一章中,我们将了解怎样通过ADO取出并显示Access数据库中的图形数据(图像)。不用担心,即使它所需的数据库技巧已远超前面课程所学。

如果你是从本教程的开头部分学起(尤其是第二章),你就会知道怎样连接数据库,并在DBGrid中显示Applications表(来源于我们的aboutdelphi.mdb数据库)。记住我们用到的三个数据组件:DBGrid、ADOTable和DataSource。回到第一章,我们创建数据库时,将Applications表的最后一个字段留为了空(在用一些虚拟的数据填充数据库后)。其字段名为Picture,类型为OLE对象类型。

向右滚到DBGrid的最后一列,你将看到如图所示内容:

 

使用MS Access时,我们可以在一个OLE对象类型的字段中存储图像(以及其他大型数据对象,如声音视频等)。该类型的数据被视为Binary Large Object Bitmap (BLOB)大型二进制位图对象。在处理图像时,可使用多种类型的图片格式。最常用的有JPEG、GIF和BMP。其中JPEG已被者广泛采用,因其所需的存储空间很小(换句话说,JPEG比BMP要小很多)。当然,BMP、GIF以及JPEG等图形格式,Delphi都能处理。接下来,我们主要讲解JPEG文件格式的处理。

 

第二节 在Access中存储图片(Storing picturesin Access)

  在讨论如何在Delphi窗口中显示表中的图像前,让我们先往数据库里增加一些图形数据。运行Access,打开about.mdb库。打开Applications表(应有一行数据)并选中Picture字段。

 

 

按下面的步骤增加图像:

1、  单击右键→插入对象→选择“由文件创建”;

 

 

2、点击“浏览”→出现一个浏览对话框选→找到你想插入的.jpg文件→确定→确定;

注:Picture字段的文本内容为一个可执行的、用于处理计算机上JPEG文件的名称。当然,在表格上你不会看见图片。若要查看图片,双击该字段,关联应用程序即会打开该JPG文件。

  现在,我们数据库里有了图片,接下来让我们在Delphi窗体中显示它。使用第二章带有数据的Delphi窗体。

 

第三节  用DBImage引出JPEG —— 错误方法

     DBImage ——思路(The DBImage - takeone)

当试图使用Delphi做新的尝试时,首先想到向其自带帮助寻求办法。文档内容:TDBImage(组件面板的Data Controls页)表示数据库当前记录中一个BLOB字段的图形图像。使用TDBImage表示图形字段值。TDBImage允许窗体显示数据库中的图形数据。TDBImage仅仅比TImage组件多了一些数据可视属性。其中最重要的两个属性是:DataSource(数据源)和Field(字段)。DataSource属性将图形组件连接到数据库。在我们的窗体上有一个名为DataSource1的DataSource组件——代表一个数据集。Field属性表示保存图像的字段(在表中)。

一切都清楚了,现在,在窗体上放置一个DBImage组件并使用默认名DBImage1。为了真正的使DBImage与表的BLOB字段相连,需要做以下配置(使用Object Inspector):

 DBImage1.DataSource = DataSource1

DBImage1.DataField = Picture

为了显示储存在Applications表中Picture字段的JPEG图像,这是必要诀窍。

为了验证此配置是否可用,将ADOTable1组件的Active属性设为True(在Object Inspector中)。一旦这样做,将出现以下错误提示框:

 

为什么会显示“位图图像无效”呢?我们有JPEG图片而不是BMP图片——问题就在这么?让我们再看看帮助。

     通过查找帮助文档得出:①为了得到数据库里的JPG图片,我们需使用TJpegImage对象;②为了显示图片,需要简单、不可视版本的Image组件;③同时,还需使用流(Stream)从BLOB对象中载出图片。帮助文档叙述:应使用TADOBlobStream来访问或改变ADO数据集中BLOB或memo(备注)字段的值。

 

第三节 用流引出JPEG—错误的方法

      引出JPEG—思路二(Pullingthe Jpeg - take two!)

      既然不能使用DBImage——那从窗体中删除它并放一个普通的TImage组件(Additional页)命名为ADOImage。不幸的是,Image组件没有任何数据可视化属性,因此,需要一个单独的方法来显示数据库表中的图片。最简单的方法是:在窗体上添加一个Button组件,将程序代码放在其OnClick事件中,按钮名称为:“btnShowImage”。

为了使用ADOBLOBStream,帮助文档建议创建一个TADOBlobStream实例,用“流”的方式从数据库中读取图形字段,然后释放BLOB流。在中间的某个地方,我们将需要用LoadFromStream方法从TADOBlobStream对象中载入JPEG图像。Image组件的Picture、Graphic属性将用于存储和显示图片。

 字段对象,它是什么?

在Delphi数据库程序的开发中,主要对象之一就是TField对象。字段组件是表示运行(或设计)时的数据集字段的非可视化对象。TADOTable(和其他TDataSet子类)提供设计时对Fields Editor(字段编辑器)的访问方法。Fields Editor使你能选择数据集中你所想包含的字段。更重要的是,它创建了应用程序数据集中使用的字段组件的稳固的列表。为了调用Fields Editor,可以双击TADOTable组件。默认情况下,字段列表是空的。点击Add按钮打开一个对话框,里面列出了Applications表的字段列表。缺省情况下,所有字段都被选择,然后选择OK。

      Delphi会按如下的方式给出字段的默认名称:Table(表)名+Field(字段)名。这意味着我们的图片字段名为:ADOTable1Picture。

      TADOBlobStream的Create(创建)方法创建一个实例用于读或写一个指定的BLOB字段对象,在这里是ADOTable1Picture字段。

      我们在btnShowImage按钮的OnClick事件中写入程序代码。该代码将从当前所选行的Picture字段中读取图片。源代码如下所示:


[delphi] 
      uses jpeg; 
      ... 
      procedure TForm1.btnShowImageClick(Sender:TObject); 
      var 
bS:TADOBlobStream; 
        Pic :TJpegImage; 
      begin 
        bS:= TADOBlobStream.Create(ADOTable1.fieldbyname('Picture') as TBlobField,bmRead); 
        try 
          Pic:=TJpegImage.Create; 
           try 
           <SPAN style="WHITE-SPACE: pre">  </SPAN>Pic.LoadFromStream(bS); 
            <SPAN style="WHITE-SPACE: pre"> </SPAN>ADOImage.Picture.Graphic:=Pic; 
          finally 
           <SPAN style="WHITE-SPACE: pre">  </SPAN>Pic.Free; 
          end; 
       finally 
         bS.Free 
       end; 
     end; 

      uses jpeg;
      ...
      procedure TForm1.btnShowImageClick(Sender:TObject);
      var
bS:TADOBlobStream;
        Pic :TJpegImage;
      begin
        bS:= TADOBlobStream.Create(ADOTable1.fieldbyname('Picture') as TBlobField,bmRead);
        try
          Pic:=TJpegImage.Create;
           try
            Pic.LoadFromStream(bS);
             ADOImage.Picture.Graphic:=Pic;
          finally
            Pic.Free;
          end;
       finally
         bS.Free
       end;
     end;
      OK,让我们运行这个工程。当然,设置ADOTable1.Active属性为True。表单显示后,点击按钮,将出现下面的显示:

 

 

      呃, 怎么哪?代码百分之百的正确但为什么不显示图像呢!记住“永不放弃,永不投降”!让我们深入到字节水平看看到底发生了什么!

 

 第四节  在BLOB中寻找JPEG的开端

      OLE对象类型格式—思路三(OLEobject type format - take three!)
      现在我们需要做的是把图片存储到磁盘(存为普通的二进制文件)并了解它里面的内容是什么。

      所有的图片文件(格式)都有用来作为唯一标识的文件头。JPG以所谓的SOI标记开始,该标记的十六进制值是$FFD8。

下面一行代码存储Picture字段的值到工作目录的相关文件(BlobImage.dat)。在窗体的OnCreate事件中放置这条代码,开始工程以后再移除该代码。

     (ADOTable1.fieldbyname('Picture')as TBlobField).SaveToFile('.\BlobImage.dat');

      一旦我们有了这个文件。我们就可以使用Hex editor看它的内容。

 

 

      MSAccess把连接的OLE对象的路径作为对象定义的一部分存储在OLE对象字段中。因为OLE对象的存储定义没有被文档化,所以没有办法知道真正的图像数据被写之前能得到什么。

      分两部分考虑。第一:我们需要找到'FFD8'并从那儿开始读取图像。第二:'FFD8'不可能总在文件的同一个位置。结论:我们需要一个函数,返回Access数据库中存储为OLE对象的JPG文件的SOI标记的位置。

      正确的方法—思路四(The correct way - take four!)

      提供了Blob类型字段后,函数应返回ADOBlobStream中'FFD8'字符串的位置。ReadBuffer(读缓冲区)从流中一个字节一个字节的读取数据。对ReadBuffer的每个调用都会一个字节一个字节的移动流的位置。当两个字节一起引出SOI标记时,函数返回流的位置。函数:


[delphi] 
functionJpegStartsInBlob(PicField:TBlobField):integer; 
var 
   bS     : TADOBlobStream; 
   buffer : Word; 
   hx     : string; 
begin 
 Result := -1; 
  bS:= TADOBlobStream.Create(PicField, bmRead); 
  try 
   while (Result = -1) and (bS.Position + 1 < bS.Size) do 
   begin 
     bS.ReadBuffer(buffer, 1); 
     hx:=IntToHex(buffer, 2); 
     if hx = 'FF' then 
     begin 
      bS.ReadBuffer(buffer, 1); 
      hx:=IntToHex(buffer, 2); 
      if hx = 'D8' then 
        Result := bS.Position - 2 
      else if hx = 'FF' then 
        bS.Position := bS.Position-1; 
     end; //if  
   end; //while  
 finally 
   bS.Free 
 end; //try  
end; 

functionJpegStartsInBlob(PicField:TBlobField):integer;
var
   bS     : TADOBlobStream;
   buffer : Word;
   hx     : string;
begin
 Result := -1;
  bS:= TADOBlobStream.Create(PicField, bmRead);
  try
   while (Result = -1) and (bS.Position + 1 < bS.Size) do
   begin
     bS.ReadBuffer(buffer, 1);
     hx:=IntToHex(buffer, 2);
     if hx = 'FF' then
     begin
      bS.ReadBuffer(buffer, 1);
      hx:=IntToHex(buffer, 2);
      if hx = 'D8' then
        Result := bS.Position - 2
      else if hx = 'FF' then
        bS.Position := bS.Position-1;
     end; //if
   end; //while
 finally
   bS.Free
 end; //try
end;
   一旦有了SOI标记的位置信息,就能在ADOBlob流中找到图片的位置。


[delphi] 
     uses jpeg; 
     ... 
procedure TForm1.btnShowImageClick(Sender:TObject); 
var 
 bS  : TADOBlobStream; 
  Pic: TJpegImage; 
  x :integer; 
begin 
  bS:= TADOBlobStream.Create(ADOTable1.fieldbyname('Picture') as TBlobField,bmRead); 
  try 
    x:= JpegStartsInBlob(ADOTable1.fieldbyname('Picture') as TBlobField); 
   bS.Seek(x, soFromBeginning); 
   Pic:=TJpegImage.Create; 
   try 
    Pic.LoadFromStream(bS); 
    ADOImage.Picture.Graphic:=Pic; 
   finally 
    Pic.Free; 
   end; 
 finally 
   bS.Free 
 end; 
end; 

     uses jpeg;
     ...
procedure TForm1.btnShowImageClick(Sender:TObject);
var
 bS  : TADOBlobStream;
  Pic: TJpegImage;
  x :integer;
begin
  bS:= TADOBlobStream.Create(ADOTable1.fieldbyname('Picture') as TBlobField,bmRead);
  try
    x:= JpegStartsInBlob(ADOTable1.fieldbyname('Picture') as TBlobField);
   bS.Seek(x, soFromBeginning);
   Pic:=TJpegImage.Create;
   try
    Pic.LoadFromStream(bS);
    ADOImage.Picture.Graphic:=Pic;
   finally
    Pic.Free;
   end;
 finally
   bS.Free
 end;
end;
运行工程,OK!

 

 

   现在谁会说编程没有趣味?

      注:在真正的代码程序中,我们会在TDataSet的AfterScroll事件中加入代码用于从当前行中读取和显示图像(它在ADOTable1AfterScroll事件过程中)。当应用程序从一个记录滚到另一个时,AfterScroll事件发生。

    思路五!

    这就是本章的主要内容。现在你可以存储和显示所有你感兴趣的JPG图片。在这篇文章的最后一页,我会提供完整的代码(form1单元);所有的数据安排都放在表单的OnCreate事件中。这确保了所有的三个组件被正确连接—在设计时你不需要使用Object Inspector(对象检视器)。

    我承认,这一章不适合初学者,但世界是残酷的!另一件事:你注意到最后你都不知道怎样改变(或增加一些新的)表中的图片!是的,那又是另一个完整的故事了!