Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Шаблоны и отчеты MS Word

01.01.2007

Многие популярные информационно-правовые системы содержат шаблоны различных документов в формате Word. Используя их и информацию данной статьи, вы можете легко и быстро создавать отчеты, если пишете свои программы на Delphi. Если пишете на другом языке, это не помеха, описанный подход справедлив к различным языкам.

Когда мы формируем сложный документ по шаблону, и когда в этом шаблоне есть таблицы, количество строк которых в выходном документе может быть произвольным и зависеть от объема информации, то нам недостаточно просто функций создания таблиц и записи в ячейки определенной информации. Для создания такого документа необходимо, как минимум, еще несколько функций работы с таблицами, в перечень которых входит перемещение по таблицам, добавление строк (столбцов) в конец или в середину таблицы. Также необходимо определять размер (количество строк, столбцов) и номер текущего столбца и строки. Чтобы понимать, о чем речь, необходимо просмотреть части 1-3 данной статьи, опубликованных в предыдущих номерах.

Для того, чтобы применить свои знания к конкретной задаче, сделаем ее постановку. Например, в нашем документе есть таблица, которая представляет собой шаблон и заполняется из массива информации, который имеет произвольную длину. Таким документом может быть счет-фактура, заголовок которой представляет собой сложную таблицу, средняя часть представляет таблицу переменной длины, а нижняя также представляет сложную таблицу. Для заполнения такого шаблона можно использовать способ, описанный во второй части данной статьи. Этот способ основан на поиске в шаблоне переменных (неповторяющиеся строковые значения длиной 3-5 символов) и подстановке вместо них реальных значений на этапе формирования документа. Поэтому для добавления информации в такую таблицу придется осуществить поиск и позиционирование в строку (по переменной), в которую и перед которой необходимо вставлять строки, и запомнить, в какие колонки какую записывать информацию, но для начала необходимо определить, находится курсор в таблице или нет.

Для этого используем свойство Information объекта Selection, в качестве параметра которого будет константа wdWithInTable. В этом случае этот метод возвращает TRUE, если курсор в таблице, или FALSE, если нет. Для использования в нашем приложении создадим функцию GetSelectionTable.

Function GetSelectionTable:boolean;
 
const wdWithInTable=12;
begin
 
GetSelectionTable:=true;
 
try
 
GetSelectionTable:=W.Selection.Information(wdWithInTable);
 
except
 
GetSelectionTable:=false;
 
end;
End;
Если в нашем документе может быть более одной таблицы, то, скорее всего, необходима возможность перехода и позиционирование курсора на следующей или предыдущей таблице. Объект Selection дает нам эту возможность через методы GoToNext и GoToPrevious, в этом случае в качестве их параметров должна использоваться константа wdGoToTable.

Function GoToNextTable (table_:integer):boolean;
 
const wdGoToTable=2;
begin
 
GoToNextTable:=true;
 
try
  W
.Selection.GoToNext (wdGoToTable);
 
except
 
GoToNextTable:=false;
 
end;
End;
Function GoToPreviousTable (table_:integer):boolean;
 
const wdGoToTable=2;
begin
 
GoToPreviousTable:=true;
 
try
  W
.Selection.GoToPrevious(wdGoToTable);
 
except
 
GoToPreviousTable:=false;
 
end;
End;
 

Когда мы позиционируемся на таблице, можем определить количество столбцов и строк в ней. Для этого также используем свойство Information объекта Selection, но в качестве аргументов используем константы wdMaximum Number Of Columns и wdMaximum NumberOfRows.

Function GetColumnsRowsTable(table_:integer;
 
var Columns,Rows:integer):boolean;
 
const
  wdMaximumNumberOfColumns
=18;
  wdMaximumNumberOfRows
=15;
begin
 
GetColumnsRowsTable:=true;
 
try
 
Columns:=W.Selection.Information (wdMaximumNumberOfColumns);
 
Rows:=W.Selection.Information (wdMaximumNumberOfRows);
 
except
 
GetColumnsRowsTable:=false;
 
end;
End;
Кроме размера таблицы, нам может быть необходим номер колонки и строки, на которой позиционирован курсор. Для этого так же используем свойство Information объекта Selection, но в качестве аргументов используем константы wdStartOfRangeColumnNumber, wdStartOfRangeRowNumber. Для реализации этого в Delphi создадим функцию GetColumnRowTable.

Function GetColumnRowTable(table_:integer;
 
var Column,Row:integer):boolean;
 
const
  wdStartOfRangeColumnNumber
=16;
  wdStartOfRangeRowNumber
=13;
begin
 
GetColumnRowTable:=true;
 
try
 
Column:=W.Selection.Information (wdStartOfRangeColumnNumber);
 
Row:=W.Selection.Information (wdStartOfRangeRowNumber);
 
except
 
GetColumnRowTable:=false;
 
end;
End;
После того, как мы нашли таблицу в шаблоне документа и позиционировались на определенной ячейке, нам необходимо выполнить некоторые действия с ней (добавить, вставить строки, записать информацию). Очевидно, что нам нужен будет набор функций для ее модификации.

Обычно во время формирования таблицы мы не знаем, сколько будет строк. Они могут добавляться в конец или вставляться в середину таблицы. Если для формирования документа мы используем шаблон таблицы и в нем уже есть, например, заголовок, то нам не обойтись без процедур добавления или вставления строк. Добавить строку в конец таблицы можно, используя метод Add коллекции Rows. Чтобы это сделать из приложения на Delphi, достаточно создать и использовать функцию. Определим ее как AddRowTableDoc.

Function AddRowTableDoc (table_:integer):boolean;
begin
 
AddRowTableDoc:=true;
 
try
  W
.ActiveDocument.Tables.Item(table_).Rows.Add;
 
except
 
AddRowTableDoc:=false;
 
end;
End;
 

Для того, чтобы вставлять строки в середину таблицы, удобно использовать пару операторов. Первый выделяет строку, перед которой необходимо вставить новую, второй вставляет строку (строки). Смотрите функцию InsertRowsTableDoc.

Function InsertRowsTableDoc(table_,position_,
  count_
:integer): boolean;
begin
 
InsertRowsTableDoc:=true;
 
try
  W
.ActiveDocument.Tables.Item(table_).Rows.Item(position_).Select;
  W
.Selection.InsertRows (count_);
 
except
 
InsertRowsTableDoc:=false;
 
end;
End;
Для добавления одной строки можно использовать также и метод Add коллекции Rows, но с параметром, в качестве которого выступает ссылка на строку, перед которой необходимо вставить новую. Первый оператор получает ссылку на строку, второй вставляет новую. Смотрите реализацию на Delphi (InsertRowTableDoc).

Function InsertRowTableDoc(table_,position_: integer):boolean;
 
var row_:variant;
begin
 
InsertRowTableDoc:=true;
 
try
  row_
:=W.ActiveDocument.Tables.Item(table_).Rows.Item(position_);
  W
.ActiveDocument.Tables.Item(table_).Rows.Add(row_);
 
except
 
InsertRowTableDoc:=false;
 
end;
End;
Когда мы в своем распоряжении имеем набор функций для изменения таблицы, можно приступать к решению задачи - созданию документа типа счета-фактуры на базе шаблона. Полный исходный текст и полную версию шаблона счета-фактуры можно скачать по адресу www.kornjakov.ru/st1_4.zip. Здесь мы рассмотрим фрагмент данного документа. Создадим шаблон - документ формата DOC - и разместим его на диске в каталоге нашего проекта. Внешний вид шаблона смотрите на рисунке.

clip0070

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

procedure TForm1.Button1Click(Sender: TObject);
 
var tablica_:integer;
     col_
,row_:integer;
     a_
:integer;
     metki_
:array[1..12] of record
      col
:integer;
      row
:integer;
      metka
:string;
     
end;
    tovar
:array[1..2,1..12] of variant;
begin
// Заполняем массив данными. Массив используется
//для простоты демонстрации, в реальной программе
//данные берутся из базы данных.
tovar
[1,1]:='Стул офисный'; tovar[1,2]:='шт.';
tovar
[1,3]:=2; tovar[1,4]:=520.00; tovar[1,5]:=1040.00;
tovar
[1,6]:='-'; tovar[1,7]:=20; tovar[1,8]:=208.0;
tovar
[1,9]:=1248.00; tovar[1,10]:=62.40;
tovar
[1,11]:='Россия'; tovar[1,12]:='-';
tovar
[2,1]:='Телефон'; tovar[2,2]:='шт.';
tovar
[2,3]:=3; tovar[2,4]:=315.25; tovar[2,5]:=945.75;
tovar
[2,6]:='-'; tovar[2,7]:=20; tovar[2,8]:=189.15;
tovar
[2,9]:=1134.90; tovar[2,10]:=56.70;
tovar
[2,11]:='Беларусь'; tovar[2,12]:='-';
if CreateWord then begin
 
VisibleWord(true);
 
If OpenDoc(ExtractFileDir (application.ExeName) +'\sf.doc')
 
then begin
  tablica_
:=1;
 
for a_:=1 to 12 do begin
   
StartOfDoc;
   
if FindTextDoc('###M'+inttostr(a_)+'&') then
   
if GetSelectionTable then begin
     messagebox
(handle,'Находимся в таблице, запоминаем
      метку(переменную), номер колонки и строки!'
,
      pchar
('Номер колонки/строки = '+inttostr(col_)+'/'+inttostr(row_)),0);
     metki_
[a_].col:=col_;
     metki_
[a_].row:=row_;
     metki_
[a_].metka:='###M'+inttostr(a_)+'&';
   
end;
   
end;
   
Messagebox(handle,'Заполняем первую строку','',0);
   
for a_:=1 to 12 do begin
   
SetTextToTable(tablica_,metki_[a_].row,metki_[a_].col,tovar[1,a_]);
   
end;
   a_
:=1;
   
Messagebox(handle,'Добавляем строку','',0);
   
InsertRowTableDoc(tablica_, metki_[a_].row);
   
Messagebox(handle,'Заполняем вторую строку','',0);
   
for a_:=1 to 12 do begin
   
SetTextToTable(tablica_,metki_[a_].row,metki_[a_].col,tovar[2,a_]);
   
end;
   
SaveDocAs(ExtractFileDir(application.ExeName)+'\Счет - фактура.doc');
   
Messagebox(handle,'Текст сохранен','',0);
   
CloseDoc;
 
end;
 
Messagebox(handle,' Текст закрыт','',0);
 
CloseWord;
 
end;
end;
 

Мы сформировали фрагмент сложного документа, но вы, возможно, захотите в дальнейшем сами развивать эту тему и использовать все возможности Word.Application. В следующей части я постараюсь на примерах объяснить, каким образом это сделать. По всем вопросам вы можете обратиться к автору по адресу www.kornjakov.ru или _kvn@mail.ru.

Василий КОРНЯКОВ

Литература: Н. Елманова, С. Трепалин, А. Тенцер "Delphi 6 и технология COM" "Питер" 2002.