Экспорт текста в Word из Delphi в текстовый шаблон Word, с элементом построения таблиц, используя маркеры
Писал я как то на сторону программу, связанную с кучей расчетов и генерировал это всё потом в отчет. Выводил в Word довольно большое количество листов. И как это обычно бывает, что то у заказчика там менялось и стабильно раз в месяц постоянно просили поменять то форму выводимых таблиц, то где то текст, то еще что.
И тут я впервые задумался о конструкторе таблиц. Точнее о выводе таблиц и простого текста в WORD по маркерам в редактируемом шаблоне.
В общем хочу поделиться крохотным примером на эту тему. Показать как можно выводить столбцы таблицы по маркерам, выводить текст в основном документе и колонитулах.
Чем хорош этот метод – в шаблоне (рыбе) документа можно сразу указать размер, цвет, стиль текста и таблицы, и не париться программно. При этом шаблон может поправить сам пользователь, зная назначение маркера.
Накидываю в WORD`e шаблон следующего содержания
Маркеры я заключил в сиволы '%', программа будет их искать и заменять на нужный нам текст. Сохраняю его и ложу рядом с будущим исполняемым фалом программы. Что то типа «отчета за смену», идиотично, но что в голову ни чего не лезет, но суть не в этом.
В итоге должно получиться как то так:
Всю работу с WORD я использовал с помощью WordDocument и WordApplication со вкладки Server. Я использовал бородатый Delphi 7. В XE, если кто не найдет, это вкладка по умолчанию отключена, ей просто надо включить.
Для наглядности покажу, что я накидал на форму Скрин формы:
StrinGrid, Edit`ы, Label`ы к ним, и SpinEdit для регулирования количества строк в таблицы В общем то все эти заполненные поля будем передавать в Word.
Приведу код кнопки, которая соединяет с Word и производит с ним работу.
Спойлер procedure TForm1.BitBtn1Click(Sender: TObject); const // массивные маркеры marker: array[1..5] of string[15] = ('%п/п%', '%Опер%', '%затрат%', '%слж%', '%прбл%'); var FTrue, Ffalse, Template, NewTemplate, ItemIndex, T, D, DD, Replese_T, Find_T: OleVariant; Tbl, L, col, Tablwdline, con: OleVariant; i, ii, j: integer; TExtend: OleVariant;
begin Memo1.Clear; Memo1.Lines.Add('Соеденение с Word. '); Application.ProcessMessages; try WA1.Connect; WA1.Visible := False; Template := EmptyParam; NewTemplate := False; FTrue := true; Ffalse := false;
except ShowMessage('Не удалось соедениться с WORD!'); Memo1.Lines.Add('Не удалось соедениться с WORD!' ); Application.ProcessMessages; exit; end;
Memo1.Lines.Add('Соеденение с документом Word. '); Application.ProcessMessages;
// Добовляем документ из имеющего со статусом несохраненого нового документа
try T := ExtractFilePath(Application.ExeName)+'\document.docx';;
ItemIndex := WA1.Documents.Add(T, NewTemplate, NewTemplate, Template);
Memo1.Lines.Add('Не удалось соедениться с шаблоном WORD! (document.docx) Проверьте путь расположения файла.' ); Application.ProcessMessages; ShowMessage('Не удалось соедениться с шаблоном WORD! (document.docx) Проверьте путь расположения файла.'); WD1.Application.Selection.EndOf(Template, Template); WA1.Application.WindowState := wdWindowStateMaximize; WA1.Application.ScreenUpdating := true; WA1.Application.ScreenRefresh; WA1.Visible := true; WA1.Disconnect; WD1.Disconnect; exit; end;
try //Количество таблиц в документе и количество сток и слобцов col := WA1.ActiveDocument.Tables.Count;
for j := 1 to col do begin Memo1.Lines.Add('Присоеденяемся к таблице '+Inttostr( ); Application.ProcessMessages;
tbl := WA1.ActiveDocument.Tables.Item(j); //присоеденяемся к таблице
//ДОБАВЛЕНИЕ СТРОК Tbl.Select;
for ii := 1 to Length(marker) do begin Find_T := marker[ii]; // текст который меняем D := wdFindStop; // найти один раз DD := wdReplaceNone; // замена один раз Replese_T := ''; // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam); if WA1.Selection.Text = marker[ii] then begin L := Stringgrid1.RowCount - 2; // количество вставляемых строк; try WD1.Application.Selection.SelectRow; WD1.Application.Selection.Borders.OutsideLineWidth := wdLineWidth050pt; except Memo1.Lines.Add('Конфликт оформления линий рамки!'); Application.ProcessMessages; // ShowMessage('Конфликт оформления линий рамки!'); end; if L > 1 then WA1.Selection.InsertRowsBelow(L) else L := 0;
try Tablwdline := wdRow; con := L; TExtend := wdMove; WD1.Application.Selection.EndOf(Tablwdline, TExtend); Tablwdline := wdline; con := L; TExtend := wdMove; WD1.Application.Selection.MoveUP(Tablwdline, con, TExtend); WD1.Application.Selection.SelectRow; TExtend := wdExtend; WD1.Application.Selection.MoveDown(Tablwdline, con, TExtend); // на строки ниже;> WD1.Application.Selection.Borders.OutsideLineWidth := wdLineWidth150pt; except Memo1.Lines.Add('Конфликт оформления линий рамки!'); Application.ProcessMessages; ShowMessage('Конфликт оформления линий рамки!'); end;
//ЗАМЕНА И ЗАПОНЕНИЕ
for ii := 1 to Length(marker) do begin Memo1.Lines.Add('Ищем маркеры '+ marker[ii]+' в таблице ' ); Application.ProcessMessages;
Tbl.Select; Find_T := marker[ii]; // текст который меняем D := wdFindStop; // найти один раз DD := wdReplaceNone; // замена один раз Replese_T := ''; // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam); if WA1.Selection.Text = '%Опер%' then begin for i := 1 to Stringgrid1.RowCount - 1 do begin WD1.Application.Selection.TypeText(Stringgrid1.Cells[0, i]); Tablwdline := wdline; con := 1; WD1.Application.Selection.MoveDown(Tablwdline, con, EmptyParam); // на строку ниже; end; end;
if WA1.Selection.Text = '%затрат%' then begin for i := 1 to Stringgrid1.RowCount - 1 do begin WD1.Application.Selection.TypeText(Stringgrid1.Cells[1, i]); Tablwdline := wdline; con := 1; WD1.Application.Selection.MoveDown(Tablwdline, con, EmptyParam); // на строку ниже; end; end;
if WA1.Selection.Text = '%слж%' then begin for i := 1 to Stringgrid1.RowCount - 1 do begin WD1.Application.Selection.TypeText(Stringgrid1.Cells[2, i]); Tablwdline := wdline; con := 1; WD1.Application.Selection.MoveDown(Tablwdline, con, EmptyParam); // на строку ниже; end; end;
if WA1.Selection.Text = '%п/п%' then begin for i := 1 to Stringgrid1.RowCount - 1 do begin WD1.Application.Selection.TypeText(IntToStr(i)); Tablwdline := wdline; con := 1; WD1.Application.Selection.MoveDown(Tablwdline, con, EmptyParam); // на строку ниже; end; end;
if WA1.Selection.Text = '%прбл%' then begin for i := 1 to Stringgrid1.RowCount - 1 do begin WD1.Application.Selection.TypeText(Stringgrid1.Cells[3, i]); Tablwdline := wdline; con := 1; WD1.Application.Selection.MoveDown(Tablwdline, con, EmptyParam); // на строку ниже; end; end;
Tbl.Select; Find_T := '%объед%'; // текст который меняем D := wdFindStop; // найти один раз DD := wdReplaceNone; // замена один раз Replese_T := ''; // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
if StringGrid1.RowCount > 2 then begin if WA1.Selection.Text = '%объед%' then begin Memo1.Lines.Add('Объеденяем строки в таблице. ' ); Application.ProcessMessages; Tablwdline := wdCharacter; Con := 1; WD1.Application.Selection.Delete(Tablwdline, con); //удалить выделенное con := StringGrid1.RowCount - 2; // tbl.Rows.Count - WD1.Application.Selection.Information[wdEndOfRangeRowNumber]; //количество строк в таблице - номер строки в которой находится курсор Tablwdline := wdline; WD1.Application.Selection.MoveDown(Tablwdline, con, TExtend); WD1.Application.Selection.Cells.Merge; WD1.Application.Selection.EndOf(Template, Template); end end else begin Con := 8; Tablwdline := wdWord; WD1.Application.Selection.Delete(Tablwdline, con); //удалить выделенное WD1.Application.Selection.EndOf(Template, Template); end;
L := wdStory; // в начало документа WA1.Selection.HomeKey(L, EmptyParam);
Memo1.Lines.Add('Меняем маркеры в тексте. ' ); Application.ProcessMessages;
Find_T := '%Фамилия%'; // текст который меняем D := wdFindStop; // найти один раз DD := wdReplaceAll; // замена все Replese_T := Edit1.Text; // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
Find_T := '%Имя%'; // текст который меняем Replese_T := Edit2.Text; // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
Find_T := '%Отчество%'; // текст который меняем Replese_T := Edit3.Text; // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
Find_T := '%время.прихода%'; // текст который меняем Replese_T := Edit4.Text; // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
Find_T := '%время.ухода%'; // текст который меняем Replese_T := Edit5.Text; // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
Find_T := '%Дата%'; // текст который меняем Replese_T := DateTostr(Now); // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
Find_T := '%опер.кол%'; // текст который меняем Replese_T := Stringgrid1.RowCount - 1; // заменить на
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
//Внос данных в колонтитул
L := wdStory; // в начало документа WA1.Selection.HomeKey(L, EmptyParam);
Memo1.Lines.Add('Меняем маркер в колонтитуле..' ); Application.ProcessMessages;
WA1.ActiveDocument.ActiveWindow.View.SeekView := wdSeekCurrentPageHeader; Find_T := '%Фирма%'; // текст который меняем D := wdFindStop; // найти один раз Replese_T := Edit6.Text; // замена
WA1.Selection.Find.Execute(Find_T, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, D, EmptyParam, Replese_T, DD, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
except WA1.Visible := true; WA1.Disconnect; WD1.Disconnect; Memo1.Lines.Add('Ошибка..' ); Application.ProcessMessages; exit; end; Memo1.Lines.Add('Экспорт завершен.' ); Application.ProcessMessages; WA1.Visible := True; WD1.Disconnect; WA1.Disconnect; end;
Прилогаяю исходники: ]]> Исходник ]]>
Вложение Размер JOB_TABLE2.zip 319.52 КБ- Добавить комментарий
- 22562 просмотра
Тебе надо было не на форум это писать, а написать и оформить в блог. Думаю будет полезно. На форуме это не то.
P.S. Кстати в свое время делал с помощью закладок. Но неудобно, т.к. визуально не видишь где эта закладка находится в тексте. Но с другой стороны из под делфи с закладками было удобнее работать.
Используя трассировку кода, можно избежать ламерских вопросов!
- Добавить комментарий
Сейчас утром прочитал и понял , что как то бредово написал, поторопился, спать хотелось, надо было более подробно остановиться на коде, расписать назначение функций. Мой первый опыт и не совсем удачный. Исправлюсь.
Именно поэтому и не рассматривал закладки, так как конечный пользователь может не разобраться с шаблоном
- Добавить комментарий
Напиши в блог, либо статью. Чтобы потом в закладки можно было запомнить. Да и легче найти будет статью/блог по поиску. В общем вот так
Используя трассировку кода, можно избежать ламерских вопросов!
- Добавить комментарий
Отличный код, только вот не подскажете, как можно сделать его покороче, мне нужно использовать только стрингрид, а в word счет на 6 столбцов. грубо говоря грид должен вставиться в таблицу между шапкой и футером
- Добавить комментарий
если коротко то попробуй так: в шаблоне между шапкой и и футером поставь одну пустую строчку
. подключаемся к WORD . подключаемся к документу .
Tbl := WA1.ActiveDocument.Tables.Item(1); // присоединяемся к первой таблице документа или к той которой надо (сверху вниз)
L := Stringgrid1.RowCount - 2; // определяем количество вставляемых строк с учетом шапки в Stringgrid;
Tbl.cell(2,2).range.select; WD1.Application.Selection.SelectRow; //если шапка в таблице WORD из одной строки то выделяем вторую строку
if L >= 1 then WA1.Selection.InsertRowsBelow(L); //вставляем строки
// вставляем значения из StringGtrid в таблицу for i:=1 to StringGrid1.RowCount-1 do for j:=0 to StringGrid1.ColCount-1 do begin Tbl.Cell(i+1,j+1).Range.InsertAfter(StringGrid1.Cells[j,i]); end;
. Закрываем все подключения .
Вложение Размер FootHeatTable.zip 22.19 КБ
- Добавить комментарий
Поправьте пожалуйста в коде комментарий: L := Stringgrid1.RowCount - 2; // оперделаяем - или определяем . ))))
- Добавить комментарий
Данный вариант действительно хорош при построении таблиц. А вот. посоветуй, поделись опытом, как быть в случае, если.
Что имеем Есть шаблон: Invoice.dot (Шаблон квитанции). В этом шаблоне, в нужных местах расставлены закладки, для получения данных из набора данных Есть полученные селектом данные из БД
Задача: Нужно полученные данные из БД заполнить в шаблоне, используя закладки. После заполнения одной квитанции, нужно ниже скопировать квитанцию и вставить следующую апись и т.д.
Что у меня получилось:
procedure TfrmWork_Invoice.btn_ProbnikClick( Sender : TObject ); var pWord : Variant; xFileName : String; i : integer; begin xFileName := 'C:\хххххх\ххххх\InvoiceNew.dot'; // Проверяем есть ли файл if not FileExists( xFileName ) then begin Application.MessageBox( PChar( 'Файл шаблона не найден:' + #13 + xFileName + #13 + 'Экспорт данных не возможен.' ), 'Ошибка', MB_ICONERROR ); Exit; end; // Открываем try pWord := CreateOleObject( 'Word.Application' ); except end;
// Открываем приложение pWord.Documents.Add( '', false, 0 );
// Открываем файл шаблона (но он уже будет .doc) pWord.Documents.Add( xFileName, false, 0 ); // заполняем закладку (метку) pWord.ActiveDocument.Bookmarks.Item( 'data1' ).Range.Text :='с ' + DateToStr(DateTimePicker1.Date) + ' по ' + DateToStr( DateTimePicker2.Date ); pWord.ActiveDocument.Bookmarks.Item( 'data2' ).Range.Text :='с ' + DateToStr(DateTimePicker1.Date) + ' по ' + DateToStr( DateTimePicker2.Date ); pWord.ActiveDocument.Bookmarks.Item( 'FIO1' ).Range.Text := DataModule2.ADQuerySQLSelectDinamic.Fields[ 4 ].AsString; pWord.ActiveDocument.Bookmarks.Item( 'FIO2' ).Range.Text := DataModule2.ADQuerySQLSelectDinamic.Fields[ 4 ].AsString; pWord.ActiveDocument.Bookmarks.Item( 'SUM1' ).Range.Text := IntToStr(DataModule2.ADQuerySQLSelectDinamic.Fields[3].AsInteger * - 1 ); pWord.ActiveDocument.Bookmarks.Item( 'SUM2' ).Range.Text := IntToStr(DataModule2.ADQuerySQLSelectDinamic.Fields[3].AsInteger * - 1 ); pWord.ActiveDocument.Bookmarks.Item( 'ADRES1' ).Range.Text := DataModule2.ADQuerySQLSelectDinamic.Fields[ 5 ].AsString; pWord.ActiveDocument.Bookmarks.Item( 'ADRES2' ).Range.Text := DataModule2.ADQuerySQLSelectDinamic.Fields[ 5 ].AsString; pWord.ActiveDocument.Bookmarks.Item( 'Num1' ).Range.Text := DataModule2.ADQuerySQLSelectDinamic.Fields[ 1 ].AsString; pWord.ActiveDocument.Bookmarks.Item( 'Num2' ).Range.Text := DataModule2.ADQuerySQLSelectDinamic.Fields[ 1 ].AsString;
// и копируем его в новый документ pWord.Selection.WholeStory; pWord.Selection.Copy; pWord.ActiveDocument.close( SaveChanges := wdDoNotSaveChanges ); pWord.Selection.EndKey( Unit := wdStory ); pWord.Selection.Paste;
// показывем что наделали pWord.Visible := true; end;
Как сделать так, чтоб автоматически множился шаблон с первой страницы на следующую (в идеале - две квитанции на один лист А4) Ровно столько сколько есть записей, полученные селектом из БД