Шаг 21.
Создание Internet-приложений в среде Delphi. Компоненты TDataSetTableProducer и TDataSetPageProducer

    На этом шаге мы рассмотрим компоненты TDataSetTableProducer и TDataSetPageProducer палитры Internet.

    Воспользуемся базой данных DBDemos из поставки Delphi 6.0, а из нее выберем таблицу events.db. В таблице перечисляются ожидаемые спортивные состязания.

    В поле Event_Name занесено название соревнования, поля Event_Date и Event_Time - дата и время соревнования, а в Memo-поле Event_Description приводится описание соревнования.

    Event_Photo это BLOB-поле, в котором хранятся фотоснимки с сюжетами соревнований и последнее поле, Ticket_price, - это цена билета на означенное соревнование.

    В программе сначала показывается все содержимое таблицы в HTML-документе. Откуда по щелчку "мыши" на нужной записи будет выводиться подробное описание соревнования с выводом графического изображения.

    Создадим новое Web-приложение. Поместим на его форму компонент ТТable, и подключим его к таблице events.db.

    Зададим в свойстве DataBaseName имя DBDemos, а свойству TableName присвоим значение events.db. При Web-запросе без параметров, когда путь не указан, будем выводить содержимое отобранных столбцов всей таблицы. В другом варианте запроса, со значением пути равным, например строке "/full", будем выводить подробную информацию о состязании.

    Добавим на форму компонент TDataSetTableProducer, и создадим объект-действие по имени initial, с пустым значением PathInfo и свойством Enabled равным True. Вслед за этим добавим действие по умолчанию: назовем его default, а путь оставим пустым. Поставим отметку на свойстве Default, a Enabled установим в False. Последняя установка делается для того, чтобы это действие гарантированно выполнялось только в самую последнюю очередь. Это действие по умолчанию будет выдавать сообщение об ошибке.

    Далее необходимо связать объекты DataSetTableProducer1 и Table1. Для этого указываем имя Table1 в свойстве DataSet поставщика данных.

    Заголовок всей таблицы находится в свойстве Caption. В свойствах Footer и Header задаются необходимые заголовочные и заключительные HTML-теги, которыми поставщик обрамляет данные из таблицы базы данных, чтобы получить корректный HTML-документ, понятный Web-браузеру.

    В этой программе для Header используется следующий фрагмент:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN"> <HTML>
<HEAD>
<TITLE>3аказ билетов на спортивные состязания</TITLE>
</HEAD>
<BODY>

    В свойстве Footer указывается только две строки

</BODY>
</HTML>

    В ответ на активизацию свойства Columns загрузится окно Редактора этого свойства, показанное на рисунке 1. Основное, что можно сделать в этом редакторе, это отобрать нужные поля для вывода в таблицу.


Рис.1. Окно редактора свойства Columns

    Кроме этого можно задать имена для выводимых колонок и настроить цвет и обрамление. Если в таблице полей много, а вывести нужно меньшую их часть, то в этом случае, можно добавить их по одному, нажимая на кнопку Add New. Либо, воспользовавшись кнопкой Add All, добавить все поля сразу, а затем, удалить ненужные с помощью кнопки Delete Selected. Если выбрать поле в правом окне редактора и раскрыть свойство Title в окне Инспектора Объектов, то можно поменять имя заголовка столбца, его цвет и выравнивание. Далее вводим строку кода в обработчик события OnAсtion объекта-действия initial.

procedure TWebModule1.WebModule1initialAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled:Boolean);
begin
    Response.Content:=DataSetTableProducer1.Content;
end;

    Оформим обработчик события ОnAсtion для действия по умолчанию, который может выглядеть так:

procedure TWebModule1.WebModule1defaultAction(Sender:TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  Response.Content:='<H2>Ошибка запроса!</H2>';
  Response.Content:=Response.Content+'<a href=../dstr2.exe>
<h2>Назад</h2></a>';
end;

    Первая часть задачи выполнена. Клиент может отправить запрос к базе данных на сервер и получить отчет о запросе в окне браузера. Внешний вид итога запроса можно видеть на рисунке 2.


Рис.2. Внешний вид итога запроса к базе данных в окне браузера

    Далее реализуем вторую половину задачи: просмотрев весь список соревнований, клиент должен иметь возможность получить полную информацию об интересующем его состязании.

    Простейший вариант, это превратить строки с названиями состязания в первой колонке в ссылки. И это можно сделать, если воспользоваться одним из трех событий поставщика, возникающих в процессе формирования таблицы. Эти события называются OnCreateContent, OnGetTableCaption и OnFormatCell.

    Первое инициируется перед созданием таблицы. В нем можно внести последние изменения в этот процесс или совсем отменить: если обработчик этого события вернет в параметре Continue значение False, то таблица формироваться не будет.

    Второе событие вызывается при формировании заголовка таблицы. В этом обработчике можно изменить название таблицы и задать режим выравнивания текста заголовка.

    Третье событие, вызывается при формировании содержимого каждой ячейки.

    Ниже приводится текст обработчика этого события:

procedure TWebModule1.DataSetTableProducer1FormatCell(Sender:TObject;
                        CellRow, CellColumn: Integer; var BgColor:THTMLBgColor;
                        var Align: THTMLAlign; var VAlign:THTMLVAlign;
                        var CustomAttrs,CellData: String);
begin
   if (CellRow>0)and(CellColumn=0) then
       Celldata:='<a href=dstr2.exe\full?EventNo='+
       table1.Fields[0].AsString+'>'+Celldata+'</A>';
end.

    Параметры этой процедуры включают в себя номера столбца и строки таблицы для обрабатываемой ячейки (нумерация начинается с нуля), цвет фона ячейки, горизонтальное и вертикальное выравнивание, дополнительные атрибуты ячейки и текст, помещаемый в ячейку. В операторе if проверяются номера столбца и строки ячейки, и если ячейка не из первой строки (строки заголовка) и находится в первом столбце, то ее содержимое меняется: добавляются соответствующие теги HTML и данные о пути и запросе. В качестве имени пути здесь вставлена строка full, но объекта-действия для этого пути пока нет.

    Далее, за именем пути прописывается строка EventNo=, к которой добавляется содержимое первого поля базы данных, поле EventNo, преобразованное в строку. В результате Query-часть запроса для одной из строк таблицы будет иметь вид EventNo=7. Теперь нужно создать объект-действие для пути /full. Это действие должно сформировать новый документ с подробными данными о выбранном спортивном состязании. Для данной цели будет использоваться другой поставщик данных из таблиц данных: ТDataSetPageProducer. Добавим его в форму приложения. В свойстве DataSet этого компонента нужно указать таблицу, с которой работает поставщик, - Table1 в данном случае. Затем заполняем свойство HTMLDoc шаблоном, заготовкой того документа, который должен получить клиент.

    Шаблоны использует еще один компонент из панели Internet: TPageProducer. Когда в процессе обработки документа, компоненту встречается шаблон, он инициирует событие OnHTMLTag. В обработчике этого события должна пройти замена шаблона на соответствующие данные. Но в случае рассматриваемого компонента, TDataSetPageProducer, обработкой шаблонов можно и не заниматься, если дать им имена совпадающие с именами полей таблицы данных. В этом случае компонент сам произведет замену шаблонов на значения соответствующих полей текущей записи таблицы данных. В качестве "шаблонного" документа можно использовать следующий текст:

<html>
<head>
<title>Обзор спортивного состязания</title>
</head>
<Table Width="100%" Border=1 BgColor="Silver"><Caption>Заказ билетов на 
спортивные соревнования</Caption>
<TR><TH Align="Right">Номер по порядку</TH>
<TH Align="Left"><#EventNo></TH></TR>
<TR><TH Align="Right">Название состязания</TH>
<TH Align="Left"><#Event_Name></TH></TR>
<TR><TH Align="Right">Дата проведения</TH>
<TH Align="Left"><#Event_Date></TH></TR>
<TR><TH Align="Right">Время проведения</TH>
<TH Align="Left"><#Event_Time></TH></TR>
<TR><TH Align="Right">Цена билета</TH>
<TH Align="Left"><#Ticket_price></TH></TR>
</Table>
<br><br>
<#Event_Description>
<br><br>
<#Event_Photo>
<br><br>
<a href="../dstr2.exe">Вернуться назад</a>
</body>
</html>

    В тексте фрагмента программы хорошо видны шаблоны, имена которых совпадают с именами полей таблицы events.db. В конце текста выводится ссылка, активизировав которую, можно вернуться к полному списку записей таблицы.

    Далее нужно создать объект-действие для значения пути full, у него в свойстве PathInfo задается строка /full. Если выбрать из списка значений свойства Producer значение DataSetPageProducer1 и откомпилировать проект, то получим следующее: при выборе разных строк таблицы выводятся данные только из первой записи. И это закономерно, так как данные для получения сведений из нужной записи есть, но они никак не используются.

    Очевидно, самым простым решением в данном случае было бы простое перемещение указателя в таблице на отобранную запись. И сделать это можно в обработчике события OnAction объекта-действия full. Но чтобы этот обработчик срабатывал первым, необходимо очистить свойство Producer и вызывать нужный поставщик из обработчика события OnAction. Обработчик будет выглядеть следующим образом:

procedure TWebModule1.WebModule1fullAction(Sender: TObject;
          Request: TWebRequest; Response: TWebResponse; var Handled:Boolean);
var
   q1:string;
   options:TLocateOptions;
begin
   q1:=Request.QueryFields.Values['EventNo'];
   Table1.Locate('EventNo',q1,options);
   Response.Content:=DataSetPageProducer1.Content;
end;

    Здесь в переменную q1 помещается значение для поля EventNo, затем выполняется переход к соответствующей записи таблицы, которая становится теперь текущей записью, а в последней строке вызывается функция Content поставщика данных, который подставляет на место шаблонов, значения из текущей записи таблицы и возвращает сформированный документ свойству Content объекта Response.

    В таблице events.db имеются еще графические изображения и Mемо-поля, их также нужно выводить в ответ на запрос пользователя. Сейчас вместо графики и текста из Mемо-поля можно увидеть в браузере "условные обозначения" (MEMO) и (GRAPHIC). Для работы с этими полями придется ввести в обработчик события OnHTMLTag компонента TDataSetPageProducer следующий код:

procedure TWebModule1.DataSetPageProducer1HTMLTag(Sender:TObject;
                          Tag: TTag; const TagString: String;
                          TagParams: TStrings; var ReplaceText: String);
begin
   if CompareText(TagString,'Event_Photo')=0 then
        ReplaceText:='<img src=Photo?EventNo='+
        table1.Fields[0].AsString+'>';
   if CompareText(TagString,'Event_Description')=0 then
       ReplaceText:=Table1.FieldByName('Event_Description').AsString;
end;

    Что касается обработки Mемо-поля, то здесь все делается в одну строку, а за рисунком придется еще раз обратиться на Web-сервер. Здесь формируется тег HTML, используемый для описания графических изображений, который ссылается на приложение (имя приложения и адрес сервера браузер в данном случае подставляет сам) с новым значением пути Photo, за которым следует ссылка на нужную запись таблицы данных. Естественно, нужно добавить в приложение еще один объект-действие, обработчик события OnAction, код которого приводится ниже:

procedure  TWebModule1.WebModule1PhotoAction(Sender: TObject;
       Request: TWebRequest; Response: TWebResponse; var Handled:Boolean);
var
  B:TBitmap;
  S:TMemoryStream;
  q1:string;
  options:TLocateOptions;
begin
   q1:=Request.QueryFields.Values['EventNo'];
   Table1.Locate('EventNo',q1,options);
   B:=TBitmap.Create;
   B.Assign(Table1.FieldByName('Event_Photo'));
   S:=TMemoryStream.Create;
   B.SaveToStream(S);
   S.Position:=0;
   Response.ContentType:='image/x-xbitmap';
   Response.ContentStream:=S;
   B.Free;
end;

    В вышеприведенном фрагменте вначале указатель в таблице устанавливается на нужную запись, затем создается объект типа BitMap и в него переписывается содержимое поля Event_Photo. Затем создается поток в памяти и в него переписывается нужный графический образ. Далее остается установить указатель в потоке на начало и передать Web-серверу указатель на поток и тип данных в потоке.

    Пример ответа на запрос приведен на рисунке 3.


Рис.3. Ответ на запрос

    Листинг программы приводится ниже.

unit DSTR1;

interface

uses
  SysUtils, Classes, HTTPApp, DBWeb, DB, DBTables, HTTPProd, DSProd,Graphics;

type
  TWebModule1 = class(TWebModule)
    Table1: TTable;
    DataSetTableProducer1: TDataSetTableProducer;
    DataSetPageProducer1: TDataSetPageProducer;
    procedure WebModule1initialAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
    procedure WebModule1defaultAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
    procedure DataSetTableProducer1FormatCell(Sender: TObject; CellRow,
      CellColumn: Integer; var BgColor: THTMLBgColor;
      var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs,
      CellData: String);
    procedure WebModule1fullAction(Sender: TObject; Request: TWebRequest;
      Response: TWebResponse; var Handled: Boolean);
    procedure DataSetPageProducer1HTMLTag(Sender: TObject; Tag: TTag;
      const TagString: String; TagParams: TStrings;
      var ReplaceText: String);
    procedure WebModule1PhotoAction(Sender: TObject; Request: TWebRequest;
      Response: TWebResponse; var Handled: Boolean);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  WebModule1: TWebModule1;

implementation

{$R *.DFM}

procedure TWebModule1.WebModule1initialAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
    Response.Content:=DataSetTableProducer1.Content;
end;

procedure TWebModule1.WebModule1defaultAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
   Response.Content:='<H2>Ошибка запроса!</H2>';
   Response.Content:=Response.Content+'<a href=../dstr2.exe><h2>Назад</h2></a>';
end;

procedure TWebModule1.DataSetTableProducer1FormatCell(Sender: TObject;
  CellRow, CellColumn: Integer; var BgColor: THTMLBgColor;
  var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs,
  CellData: String);
begin
   if (CellRow>0)and(CellColumn=0) then
     Celldata:='<a href=dstr2.exe\full?EventNo='+
        table1.Fields[0].AsString+'>'+Celldata+'</A>';
end;

procedure TWebModule1.WebModule1fullAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
   q1:string;
   options:TLocateOptions;
begin
   q1:=Request.QueryFields.Values['EventNo'];
   Table1.Locate('EventNo',q1,options);
   Response.Content:=DataSetPageProducer1.Content;
end;

procedure TWebModule1.DataSetPageProducer1HTMLTag(Sender: TObject;
  Tag: TTag; const TagString: String; TagParams: TStrings;
  var ReplaceText: String);
begin
   if CompareText(TagString,'Event_Photo')=0 then
      ReplaceText:='<img src=Photo?EventNo='+table1.Fields[0].AsString+'>';
    if CompareText(TagString,'Event_Description')=0 then
      ReplaceText:=Table1.FieldByName('Event_Description').AsString;
end;

procedure TWebModule1.WebModule1PhotoAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
  B:TBitmap;
  S:TMemoryStream;
  q1:string;
  options:TLocateOptions;
begin
   q1:=Request.QueryFields.Values['EventNo'];
   Table1.Locate('EventNo',q1,options);
   B:=TBitmap.Create;
   B.Assign(Table1.FieldByName('Event_Photo'));
   S:=TMemoryStream.Create;
   B.SaveToStream(S);
   S.Position:=0;
   Response.ContentType:='image/x-xbitmap';
   Response.ContentStream:=S;
   B.Free;
end;

end.
Текст приложения можно взять здесь.

    На следующем шаге мы рассмотрим компонент TPageProducer.




Предыдущий шаг Содержание Следующий шаг