Программы на сервер g word

Время на прочтение
7 мин

Количество просмотров 43K

В разработке корпоративных приложений очень часто приходится решать задачу выгрузки данных в документы — от небольших справок до больших отчетов.

Хочу поделиться нашим opensource-решением для генерации docx документов, которое позволяет заполнять документы по шаблону, оформление которого можно менять в Word без переписывания кода.

Для начала — немного вводных.

Что нам было нужно от шаблонизатора

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

Поиски шаблонизатора

Наши поиски подходящего «шаблонизатора» не увенчались успехом: одни предлагают создавать документ с оформлением на сервере, вторые позволяют заменять только статический текст (например, https://github.com/swxben/docx-template-engine), третьи не поддерживают вложенность элементов (http://livedocx.com/), четвертые добавляют в шаблон разные программерские обозначения, чего мы хотели бы избежать, оставив шаблон максимально чистым (https://github.com/tssoft/TsSoft.Docx.TemplateEngine).

Что получилось у нас

Со стороны кода мы работаем с привычными сущностями, такими как «Таблица», «Список», «Строка», «Ячейка».

Со стороны шаблона используется документ с расставленными по нему Content Controls, которые связаны с данными через свойство tag. Content Controls добавляются достаточно легко, при этом их достаточно сложно испортить при дальнейшей эксплуатации в отличие от текстовых вставок типа {FIO}, а при отключенном режиме конструктора спец-обозначений контролов и вовсе не видно.

Например, необходимо заполнить таблицу, указать дату её заполнения и количество записей.
Создадим шаблон этой таблицы в Word-документе:

Каждое поле, которое мы будем заполнять, необходимо поместить в контрол, связать его с данными в коде. Для этого:

  1. Переходим на вкладку «Разработчик» (если она отсутствует, включается через Файл → Параметры → Настроить ленту → Ставим галочку возле «Разработчик») и включаем режим конструктора.
  2. Выделяем текст, который будет заполняемым полем.
  3. Нажимаем «Вставить элемент управления содержимым «Форматированный текст».
  4. Нажимаем «Свойства» и заполняем поля «Название» и «Тег».

Если требуется заполнить таблицу или список, их также нужно поместить в отдельный контент-контрол.

Так выглядит шаблон с добавленными элементами управления содержимым:

Теперь заполним шаблон данными:
нам нужно добавить одно поле и одну таблицу с двумя строчками, и в футере таблицы указать количество записей.

var valuesToFill = new Content(
	new FieldContent("Report date", DateTime.Now.Date.ToString()),
	new TableContent("Team members")
		.AddRow(
			new FieldContent("Full name", "Семёнов Илья Васильевич"),
			new FieldContent("Role", "Разработчик"))
		.AddRow(
			new FieldContent("Full name", "Петров Фёдор Анатольевич"),
			new FieldContent("Role", "Разработчик"))
		.AddRow(
			new FieldContent("Full name", "Артемьев Вячеслав Геннадьевич"),
			new FieldContent("Role", "Ведущий разработчик")),
	new FieldContent("Count", "3")
);

Запускаем TemplateProcessor…

using(var outputDocument = new TemplateProcessor("OutputDocument.docx")
				.SetRemoveContentControls(true))
	{
		outputDocument.FillContent(valuesToFill);
		outputDocument.SaveChanges();
	}

Если всё получилось, на выходе следующий документ:

С помощью метода SetRemoveContentControls(bool value) можно удалить элементы управления содержимым, если они уже не нужны в результирующем документе.

TemplateEngine.Docx позволяет заполнять простые поля, таблицы, списки, вложенные списки, таблицы со списками, списки с таблицами и даже списки с таблицами, в которых есть списки… Структура класса Content позволяет создавать шаблоны с неограниченной вложенностью элементов.

Еще больше примеров!

Заполнение простых полей

var valuesToFill = new Content(new FieldContent("Report date", DateTime.Now.ToString()));

Заполнение таблиц

var valuesToFill = new Content(
	new TableContent("Team Members Table")
		.AddRow(
			new FieldContent("Name", "Eric"),
			new FieldContent("Role", "Program Manager"))
		.AddRow(
			new FieldContent("Name", "Bob"),
			new FieldContent("Role", "Developer")),
		new FieldContent("Count", "2"));

Заполнение списков

var valuesToFill = new Content(
	new ListContent("Team Members List")
		.AddItem(
			new FieldContent("Name", "Eric"), 
			new FieldContent("Role", "Program Manager"))
		.AddItem(
			new FieldContent("Name", "Bob"),
			new FieldContent("Role", "Developer")));

Заполнение вложенных списков

var valuesToFill = new Content(
	new ListContent("Team Members Nested List")
		.AddItem(new ListItemContent("Role", "Program Manager")
			.AddNestedItem(new FieldContent("Name", "Eric"))
			.AddNestedItem(new FieldContent("Name", "Ann")))
		.AddItem(new ListItemContent("Role", "Developer")
			.AddNestedItem(new FieldContent("Name", "Bob"))
			.AddNestedItem(new FieldContent("Name", "Richard"))));

Таблица внутри списка

var valuesToFill = new Content(
	new ListContent("Projects List")
		.AddItem(new ListItemContent("Project", "Project one")
			.AddTable(TableContent.Create("Team members")
				.AddRow(
					new FieldContent("Name", "Eric"), 
					new FieldContent("Role", "Program Manager"))
				.AddRow(
					new FieldContent("Name", "Bob"), 
					new FieldContent("Role", "Developer"))))
		.AddItem(new ListItemContent("Project", "Project two")
			.AddTable(TableContent.Create("Team members")
				.AddRow(
					new FieldContent("Name", "Eric"),
					new FieldContent("Role", "Program Manager"))))
		.AddItem(new ListItemContent("Project", "Project three")
			.AddTable(TableContent.Create("Team members")
				.AddRow(
					new FieldContent("Name", "Bob"),
					new FieldContent("Role", "Developer")))));

Список внутри таблицы

var valuesToFill = new Content(
	new TableContent("Projects Table")
		.AddRow(
			new FieldContent("Name", "Eric"), 
			new FieldContent("Role", "Program Manager"), 
			new ListContent("Projects")
				.AddItem(new FieldContent("Project", "Project one"))
				.AddItem(new FieldContent("Project", "Project two")))
		.AddRow(
			new FieldContent("Name", "Bob"),
			new FieldContent("Role", "Developer"),
			new ListContent("Projects")
				.AddItem(new FieldContent("Project", "Project one"))
				.AddItem(new FieldContent("Project", "Project three"))));

Таблица, состоящая из нескольких блоков, которые заполняются независимо

var valuesToFill = new Content(
	new TableContent("Team Members Statistics")
		.AddRow(
			new FieldContent("Name", "Eric"),
			new FieldContent("Role", "Program Manager"))
		.AddRow(
			new FieldContent("Name", "Richard"),
			new FieldContent("Role", "Program Manager"))
		.AddRow(
			new FieldContent("Name", "Bob"),
			new FieldContent("Role", "Developer")),

	new TableContent("Team Members Statistics")
		.AddRow(
			new FieldContent("Statistics Role", "Program Manager"),
			new FieldContent("Statistics Role Count", "2"))                     
		.AddRow(
			new FieldContent("Statistics Role", "Developer"),
			new FieldContent("Statistics Role Count", "1")));

Таблица с объединенными вертикально ячейками

var valuesToFill = new Content(
	new TableContent("Team members info")
		.AddRow(
			new FieldContent("Name", "Eric"),
			new FieldContent("Role", "Program Manager"),
			new FieldContent("Age", "37"),
			new FieldContent("Gender", "Male"))
		.AddRow(
			new FieldContent("Name", "Bob"),
			new FieldContent("Role", "Developer"),
			new FieldContent("Age", "33"),
			new FieldContent("Gender", "Male"))
		.AddRow(
			new FieldContent("Name", "Ann"),
			new FieldContent("Role", "Developer"),
			new FieldContent("Age", "34"),
			new FieldContent("Gender", "Female")));

Таблица с объединенными горизонтально ячейками

var valuesToFill = new Content(
	new TableContent("Team members projects")
		.AddRow(
			new FieldContent("Name", "Eric"),
			new FieldContent("Role", "Program Manager"),
			new FieldContent("Age", "37"),
			new FieldContent("Projects", "Project one, Project two"))
		.AddRow(
			new FieldContent("Name", "Bob"),
			new FieldContent("Role", "Developer"),
			new FieldContent("Age", "33"),
			new FieldContent("Projects", "Project one"))
		.AddRow(
			new FieldContent("Name", "Ann"),
			new FieldContent("Role", "Developer"),
			new FieldContent("Age", "34"),
			new FieldContent("Projects", "Project two")));

Где скачать

Проект доступен в NuGet (http://www.nuget.org/packages/TemplateEngine.Docx/), и открыт для пулл-реквестов на GitHub (https://github.com/UNIT6-open/TemplateEngine.Docx).

Всем спасибо за внимание, надеемся, что данный инструмент поможет вам в ваших проектах.

Авторы: Алексей Волков, Руслана Котова

Студворк — интернет-сервис помощи студентам

Доброго времени суток уважаемые форумчане!
Возникла идея, создать программу по работе с бланками в MS Office для облегчения себе жизни. В общем, в последнее время работаю с парой конкретных бланков в Word. Стало весьма неудобно каждый раз заполнять бланк А4 с нуля. Возникла мысль частично это автоматизировать.
Рассмотрим задумку на простеньком примере. Скажем у меня имеется подобный документ:
ФИО______________________
Дата рождения: день_____ месяц______ год______

Каждый раз в поля впечатываются (!) данные. Мне необходимо реализовать следующее:
Как пример вот такой схематический рисунок

Автоматизировать процесс заполнения документа в Word

.Имеем интерфейс программки, в которой поля «ФИО», «Дата рождения», «день», «месяц», «год» — статичные, неизменные. Через интерфейс программы их редактировать нельзя. В поле под/возле ФИО вводятся данные. Возле «день», «месяц», «год» — поля с выбором из готовых элементов (чтобы не вводить вручную скажем 21 апреля 1856). Далее, когда бланк заполнен, необходимо чтобы он сохранялся в в Word-овский документ (.doc или .docs) так же, как он сверстан в интерфейсе программы и чтобы в итоге получалось следующее в интерфейсе программы

Автоматизировать процесс заполнения документа в Word

и такое при открытии в Word

Автоматизировать процесс заполнения документа в Word

. Далее необходима реализация возможности открытия уже сверстанного документа в программе для редактирования (на случай ошибки) с повторным сохранением с перезаписью. Так же возможность вывода на печать.
Собственно, хотелось бы получить дельные советы и подходящий справочный материал (книги, туториалы, видео). Может у кого-либо был опыт реализации подобного. Буду весьма благодарен!

P.S. 1С, стандартные средства верстки документа Word и прочие программы для верстки не предлагать. Необходимо именно создание программы по работе с бланками. Все данные заполняются шрифтом, заданным по умолчанию в программе.

Всем добра!

Часть 3. Использование COM-серверов Microsoft Office

Основные объекты серверов Excel и Word

   Объекты Excel

   Объекты Word

Позднее связывание

Раннее связывание

Практика
показывает, что приложения Microsoft
Office (Excel,
Word, Power
Point и т.п.)
являются одними из наиболее часто
используемых Windows-приложений.
Каждое  из них является СОМ-сервером, а
следовательно, любой входящий в него объект
может быть использован вашей программой
как собственный.

Существуют два способа обращения к методам и свойствам СОМ-объекта: путем ссылки
на его библиотеку типов (раннее связывание) и по имени (позднее связывание).
Для Object Pascal предпочтительным является раннее связывание, так как в этом
случае компилятор может проконтролировать правильность обращения к свойствам
и методам внешних объектов, а создаваемый им код исполняется, как правило, быстрее.
В то же время базовый язык обращения к серверам Microsoft Office — Visual Basic
for Application (VBA)  не поддерживает работу с указателями и, следовательно,
не может использовать интерфейсы. Специально для такого рода языков (помимо
VBA c указателями не работают также языки JavaScript, SmallTalk и некоторые
другие) в технологию СОМ введены диспинтерфейсы, позволяющие обращаться к методам
и свойствам по имени, а не по адресу. При инсталлировании Office можно установить
справку по VBA, в которой детально описываются интерфейсы серверов Microsoft
Office с указанием назначения методов и свойств, а также параметров обращения
к ним. Фактически это единственные доступные программисту документы, на которые
ему следует опираться при программировании доступа к мощным возможностям серверов
Microsoft Office. Замечу, что при стандартном инсталлировании Microsoft Office
справки по VBA не устанавливаются. Если в каталоге Program Files | Microsoft
Office | Office вы не найдете файлов vbaxl8.hlp1
(справка по Excel), vbawrd8.hlp (справка по Word) и т. п., вы должны их
добавить с помощью аплета Пуск | Настройка | Панель управления | Установка и
удаление программ.

В версию 5 Delphi включены компоненты страницы Servers,
позволяющие обращаться к СОМ-объектам этих
серверов с помощью библиотек типов, однако
эти компоненты практически не
документированы. Более того, сами
библиотеки уже внедрены в пакет dclaxserver50,
так что с помощью этой версии Delphi
мне так и не удалось получить их тексты. Во
всех случаях изучение обширных текстов
библиотек (например, файл Excel_TLB.pas
содержит более 20 тыс. строк) мало что дает
даже опытному программисту.

В этом разделе приводятся краткое
описание основных объектов двух наиболее
популярных серверов — Excel
и Word, а
также примеры использования Excel
в стиле VBA
(по имени) и с помощью компонентов страницы Servers.
Поскольку специально для версии MS
Office 97
язык VBA
был существенно расширен, этот материал
нельзя использовать для работы с более
ранними версиями пакета.

Основные объекты серверов Excel и Word

В терминологии VBA
используются
понятия «объект» и «коллекция». Объект —
это обычный интерфейсный объект СОМ,
имеющий свойства, методы и события.
Коллекция — это группа однотипных объектов.
Например, главный объект сервера Excel
— Application 
определяет основные свойства и методы
сервера, а коллекция Worksheets
представляет  собой
набор табличных страниц в текущей рабочей
книге и т.д. Представленные ниже иерархии
объектов и коллекций взяты из файлов vbaXXX.hlp. В отличие от объектов VCL
они построены не по принципу наследования,
а по функциональной подчиненности.

Объекты Excel

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

На рис. 1 представлена функциональная структура объектов
и коллекций Excel.

Объект Application имеет многочисленные свойства,
методы и события, управляющие сервером в
целом. Только с его помощью, например, можно
визуализировать полнофункциональное окно
текстового процессора. Его центральное
свойство Workbooks предоставляет доступ ко всем
открытым в процессоре рабочим книгам.

У каждой рабочей книги есть
свойства Worksheets
и Charts,
представляющие собой коллекции листов и
диаграмм. Первоначально коллекция Workbooks
пуста. Чтобы создать хотя бы одну рабочую
книгу, нужно обратиться к методу Workbook.Add,
который создает рабочую книгу с
количеством пустых листов, определяемым
значением свойства Application.SheetsInNewWorkbook. У
каждого рабочего листа есть свойство Cells(I,J), определяющее содержимое
ячейки, лежащей на пересечении I-го
ряда с J-м
столбцом (нумерация рядов и столбцов
начинается с 1). Если при обращении к Cells
номера столбца и ряда опущены, считается,
что речь идет о текущем диапазоне ячеек,
заданном значением свойства Worksheets.Range.
Если необходимо изменить умалчиваемые
свойства столбца или ряда, используются
объекты Worksheets.Columns и
Worksheets.Rows. Помимо рабочих листов с рабочей книгой
связывается объект Charts,
представляющий собой коллекцию диаграмм. С
каждой диаграммой связывается объект SeriesCollection,
хранящий данные, по которым строится
диаграмма.

Объекты Word

Текстовый процессор Word
является популярнейшим средством создания
и оформления (форматирования) текстовых
документов. При работе с Word
фундаментальными понятиями являются
документ, абзац и стиль. Документ
определяет файл данных. Абзац — это
совокупность символов, обрамленная
служебными символами конца строк, разрыва
колонки или разрыва раздела. Наконец, стиль
— это совокупность признаков оформления
текста: его шрифт, положение на странице,
выравнивание и т.п. Стиль — непременный
атрибут каждого абзаца, то есть изменение
стиля абзаца автоматически приводит к его
переформатированию. Однако стиль может
изменяться внутри абзаца — для выделения
группы символов шрифтом, цветом символов и (или)
фона и т.п.

На рис. 2 показана функциональная иерархия объектов Word.

Центральный объект Application
имеет такое же назначение, что и
одноименный объект Excel,
– он определяет свойства, методы и события
на уровне всего сервера. Его свойство Documents представляет собой коллекцию открытых
документов. Посредством метода Open этого объекта можно открыть ранее
созданный документ, а метода Add — создать новый документ, основанный на
стандартном шаблоне Normal.dot. Каждый документ имеет коллекцию абзацев
Paragraphs.
С помощью таких методов этого объекта, как Add, InsertParagraph,
InsertParagraphAfter,
InsertParagraphBefore,
можно вставить новый абзац в уже
существующий текст или добавить абзац в
конец документа. В свою очередь, каждый
абзац имеет многочисленные свойства,
позволяющие нужным образом
отформатировать текст. Как и в Excel,
важную роль в иерархии объектов Word
играет объект Range, определяющий диапазон абзацев. Свойство
Text этого объекта содержит текст диапазона.

Позднее связывание

Приведенный ниже пример взят из моей
практики и, думаю, сможет пригодиться и вам.
В нем прайс-лист крупного оптового
поставщика книг создается с помощью Excel.
Необходимость в обращении к Excel возникла по той причине, что прайс-лист
периодически (примерно раз в две недели)
рассылается многочисленным покупателям,
которые составляют на его основе заказы на
поставку книг. У получателей прайс-листов
нет средств прочтения отчетов в
стандартном для Delphi
формате Quick Report
(файлы с расширением qrp). Экспорт прайс-листов в файлы других
форматов не позволяет получать документы
высокого качества, поэтому я решил для
создания прайс-листов использовать
средства Excel.

На рис. 3 показан прайс-лист в окне Excel, а на рис.
4 — вид формы примера на этапе конструирования.

Перед началом работы над проектом следует скопировать все файлы BOOKS.*
в отдельную папку на жестком диске (потребуется чуть больше 800 Кбайт
свободного пространства) и связать с папкой псевдоним BDE BIBLDATA типа Standard.
Эту процедуру упростит программа SetupBooks.exe, расположенная в том же каталоге
CD-ROM.

Начните новый проект и поместите на форму компоненты Query1, Label1, Button1 и ProgressBar1.
Для компонента Query1 измените значение свойства Name на Books, свяжите его
с псевдонимом BIBLDATA и поместите в свойство SQL такой запрос:

SELECT  
  BookID, bName, bAuthor, bPublish, bOpt, bPages, bYear  
FROM   
  Books  
WHERE  
  bQuan>0  
ORDER BY  
  bName  

Создайте для него все объекты-поля.
В свойство Caption
компонента Label1
поместите значение Щелкните по кнопке Пуск,
чтобы создать таблицу Excel,
в такое же свойство кнопки — значение Пуск и измените имя компонента ProgressBar1 на pb.

В окне кода в разделе private класса TForm1 поместите поле Excel типа Variant.
В предложении uses укажите ссылку на модуль COMObj и напишите следующий обработчик
события OnClick кнопки Button1 (см. листинг 1).

Теперь небольшие пояснения. Переменные Sheet и Range
введены только для сокращения текста
программы: везде вместо Sheet,
например, можно писать Excel.Workbooks[1].Sheets[1]. С версией Delphi 4 поставлялись файлы XLCONST.PAS и XLCONST.DCU, в которых определены используемые в
документации vbaxl8.hlp константы xlXXX.
С версией 5 эти файлы не поставляются,
поэтому я использую их числовые
эквиваленты. Ширина полей печатного
документа Excel
задается во внутренних единицах,
соответствующих приблизительно 3,5 мм, так
что указанные в операторах Sheet.PageSetup.ХХХMargin значения установят левое, нижнее и
правое поля шириной 1,1 см, а верхнее — 1,4 см.
Ширина столбца определяется в символах
текста, умещающегося в столбце без
отсечения.

Переменная Excel
определяет поле класса TForm1. При создании класса в него
автоматически помещается значение VarEmpty. После завершения работы с Excel
пользователь может закрыть его. Но в моей
программе Excel не визуализировался, его работа
проходила «за кулисами», а созданная
таблица записывалась в указанный
пользователем файл с помощью оператора Excel.Workbooks[1].SaveAs(FileName).

После этого Excel закрывался. Поскольку в нашем случае Excel
показывает свое окно, а пользователь может
его не закрыть, полезно написать такой
обработчик события OnDestroy формы:

procedure   TForm1.FormDestroy(Sender: TObject);      
begin  
  if not VarIsEmpty(Excel) then  
    Excel.Quit  
end;      

Запуская пример, помните, что
создание прайс-листа с помощью Excel
— процесс достаточно длительный. На моем
компьютере (400 МГц, 64 Мбайт) он занял около
минуты (для примера — аналогичный прайс-лист
средствами Quick Report
создается менее чем за 2 с). В конце
обработчика в метку lb
помещается общее время работы.

Раннее связывание

Следующий пример в функциональном плане повторяет предыдущий. В нем также с помощью Excel
создается прайс-лист, но на этот раз используется доступ непосредственно через
интерфейсы сервера. Вас ожидает «сюрприз»: время выполнения второго примера
на 40 с больше! Я не смог найти разумного объяснения этому феномену, но оба
примера находятся на сопровождающем диске, так что вы в любой момент можете
убедиться в этом сами.

Поскольку форма второго примера в точности повторяет форму первого, я не буду
объяснять, что нужно сделать для ее создания. Добавьте только на форму компонент
TExcelApplication и настройте его свойства: Name=Excel, AutoConnect=True, AutoQuit=True.
Если вы используете форму предыдущего примера как шаблон, не вставляйте поле
Excel в класс TForm1. Обработчик Button1Click должен выглядеть так (см. листинг
2).

Как видите, он во многом напоминает
обработчик предыдущего примера. Поэтому
остановлюсь на различиях.

При обращении к свойству SheetsInNewWorkbook,
как и во многих других случаях обращения к
интерфейсным свойствам и методам,
требуется указание идентификатора языка
локализации (lcid).
Значением 0 кодируется умалчиваемый язык.
Этот же идентификатор передается вторым
параметром обращения к методу Excel.Workbooks.Add. Первым параметром нужно указать имя
файла (в формате WideString),
если рабочая книга уже была ранее создана,
или «пустой» параметр EmptyParam,
если книга создается впервые.

Все мои попытки работать с объектами Range оказались неудачными. Чтобы вы не
слишком осуждали меня, я поместил
библиотеку типов Excel_TLB.pas
в каталог размещения примера — полистайте
ее на досуге и попробуйте найти нужное
решение для изменения ширины колонок и
полей листа, а также для раскрашивания
диапазона, выравнивания текста и т.п.

Есть свои нюансы и при обращении к
ячейкам. Во-первых, ими владеет объект Application,
а не Sheet.
Во-вторых, обращение к конкретному элементу
коллекции Cells (как и любой другой коллекции) возможно
только через ее свойство Item.

Подводя итоги, еще раз хочу обратить ваше
внимание на то, что по времени выполнения
позднее связывание хотя бы не проигрывает
раннему — во всяком случае, для
рассмотренных примеров. Если учесть, что
единственными доступными подавляющему
большинству программистов документами по
серверам MS
Office
являются справочные файлы vbaXXX.hlp, можно сделать вывод: использование
вариантов (позднее связывание) проще,
удобнее, а главное — намного понятнее, чем
непосредственная работа с интерфейсами (раннее
связывание).

КомпьютерПресс 6’2001

(Нажмите, чтобы увеличить изображение)

\

22f2bf0ab4fed65f1b79ab341635f94e.jpg

\

В октябре 2016 года я участвовал в 48-часовом хакатоне DevDays Asia 2016 по разработке приложений Office 365 в Пекине. Демонстрационная версия надстройки Word, разработанная мной — WordTemplateHelper, получила второе место. Мне посчастливилось познакомиться с г-ном Чен Сичжаном на площадке, и я получил большую пользу от обмена с г-ном Ченом. Узнав, что г-н Чен готовит серию решений для Office, я хотел бы кратко представить всем процесс разработки этой демонстрации, чтобы поддержать г-на Чена. Самоотверженная самоотдача, я также надеюсь, что больше разработчиков будут участвовать в разработке Office 365.

\

Для разработки, связанной с Office, перейдите по этому адресу: https://dev.office.com/getting-started

\

Эта статья в основном вводитРазработка надстройки Office.

\

нота: Оба URL в текстене поддерживаетсяНажмите, чтобы прыгать, нажмите в конце статьиПрочтите оригинал Чтобы лучше просмотреть соответствующий код.

\

1. Что такое надстройки Office

\

Что такое надстройки Office? В этих двух статьях Учителя Чена:

\

Обзор разработки Office 365 и введение в экологическую среду (1)

\

Обзор разработки Office 365 и введение в экологическую среду (2)

\

Вся история развития Office разобрана. Я лично понимаю, что разработчики могут делать определенные расширения Office для достижения различных функций на платформе, предоставляемой Office, например, ранее записанных макросов и написанных сценариев VBS. В некотором смысле все они могут рассматриваться как Надстройки для Office. Конечно, это только личное понимание, не обязательно точное. Текущие надстройки Office поддерживают только Office 2013 и более поздние версии, а метод разработки сильно отличается от предыдущей VBS.

\

Текущая структура надстроек Office выглядит следующим образом:

\

(Нажмите, чтобы увеличить изображение)

\

afd33040a50fce946c2940aa223cddbc.jpg

\

Надстройка Office на самом деле представляет собой веб-приложение, которое можно развернуть в любом месте и запустить в приложении Office. Существует файл манифеста manifest.xml, в котором можно указать способ представления веб-приложения, включая определение URL-адреса веб-приложения. Когда Office загружает эту надстройку, он фактически предоставляет среду браузера для запуска указанного веб-приложения. Другими словами, разработка надстройки Office почти такая же, как и разработка веб-программ, что очень просто для интерфейсных разработчиков, знакомых с html + JavaScript + css. Microsoft предоставляет богатый JavaScript API для работы с Office, чего можно достичь, зависит от воображения разработчика.

\

Пример надстройки Word:

\

(Нажмите, чтобы увеличить изображение)

\

25df4dec49fc600a0409daf4ca5338d7.jpg

\

Два, анализ спроса на помощник по шаблонам Word

\

Когда я узнал об этом событии, я не думал, что делать. Только когда я сел на скоростной поезд до Пекина, у меня постепенно появилась идея, которая пришла из моих обычных рабочих потребностей. В работе часто пишется множество документов, таких как различные спецификации требований к программному обеспечению, официальные письма, документы, руководства по эксплуатации и т.д., эти документы имеют установленный формат. Обычно я сохраняю некоторые уже написанные документы Word в папке в качестве шаблонов. В следующий раз, когда я напишу такой документ, я сделаю копию, удалю, удалю и изменю. Почему бы не написать программу самостоятельно и не использовать эти документы с фиксированным шаблоном в качестве шаблонов Word? Хотя Word также имеет свои собственные шаблоны, на самом деле он очень ограничен и не может полностью удовлетворить наши потребности. Если эту функцию превратить в магазин шаблонов, каждый сможет свободно загружать и делиться своими собственными шаблонами, что может быть намного удобнее.

\

Word поставляется с таким шаблоном:

\

(Нажмите, чтобы увеличить изображение)

\

6da2900be78d1215d6777fa9d3b1fc37.jpg

\

Этих общих шаблонов недостаточно для более профессиональной работы. Эффект от Word Template Helper выглядит так:

\

(Нажмите, чтобы увеличить изображение)

\

e0b52b3f6e6969084cc73cb496b50d0e.jpg

\

Если есть идея, давайте посмотрим, как ее реализовать. Когда я участвовал в мероприятии, проект размещался в Code Cloud. Чтобы написать эту статью, я реорганизовал эту небольшую демонстрацию, построил проект на Github и попытался использовать последнюю версию .NET Core для реализации части фонового API. Давай сделаем это со мной дальше.

\

3. Структура проекта

\

Сначала проанализируйте структуру проекта. Данные шаблона документа, такие как заголовок шаблона, атрибуты и т. Д., Должны храниться в базе данных, а для предоставления данных требуется проект веб-API. Надстройка Office — это чисто интерфейсный проект, использующий платформу Angular2 и асинхронно вызывающий данные веб-API. Поиск, загрузка шаблонов и другие функции. Пользовательский интерфейс подключаемого модуля использует пользовательский интерфейс Fabric, предоставленный Microsoft. Стек технологий всего проекта выглядит следующим образом:

\

(Нажмите, чтобы увеличить изображение)

\

b8de3a52e56601ef0ff0f048480a6c87.jpg

\

Что касается сущности документа — документа Word, он хранится в формате Word или непосредственно в базе данных? Если это формальный проект, то, конечно, лучше всего хранить его в облачном хранилище, но в случае с образцом его можно сохранить непосредственно в базе данных. Поскольку он участвует в марафоне развития, приходите как можно скорее. Фреймворк ORM также включен, просто чтобы быстро реализовать принятый метод, а не лучшую практику.

\

Конфигурация среды разработки в этом примере следующая:

\

Windows 10 x64,

\

VS 2017 (убедитесь, что установлены инструменты разработки Office)

\

VS Code

\

Node.js v7.10.0

\

NPM v4.2.0

\

ASP.NET Core 1.1

\

В-четвертых, разработка веб-API

\

VS2017 был официально выпущен, и я использую последнюю версию .NET Core для реализации уровня веб-API.

\

1. Новый проект

\

Создайте новое пустое решение, назовите его WordTemplateHelpe и добавьте к нему проект ASP.NET Core:

\

(Нажмите, чтобы увеличить изображение)

\

2c1d20d0355d0fc306c873c9f5c58762.jpg

\

Выберите веб-API:

\

(Нажмите, чтобы увеличить изображение)

\

16ec844e383f7ae561e7fc720b9f626f.jpg

\

2. Установите EF Core.

\

Найдите и установите следующие пакеты Nuget в диспетчере Nuget:

\

Microsoft.EntityFrameworkCore.SqlServer:EF Core SQL Server

\

Microsoft.EntityFrameworkCore.Tools: инструменты командной строки EF

\

Microsoft.EntityFrameworkCore.Tools.DotNet: инструменты командной строки EF Core

\

(Нажмите, чтобы увеличить изображение)

\

c2ff9dfac8c519a3f0d1d75b4f469535.jpg

\

3. Создание моделей

\

В настоящее время последняя версия EF рекомендует использовать режим Code First, то есть писать модель напрямую, и инфраструктура EF автоматически создаст необходимую базу данных. Если вы привыкли к DB First, есть также хорошая рекомендация по инструменту: EntityFramework-Reverse-POCO-Code-First-Generator: https://visualstudiogallery.msdn.microsoft.com/ee4fcff9-0c4c-4179-afd9-7a2fb90f5838

\

Его можно скачать прямо в расширении VS и обновить. Этот инструмент может легко сгенерировать необходимые классы сущностей на основе базы данных.

\

Сначала добавьте перечисление типов шаблонов:

\

(Нажмите, чтобы увеличить изображение)

\

c82f0074154973bc0879d1297f8a52c6.jpg

\

Добавьте класс шаблона:

\

(Нажмите, чтобы увеличить изображение)

\

e28d8885714ee6ee29b96479722cec26.jpg

\

Поскольку также необходимы несколько таблиц, таких как шаблон организации и избранное пользователя, я не буду писать их здесь. Вы можете обратиться к примерам на Github.

\

4. Создайте контекст базы данных.

\

В модели вам нужно указать, какие сущности включены в модель данных. Добавьте папку Data и создайте в ней файл с именем WordTemplateContext.cs:

\

(Нажмите, чтобы увеличить изображение)

\

081a9ae67835475f41b594a8d77b85d6.jpg

\

Таким образом, DbSet создается для каждой сущности, соответствующей таблице в базе данных, а сущность соответствует строке в таблице.

\

5. Используйте внедрение зависимостей для регистрации контекста.

\

ASP.NET Core по умолчанию реализует внедрение зависимостей. Чтобы зарегистрировать WordTemplateContext, только что созданный как службу, вам необходимо добавить следующий код в Startup.cs:

\

(Нажмите, чтобы увеличить изображение)

\

681e5bed4b2e250e750b47641aa06f53.jpg

\

Обратите внимание на добавление с помощью Microsoft.EntityFrameworkCore, иначе метод UseSqlServer не будет найден.

\

Строка подключения к базе данных настраивается в appsettings.json:

\

(Нажмите, чтобы увеличить изображение)

\

5bdf1e4e45017ea5a631a8c2d61e5aae.jpg

\

LocalDb используется здесь для тестирования. Когда требуется формальное развертывание, здесь необходимо изменить адрес, имя пользователя и пароль официального сервера базы данных.

\

6. Инициализируйте базу данных.

\

Теперь используйте командную строку для инициализации базы данных. Создайте новый класс DbInitializer в каталоге данных и введите следующий метод:

\

(Нажмите, чтобы увеличить изображение)

\

16760aa019e92d84cf17c36647cfb8bd.jpg

\

Убедитесь, что данные созданы. Затем измените метод Configure в файле Startup.cs:

\

(Нажмите, чтобы увеличить изображение)

\

5626320234f5a607acdb37c7922ffe2b.jpg

\

7. Создайте интерфейс API.

\

Теперь напишите Контроллер, чтобы увидеть. Добавьте контроллер в папку Controller:

\

(Нажмите, чтобы увеличить изображение)

\

01ba8a9470a834d67bff5a1a79e88f30.jpg

\

Здесь вы можете использовать инъекцию зависимостей для внедрения контекста базы данных:

\

(Нажмите, чтобы увеличить изображение)

\

d36cf9e384e61bf531c628d7f8dbc087.jpg

\

В качестве примера возьмем API шаблона поиска:

\

(Нажмите, чтобы увеличить изображение)

\

21c0ee0b3c2153fede2e8e4c57d4ad0c.jpg

\

Перейдите в каталог проекта из командной строки и выполните следующую команду

\

dotnet run

\

Вы можете использовать интерфейсный инструмент отладки Postman для проверки:

\

(Нажмите, чтобы увеличить изображение)

\

bae03e7687a41545e48516068c0bba48.jpg

\

Необходимо запомнить конкретный адрес операции проекта API, и он будет использован позже при выполнении надстройки. Пожалуйста, обратитесь к Github за конкретным кодом.

\

8. Разрешить междоменный доступ

\

Для поддержки надстройки для доступа к нашему интерфейсу через домены необходимо установить следующие библиотеки:

\

(Нажмите, чтобы увеличить изображение)

\

45b353f00e9923d7607f342331be20cd.jpg

\

Затем добавьте следующий код в метод ConfigureServices файла Startup.cs:

\

(Нажмите, чтобы увеличить изображение)

\

91863a5b92ad4111093e295deac3ece4.jpg

\

Добавьте в WordTemplateController строку, которая должна быть междоменной:

\

[EnableCors("AllowCrossDomain ")]

\

Этот API может поддерживать междоменный доступ.

\

Пять, разработка надстройки Word

\

С помощью API вы можете разработать надстройку. Как упоминалось в начале, надстройка на самом деле представляет собой веб-приложение, которое управляет объектами документов Office с помощью JavaScript. В этом проекте он использует асинхронные js-файлы для запроса, загрузки и поиска файлов шаблонов, хранящихся на сервере, и динамического сравнения Текущий документ Word работает.

\

Microsoft открыла исходный код этого JavaScript API на Github: https://github.com/Microsoft/Office-js-docs_zh-cn, связанные документы: https://msdn.microsoft.com/zh-cn/library/office/fp142185 .aspx

\

Шаги разработки можно ссылаться на: https://github.com/OfficeDev/Office-js-docs_zh-cn/blob/staging/docs/get-started/create-and-debug-office-add-ins-in-visual-studio .md

\

Давайте разработаем надстройку.

\

1. Создайте новый проект надстройки.

\

Щелкните решение правой кнопкой мыши и добавьте надстройку Word Web:

\

(Нажмите, чтобы увеличить изображение)

\

5814b8d493b995f4c57c41d699d0a45f.jpg

\

После завершения добавления остается еще два предмета:

\

(Нажмите, чтобы увеличить изображение)

\

01e312e90c6f54bd80457011c55e2847.jpg

\

Один из них — это файл манифеста, а файл с суффиксом Web — это Web App.

\

2. Установите manifest.xml.

\

Файл манифеста — это очень важный файл, в котором описаны все настройки надстройки. Этот файл создается автоматически, но нам нужно изменить некоторые места вручную. К счастью, в файле есть комментарии, поэтому его легче изменить:

\

(Нажмите, чтобы увеличить изображение)

\

b12a496a0ae6333c94d5675dc9fef14a.jpg

\

Самым важным является изменение узла SourceLocation. Этот адрес устанавливает расположение, в котором размещается веб-приложение. После разработки и развертывания на веб-стороне вы должны изменить этот узел на правильное расположение перед публикацией. Следующий узел также следует изменить.

\

(Нажмите, чтобы увеличить изображение)

\

c8b918d177a672563be0370bef6617dc.jpg

\

3. Анализ веб-приложений.

\

Вы можете запустить этот шаблон, чтобы попробовать, напрямую F5:

\

(Нажмите, чтобы увеличить изображение)

\

bebea85c0a8262a7d011a6ae0a30ae52.jpg

\

Щелкните здесь, чтобы вызвать этот плагин:

\

(Нажмите, чтобы увеличить изображение)

\

3aab602eca342adde1e57f54f02d0e9b.jpg

\

Он автоматически откроет Word и загрузит этот плагин. Текст в документ вставляется плагином. Так где же работает код?

\

Откройте файл Home.js и найдите следующий код:

\

(Нажмите, чтобы увеличить изображение)

\

c07a029132bdb66554a870acc7789724.jpg

\

Word — это объект, предоставляемый JavaScript API, который может удобно работать с содержимым текущего документа. Такой способ мышления существует: вы можете использовать JavaScript для динамического вызова веб-API для получения результатов запроса и вставки запрошенного содержимого документа в текущий документ для достижения исходной цели. В то же время вы можете сохранить содержимое текущего документа в качестве шаблона и загрузить его на сервер для публикации.Полнофункциональный образец готов к выходу.

\

4. Используйте Angular

\

Мы используем последнюю версию Angular4 для разработки интерфейсных страниц. Конечно, вы можете использовать JQuery, но его сейчас немного, не так ли? Используя Angular, вы можете быстро разработать одностраничное веб-приложение с архитектурой MVVM, которая очень подходит для этой потребности.

\

Часть кода этой демонстрации относится к проекту с открытым исходным кодом Microsoft: https://github.com/OfficeDev/Office-Add-in-UX-Design-Patterns-Code

\

Кривая начала работы с Angular все еще немного крута.Официальный инструмент Angular CLI предназначен для быстрого создания приложения Angular. Сначала установите TypeScript:

\

npm install -g typescript

\

Затем установите Angular CLI: https://github.com/angular/angular-cli

\

npm install -g @angular/cli

\

Выполните следующую команду, чтобы создать проект Angular:

\

ng new WordTemplateHelperSource

\

(Нажмите, чтобы увеличить изображение)

\

e5c489e55ed4aacba3595ac9e096349a.jpg

\

Затем с помощью команды cd WordTemplateHelperSource перейдите в каталог проекта и выполните следующую команду:

\

npm install

\

Эта команда установит зависимости, необходимые для проекта ng. Если установка не удалась, рекомендуется переключиться на зеркало Taobao npm для установки.

\

Запустите проект ng с помощью следующей команды:

\

ng serve

\

Вы можете просмотреть http: // localhost: 4200 в браузере Chrome, чтобы увидеть эффект:

\

(Нажмите, чтобы увеличить изображение)

\

60b0fcf6e3cef9c2a00814407255d63b.jpg

\

Обратите внимание, что если просмотр в IE является ненормальным, мы дадим решение этой проблемы в последнем разделе.

\

Почему бы не создать его прямо в WordTemplateHelperWeb? Поскольку приложения Angular должны быть упакованы, каталог dist будет создан в каталоге проекта, который является частью, которая официально запускается. Поэтому после завершения разработки скопируйте файлы из созданного каталога dist в WordTemplateHelperWeb.

\

В процессе разработки Angular рекомендуется использовать VS Code.Поддержка TypeScript и Angular очень хороша.

\

Поскольку эта статья не является учебным пособием по разработке на Angular, конкретные знания Angular здесь не будут подробно описаны.Если вам интересно, вы можете загрузить код Github и запустить его самостоятельно.

\

5. Добавьте сервис для работы с файлами Word.

\

Чтобы управлять файлами Word, нам нужно упаковать их в сервисы. Используйте следующую команду, чтобы добавить службу:

\

ng g service services\word-document\WordDocument

\

Это создаст службу с именем WordDocumentService по соответствующему пути в каталоге приложения. Аналогичным образом сгенерируйте несколько других сервисов. Основные методы следующие:

\

Метод поиска по запросу:

\

(Нажмите, чтобы увеличить изображение)

\

303c322f2fec08b8ea3685eb64acec29.jpg

\

Таким образом можно получить шаблон документа, хранящийся на сервере, который на самом деле представляет собой строку, сохраненную в формате Ooxml.

\

В этом примере нет ничего слишком сложного в использовании Office JavaScript API. В основном используются два метода: getOoxml () и insertOoxml (). Первый может читать формат Ooxml текущего текстового документа, а второй может устанавливать текущий текстовый документ. Формат Ooxml. Ooxml — это формат, используемый версиями после Office2007, например docx.

\

Исходный API предоставляет функции обратного вызова. Для простоты использования я инкапсулировал их в Promises:

\

(Нажмите, чтобы увеличить изображение)

\

a56961fa7637432c3ccbcd96adfd13f2.jpg

\

После поиска подходящего шаблона вы можете нажать кнопку, чтобы вызвать метод setOoxml (), чтобы вставить его в текущий документ Word:

\

applyTemplate(template: WordTemplateInfo) {    this.wordDocument.setOoxml(template.TemplateContent);  }

\

Это завершает функцию шаблона приложения.

\

Если вы хотите сохранить содержимое текущего документа в виде шаблона и загрузить его на сервер, вы можете вызвать метод getOoxml (), чтобы получить текст в формате Ooxml текущего документа и загрузить его на сервер для сохранения. Что касается других добавленных в избранное, добавленных в качестве институциональных шаблонов, установленных в качестве личных шаблонов и т. Д., Атрибуты установленных шаблонов были обновлены, и конкретный код не будет повторяться.

\

Еще одно замечание: при разработке адрес сервера здесь должен быть адресом ASP.NET Core, который мы только что разработали.

\

6. Используйте интерфейс Fabric.

\

Для надстройки Office простой и красивый пользовательский интерфейс, объединенный с Office, является обязательным. Microsoft рекомендует использовать Fabric UI для достижения унифицированного стиля интерфейса, см. Https://dev.office.com/fabric.

\

Существует множество ресурсов, таких как стили, значки, спецификации дизайна и даже компоненты версии React. Если вы используете React для разработки, вы можете использовать их напрямую. Эта демонстрация представляет собой файл стиля с прямой ссылкой, который настраивается в файле .angular-cli.json:

\

(Нажмите, чтобы увеличить изображение)

\

08f49736c77126275b2aeff49cd08d05.jpg

\

После нанесения получается так:

\

(Нажмите, чтобы увеличить изображение)

\

92d8ad6068ad5f27c2dbc0b049129e2b.jpg

\

7. Надстройка пакета

\

Я только что разработал статическое веб-приложение в новом проекте, упаковал его и скопировал в проект WordTemplateHelperWeb. Используйте ng build -prod для упаковки приложений Angular. Упакованный файл будет выведен в каталог dist:

\

(Нажмите, чтобы увеличить изображение)

\

5757b0bfe4a1b4d531c8707cad26a020.jpg

\

Обратите внимание, что есть еще одна вещь, на которую следует обратить внимание. Если он упакован только таким образом, браузер IE не поддерживается, но встроенный браузер надстройки Office на самом деле является ядром IE, поэтому нам нужно внести следующие изменения и найти каталог src В файле polyfills.ts раскомментируйте следующие части:

\

(Нажмите, чтобы увеличить изображение)

\

d54c86ae88c480f4e77ebb6fa2145415.jpg

\

Согласно подсказкам, запустите команду npm install, чтобы установить несколько необходимых зависимостей. Только тогда он сможет нормально работать в браузерах серии IE. Снова запустите ng build –prod, чтобы упаковать. Значение параметра —prod состоит в том, чтобы построить в производственном режиме, чтобы сгенерированный код был меньше и работал быстрее.

\

Удалите все исходные файлы в проекте WordTemplateHelperWeb, кроме Web.config. Скопируйте файлы в каталог dist.

\

Хотя компьютер можно напрямую отлаживать и запускать во время разработки, для моделирования реального использования мы также официально выпустим это веб-приложение. Если у нас есть Azure или другие хосты, мы развернем его непосредственно на сервере.Теперь мы используем только собственный IIS для размещения этого веб-приложения:

\

(Нажмите, чтобы увеличить изображение)

\

6a86317894fc07fc04848da67fac9444.jpg

\

Итак, адрес надстройки: http: // localhost / WordTemplateHelperWeb,

\

Запустим API, войдем в каталог WordTemplateHelperApi и запустим команду dotnet run:

\

(Нажмите, чтобы увеличить изображение)

\

8338b7ad3ed305b7cd8ddb41e9edd55e.jpg

\

Адрес проекта API: http: // localhost: 5000 / api /

\

Не путайте эти два адреса. Просто обратите внимание при упаковке WebApp. Адрес API в файле common app-global.ts должен быть изменен на тот же, что и фактический адрес API:

\

(Нажмите, чтобы увеличить изображение)

\

4c5862ce9500daf19f6fa1bad4b37a41.jpg

\

Теперь откройте файл манифеста WordTemplateHelperManifest и измените следующее расположение:

\

(Нажмите, чтобы увеличить изображение)

\

a9f775458d742630f4829208287dbd08.jpg

\

(Нажмите, чтобы увеличить изображение)

\

5254d4961c88e52150408862c47ddcab.jpg

\

Вот адрес надстройки, не ошибитесь.

\

6. Запустите тест.

\

Теперь вы можете повторно запустить проект надстройки, установить для запускаемого проекта WordTemplateHelper и запустить:

\

(Нажмите, чтобы увеличить изображение)

\

f6a0d7cef6f31503cf012d480eca83e0.jpg

\

Мы можем вставить шаблон и загрузить его на сервер:

\

(Нажмите, чтобы увеличить изображение)

\

e5bb6a372c3f6dd7aca065576c4a0078.jpg

\

Нажмите кнопку «Загрузить», чтобы загрузить текущий документ в качестве шаблона на сервер для совместного использования.

\

(Нажмите, чтобы увеличить изображение)

\

039b1179b8964106a35ae77deeb3798b.jpg

\

После поиска соответствующего шаблона нажмите кнопку «Применить», чтобы вставить содержимое шаблона в текущий документ.

\

Мы можем искать шаблоны, добавлять собственные шаблоны и применять содержимое шаблона к текущему документу. Организациями и отдельными лицами также можно управлять отдельно. Я считаю, что этот небольшой плагин можно превратить в платформу, например, в хранилище шаблонов, где пользователи могут свободно обмениваться шаблонами документов друг друга, а также собирать и добавлять в библиотеку шаблонов своей организации. Средний и так далее. Небольшое расширение может быть превращено в формальный продукт.

\

7. Загрузить анимацию загрузки.

\

При загрузке страницы можно добавить приглашение загрузки, чтобы сделать работу пользователя более удобной. Конкретный код может относиться к стилю css в index.html.

\

Шесть, резюме

\

Надстройка Office — это относительно новая область разработки, которая отличается от предыдущих методов разработки, но студенты, знакомые с интерфейсом, могут быстро войти в это поле, которое фактически является написанием веб-страниц. Этот пример представляет собой относительно полный проект от внутреннего интерфейса до клиентской реализации. Я надеюсь, что студенты, интересующиеся разработкой Office, смогут загрузить код для изучения и разработки более практичной надстройки. Поскольку этот проект фактически не был развернут, он не был загружен в магазин. Пользователи, скачивающие код, не должны использовать его в коммерческих целях. Настоящим объясните.

\

об авторе

\

Ян Сяоди, Самый ценный эксперт Microsoft, любит делиться. Знаком с ASP.NET, WPF, UWP, TypeScript и другими технологиями разработки, неоднократно участвовал в марафоне разработки, организованном Microsoft, и выигрывал награды.

Понравилась статья? Поделить с друзьями:
  • Прогрессия на время в excel
  • Программы на подобии word
  • Прогрессия для в excel 2007
  • Программы на основе microsoft excel
  • Прогрессия в excel число