Суперфункции MS Word and MS Excel (статья)

Суперфункции
Так как многие разрабатываемые приложения формируют множество выходных отчетных форм для печати, то возможность формирования ими документов непосредственно в формате Word или Excel выгодно отличало бы их от своих аналогов.
Каждый, кто серьезно занимается разработкой программного обеспечения, заинтересован, чтобы его произведение пользовалось успехом у пользователей. Поэтому он вынужден удовлетворять постоянно растущие их требования к программному обеспечению. Не секрет, что большинство пользователей начинали свое знакомство с компьютером с изучения текстового редактора Word и электронных таблиц Excel. К тому же эти редакторы имеют огромные возможности и постоянно совершенствуются, они получили широкое распространение, для них написано сотни томов обучающей литературы, их изучают во всех учебных заведениях, и, самое главное, они - очень удобный инструмент для создания текстовых документов любой сложности. Отсюда простой вывод: большинство пользователей положительно воспримут тот факт, что ваша программа будет формировать выходные документы в формате Word или Excel.
== Как формировать документ в формате Word? ==
Теперь остановимся на постановке задачи, описанной в этой статье. Как формировать документ в формате Word? Очень просто. Текстовый редактор Word представляет собой COM-сервер и может получать и обрабатывать запросы от внешних программ. Все это позволяет организовать процесс управления и создания документа из внешней программы. Используя этот механизм, можно создать документ программно так же, как это делается вручную (посредством меню, кнопок и клавиатуры), но гораздо быстрей и эффектней.
Приступим к решению задачи. Как было сказано выше, Word является COM-сервером и может управляться внешними программами. Для этого Word предоставляет три объекта, через которые можно получить доступ к внутренним объектам Word`а и документов. Эти объекты - Word.Application, Word.Document и Word.Basic. Ко всем остальным объектам (текст, таблицы, кнопки, меню и др.) доступ возможен только через них.
Чтобы реализовать все эти возможности Word`а и для удобства своей работы мне пришлось разработать динамическую библиотеку процедур и функций, которую можно было использовать в различных своих приложениях для формирования и печати выходных документов. Зачем нужна такая библиотека, почему бы не вставлять программный код непосредственно в программу? Здесь причина в универсальности и гибкости использования библиотеки. Поэтому все ниже описанные коды легко могут быть оформлены в виде библиотеки для того, чтобы вы могли использовать ее непосредственно в своих приложениях, не теряя зря времени.
Чтобы почувствовать эффективность использования объектов Word, для начала попробуем написать несколько функций, которые позволят запустить Word, создать документ, изменить документ (записать текст), сохранить документ и закрыть Word. Для создания объекта и его использования применяем переменную W типа variant и библиотеку ComObj.
Рассмотрим следующий фрагмент кода:
uses ComObj;
var
 W: Variant;
function CreateWord(): Boolean;
begin
 Result := Rrue;
 try
 W := CreateOleObject('Word.Application');
 except
 Result := False;
 end;
end;
Для получения доступа к объекту Word.Application в нашей функции CreateWord() используем конструктор CreateOleObject('Word. Application'). Если редактор Word не установлен в системе, то будет сгенерирована ошибка, и мы получим значение функции = False, если Word установлен, и объект будет создан, то получим значение функции = True.
Эта функция создает объект (W), свойства и методы которого мы будем использовать в дальнейшем. Если выполнить нашу функцию CreateWord(), то Word будет запущен, но не появится на экране, потому что по умолчанию он запускается в фоновом режиме. Чтобы его активировать (сделать видимым) или деактивировать (сделать невидимым), используйте свойство Visible объекта W. Оформим это в виде функции VisibleWord(). Скобки try .. except везде используются для обработки исключительных ситуаций.
function VisibleWord(AVisible: Boolean): Boolean;
begin
 Result := True;
 try
 W.Visible := AVisible;
 except
 Result := False;
 end;
end;
Используя эту функцию, мы можем показывать или прятать Word с документами.
Следующим шагом будет создание документа. Для этого используем объект Documents объекта W. Этот объект имеет метод Add(), используя который, и создаем новый документ. При этом, как альтернативный вариант, вместо двух операторов Doc_ := W.Documents; Doc_.Add(); можем использовать один W.Documents.Add();.
function AddDoc(): Boolean;
var
 Doc_: Variant;
begin
 Result := True;
 try
 Doc_ := W.Documents;
 Doc_.Add();
 except
 Result := False;
 end;
end;
Создали документ, что дальше? Следующим шагом, естественно, является запись любого текста непосредственно в документ. Создадим для этого функцию SetTextToDoc().
function SetTextToDoc(text_: string; InsertAfter_: Boolean): Boolean;
var
 Rng_: Variant;
begin
 Result := True;
 try
 Rng_ := W.ActiveDocument.Range;
 if InsertAfter_ then
 Rng_.InsertAfter(text_)
 else
 Rng_.InsertBefore(text_);
 except
 Result := False;
 end;
end;
В этой функции используем объект Range и его методы InsertAfter() и InsertBefore() для того, чтобы вставить текст в документ с позиции курсора или до позиции курсора. Наша функция будет вставлять текст в активный документ в область курсора или выделенного текста.
Фрагмент кода:
Rng_ := W.ActiveDocument.Range;
if InsertAfter_ then
 Rng_.InsertAfter(text_)
else
 Rng_.InsertBefore(text_);
можно заменить следующим фрагментом:
if InsertAfter_ then
 W.ActiveDocument.Range.InsertAfter(text_)
else
 W.ActiveDocument.Range.InsertBefore(text_);
После того, как документ создан и в него записан текст, его необходимо сохранить. Для этого используем метод SaveAs() объекта ActiveDocument. Функция SaveDocAs() использует этот метод и сохраняет документ в заданный файл.
function SaveDocAs(file_: string): Boolean;
begin
 Result := True;
 try
 W.ActiveDocument.SaveAs(file_);
 except
 Result := False;
 end;
end;
Закрыть сохраненный документ можно, используя метод Close() объекта ActiveDocument.
function CloseDoc(): Boolean;
begin
 Result := True;
 try
 W.ActiveDocument.Close();
 except
 Result := False;
 end;
end;
Закрыть Word можно, используя метод Quit() объекта Application(W).
function CloseWord(): Boolean;
begin
 Result := True;
 try
 W.Quit();
 except
 Result := False;
 end;
end;
Таким образом, мы уже имеем несколько функций, которыми можно создать документ, записать в него текст, сохранить документ и отобразить его на экране монитора. Используя несколько строк, состоящих из функций нашей библиотеки, мы создаем документ, записываем в него текст, сохраняем и закрываем. Для демонстрации этого на новой форме разместим на кнопку и скопируем исходные тексты функций в модуль формы. В процедуру обработки нажатия кнопки разместим следующий программный текст.
procedure TForm1.Button1Click(Sender: TObject);
begin
 if CreateWord then
 begin
 Messagebox(0, 'Word запущен.', , 0);
 VisibleWord(True);
 Messagebox(0, 'Word видим.', , 0);
 VisibleWord(False);
 Messagebox(0, 'Word невидим.', , 0);
 VisibleWord(True);
 Messagebox(0, 'Word видим.', , 0);
 if AddDoc() then
 begin
 Messagebox(0, 'Документ создан.', , 0);
 SetTextToDoc('Мой первый текст', True);
 Messagebox(0, 'Добавлен текст', , 0);
 SaveDocAs('c:\Мой первый текст');
 Messagebox(0, 'Текст сохранен', , 0);
 CloseDoc();
 end;
 Messagebox(0, ' Текст закрыт', , 0);
 CloseWord();
 end;
end;
Конечно, набора данных функций недостаточно для создания полноценного отчета. Было бы эффективным создать шаблон некоего документа и затем заполнять его реальными значениями из базы данных, но для этого, как минимум, потребуется еще ряд функций. Такими функциями могут быть открытие ранее созданного документа, поиск текста, замена, копирование. Далее будут рассмотрены реализации этих функций и создание на их базе простого документа, например, платежного поручения.
== Cоздание документа MS Word ==
Труд программиста оценивается только пользователями и почти всегда по конечному результату. Используя материалы первой и второй частей статьи "Суперфункции", мы уже имеем возможность предоставить пользователю свою программу формирования простого документа в редакторе Word в законченном виде.
В первой части мы разработали и использовали функции создания и сохранения документа, а также функции записи текста, но этого недостаточно для подготовки даже простого документа. Каким набором функций нужно пользоваться, чтобы подготовить, например, документ типа "Платежное поручение"?
Поставим задачу: создание документа "Платежное поручение", - и определим, какие функции для этого необходимы. Сам по себе документ "Платежное поручение" является табличным, т.е. в определенную таблицу вписаны значения некоторых текстовых и числовых переменных. К тому же этот документ не является многострочным. Можно найти еще массу примеров таких документов, поэтому технология подготовки "Платежного поручения" подойдет к большому количеству аналогичных документов.
Одним из способов, который можно применить для подготовки простых документов, является заполнение шаблона. Шаблон представляет собой некоторый документ, часть которого выполнена в виде таблицы, определенные ячейки которой заполнены наборами символов (словами). Обозначим эти слова как переменные, вместо которых будут подставляться реальные значения из программы в момент формирования документа. Способ с использованием переменных удобен тем, что он позволяет легко изменить документ, не изменяя программу формирования. Так как мы используем шаблон (подготовленный в виде документа Word), то для работы с ним необходима функция открытия ранее созданного документа. Функции поиска текста и перевода курсора в начало документа необходима для поиска слов-переменных для дальнейшей замены их реальными данными. Также может быть необходима функция задания шрифта и вывода на печать готового документа.
Определим функцию открытия ранее созданного документа OpenDoc. Для этого используем метод Open коллекции Documents, которая нами уже использовалась в функции создания нового документа. Функция Open, кроме обязательного аргумента (имени файла), может иметь ряд дополнительных аргументов, которые определяют режим открытия. Она возвращает ссылку на объект типа Document, но в нашем случае будем использовать только обязательный аргумент.
function OpenDoc(file_: string): Boolean;
var
 Doc_: Variant;
begin
 Result := True;
 try
 Doc_ := W.Documents;
 Doc_.Open(file_);
 except
 Result := False;
 end;
end;
Фрагмент из двух операторов Doc_ := W.Documents; Doc_.Open(file_); можно заменить одним W.Documents.Open(file_);
Для перевода курсора в начало документа используем свойства End и Start объекта W.Selection. Эту функцию необходимо использовать каждый раз перед началом поиска текста, чтобы поиск осуществлялся с начала документа. Свойства End и Start объекта Selection можно использовать и для выделения диапазона текста, при этом в Start записывается номер начального символа фрагмента в тексте, а в End - номер конечного. В данном случае необходимо в оба поля записать нули.
function StartOfDoc: Boolean;
begin
 Result := True;
 try
 W.Selection.End := 0;
 W.Selection.Start := 0;
 except
 Result := False;
 end;
end;
Функция поиска фрагмента текста FindTextDoc() состоит из трех операторов. Первый и второй задают направление поиска (от начала к концу) и фрагмент для поиска, соответственно. Третий оператор выполняет поиск и возвращает результат. Функция возвращает True, если поиск удачный, и False - если нет. Во всех трех операторах используем поля и методы объекта Selection.
function FindTextDoc(text_: string): Boolean;
begin
 Result := True;
 try
 W.Selection.Find.Forward := True;
 W.Selection.Find.Text := Text_;
 Result := W.Selection.Find.Execute;
 except
 Result := False;
 end;
end;
Функция FindTextDoc() находит и выделяет фрагмент текста в документе. Для того, чтобы вставить новый текст вместо выделенного, создадим еще одну функцию. PasteTextDoc() состоит из двух операторов, удаления выделенного фрагмента и вставки нового текста с положения курсора. Оба эти оператора используют объект Selection объекта W. Действие этой функции отличается от SetTextToDoc() тем, что она вставляет изменения вместо выделенного фрагмента текста.
function PasteTextDoc(text_: string): Boolean;
begin
 Result := True;
 try
 W.Selection.Delete();
 W.Selection.InsertAfter(text_);
 except
 Result := False;
 end;
end;
Но более удобной была бы функция, которая одновременно подставляла бы новый текст на место найденного фрагмента. Создадим такую функцию.
function FindAndPasteTextDoc(findtext_, pastetext_: string): Boolean;
begin
 Result := True;
 try
 W.Selection.Find.Forward := True;
 W.Selection.Find.Text := findtext_;
 if W.Selection.Find.Execute then
 begin
 W.Selection.Delete();
 W.Selection.InsertAfter(pastetext_);
 end
 else
 Result := False;
 except
 Result := False;
 end;
end;
И последнее, печать документа. В данной части рассмотрим только процедуру активизации диалогового окна печати. Этот диалог активизируется через метод Show() объекта Dialogs(wdDialogFilePrint).Show. С помощью объекта Dialogs можно вызвать практически любое диалоговое окно Word'а, но об этих возможностях поговорим позже.
Функция будет выглядеть следующим образом:
function PrintDialogWord(): Boolean;
const wdDialogFilePrint = 88;
begin
 Result := True;
 try
 W.Dialogs.Item(wdDialogFilePrint).Show;
 except
 Result := False;
 end;
end;
Имея необходимый набор функций, можно приступать к написанию программы (процедуры) создания простого документа Word средствами Delphi. Как говорилось ранее, для формирования документа нам необходим шаблон - текст в формате Word (файл с расширением doc, rtf). Создадим вручную этот файл и разместим его, например, на диске C: "C:\Шаблон платежного поручения.doc". Полный пример с исходными текстами можно взять на моей домашней странице (www.kornjakov.ru/st1_2.zip), а здесь, с целью экономии объема, представлен только фрагмент документа (см.рис.).
Общий алгоритм формирования документа таков:
# Открываем шаблон, используя функцию открытия ранее созданного документа.
# Ищем слова-переменные и подставляем вместо них реальные значения, например, из базы данных.
# Сохраняем документ под новым именем.
# Печатаем документ, если это необходимо.
# Закрываем документ. Документ готов, и с ним можно работать как обычно, копировать, переименовывать и др.
Описанный выше алгоритм реализуем в виде небольшой программы. Для этого создаем новый проект и переписываем все функции (также и ранее созданные в первой части статьи) в модуль формы. Создаем на форме кнопку и в процедуру обработки ее нажатия пишем следующий программный код.
procedure TForm1.Button2Click(Sender: TObject);
begin
 if CreateWord() then
 begin
 VisibleWord(True);
 if OpenDoc('c:\Шаблон платежного поручения.rtf') then
 begin
 MessageBox(0, 'Переходим к заполнению шаблона', 'Шаблон открыт', 0);
 StartOfDoc;
 while not FindAndPasteTextDoc('###№ П.П.&', '21') do;
 StartOfDoc;
 while not FindAndPasteTextDoc('###Дата&', '21.05.2003') do;
 StartOfDoc;
 while not FindAndPasteTextDoc('###Вид платежа&', 'обычный') do;
 StartOfDoc;
 while not FindAndPasteTextDoc('###Сумма прописью&', 'Сто пятьдесят рублей 40 коп.') do;
 SaveDocAs('c:\Платежное поручение.rtf');
 MessageBox(0, 'Переходим к печати документа', 'Документ сформирован и сохранен', 0);
 PrintDialogWord();
 CloseDoc();
 end;
 CloseWord();
 end;
end;
Данная процедура сформирует документ и откроет окно диалога печати.
Мы сформировали простой документ, но обычно сложные документы содержат таблицы, графики, рисунки и другие компоненты. Все эти компоненты также можно создавать и настраивать из внешних программ, используя объекты и коллекции объекта Application. Все эти вопросы будут рассмотрены в следующей части статьи, там же в качестве примера рассмотрим создание документа, содержащего таблицу.
== Как создать таблицу в Word'е? ==
Темой этой части статьи будет создание таблиц в редакторе Word из приложений, написанных на Delphi. В прошлый раз мы рассмотрели создание простых документов, которые не содержат таблиц переменной длины, но большинство отчетов так или иначе содержит табличное представление информации. Уверен, что хоть один отчет в вашей программе содержит хоть одну таблицу. Поэтому информации двух предыдущих частей статьи было бы недостаточно для создания отчета, содержащего табличное представление информации.
Как создать таблицу в Word'е? Так же просто, как это делается в обычном объектно-ориентированном языке. В объекте Document мы имеем коллекцию Tables, с помощью методов и объектов которой можем создать таблицу и получить доступ к ее свойствам, ячейкам и тексту в ячейках. Если в нашем отчете несколько таблиц, то к любой из них мы имеем доступ с помощью коллекции Tables и индекса таблицы.
Чтобы более ясно представить процесс создания таблицы, используя объектные модели MS Office, создадим документ, аналог которого после простой доработки можно будет использовать в своих программах для вывода информации. Одним из самых распространенных отчетов, содержащих таблицу, является документ типа "Прайс-лист". Его мы и будем создавать. Попробуем создать его без шаблона, т.е. с чистого листа. Определимся, каким набором функций нужно владеть для создания этого документа. Во-первых, нам нужна будет функция создания таблицы, затем потребуется задать(изменить) размеры этой таблицы, вписать данные в ячейки, объединить ячейки. Возможно, потребуется еще несколько вспомогательных функций. Творчески используя материал статьи, вы сможете сами определить и создать для себя еще несколько функций для работы с таблицами.
Определим функцию создания таблицы CreateTable. Так как количество таблиц в документе может быть больше одной, то для идентификации каждой таблицы используем ее номер. Для создания применим метод ADD коллекции Tables. Метод ADD имеет аргументы: область, где создается таблица, количество строк и количество столбцов. Наша функция будет создавать таблицу там, где расположен курсор, и иметь еще один аргумент: числовую переменную, через которую будет возвращаться порядковое значение(индекс) таблицы в документе. Функция выглядит следующим образом:
function CreateTable(NumRows, NumColumns: Integer; var Index: Integer): Boolean;
var
 sel_: Variant;
begin
 Result := True;
 try
 sel_ := W.Selection;
 W.ActiveDocument.Tables.Add(Range := sel_.Range, NumRows := NumRows, NumColumns := NumColumns);
 Index := W.ActiveDocument.Tables.Count;
 except
 Result := False;
 end;
end;
Первый оператор определяет область выделения или положения курсора, второй оператор создает таблицу и третий возвращает количество таблиц в документе или порядковый номер вновь созданной таблицы, который будет использоваться другими функциями для доступа к этой таблице.
Наша функция создаст таблицу произвольного размера, но для корректного представления данных необходимо задать определенные размеры строк и столбцов. Чтобы задать размер таблицы или строки(столбца), нужно получить доступ к таким свойствам таблицы, как коллекции Columns и Rows (список столбцов и строк), через которые сможем получить доступ к конкретной строке или(и) столбцу, к ячейкам и к параметрам ячейки(строки, столбца). Для этого используем объект ActiveDocument.Tables.Item(TableNum), где TableNum - номер таблицы в документе. Создадим функцию, которая будет задавать ширину и высоту всех ячеек таблицы.
function SetSizeTable(TableNum: Integer; RowsHeight, ColumnsWidth: Real): Boolean;
begin
 Result := True;
 try
 W.ActiveDocument.Tables.Item(TableNum).Columns.Width := ColumnsWidth;
 W.ActiveDocument.Tables.Item(TableNum).Rows.Height := RowsHeight;
 except
 Result := False;
 end;
end;
Аналогично мы можем задавать высоту любой строки или(и) ширину любого столбца на выбор. Для доступа к размерам ячейки используем также коллекции Rows, Columns объекта Table. Чтобы воспользоваться этими возможностями из нашего приложения, создадим следующие функции.
function SetHeightRowTable(TableNum, RowNum: Integer; RowHeight: Real): Boolean;
begin
 Result := True;
 try
 W.ActiveDocument.Tables.Item(TableNum).Rows.Item(RowNum).Height := RowHeight;
 except
 Result := False;
 end;
end;
function SetWidthColumnTable(TableNum, ColumnNum: Integer; ColumnWidth: Real): Boolean;
begin
 Result := True;
 try
 W.ActiveDocument.Tables.Item(TableNum).Columns.Item(ColumnNum).Width := ColumnWidth;
 except
 Result := False;
 end;
end;
Возможно, нам придется не только задавать размеры таблицы, но и определять(считывать) размеры ячеек таблицы. Для этого используем те же коллекции, объекты и свойства таблицы, что и в функции SetSizeTable(), но немного изменим внутренние операторы таким образом, что в возвращаемые переменные RowsHeight и ColumnsWidth будут записываться значения размеров строк и столбцов таблицы.
function GetSizeTable(TableNum: Integer; out RowsHeight, ColumnsWidth: Real): Boolean;
begin
 Result := True;
 try
 ColumnsWidth := W.ActiveDocument.Tables.Item(TableNum).Columns.Width;
 RowsHeight := W.ActiveDocument.Tables.Item(TableNum).Rows.Height;
 except
 Result := False;
 end;
end;
Также можно считать и размеры строки или столбца на выбор, для этого достаточно использовать коллекции Rows, Columns объекта Table (Tables.Item(Table)).
Следующим этапом формирования табличного документа определим запись текстовой информации в выбранную ячейку таблицы. Одним из способов такой записи является доступ к полю Text ячейки, но и в этом случае текст записывается не напрямую, а в объект Range ячейки таблицы. Функция SetTextToTable выполняет такую запись.
function SetTextToTable(TableNum, RowNum, ColumnNum: Integer; AText: string): Boolean;
begin
 Result := True;
 try
 W.ActiveDocument.Tables.Item(TableNum).Columns.Item(ColumnNum).Cells.Item(RowNum).Range.Text := AText;
 except
 Result := False;
 end;
end;
И последнее действие, которое необходимо произвести над таблицей для создания простого табличного документа, это объединение ячеек. Для этого воспользуемся методом Merge() объекта Cell (ячейка). Первый оператор функции объединения ячеек возвращает указатель на объект - конечную ячейку (Cel). Второй оператор объединяет начальную ячейку Row1, Column1 с конечной ячейкой, табличные координаты которой уже заданы и равны Row2, Column2.
function SetMergeCellsTable(TableNum: Integer; Row1, Column1, Row2, Column2: Integer): Boolean;
var
 Cel: Variant;
begin
 Result := True;
 try
 Cel := W.ActiveDocument.Tables.Item(TableNum).Cell(Row2, Column2);
 W.ActiveDocument.Tables.Item(TableNum).Cell(Row1, Column1).Merge(Cel);
 except
 Result := False;
 end;
end;
Переходим к заключительной стадии - созданию документа.
Для этого все определенные в этой части статьи функции объединим с ранее созданными и перенесем во вновь созданную библиотеку процедур и функций. Например, это будет файл MyWord.pas, в разделе interface которого будут описаны заголовки всех наших функций, а в разделе implementation - сами функции (в дальнейшем будем пользоваться этой библиотекой). Не забудьте после implementation вставить строки 
uses ComObj;
var
 W: Variant;
Создадим новый проект, в программном модуле которого сделаем ссылку на нашу библиотеку uses MyWord;. На форме разместим кнопку и в процедуру обработки нажатия ее впишем следующий программный код.
procedure TForm1.Button1Click(Sender: TObject);
var
 tablica_: Integer;
begin
 if CreateWord() then
 begin
 VisibleWord(True);
 if AddDoc() then
 begin
 // создаем таблицу
 if CreateTable(5, 3, tablica_) then
 begin
 MessageBox(0, PChar('Таблица создана = ' + IntToStr(tablica_)), , 0);
 // изменяем размеры таблицы
 SetSizeTable(tablica_, 25, 37);
 SetWidthColumnTable(tablica_, 1, 300);
 SetWidthColumnTable(tablica_, 2, 80);
 SetWidthColumnTable(tablica_, 3, 80);
 Messagebox(0, 'Размер таблицы изменен', , 0);
 // записывает информацию в ячейки таблицы
 SetTextToTable(tablica_, 1, 1, 'ПРОЦЕССОРЫ (данные от 27.05.2003) ');
 SetTextToTable(tablica_, 2, 1, 'Наименование');
 SetTextToTable(tablica_, 2, 2, 'Стоимость');
 SetTextToTable(tablica_, 2, 3, 'Гарантия');
 SetTextToTable(tablica_, 3, 1, 'ПРОЦЕССОР AMD K7- 1333 ATHLON 266MHz (Socket-A)');
 SetTextToTable(tablica_, 3, 2, '47.52 $');
 SetTextToTable(tablica_, 3, 3, '12 мес.');
 SetTextToTable(tablica_, 4, 1, 'ПРОЦЕССОР AMD K7- 800 DURON (Socket-A)');
 SetTextToTable(tablica_, 4, 2, '23.54 $');
 SetTextToTable(tablica_, 4, 3, '12 мес.');
 // объединяем необходимые ячейки таблицы
 SetMergeCellsTable(tablica_, 1, 1, 1, 3);
 end;
 SaveDocAs('c:\Прайс лист');
 MessageBox(0, 'Текст сохранен', , 0);
 CloseDoc();
 end;
 Messagebox(0, ' Текст закрыт', , 0);
 CloseWord;
 end;
end;
В результате выполнения приведенной выше процедуры получим результат, который выглядит, как показано на рисунке.
Полный исходный текст смотрите по адресу www.kornjakov.ru/st1_3.zip.
Мы сформировали достаточно простую таблицу, но редактор Word позволяет создавать очень сложные документы (см. как пример бланки налоговых деклараций). Практика показывает, что для таких документов лучше использовать шаблоны, которые можно заполнять информацией из программы. Причем такие шаблоны не обязательно создавать самому, они есть в любых правовых справочных системах. В следующей части статьи будет рассмотрен пример создания сложного документа, который сочетает заполнение шаблона и заполнение таблицы переменной длины, для этого понадобится ряд дополнительных функций, которыми будет дополнена наша библиотека.
== Шаблоны и отчёты MS Word ==
Многие популярные информационно-правовые системы содержат шаблоны различных документов в формате Word. Используя их и информацию данной статьи, вы можете легко и быстро создавать отчеты, если пишете свои программы на Delphi. Если пишете на другом языке, это не помеха, описанный подход справедлив к различным языкам.
Когда мы формируем сложный документ по шаблону, и когда в этом шаблоне есть таблицы, количество строк которых в выходном документе может быть произвольным и зависеть от объема информации, то нам недостаточно просто функций создания таблиц и записи в ячейки определенной информации. Для создания такого документа необходимо, как минимум, еще несколько функций работы с таблицами, в перечень которых входит перемещение по таблицам, добавление строк (столбцов) в конец или в середину таблицы. Также необходимо определять размер (количество строк, столбцов) и номер текущего столбца и строки. Чтобы понимать, о чем речь, необходимо просмотреть части 1-3 данной статьи, опубликованных в предыдущих номерах.
Для того, чтобы применить свои знания к конкретной задаче, сделаем ее постановку. Например, в нашем документе есть таблица, которая представляет собой шаблон и заполняется из массива информации, который имеет произвольную длину. Таким документом может быть счет-фактура, заголовок которой представляет собой сложную таблицу, средняя часть представляет таблицу переменной длины, а нижняя также представляет сложную таблицу. Для заполнения такого шаблона можно использовать способ, описанный во второй части данной статьи. Этот способ основан на поиске в шаблоне переменных (неповторяющиеся строковые значения длиной 3-5 символов) и подстановке вместо них реальных значений на этапе формирования документа. Поэтому для добавления информации в такую таблицу придется осуществить поиск и позиционирование в строку (по переменной), в которую и перед которой необходимо вставлять строки, и запомнить, в какие колонки какую записывать информацию, но для начала необходимо определить, находится курсор в таблице или нет.
Для этого используем свойство Information объекта Selection, в качестве параметра которого будет константа wdWithInTable. В этом случае этот метод возвращает TRUE, если курсор в таблице, или FALSE, если нет. Для использования в нашем приложении создадим функцию GetSelectionTable().
function GetSelectionTable(): Boolean;
const
 wdWithInTable = 12;
begin
 Result := True;
 try
 Result := W.Selection.Information(wdWithInTable);
 except
 Result := False;
 end;
end;
Если в нашем документе может быть более одной таблицы, то, скорее всего, необходима возможность перехода и позиционирование курсора на следующей или предыдущей таблице. Объект Selection дает нам эту возможность через методы GoToNext() и GoToPrevious(), в этом случае в качестве их параметров должна использоваться константа wdGoToTable.
function GoToNextTable(table_: Integer): Boolean;
const
 wdGoToTable = 2;
begin
 Result := True;
 try
 W.Selection.GoToNext(wdGoToTable);
 except
 Result := False;
 end;
end;
function GoToPreviousTable(table_: Integer): Boolean;
const
 wdGoToTable = 2;
begin
 Result := True;
 try
 W.Selection.GoToPrevious(wdGoToTable);
 except
 Result := False;
 end;
end;
Когда мы позиционируемся на таблице, можем определить количество столбцов и строк в ней. Для этого также используем свойство Information объекта Selection, но в качестве аргументов используем константы wdMaximumNumberOfColumns и wdMaximumNumberOfRows.
function GetColumnsRowsTable(table_: Integer; var Columns, Rows: Integer): Boolean;
const
 wdMaximumNumberOfColumns = 18;
 wdMaximumNumberOfRows = 15;
begin
 Result := True;
 try
 Columns := W.Selection.Information(wdMaximumNumberOfColumns);
 Rows := W.Selection.Information(wdMaximumNumberOfRows);
 except
 Result := False;
 end;
end;
Кроме размера таблицы, нам может быть необходим номер колонки и строки, на которой позиционирован курсор. Для этого так же используем свойство Information объекта Selection, но в качестве аргументов используем константы wdStartOfRangeColumnNumber, wdStartOfRangeRowNumber. Для реализации этого в Delphi создадим функцию GetColumnRowTable().
function GetColumnRowTable(table_: Integer; var Column, Row: Integer): Boolean;
const
 wdStartOfRangeColumnNumber = 16;
 wdStartOfRangeRowNumber = 13;
begin
 Result := True;
 try
 Column := W.Selection.Information(wdStartOfRangeColumnNumber);
 Row := W.Selection.Information(wdStartOfRangeRowNumber);
 except
 Result := False;
 end;
end;
После того, как мы нашли таблицу в шаблоне документа и позиционировались на определенной ячейке, нам необходимо выполнить некоторые действия с ней (добавить, вставить строки, записать информацию). Очевидно, что нам нужен будет набор функций для ее модификации.
Обычно во время формирования таблицы мы не знаем, сколько будет строк. Они могут добавляться в конец или вставляться в середину таблицы. Если для формирования документа мы используем шаблон таблицы и в нем уже есть, например, заголовок, то нам не обойтись без процедур добавления или вставления строк. Добавить строку в конец таблицы можно, используя метод Add() коллекции Rows. Чтобы это сделать из приложения на Delphi, достаточно создать и использовать функцию. Определим ее как AddRowTableDoc().
function AddRowTableDoc(table_: Integer): Boolean;
begin
 Result := True;
 try
 W.ActiveDocument.Tables.Item(table_).Rows.Add();
 except
 Result := False;
 end;
end;
Для того, чтобы вставлять строки в середину таблицы, удобно использовать пару операторов. Первый выделяет строку, перед которой необходимо вставить новую, второй вставляет строку (строки). Смотрите функцию InsertRowsTableDoc().
function InsertRowsTableDoc(table_, position_, count_: Integer): Boolean;
begin
 Result := True;
 try
 W.ActiveDocument.Tables.Item(table_).Rows.Item(position_).Select;
 W.Selection.InsertRows(count_);
 except
 Result := False;
 end;
end;
Для добавления одной строки можно использовать также и метод Add коллекции Rows, но с параметром, в качестве которого выступает ссылка на строку, перед которой необходимо вставить новую. Первый оператор получает ссылку на строку, второй вставляет новую. Смотрите реализацию на Delphi InsertRowTableDoc().
function InsertRowTableDoc(table_, position_: Integer): Boolean;
var
 row_: Variant;
begin
 Result := True;
 try
 row_ := W.ActiveDocument.Tables.Item(table_).Rows.Item(position_);
 W.ActiveDocument.Tables.Item(table_).Rows.Add(row_);
 except
 Result := False;
 end;
end;
Когда мы в своем распоряжении имеем набор функций для изменения таблицы, можно приступать к решению задачи - созданию документа типа счета-фактуры на базе шаблона. Полный исходный текст и полную версию шаблона счета-фактуры можно скачать по адресу www.kornjakov.ru/st1_4.zip. Здесь мы рассмотрим фрагмент данного документа. Создадим шаблон - документ формата DOC - и разместим его на диске в каталоге нашего проекта. Внешний вид шаблона смотрите на рисунке.
Здесь будем заполнять только табличную часть. О том, как заполнять остальное, читайте вторую часть данной статьи. Для начала наши новые функции скопируем в библиотеку 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. В следующей части я постараюсь на примерах объяснить, каким образом это сделать.
== Рисунки и другие объекты MS Word ==
Используя доступ к Word.Application из приложений Delphi, можно вставлять в текст документа записи, рисунки и другие объекты. Сложные документы в формате Word обычно могут содержать не только таблицы или текст, но также записи, линии и фигуры, объекты WordArt, рисунки, графику и многое другое.
Все эти объекты можно разделить на две группы: те, которые являются внутренними объектами Word, и внешние объекты, создаваемые внешними по отношению к самому Word серверами OLE. Все объекты, рассмотренные в 1-4 части статьи, - это внутренние объекты.
Кто программирует в Visual Basic в среде Word и в Delphi, тот может дальше сам развивать тему "Суперфункций". Все просто. Объекты, коллекции и методы, которые работают в среде Word, переносятся почти без изменений в среду Delphi. Главное здесь - применить немного изобретательности и находчивости. Можно использовать палитру компонентов Servers, которая есть в Delphi, начиная с 5-й версии. Выбор между готовыми компонентами и работой "напрямую" с Word.Application зависит от профессионализма, сложности поставленных задач, отпущенного времени и главное - от вкусов и стиля программирования. Это индивидуально для каждого, кто занимается разработкой сложных и не очень сложных приложений на Delphi и других языках программирования. Я свой выбор остановил на работе с Word.Application, так как это дает больше гибкости и возможностей при решении сложных и нестандартных задач
Рассмотрим еще несколько необходимых внутренних и использование некоторых внешних объектов, их создание и управление из приложений на Delphi.
Одним из часто используемых объектов является Textbox. Для его создания используем коллекцию Shapes(формы) и ее метод AddTextbox(). Объект коллекции Shapes имеет атрибут - имя, его можно считать, можно изменить и обращаться к объекту не только через индекс, но и через имя. В функцию создания объекта Textbox передаем в качестве аргументов координаты и размеры области, а возвращаем имя объекта. Она выглядит следующим образом.
function CreateTextBox(ALeft, ATop, AWidth, AHeight: Real; out AName: string): Boolean;
const
 msoTextOrientationHorizontal = 1;
begin
 Result := True;
 try
 AName := W.ActiveDocument.Shapes.AddTextbox(msoTextOrientationHorizontal, ALeft, ATop, AWidth, AHeight).Name;
 except
 Result := False;
 end;
end;
Следующей естественной задачей является запись текста в TextBox. Используем доступ к созданному объекту (Shapes.Item) через индекс(число) или имя(строка). Текст можно записать в свойство Text объекта TextRange. Перед записью текста проверяем тип формы (Shape). Если форма имеет тип TextRange, тогда записываем текст. Смотрите реализацию в виде функции TextToTextBox() на Delphi.
function TextToTextBox(TextBox: Variant; AText: string): Boolean;
const
 msoTextBox = 17;
begin
 Result := True;
 try
 if w.ActiveDocument.Shapes.Item(TextBox).Type = msoTextBox then
 W.ActiveDocument.Shapes.Item(TextBox).TextFrame.TextRange.Text := AText
 else
 Result := False;
 except
 Result := False;
 end;
end;
Объект - форма (Shape) - может иметь тип не только как запись, он может содержать картинку, звук, линию и др. В зависимости от типа формы, процедура ее создания различна, но некоторые поля не зависят от типа. Одним из таких полей является имя формы, которое можно получить или изменить. Создадим две функции для определения имени (индекса) формы и для его изменения.
function GetNameIndexShape(NameShape: Variant): Variant;
begin
 try
 Result := W.ActiveDocument.Shapes.Item(NameShape).Name;
 except
 Result := False;
 end;
end;
В качестве аргумента этой функции можно использовать как индекс, так и имя формы.
function SetNewNameShape(NameShape: Variant; NewNameShape: string): string;
begin
 try
 W.ActiveDocument.Shapes.Item(NameShape).Name := NewNameShape;
 Result := NewNameShape;
 except
 Result := ;
 end;
end;
Здесь можно было бы рассмотреть реализацию функций перемещения, изменения размеров, а также определение положения и размеров формы в документе. Можете сделать это самостоятельно. В Visual Basic для этого используются следующие операторы:
ActiveDocument.Shapes.Item(NameShape).Left = Left
ActiveDocument.Shapes.Item(NameShape).Top = Top
ActiveDocument.Shapes.Item(NameShape).Width = Width
ActiveDocument.Shapes.Item(NameShape).Height = Height
Или наоборот
Left = ActiveDocument.Shapes.Item(NameShape).Left
Top = ActiveDocument.Shapes.Item(NameShape).Top
Width = ActiveDocument.Shapes.Item(NameShape).Width
Height = ActiveDocument.Shapes.Item(NameShape).Height
В документах часто могут использоваться рисованные объекты, например, линии. Для их создания также используем коллекцию Shapes (формы) и ее метод AddTextbox. В функцию создания объекта Line передаем в качестве аргументов начальные и конечные координаты линии, а возвращаем имя объекта. Эта функция выглядит следующим образом:
function CreateLine(BeginX, BeginY, EndX, EndY: Real; var AName: string): Boolean;
begin
 Result := True;
 try
 AName := W.ActiveDocument.Shapes.AddLine(BeginX, BeginY, EndX, EndY).Name;
 except
 Result := False;
 end;
end;
Для прорисовки сложной фигуры необходимо использовать метод AddPolyline коллекции Shapes. Аргументом этой функции должен быть массив точек (massiv). Реализация на Visual Basic имеет следующий вид:
ActiveDocument.Shapes.AddPolyline(massiv)
Для того, чтобы вставить рисунок из внешнего файла, необходимо использовать метод AddPicture коллекции Shapes, а в качестве аргумента имя файла и координаты. Создадим такую функцию.
function CreatePicture(FileName: string; ALeft, ATop: Real; var AName: string): Boolean;
begin
 Result := True;
 try
 AName := W.ActiveDocument.Shapes.AddPicture(FileName).Name;
 W.ActiveDocument.Shapes.Item(AName).Left := ALeft;
 W.ActiveDocument.Shapes.Item(AName).Top := ATop;
 except
 Result := False;
 end;
end;
Мы должны иметь возможность не только создать новый объект, но и удалить ранее созданный, например, рисунок или запись. Для этого используется метод Delete коллекции Shapes. Для использования этого в своих приложениях создадим функцию DeleteShape, в качестве аргумента которой будет имя или индекс объекта Shape.
function DeleteShape(NameShape: Variant): Boolean;
begin
 Result := True;
 try
 W.ActiveDocument.Shapes.Item(NameShape).Delete;
 except
 Result := False;
 end;
end;
Внешний объект в документе представляет собой Ole-объект, отображаемый внешней программой, которая является Ole-сервером по отношению к редактору Word. Такими объектами могут быть рисунки (BMP), созданные программой Paint или лист Excel. Внешний объект может отображаться в документе только тогда, когда установлена поддерживающая его программа. Для внедрения внешних объектов в документ используется метод AddOLEObject() коллекции Shapes. Например, чтобы получить доступ к объекту в Visual Basic, используется следующий оператор:
Set obb = ActiveDocument.Shapes.AddOLEObject("MSGraph.Chart.8")
В Delphi он выглядит следующим образом:
var Obb: Variant;
Obb := W.ActiveDocument.Shapes.AddOLEObject("MSGraph.Chart.8");
Где W - Word.Application.
Но чтобы программировать Ole-объект, необходимо знать его поля и методы. Эта информация индивидуальна для каждого объекта и ее рассмотрение - отдельная тема.
На основе созданных нами функций создадим небольшую демонстрационную программу, которая будет создавать объект-запись и объект-линию, а затем удалит их из документа. Как всегда, используем ранее созданные функции, разместим на форме кнопку и процедуре обработки ее нажатия напишем следующий программный текст:
procedure TForm1.Button1Click(Sender: TObject);
var
 BoxName_, LineName_: string;
begin
 if CreateWord() then
 begin
 MessageBox(0, 'Word запущен.', , 0);
 VisibleWord(True);
 MessageBox(0, 'Word видим.', , 0);
 if AddDoc() then
 begin
 MessageBox(0, 'Документ создан.', , 0);
 CreateTextBox(1, 1, 100, 50, BoxName_);
 MessageBox(0, 'Создали форму - надпись.', , 0);
 MessageBox(0, PChar(GetNameIndexShape(1)), 'Считали имя формы', 0);
 BoxName_ := SetNewNameShape(BoxName_, 'Новое имя');
 MessageBox(0, PChar(GetNameIndexShape (1)), 'Изменили имя формы и считываем его снова', 0);
 TextToTextBox(BoxName_, 'Добавляем текст в TextBox');
 MessageBox(0, 'Рисуем линию', , 0);
 CreateLine(1, 15, 300, 200, LineName_);
 MessageBox(0, 'Удаляем линию', , 0);
 DeleteShape(LineName_);
 MessageBox(0, 'Удаляем надпись', , 0);
 DeleteShape(BoxName_);
 SaveDocAs('c:\Документ, содержащий объекты');
 MessageBox(0, 'Текст сохранен', , 0);
 CloseDoc();
 end;
 Messagebox(0, ' Текст закрыт', , 0);
 CloseWord();
 end;
end;
В своей статье я постарался показать основы создания документов Word на основе Word.Application и Delphi. На самом деле возможности здесь таковы, что позволяют использовать редактор Word в качестве генератора отчетов для создания документов любой сложности. Пожалуй, только используя редактор Excel, их можно превзойти. Продолжение будет посвящено программированию документов в Excel из приложений на Delphi.
Исходный текст www.kornjakov.ru/st1_5.zip.
== Заключение ==
MS Word и Excel предоставляют мощное средство для разработки выходных документов. А раз это есть, то этим нужно пользоваться и создавать конкурентно-способные приложения. Мы рассмотрели только малую часть, но вы можете сами развивать эту тему и применить свой опыт в практической деятельности. Для тех, кто желает дальше и глубже копнуть, могу сказать, что Word и Excel предоставляют возможность не только создавать и модифицировать документы из внешних программ, но и модифицировать свой вид, работать с меню, панелями и кнопками, но это тема для отдельной статьи. Кто желает посмотреть практический результат от применения данных библиотек, прошу скачать и установить программу www.kornjakov.ru/minimax.exe, которая демонстрирует возможности использования Word.Application.
 По всем вопросам, касающимся материала этой статьи, вы можете обратиться к автору по адресу www.kornjakov.ru или _kvn@mail.ru.
 Василий КОРНЯКОВ,
 www.kornjakov.ru,
 _kvn@mail.ru
 Литература: Н. Елманова, С. Трепалин, А.Тенцер "Delphi 6 и технология COM" "Питер" 2002.
== См. также ==
* Суперфункции MS Word and MS Excel (статья)
* Суперфункции EXCEL.
* Настройки листа и печать в MS Excel
* Диаграммы Excel
* Создание DLL-библиотеки для работы с Word/Excel из приложений на Delphi
* Суперфункции:FAQ
Author: Василий КОРНЯКОВ
Source: http://www.kornjakov.ru
ID: 04365
Category:Статьи
Category:COM
Category:MS Office