Разработка клиент-серверного приложения на Delphi. Часть 3
Автор: Беднягин Павел
25 ноября 2010
В предыдущей статье мы создали клиент-серверное приложение при помощи компонентов TSofteriumHTTPController. Теперь я расскажу Вам, как можно передать по сети таблицу с данными, ведь это одна из главнейших задач, которые должны решать такие программы. Рассмотрим пример работы с TClientDataSet, хотя вместо него можно использовать и другие похожие компоненты, например TdxMemData, которая содержится в наборе "DevExpress" или аналоги TQuery.
Для начала откроем новый проект и добавим на главную форму следующие компоненты:
- DBGrid: TDBGrid (закладка "Data Controls");
- mtCleintTable и mtServerTable: TClientDataSet (закладка "Data Access");
- dsClientTable: TDataSource (закладка "Data Access");
- myClient: TSHttpSenderCommands (закладка "Softerium", описание в предыдущей статье);
- myServer: TSHttpReceiver (закладка "Softerium");
- iServer: TIdHTTPServer; (закладка "Indy Servers").
Теперь создадим для mtCleintTable и mtServerTable поля "Id"(integer) и "Name"(string). Не забудте после этого нажать правой кнопкой по этим компонентам и выбрать пункт меню "Create DataSet". После проведенных манипуляций привяжем клиентскую таблицу к нашему DBGrid-у через dsClientTable, чтобы увидеть результат передачи данных.
Итак, мы хотим получить список пользователей и для работы нам понадобятся константы команд и параметров.
const
//Список команд
CMD_GET_USERS_LIST = 'getUsersList';
//Список параметров
PRM_ID = 'id';
PRM_NAME = 'name';
Представим, что наш сервер уже умеет взаимодействовать с базой данных и у него есть некая таблица пользователей. Хранятся они в mtServerTable. Теперь надо придумать, как нам грамотно превратить эти данные в обычный string и передать по TCP/IP протоколу. Для этих целей я написал функцию GetMemList и хочу поделиться ей с Вами. Ничего сложного, конечно, там нет, но, возможно, кому-то это пригодится. Так вот:
function GetMemList(mtData: TClientDataSet; const aFields: array of string): string;
var
i: integer;
begin
result := '';
mtData.First;
while not mtData.Eof do
begin
for i := 0 to (Length(aFields) - 1) do
result := result + Format('"%s=%s",', [aFields[i],
mtData.FieldByName(aFields[i]).AsString]);
mtData.Next;
if not mtData.Eof then
result := result + sLineBreak;
end;
end;
На выходе мы получим примерно такую строчку: "id=1","name=Пользователь 1" + #13#10 + "id=2","name=Пользователь 2" и так далее. Только будьте внимательнее, заполняя параметр aFields, т.к. если таких полей в таблице не будет, то мы получим исключительную ситуацию. Представим, что у нас есть все необходимые данные и теперь их надо снова преобразовать в табличный вид. Для этого была написана процедура AddListToMemData:
procedure AddListToMemData(var mtData: TClientDataSet; commaText: string;
aFields: array of string; aFieldsTypes: array of TFieldType);
var
i, j: integer;
recCount: integer;
sl, tmpSl: TStringList;
begin
//Создаем 2 стринг-листа, с которыми будем работать
sl := TStringList.Create;
tmpSl := TStringList.Create;
try
sl.Text := commaText;
recCount := sl.Count - 1;
if not mtData.Active then
mtData.Open
else
begin
mtData.Close;
mtData.Open;
end;
//Если датасет связан с таблицей - преобразования будут замедлены, поэтому
//отключаем контроль.
mtData.DisableControls;
try
for i := 0 to recCount do
begin
mtData.Append;
//Каждую строчку присланной информации разбиваем как комма-текст
tmpSl.CommaText := sl[i];
//В зависимости от типа поля - заполняем датасет
for j := 0 to (Length(aFields) - 1) do
begin
case aFieldsTypes[j] of
ftInteger:
mtData.FieldByName(aFields[j]).AsInteger :=
StrToIntDef(tmpSl.Values[aFields[j]], -1);
ftString:
mtData.FieldByName(aFields[j]).AsString :=
tmpSl.Values[aFields[j]];
ftDate:
mtData.FieldByName(aFields[j]).AsDateTime :=
StrToDateDef(tmpSl.Values[aFields[j]], 0);
ftDateTime:
mtData.FieldByName(aFields[j]).AsDateTime :=
StrToDateTimeDef(tmpSl.Values[aFields[j]], 0);
ftBoolean:
mtData.FieldByName(aFields[j]).AsBoolean :=
StrToBoolDef(tmpSl.Values[aFields[j]], false);
end;
end;
mtData.Post;
end;
mtData.First;
finally
mtData.EnableControls;
end;
finally
FreeAndNil(sl);
FreeAndNil(tmpSl);
end;
end;
Достаточно передать в эту процедуру нужный нам датасет, полученную от сервера информацию, а так же массив имён полей и их типов. Поля у нас всего 2: это "Id" и "Name" (для них мы сделали константы PRM_ID и PRM_NAME). Их типы — ftInteger и ftString соответственно. Теперь рассмотрим весь процесс передачи данных. Начнём с клиентского приложения. В нашем примере при создании главной формы делаем следующее:
procedure TForm1.FormCreate(Sender: TObject);
begin
mtCleintTable.Open;
mtServerTable.Open;
FillUsersTable;
myClient.SendCommand(CMD_GET_USERS_LIST, [], []);
AddListToMemData(mtCleintTable, myClient.Receive, [PRM_ID, PRM_NAME],
[ftInteger, ftString]);
end;
Всё, что мы делаем — это посылаем серверу команду CMD_GET_USERS_LIST и результат запроса записываем в наш mtCleintTable при помощи процедуры AddListToMemData. После этого переходим к описанию события onGetCommand компонента myServer:
procedure TForm1.myServerGetCommand(command: String; params: TStrings;
var response: String);
begin
if command = CMD_GET_USERS_LIST then
response := GetMemList(mtServerTable, [PRM_ID, PRM_NAME]);
end;
Результатом проведённых нами действий будет заполненный грид, что, как я уверен, Вы и наблюдаете в данный момент. Не забывайте, что эти процедуры можно легко переписать и под другие похожие компоненты. Например, я часто работаю с TpFIBQuery на стороне сервера и с TdxMemData на стороне клиента. К этой статье прикреплены исходные файлы этого примера, поэтому, если Вы что-то недопоняли, то просто скачайте их и посмотрите сами.
Исходники можно скачать здесь.
Назад