Экспорт из БД в Word
Глущенко Юрий, aka YurikGL
К статье прилагаются исходный код примера и база данных. Выполнены в Delphi7 и Access2003 соответственно. Статья написана на основе собственного опыта, и опыта других людей, полученного при общении в конференциях. И надеюсь, кому-нибудь хоть сколько поможет. В статье приведены примеры работы с
Не буду углубляться в особенности позднего и раннего связывания, т.к. информации на эту тему уже достаточно. Итак, стоит задача экспортировать некую таблицу в Word. Даже не так, стоит задача сформировать сложный документ с колонтитулами таблицами заголовками и т.д., и т.п. Есть два замечательных способа добывать информацию об интерфейсе Word.
Есть, еще один, как мне сказали, наиболее логичный способ - справка VBA, но что-то не довелось мне ею пользоваться… Теперь немного теории, добытой этими путями… W1: TWordApplication; Vr: OleVariant; У Word есть коллекция документов Vr := номер нужного документа; w1.Documents.Item(vr); //так можно обратиться к нужному документу У всякой коллекции есть свойство count - количество таким образом w1.Documents.count - количество документов. Использование переменной типа olevariant (в данном случае vr) иногда требуется, иногда нет. У документа есть свои коллекции:
Ну и т.д…. Еще есть полезные объекты selection - выбранная область и range - диапазон, а так-же функция select - выбрать. Выбрать можно таблицу, колонку (опять же W1.ActiveDocument.Tables.Item(W1.ActiveDocument.Tables.Count).Columns.Item(2).Select), букву, диапазон, абзац и т.д… Кстати, очень часто приходится работать с объектом range. Так, например, чтобы вытащить текст из документа, можно написать st:=w1.ActiveDocument.Range(1,5).Text; // Это будет текст с 1-го по 5-й символ. st:=W1.ActiveDocument.Paragraphs.Item(1).Range.Text; // это будет текст первого абзаца текущего документа. Теперь перейдем к конкретной реализации Flat(IdFlat,NameFlat) Korp(IdKorp,NameKorp) Ul(IdUl,NameUl) и дочерняя таблица Main(Id,Tel,Name,IdUl,House,IdKorp,IdFlat) Почему структура именно такая - не важно… это - просто пример. Конкретная задача - экспортировать жильцов улиц Ахметова и Летчиков.
Получаем следующий макрос: Sub Макрос1() ' ' Макрос1 Макрос ' Макрос записан 08.11.2004 YurikGL ' ActiveDocument.Shapes("Text Box 8").Select Selection.TypeText Text:="текст на титульной" Selection.EndKey Unit:=wdStory If ActiveWindow.View.SplitSpecial <> wdPaneNone Then ActiveWindow.Panes(2).Close End If If ActiveWindow.ActivePane.View.Type = wdNormalView Or ActiveWindow. _ ActivePane.View.Type = wdOutlineView Then ActiveWindow.ActivePane.View.Type = wdPrintView End If ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader Selection.HeaderFooter.Shapes("Text Box 5").Select Selection.TypeText Text:="Текст в колонтитуле" ActiveWindow.ActivePane.View.SeekView = wdSeekMainDocument End Sub Если преобразовать в код делфи, то получим //выбираем первую надпись vr:='Text Box 8'; w1.ActiveDocument.Shapes.Item(vr).Select(EmptyParam); //пишем туда текст w1.Selection.TypeText('текст на титульной'); //переходим в конец документа vr:=wdStory; w1.Selection.EndKey(vr,EmptyParam); {Всякие if-ы нам не нужны} //переходим в верхний колонтитул w1.ActiveWindow.ActivePane.View.SeekView:=wdSeekCurrentPageHeader; vr:='Text Box 5'; w1.Selection.HeaderFooter.Shapes.Item(vr).Select(EmptyParam); w1.Selection.TypeText('текст в колонтитуле'); w1.ActiveWindow.ActivePane.View.SeekView:=wdSeekMainDocument; Итак, мы сформировали титульную страницу и колонтитул... Кстати, кроме объектов “Надпись” можно еще эффективно использовать закладки. Что-бы узнать, как с ними работать, достаточно записать соответствующий макрос, а например, выбрать текст между первой и второй закладками можно так: vr1,vr2,vr3,vr4:OleVariant; vr1:=1; vr2:=2; vr3:=W1.ActiveDocument.Bookmarks.Item(vr1).End_; vr4:=W1.ActiveDocument.Bookmarks.Item(vr2).End_; W1.ActiveDocument.Range(vr3,vr4).Select; Кстати, если кто-нибудь найдет красивое решение без использования переменных OleVariant - отпишите мне на мыло. Вывести данные из базы в документ можно либо используя слияние, либо построчно. Для слияния код выглядит примерно так: var vr1,vr2,vr3,vr4,vr5,vr6: OleVariant; begin vr1:=0; vr2:=false; vr3:='Provider=Microsoft.Jet.OLEDB.4.0;Password="""";User ID=Admin;Data Source=C:\Dataware\Deplhi7\Для статьи\db1.mdb;Mode=Read;Extended Properties="""";Jet OLEDB:System database="""";Jet OLEDB:Database Password="""";Jet OLEDB:Engine Type=5;'; vr4:='SELECT Телефон, Корпус FROM QMain ORDER BY ФИО'; vr5:=GetCurrentDir+'\db1.mdb'; vr6:=-1; winit; try w1.Connect; w1.Documents.Add(EmptyParam,EmptyParam,EmptyParam,EmptyParam); w1.Visible:=true; w1.Selection.Range.InsertDatabase(vr1,vr1,vr2,vr3,vr4,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,vr5,EmptyParam,EmptyParam,EmptyParam); except w1.Disconnect; end; Человек, знающий основы баз данных легко сможет преобразовать этот код под свои нужды изменив строку подключения и запрос. Для построчного вывода я пользовался созданной мною процедурой TableExport(DataSet:TDataSet; Title, FlagText:string), которая приведена в примере. В нее передаются датасет, заголовок таблицы и текстовый параметр FlagText. Если он равен ‘’ то экспортируется вся таблица. В противном случае, экспортируются лишь те записи, у которых значение последнего поля равно FlagText. Это сделано для того, чтобы получив выборку, которая долго вычислялась, можно было ее разнести по нескольким таблицам в отчете. ADODataSet1.fields[1].DisplayLabel:='ФИО'; //заголовок ADODataSet1.fields[1].tag:=round(w1.CentimetersToPoints(10)); //ширина столбца ADODataSet1.fields[1].Visible:=true;//отображать поле Кстати, в приведенном примере, данные сначала выбрасываются в Word, а потом одной командой преобразуются в таблицу. Теперь еще некоторые полезные возможности: w1.Selection.Cells.Borders.Item(wdBorderLeft).LineStyle:=wdLineStyleSingle; // проведение границы с левой стороны выбранной области. Используется при формировании таблиц. Изменяя параметры, можно создавать произвольные таблицы. W1.Selection.Font.Size:=15; //изменение параметров шрифта W1.Selection.Font.bold:=1; //изменение параметров шрифта I:= W1.Selection.End_; // получить номер последнего символа выбранной области. Если Вы работаете через OleContainer, то нелишними будут команды типа OleContainer1.OleObject.CommandBars.Item['Standard'].Visible:=false; OleContainer1.OleObject.CommandBars.Item['Formatting'].Visible:=false; OleContainer1.OleObject.CommandBars.Item['Drawing'].Visible:=false; которые убирают соответствующие менюшки, которые обычно разлетаются по всей форме. Теперь привожу код всей программы unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, DB, ADODB, OleServer, Word2000; type TForm1 = class(TForm) ADOConnection1: TADOConnection; ADODataSet1: TADODataSet; Button1: TButton; W1: TWordApplication; Здесь удалено несколько строчек StatusLabel: TLabel; Button2: TButton; Button3: TButton; procedure Button1Click(Sender: TObject); procedure TableExport(DataSet:TDataSet; Title, FlagText:string); Procedure TableLineSet; procedure WInit; procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses ComObj; {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var vr:olevariant; begin try statusLabel.Caption:='Формирую отчет ждите'; winit; w1.Connect; //w1.Visible:=true; vr:=GetCurrentDir+'\Shablon.doc'; W1.Documents.Open(vr,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam); //выбираем первую надпись vr:='Text Box 8'; w1.ActiveDocument.Shapes.Item(vr).Select(EmptyParam); //пишем туда текст w1.Selection.TypeText('текст на титульной'); //переходим в конец документа vr:=wdStory; w1.Selection.EndKey(vr,EmptyParam); {Всякие if-ы нам не нужны} //переходим в верхний колонтитул w1.ActiveWindow.ActivePane.View.SeekView:=wdSeekCurrentPageHeader; vr:='Text Box 5'; w1.Selection.HeaderFooter.Shapes.Item(vr).Select(EmptyParam); w1.Selection.TypeText('текст в колонтитуле'); w1.ActiveWindow.ActivePane.View.SeekView:=wdSeekMainDocument; ADODataSet1.Close; ADODataSet1.CommandText:='SELECT [Main].[Tel], '+ '[Main].[name], '+ '[Main].[House], '+ '[Korp].[NameKorp], '+ '[Flat].[NameFlat], '+ '[Ul].[NameUl] '+ ' FROM Main, Ul, Korp, Flat '+ ' WHERE ([Main].[IdUl]=[Ul].[IdUl]) And ([Main].[IdKorp]=[Korp].[IdKorp]) And ([Main].[IdFlat]=[Flat].[IdFlat]) '; ADODataSet1.Open; / несколько строчек убрано //настраиваем параметры экспорта ADODataSet1.fields[0].DisplayLabel:='Телефон'; //заголовок ADODataSet1.fields[0].tag:=round(w1.CentimetersToPoints(2)); //ширина столбца ADODataSet1.fields[0].Visible:=true;//отображать поле ADODataSet1.fields[1].DisplayLabel:='ФИО'; //заголовок ADODataSet1.fields[1].tag:=round(w1.CentimetersToPoints(10)); //ширина столбца ADODataSet1.fields[1].Visible:=true;//отображать поле ADODataSet1.fields[2].DisplayLabel:='Дом'; //заголовок ADODataSet1.fields[2].tag:=round(w1.CentimetersToPoints(1.5)); //ширина столбца ADODataSet1.fields[2].Visible:=true;//отображать поле ADODataSet1.fields[3].DisplayLabel:='Корпус'; //заголовок ADODataSet1.fields[3].tag:=round(w1.CentimetersToPoints(1.5)); //ширина столбца ADODataSet1.fields[3].Visible:=true;//отображать поле ADODataSet1.fields[4].DisplayLabel:='Квартира'; //заголовок ADODataSet1.fields[4].tag:=round(w1.CentimetersToPoints(1.5)); //ширина столбца ADODataSet1.fields[4].Visible:=true;//отображать поле ADODataSet1.fields[5].Visible:=false;//не отображать поле //вызываем процедуру экспорта TableExport(ADODataSet1,'Живущие на улице Ахметова','АХМЕТОВА'); TableExport(ADODataSet1,'Живущие на улице Летчиков','ЛЕТЧИКОВ'); //отображем Word. Это можно было сделать и вначале, но //тогда вывод данных был бы значительно медленнее w1.Visible:=true; w1.Disconnect; statusLabel.Caption:=''; except on e:exception do begin w1.Visible:=true; statusLabel.Caption:='Отчет был сформирован неверно'; w1.Disconnect; raise Exception.Create('Ошибка формирования отчета.'+#13+e.Message); end; end; end; procedure TForm1.TableExport(DataSet:TDataSet; Title, FlagText:string); var i,ColCount, //количество колонок в таблице TableBeg, //Номер символа в начале таблицы TableBeg2 //Номер символа в начале данных таблицы :integer; vr1,vr2:OleVariant; f:boolean; st:string; Function ConvertString(S:string):string; {это, казалось бы глупая функция, делает очень важное дело. При формировании таблицы в качестве разделителя по умолчанию используется "-", который может встречаться в экспортируемых записях. В этом случае в таблицу преобразутеся абсолютно неверно. Чтобы избежать этого, мы меняем обычный "-" на символ с кодом #173, который отображается точно так-же} Begin Result := StringReplace(S, '-', #173,[]); End; Begin {Процедура экспортирует лишь те записи датасета, у которых значение последнего поля совпадает с FlagText Если FlagText='' то экспортируются все записи. Это связано с тем, что зачастую нужно разнести в разные таблицы записи, полученные в результате долго выполняемого запроса} Application.ProcessMessages; vr1:=wdStory; w1.Selection.EndKey(vr1,EmptyParam); //переходим в конец документа //вставляем заголовок таблицы W1.ActiveDocument.Range(EmptyParam,EmptyParam).InsertAfter(Title); //далее идут настройки, что-бы заголовок не отрывался от основной таблицы //и все красиво выглядело W1.ActiveDocument.Paragraphs.Item(w1.ActiveDocument.Paragraphs.Count).Range.Select; W1.Selection.ParagraphFormat.KeepWithNext:=-1; W1.Selection.ParagraphFormat.SpaceAfter:=14; W1.Selection.Font.Size:=15; //применяем шрифт W1.Selection.Font.bold:=1; W1.ActiveDocument.Paragraphs.Add(EmptyParam); //добавляем строчку //выбираем ее W1.ActiveDocument.Paragraphs.Item(w1.ActiveDocument.Paragraphs.Count).Range.Select; W1.Selection.ParagraphFormat.SpaceAfter:=0; vr1:=wdStory; w1.Selection.EndKey(vr1,EmptyParam); //переходим в конец документа //запоминаем положение курсора. Это - начало будущей таблицы. //потом выберем весь оставшийся текст, что-бы преобразовать его в таблицу //Во ворде есть такая фунция "Преобразовать в таблицу" ею и воспользуемся TableBeg:=W1.Selection.End_; DataSet.First; //вставляем заголовки для всех видимых полей for i:=0 to DataSet.FieldCount-1 do if DataSet.Fields[i].Visible then W1.ActiveDocument.Range(EmptyParam,EmptyParam).InsertAfter(convertstring(DataSet.Fields[i].DisplayLabel)+#9); Application.ProcessMessages; w1.Selection.EndKey(vr1,EmptyParam); //убираем последний символ табуляции {Вообще символ табуляции используется в качесве разделителя для столбцов таблиццы} w1.Selection.TypeBackspace; //применяем шрифт W1.ActiveDocument.Paragraphs.Item(w1.ActiveDocument.Paragraphs.Count).Range.Select; W1.Selection.Font.Size:=14; W1.Selection.Font.Italic:=1; W1.Selection.Font.bold:=0; //добавляем строчку W1.ActiveDocument.Paragraphs.Add(EmptyParam); f:=true;//флаг для определения, были ли в таблице вообще записи для экспорта st:=''; //в эту строчку будем экспортировать текст таблицы TableBeg2:=W1.Selection.End_; //начало данных в таблице if dataset.RecordCount>0 then begin Repeat Application.ProcessMessages; if (dataset.fields[DataSet.Fields.Count-1].AsString=FlagText) or (FlagText='') then begin for i:=0 to DataSet.FieldCount-1 do if DataSet.Fields[i].Visible then st:=st+DataSet.Fields[i].AsString+#9; //через табуляцию выводим все видимые поля SetLength(st,length(st)-1); //убираем последний символ табуляции st:=st+#13; //перенос строки f:=false; end; dataset.Next; until dataset.Eof; w1.Selection.EndKey(vr1,EmptyParam);//уходим в конец текста W1.Selection.InsertAfter(convertstring(st)); //вставляем данные таблицы vr1:=TableBeg2; //начало данных таблицы vr2:=W1.Selection.End_; //конец таблицы W1.ActiveDocument.Range(vr1,vr2).Select; W1.Selection.Font.Size:=12; W1.Selection.Font.bold:=0; W1.Selection.Font.Italic:=0; end; //в том случае, если не экспортировалось ни одной записи //формируем пустую строчку if f then begin for i:=0 to DataSet.FieldCount-1 do if DataSet.Fields[i].Visible then W1.ActiveDocument.Range(EmptyParam,EmptyParam).InsertAfter(' '+#9); w1.Selection.EndKey(vr1,EmptyParam); w1.Selection.TypeBackspace; end; Application.ProcessMessages; vr1:=TableBeg;//начало будущей таблицы vr2:=W1.Selection.End_;//конец будущей таблицы W1.ActiveDocument.Range(vr1,vr2).Select;//выбираем этот диапазон //и преобразуем его в таблицу W1.Selection.ConvertToTable(EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam); colcount:=1; //выставляем ширины колонок for i:=0 to DataSet.FieldCount-1 do if DataSet.Fields[i].Visible then begin W1.ActiveDocument.Tables.Item(W1.ActiveDocument.Tables.Count).Columns.Item(colcount).Width:=DataSet.Fields[i].Tag; inc(colcount); Application.ProcessMessages; end; TableLineSet; //эта процедура прорисовывает нужные границы таблицы W1.ActiveDocument.Paragraphs.Add(EmptyParam); W1.ActiveDocument.Paragraphs.Item(w1.ActiveDocument.Paragraphs.Count-1).Range.Select; W1.Selection.ParagraphFormat.KeepWithNext:=0; End; Procedure TForm1.TableLineSet; //эта процедура прорисовывает нужные границы таблицы Begin w1.Selection.Cells.Borders.Item(wdBorderLeft).LineStyle:=wdLineStyleSingle; w1.Selection.Cells.Borders.Item(wdBorderRight).LineStyle:= wdLineStyleSingle; w1.Selection.Cells.Borders.Item(wdBorderHorizontal).LineStyle:= wdLineStyleSingle; w1.Selection.Cells.Borders.Item(wdBorderTop).LineStyle:= wdLineStyleSingle; w1.Selection.Cells.Borders.Item(wdBorderBottom).LineStyle:= wdLineStyleSingle; w1.Selection.Cells.Borders.Item(wdBorderVertical).LineStyle:= wdLineStyleSingle; End; procedure TForm1.WInit; Begin //для избежания глюков полезно убивать используемые компоненты //и потом их создавать заново... W1.free; W1:=TWordApplication.Create(Form1); w1.connectkind:=ckNewInstance;//Чтобы всегда новое приложение запускалось End; procedure TForm1.Button2Click(Sender: TObject); var vr1,vr2,vr3,vr4,vr5:OleVariant; begin vr1:=0; vr2:=false; vr3:='Provider=Microsoft.Jet.OLEDB.4.0;Password="""";User ID=Admin;Data Source=C:\Dataware\Deplhi7\Для статьи\db1.mdb;Mode=Read;Extended Properties="""";Jet OLEDB:System database="""";Jet OLEDB:Database Password="""";Jet OLEDB:Engine Type=5;'; vr4:='SELECT Телефон, Корпус FROM QMain ORDER BY ФИО'; vr5:=GetCurrentDir+'\db1.mdb'; winit; try w1.Connect; w1.Documents.Add(EmptyParam,EmptyParam,EmptyParam,EmptyParam); w1.Visible:=true; w1.Selection.Range.InsertDatabase(vr1,vr1,vr2,vr3,vr4,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,vr5,EmptyParam,EmptyParam,EmptyParam); except w1.Disconnect; end; end; end.
Страница сайта http://silicontaiga.ru
Оригинал находится по адресу http://silicontaiga.ru/home.asp?artId=6096 |