|Delphi Clinic||C++Builder Gate||Training & Consultancy||Delphi Notes Weblog||Dr.Bob's Webshop|
One of the frequently asked question that I hear during (of after) my WebBroker sessions is related to showing images (gif or jpeg bitmaps) dynamically produced by WebBroker applications. As usual, this is not hard to do. However, the existing tables that come with DBDEMOS are not exactly equipped with useful bitmaps (the picture in BIOLIFE is neither GIF or JPEG), so unless it's part of the session itself, I could seldom actually demonstrate this feature in less than 30 seconds. This paper will change that, because it is based on a table that I created especially for the purpose of demonstrating how to produce dynamic images from a WebBroker application.
The table itself is pretty easy, but needs to be around. In this case, I've designed a table called developer.db with developer information, like Name, Email, Website and Photo.
Note that although Photo is of type G (for Graphic inside Paradox), we should make sure ourselves that the actual photos are stored as GIF or JPEG format. In this case, I use JPEG format (which is best suited for pictures anyway). This greatly simplifies the act of producing a photo from the contents in the database, as we'll see in a moment.
So far the preparation (an important essence of this month's column). Let's now start Delphi 5, and perform a File | New to get the Object Repository:
If you select the Web Server Application icon, you can specify what kind of new web server application you want.
It doesn't really matter which one, as long as you remember, because sometime later in the article you need to know whether or not you're using a CGI executable or an ISAPI DLL.
For the sake of the example, I usually start with a CGI executable here:
For this web server application, we need two actions: one to produce HTML (that shows other information from the developer.db table) and another to actually produce the JPEG image. The first action item is the default one, and has no pathinfo, but the second one has the pathinfo /image:
The first - default - web action item could use a TDataSetPageProducer, connected to the developer.db table to show the fields and contents from this table. In order to do this, first drop a TTable on the web module, set its DatabaseName to the alias (or local directory) where you can find the developer.db table, and set the tablename to developer.db. Now you can drop a TDataSetPageProducer component (from the internet tab) and set its DataSet property to the developer table. Click on the ellipsis next to the HTMLDoc property to edit this property, and enter the following HTML code:
Name: <#Name> Email: <#Email> Website: <#Website> Photo: <#Photo>The #-tags will be replaced automatically by the DataSetPageProducer by the value of the fields of the connected DataSet - in this case developer.db.
We're almost done now. If you save and compile the current project (for example named webimage.exe), the output of calling the default web action item should be as follows:
So far so good. Although you could say that this shows nothing more than the usual (GRAPHIC), and we want to see the actual Photo here, not the "DisplayText", so we must find some way to actually replace the DisplayText with an image file.
The HTML Code
The trick is to use the fields editor to create persistent fields for all four fields inside the developer.db table, and then use the OnGetText event of the Photo field to produce something else than the usual DisplayText (which simply returns (GRAPHIC) for a picture inside).
Since we cannot return a binary image embedded in an HTML page itself, we should generate HTML code to produce another call to ourselves (and this is where you should remember the name and type of your web server application - webimage.exe in my case). The OnGetText event will be called when the <#photo> tag is received, and should return a dynamic image redirection containing the following HTML code:
'<img src="/cgi-bin/webimage.exe/image?Name=' + FieldByName('Name').AsString + '>';Note that I'm using the value of Name here to maintain the "state" (any key value will do here, as long as it's unique enough to find the correct record in the /image action item).
procedure TWebModule1.TableDeveloperPhotoGetText(Sender: TField; var Text: String; DisplayText: Boolean); begin Text := '<img src="/cgi-bin/webimage.exe/image?Name=' + Sender.DataSet.FieldByName('Name').AsString + '">' end;Note that we need to go to Sender's DataSet (the TableDeveloper) and then back to find the value using FieldByName of the 'Name' field. We could have hardcoded this all, but now you see a dynamic solution.
As an alternative, when using the DataSetpageProducer, we can hook into the OnHTMLTag event and see if a value is (GRAPHIC) and replace it with the indirect image code.
Note that (Graphic) means an empty image!
The extended implementation of the OnHTMLTag event - to produce this HTML code - is as follows:
procedure TWebModule1.DataSetPageProducer1HTMLTag(Sender: TObject; Tag: TTag; const TagString: String; TagParams: TStrings; var ReplaceText: String); begin if ReplaceText = '(GRAPHIC)' then ReplaceText := '<img src="/cgi-bin/webimage.exe/image?Name=' + (Sender AS TDataSetPageProducer). DataSet.FieldByName('Name').AsString + '">' end;Both methods will work, and you can even use them at the same time, since using the first (OnGetText) will prevent any (GRAPHIC) from ever reaching the OnHTMLTag event in the first place.
The next step is actually returning the image, which cannot be done in a normal (non-binary, text) way, but has to be using the ContentStream property. This also means we need to set the Response.ContentType property, by the way:
procedure TWebModule1.WebModule1WebActionItem2Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var Name: String; ImageStream: TmemoryStream; begin Name := Request.QueryFields.Values['Name']; TableDeveloper.Open; TableDeveloper.FindKey([Name]); ImageStream := TMemoryStream.Create; TableDeveloperPhoto.SaveToStream(ImageStream); ImageStream.Position := 0; // reset ImageStream Response.ContentType := 'image/jpg'; Response.ContentStream := ImageStream; Response.SendResponse end;Note that we should not free the ImageStream ourselves (as the Response will take care of that once the ContentStream is send over). We did not do an explicit Assign but rather a pointer-copy, so Response.ContentStream is now in effect owning the memory here.
The result is now indeed what we want: a HTML page with a dynamic image inside it:
Note that when using the BIOLIFE table, the code in this column will not show the correct image in the browser, since the image format in the BIOLIFE table is not JPEG (nor GIF). For our own tables, however, we can just make sure the graphic fields are already stored in JPEG or GIF format.
Finally, note that the e-mail address firstname.lastname@example.org is no longer working as of 2008-01-01...