VSTO и CAB: Интеграция .NET приложения в Microsoft Word
VSTO расшифровывается как Visual Studio Tools for Office. Эти средства позволяют довольно легко скрещивать ужа с ежом — писать .NET приложения, исполняемые CLR в среде Microsoft Office. В частности, программисты обладают возможностью создавать подключаемые модули (плагины) и «кастомизированные» шаблоны для документов почти ко всему основному семейству продуктов Microsoft Office.
В статье приведена инфраструктура Windows Forms проекта, в котором Microsoft Word воспринимается приложением в качестве шелла. В статье раскрыты несколько интересных моментов использования Composite UI Application Block, в частности подключение инфраструктуры доменной модели Word в сервисам расширения каркаса, а так же приведены некоторые факты и особенности разработки с использованием средств VSTO.
Задача
Предположим, у нас есть несколько десятков людей, которые пишут документы и постоянно работают с их редакциями. Sharepoint и другие порталы по каким-то причинам не подходят, поэтому требуется реализация собственной бизнес-логики. Усложним задачу — люди работают в области, очень далекой от компьютерной тематики и хорошо знают только пакет Microsoft Office. Еще усложним — контора бедная, Microsoft Office у большинства народа версии 2003-ей. Кое где, у больших начальников и Главного босса стоит 2007ой. Парк машин разнородный — начиная от 2000-ой винды и до Windows Vista.
Решение
Одним из решений является плагин для Microsoft Office. Его можно написать на COM-е, через голый Office Interop, или же через такой же интероп, но по хитрому обернутый в средства VSTO. Эти средства и задействуем. Пользователь будет запускать Word, среда исполнения VSTO будет подцеплять сборки .NET и каким-то образом дополнять элементы управления Microsoft Word, позволяя пользователю выполнить нужную в задаче бизнес-логику.
А к чему все это?
(Это кому не терпиться узнать, чем все закончится). На выходе я выложу Visual Studio solution, который позволит в полпинка создать свое приложение, подключаемое в Microsoft Word. По ходу дела я объясню практически все, что и как в этом солюшене используется.
Что потребуется для работы
- Microsoft Word 2003 SP3
- Visual Studio 2005 или 2008
Зачем CAB?
Для того, чтобы определить явное разделение функционала, связанного с Microsoft Word от простой Windows Forms логики. Проще говоря, наш плагин будет являться модулем CAB, и при желании, мы легко сможем подключить эту логику в другие приложения CAB. Еще проще говоря, использование CAB минимизирует количество glue-кода.
Есть еще две причины, по которым я включил CAB в решение. Первая состоит в том, что я уже давал описание этого каркаса здесь, а теперь хочу привести полноценный работающий пример. Вторая причина более банальная — мне нравится CAB, он делает мир и вещи в нем проще:)
Подробнее про VSTO
Вообще говоря, VSTO не только плагины для Word-а умеет создавать. Это мощная система, сейчас она уже есть в третьей версии. VSTO — это врата для разработчика в мир автоматизации и программирования Office-based решений. В двух словах — разработчик .NET получает доступ к доменной модели приложения Office и делает с ней что хочет.
- Уровень документа (Document Level Customization)
- Уровень приложения (Application Level Customization)
Application Level Customization означает то, что в среду приложения Microsoft Office подключается модуль расширения (т.н. add-in). Собственно, про него я изначально и начал говорить. Этот модуль расширения является локомотивом бизнес-приложения и позволяет подключать в Microsoft Office все возможности платформы .NET. Я буду показывать именно решение второго уровня.
Если кто заинтересовался возможностями VSTO — очень много про них есть в msdn.
Псевдозадача
Создадим маленький пример, иллюстрирующий все вышесказанное. Пусть есть требование, согласно которому пользователь может выделять текст из документа Microsoft Word и получать этот выделенный текст в окошке пользовательского элемента (знаю, дурацкий пример, но более сложного делать не хочется, а менее сложный уже трудно придумать).
Более подробно. Пользователь запускает Microsoft Word. После запуска среди тулбаров пользователю будет доступен ТР — тулбар расширения (т.е., нашего расширения). На нем находится кнопка, по нажатию которой пользователю показывется окно с кнопкой «Получить текст» и элементом управления multiline textbox. Пользователь нажимает на кнопку «Получить текст», выделенный в документе текст копируется в textbox. Все счастливы.
Понятно, что для жизни пример малопригоден. Но создаваемая инфраструктура позволит довольно просто нарастить на него «мускулы», если это, конечно, кому-то потребуется.
Создание solution
После установки VSTO в студии при создании нового solution станут доступны Office проекты:

Будьте аккуратны с названием — оно одновременно будет являться названием root namespace и поменять его можно будет только ручками через выгрузку проекта.
На выходе получается солюшен с двумя проектами: 
WordCAB — это, собственно, add-in. Второй — не менее важный, это deployment проект для модуля расширения. Почему он важен?
Дело в том, что установка модуля расширения на рабочие станции пользователей — весьма трудоемкая затея. Она заключается не только в том, чтобы просто скопировать библиотеки в нужную папку. Для успешного фукнционирования модуля требуется прописать кучу ключей в реестр. Если копнуть глубже, получается, что модуль расширения подключается к Microsoft Office как COM-библиотека, со всеми вытекающими. Кому интересно, все нужные ключи реестра можно поглядеть в deployment-проекте (Нажать правой кнопкой, View->Registry).
В классе ThisAddIn находится точка доступа в модуль:
public partial class ThisAddIn
<
private void ThisAddIn_Startup( object sender, System. EventArgs e)
<
new AddInApplication().Run();
>
private void ThisAddIn_Shutdown( object sender, System. EventArgs e)
<
>
#region VSTO generated code
/// <summary>
/// Required method for Designer support — do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
<
this .Startup += new System.EventHandler(ThisAddIn_Startup);
this .Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
>
* This source code was highlighted with Source Code Highlighter .
В ThisAddIn_Startup будем писать код. Если быть точнее, будем запускать CAB-приложение.
Что такое CAB-приложение?
Это класс такой. В нем есть точка запуска — метод Run() , и некоторая инфраструктура каркаса — в частности, имеются доступ к главному прецеденту «Приложение» (т.е., к основному, рутовому WorkItem) и возможности переопределить системные сервисы, добавляемые в приложения по-умолчанию. Этот класс уже реализован в базовом виде, а программисту требуется параметризировать его — указать тип рутового прецедента и тип главной формы:
internal sealed class AddInApplication : WindowsFormsApplication<AddInWorkItem, object >
<
protected override void Start()
<
>
>
* This source code was highlighted with Source Code Highlighter .
В качестве типа главной формы (шелловой формы), указан object. Почему? Потому что приложение запускается в Microsoft Word, поэтому необходимости создавать главную форму нет. Форма Microsoft Word и есть главная форма, правда, без традиционных для нее возможностей.
По ходу пьесы мне пришло в голову немного прояснить модель приложения для модуля расширения в Microsoft Word.
Модель приложения Word Add-in
Итак, пользователь работает с документами. Более того, в единицу времени он может работать только с одним документом. Поэтому модуль расширения имеет четкий контекст — текущий открытый документ. (Кстати, в модели Microsoft Word VSTO этот документ обозначен как Globals.ThisAddIn.Application.ActiveDocument ). Отсюда (я сделал) вывод (или упрощение) — показывать пользовательские бизнес-элементы имеет смысл в модальном режиме, поскольку в ином случае нарушается контекст. Элементы управления, открытые для одного документа, должны существовать только во время активности этого документа.
Пример — пользователь открыл документ и открыл его карточку (не в модальном режиме). Переключился на другой документ, а карточка осталась для предыдущего — нарушение контекста и целостности восприятия. Разумеется, подобное поведение контролировать можно (и даже нужно, в некоторых случаях), но модальность элементов управления и жесткая их привязка к текущему активному документу делает жизнь проще.
Modal Workspace
Пользовательские элементы управления в каркасе CAB показываются в специальных так называемых рабочих зонах (Workspace). Согласно описанному выше поведению, нам требуется показывать элементы управления в модальном режиме. Поскольку родной CAB-овский WindowWorkspace оказался дубоват и выглядит некрасиво, я написал простенький свой — приводить код не буду, потому что его пара-тройка экранов. Насладиться творчеством можно скачав solution с кодом (ссылка на архив приведена в конце статьи).
Регистрация модальной рабочей зоны происходит в методе InitializeServices() главного прецедента — AddInWorkitem :
Workspaces.Add(( new SimpleModalWorkspace() ), WorkspaceNames.MainWorkspace);
* This source code was highlighted with Source Code Highlighter .
Итого: под ключем-идентификатором WorkspaceNames.MainWorkspace зарегистрировали модальную рабочую зону. Доступиться до нее теперь можно откуда угодно, где есть ссылка на главный прецедент: запрос интерфейса IWorkspace из коллекции Workspaces по указанному выше ключу. Все просто, как апельсин! (с) х/ф «Терминатор 2»
Фабрика элементов управления Microsoft Word
Речь пойдет о тулбаре Word и кнопках на нем. В VSTO это обертки над COM-объектами — CommandBar и CommandBarButton из пространства имен Microsoft.Office.Core . Жутко глючные штуки, если честно, особенно их анимация. Понять все тонкости и детали удалось только после ударного троллинга на форумах VSTO.
В чем идея — идея состоит в том, чтобы избавить программиста и разработчиков бизнес-модулей от необходимости работать с COM-обертками. Для этого, мы (то есть, я) интегрируем модель Misrosoft Word в механизм так называемых мест расширения (UiExtensionSite) каркаса CAB.
- В массив тулбаров Microsoft Word вставляется тулбар
- В коллекцию кнопок на тулбаре вставляется кнопка
- В кнопку уже ничего не вставляется (не рассматриваем случае комбобоксов, хотя они присутствуют), поэтому это терминальный объект
- IWordCommandBarContainer — контейнер тулбаров Microsoft Word
- IWordCommandBar — контейнер кнопок на тулбаре
- IWordButton — кнопка
К слову сказать, помимо определения элементов управления Word конкретные типы интерфейсов IWordCommandBar и IWordButton являются адаптерами к упомянутым выше CommandBar и CommandBarButton соответственно.
Для того, чтобы каркас понял, что, куда и, главное, каким образом вставлять, ему необходимо зарегистировать фабрику адаптеров для пользовательских элементов управления. В нашем случае, вставлять можно тулбары (в коллекцию тулбаров) и кнопки (в коллекцию кнопкок на тулбаре). Поэтому, регистрировать фабрику адаптеров нужно для IWordCommandBarContainer и для IWordCommandBar . Потом, когда пользователь будет получать место расширения, каркас будет искать инстансы классов добавления подчиненных элементов для этого места расширения, после чего использовать их по назначению — добавлять элементы. Ну а чтобы не заморачиваться, эти инстансы порождаются фабрикой:
public class CommandBarUIAdapterFactory : IUIElementAdapterFactory
<
public IUIElementAdapter GetAdapter( object uiElement)
<
if ( uiElement is IWordCommandBarContainer ) //тулбары
return new CommandBarUIAdapter(( IWordCommandBarContainer )uiElement);
if ( uiElement is IWordCommandBar ) //кнопки в тулбарах
return new CommandBarButtonUIAdapter(( IWordCommandBar )uiElement);
throw new ArgumentException( «uiElement» );
>
public bool Supports( object uiElement)
<
return ( uiElement is IWordCommandBarContainer ) || ( uiElement is IWordCommandBar );
>
>
* This source code was highlighted with Source Code Highlighter .
А вот реализация коллекции тулбаров (с возможностью добавить новый!)
internal class BarCollection : IWordCommandBarContainer
<
#region IWordCommandBarContainer Members
public void AddBar(IWordCommandBar bar)
<
CommandBar commandBar = null ;
//todo: сделать так, чтобы второй раз создать нельзя было
try
<
commandBar = Globals.ThisAddIn.Application.CommandBars[bar.Id];
>
catch ( ArgumentException )
if ( commandBar == null )
commandBar = Globals.ThisAddIn.Application.CommandBars.Add(bar.Id, ( object )MsoBarPosition.msoBarTop, _null, true );
private object _null = System.Reflection.Missing.Value;
* This source code was highlighted with Source Code Highlighter .
С кнопками на тулбарах аналогично. Гляньте код на досуге.
Пришлось, к слову, создавать фабрику самих кнопкок и тулбаров (см. IWordUIElementFactory ). Дело в том, что модули CAB работают с интерфейсами IWordCommandBar и IWordButton . Конкретные типы этих интерфейсов находятся в сборке VSTO Word-AddIn и завязаны на Microsoft.Office.Core . Поэтому, чтобы в модулях (где отсутствует ссылка на Office) была возможность получать инстансы указанных элементов, создается фабрика. Она регистрируется в главном WorkItem.
Регистрация фабрик и места расширения под тулбары:
Services.AddNew<WordUIElementFactory, IWordUIElementFactory>();
IUIElementAdapterFactoryCatalog factoryService = base .Services.Get<IUIElementAdapterFactoryCatalog>();
factoryService.RegisterFactory( new CommandBarUIAdapterFactory());
UIExtensionSites.RegisterSite(UIExtensionSiteNames.WordBarsSite, new BarCollection());
* This source code was highlighted with Source Code Highlighter .
Если вы запутались, извините:) Я сам понимаю, что тут без поллитры не разберешься. (Если подебажиться, то многое становится понятным.)
Модуль CAB
Это обычная сборка. В ней должен быть класс ModuleInit . В этом классе есть ссылка на главный прецедент AddInWorkitem и, как следствие, есть доступ ко всему добру, про которое я писал выше.
Задача модуля CAB такова — подгрузиться к главному преценденту, вставить тулбар, вставить кнопку на него, описать обработчик кнопки:
UIExtensionSite site = _rootWorkItem.UIExtensionSites[UIExtensionSiteNames.WordBarsSite];
IWordCommandBar mainBar = site.Add<IWordCommandBar>(_factory.CreateBar( «AddInToolbar» ));
IWordButton btn = _factory.CreateButton(mainBar, CommandNames.OpenForm, ToolStripItemDisplayStyle.ImageAndText, «Открыть окно» ,
«Открыть форму просмотра Custom Control» , Resources.OpenForm, false );
mainBar.AddButton(btn);
btn.Click += new EventHandler<WordButtonClickArgs>(ButtonClick);
* This source code was highlighted with Source Code Highlighter .
Обработчик кнопки будет создавать пользовательское окно с кнопкой «получить текст» и текстовым полем, после чего показывать его (применив специальный модификатор показа WindowSmartPartInfo ) в ранее упомянутой рабочей зоне (которую, как мы помним, я зарегистрировал в прецеденте под ключем WorkspaceNames.MainWorkspace ):
private void ButtonClick( object sender, WordButtonClickArgs e)
<
object smartPart = _rootWorkItem.SmartParts.AddNew<SampleSmartPart>();
WindowSmartPartInfo info = new WindowSmartPartInfo();
info.FormStartPosition = FormStartPosition.CenterScreen;
info.MaximizeBox = false ;
info.MinimizeBox = false ;
info.Resizable = false ;
info.Title = «Custom Control» ;
info.ShowInTaskbar = false ;
_rootWorkItem.Workspaces[WorkspaceNames.MainWorkspace].Show(smartPart, info);
>
* This source code was highlighted with Source Code Highlighter .
Подгрузка модуля
public class CustomModuleEnumerator : IModuleEnumerator
<
#region Constants
private const string ModuleName = «CustomModule.dll» ;
#endregion
#region IModuleEnumerator Members
public IModuleInfo[] EnumerateModules()
<
List <IModuleInfo> result = new List <IModuleInfo>();
string path = GetModulePath(ModuleName);
if ( File .Exists(path) )
result.Add( new ModuleInfo(ModuleName));
return result.ToArray();
>
#region Private Methods
private string GetModulePath( string assemblyFile)
<
if ( !Path.IsPathRooted(assemblyFile) )
assemblyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyFile);
return assemblyFile;
>
#endregion
>
* This source code was highlighted with Source Code Highlighter .
Сейчас эта служба очень простая, но при желании, разумеется, в нее можно нагородить хоть черта лысого. Например, подгружать модули из базы данных:)
Перегрузить эту службу нужно в классе приложения CAB:
protected override void AddServices()
<
base .AddServices();
RootWorkItem.Services.Remove<IModuleEnumerator>();
RootWorkItem.Services.AddOnDemand<CustomModuleEnumerator, IModuleEnumerator>();
>
* This source code was highlighted with Source Code Highlighter .
Доктор, я устал. Что получилось?
Получилось то, что задумывалось в изначальном псевдопримере:
В тулбарах Microsoft находится наш тулбар, на нем висит кнопка с иконкой, по событию нажатия кнопки вылезает пользовательский элемент управления, в текстовое поле которого с помощью кнопки можно загнать выделенный в документе текст:) Тривиально, но обратите внимание, как ничтожна связность между объектами! Хочется подметить, что у каркаса, при должном обращении, идеальная code maintainability.
Внимание! Подводные камни!
Security
Когда скомпилируете и запустите пример, ничего не запустится. Более того, вывалится сообщение, которое скажет вам — дескать, не хватает прав на запуск стороннего модуля (в нашем случае, CustomModule.dll). Проблема в том, что по умолчанию приложение VSTO (в режиме разработки!) дает Full Trust права только на исполняемую сборку и на все сборки, от которых она зависит — т.е. на WordCAB.dll. Для того, чтобы разрешить использование кода сторонних библиотек, выполните следующее действие:
C:\Windows\Microsoft.NET\Framework\v2.0.50727>caspol -u -ag All_Code -url «D:\Projects\WordCAB\bin\Debug\*» FullTrust
Microsoft (R) .NET Framework CasPol 2.0.50727.3053
Copyright (c) Microsoft Corporation. All rights reserved.
The operation you are performing will alter security policy.
Are you sure you want to perform this operation? (yes/no)
yes
Added union code group with «-url» membership condition to the User level.
Success
Вместо «D:\Projects\WordCAB\bin\Debug\*» нужно указать папку, из которой производится запуск Add-in. Не забудьте про звездочку.
Add-in перестал загружаться!
Иногда, когда в модуле возникает необработанный Exception, Word блокирует исполнение этого модуля при следующей загрузке. Зайдите Help->About->Disabled Items и посмотрите, нет ли в списке вашего расширения. Если есть, уберите его оттуда. При следующем Run-Debug он появится.
Как убрать
Удаление расширения не такое тривиальное. Вытащите кнопку COM-AddIns на тулбар Microsoft Word. Для этого надо зайти в Tools->Customize->Tools->(Drag’n’Drop)COM-AddIns. Кликните на нее и снимите галку с вашего расширения. Он выгрузится. Чтобы заново подгружать, наоборот, выставите галку обратно. Другой способ — зайти в панель управления и удалить из программ.
А что с Word 2007?
Add-in замечательно подгружается в Ribbon на последнюю вкладку.
Там еще кучка мелких подводных камней. Но я тут и так уже настолько много написал, что не уверен, что кто-нибудь до конца дочитает:)
Пошаговые руководства с помощью Word
В следующих пошаговых руководствах показано, как можно использовать настройки на уровне документа и надстройки VSTO для автоматизации Microsoft Office Word, а также для настройки пользовательского интерфейса.
Пошаговые руководства по уровню документа
Пошаговое руководство. Создание первой настройки на уровне документа для Word Демонстрирует создание базовой настройки на уровне документа для Word.
Пошаговое руководство. Вставка текста в документ из области действий Демонстрирует добавление текста в документ Word при вводе текста в элементы управления на панели действий.
Пошаговое руководство. Привязка данных к элементам управления на панели действий Word Демонстрирует привязку данных к элементам управления на панели действий. Элементы управления показывают отношение «Основной/подробности» между таблицами в базе данных SQL Server.
Пошаговое руководство. Обновление диаграммы в документе с помощью переключателей Демонстрирует изменение стилей диаграмм в документе Word с помощью кнопок параметров в настройке на уровне документа.
Пошаговое руководство. Создание контекстных меню для закладок Демонстрирует создание контекстного меню, которое можно использовать для форматирования текста в закладке.
Пошаговое руководство. Вызов кода из VBA в проекте Visual Basic Демонстрирует вызов кода в сборке настройки из кода VBA в документе.
Пошаговое руководство. Создание шаблона с помощью элементов управления содержимым Демонстрирует использование элементов управления содержимым для создания структурированного и многократно используемого содержимого в шаблоне Word.
Пошаговое руководство. Привязка элементов управления содержимым к пользовательским XML-частям Демонстрирует, как привязать элементы управления содержимым в документе Word к XML-данным, хранящимся в документе.
Пошаговые руководства на уровне приложения
Пошаговое руководство. Создание настраиваемой вкладки с помощью XML-кода ленты Демонстрирует создание настраиваемой вкладки ленты с помощью XML-файла дескриптора в надстройке VSTO.
Как подключить word к visual studio
Прежде чем приступить к рассмотрению поставленных вопросов, создадим новый простой проект (Рис.1.) с именем AppWordExcel (или любым другим, или используем ). Поместим на панели приложения три контрола Button. Свойства Tag контролов установим, соответственно, в значения 1, 2 и 3. Выберем способ «раннего связывания» для сервера автоматизации Word (см. Параграф 1. этого раздела). При написании кода будем использовать .Net сборки взаимодействия с приложениями Microsoft Office. Напомним, что при использовании импортирования библиотеки типов Com объекта и добавлении ссылки на него в Solutation Explorer (References, вкладка Com), нам сразу становится доступно пространство имен объекта (в данном случае Word). При использовании .Net сборок взаимодействия с приложениями Microsoft Office нам, после добавления ссылки на Microsoft.Office.Interop.Word (References, вкладка .Net), потребуется введение алиаса пространства имен Word:
Введение алиаса позволяет не только сократить количество кода при обращении к объектам, но и дает возможность использовать весь код, который мы напишем ниже, для любого способа связывания.
Рис.1. Проект решения AppWordExcel
Создадим обработчик нажатия для кнопки 1 и замкнем на него обработчики нажатия всех кнопок. Запишем следующий, общий для всех примеров, приводимых ниже, код:
В case 1 будем размещать код, в котором выполняется какое-либо действие по старту Excel и выводу информации, в case 2 — дополнительные действия и действия при закрытии приложения, в default — поместим код выхода из приложения.
Обратите внимание на разный способ получения объектов коллекций в Visual Studio 2003 и 2005/2008. Других отличий в примерах приводимого ниже кода нет !
Параграф 1. Запуск и закрытие Word из приложения
Объекты, которыми оперирует сервер Word, столь же многочисленны как и объекты для Excel. Основное внимание в параграфе будет уделено тем объектам, которые непосредственно требуются для обмена информацией приложения и сервера.
Рис.2. демонстрирует иерархию объектов Word. Все объекты доступны и из приложения на C#. Отличие заключается в том, что для приложения-контроллера доступны непосредственно Word.Application (экземпляр Word без открытых документов) и Word.Document, (экземпляр Word с открытым или загруженным документом). Остальные объекты Word являются так называемыми внутренними объектами. Это означает, что они не могут быть созданы сами по себе; так, объект Paragraph не может быть создан без документа (впрочем, параграф или абзац равно не доступен и макросу в Word при отсутствии открытого документа).
Рис.2. Основные объекты Word.
С точки зрения приложения все объекты Word имеют иерархическую структуру. Объект Application — это СOM сервер и оболочка для других объектов. Он может содержать один или более объектов Document. Объекты Document могут содержать такие объекты как Paragraph, Table, Range, Bookmark, Chapter, Word, Sentence, Sections, Headers, Footers. Более правильнее, объект Application может содержать коллекцию Documents — ссылок на объекты типа Document, а каждый объект типа Document — коллекцию Paragraphs или ссылок на объекты типа Paragraph и т.д.
Основные объекты в иерархии объектов Word, необходимые при разработке приложения показаны на Рис.3. И, в тоже время, рисунок лишь поверхностно отображает иерархию. Так, если объект Word.Document содержит объекты Table, Paragraph, Character, Range, Bookmark, InlineShape, Comment, CommandBar. (доступные через коллекции соответствующих Tables, Paragraphs, Characters, Range, Bookmarks, InlineShapes, Comments, CommandBars . ), то большинство свойств и методов объектов Word доступны приложению и через специфический объект Selection, представляющий некоторую выделенную область документа и, в зависимости от того, какая область документа выделена, меняются его методы и свойства. Объект Selection всегда существует и доступен (даже если ничего не выделено в документе, то он представляет курсор ввода — insertion point). В тоже время, большинство объектов доступно и объекту Range, в том числе и сам документ. Поэтому, когда необходимо «достучаться» до объекта не прямыми путями, то более подойдет для ориентации Рис.2., иначе достаточно и иерархии Рис.3.
Рис.3. Иерархия основных объектов Word, необходимых при разработке приложения
Работа с документами, параграфами, символами, закладками и т.д. осуществляется путем использования свойств и методов этих объектов. Объекты при создании решений принято определять глобально для того, чтобы обеспечить доступ к ним из любой функции проекта. Определим глобально основной объект Word.Application.
Следующий код теперь позволяет выполнять запуск Word и его закрытие при нажатии соответственно кнопок 1 и 2.
Прежде чем рассматривать приведенный выше код, заметим, что как и при использовании метода Activate в Excel (см.Глава 2., Параграф 10. этого раздела), в данном коде возникает предупреждение компилятора при использовании метода (только в VisualStudio 2005/2008) Quit. Оно выглядит следующим образом:
Эта двусмысленность в использовании одноименных свойства и метода объявленных в интерфейсе _Application(namespace Microsoft.Office.Interop.Word) и интерфейсе ApplicationEvents4_Event (namespace Microsoft.Office.Interop.Word). Оба эти интерфейса наследует класс объекта:
И, хотя использование метода Quit не приводит к двусмысленности в выполнении кода, для тех, кто привык писать «чистый код», этот «глюк» лучше устранить. Устранение подобных неудобств можно выполнить через обработчики событий (как это делается мы видели в Главе 2. Параграф 9. этого раздела) или, как рекомендует компилятор, через использование приведения к группе (Using method group). Следующий код не будет давать предупреждения компиляции:
Поскольку этот способ проще, описанного в Главе 2, то будем далее использовать именно его.
Рассмотрим приведенный код создания сервера Word.
Создание объекта Word.Application аналогично тому, как мы это делали в Excel, закрытие же сервера требует использования ряда параметров с ключевым словом ref, которые, как и для многих других методов в Word, должны быть записаны в переменную до передачи в метод. В каждом случае требуется создавать переменную типа Object, присваивать ей значение и использовать эту переменную с ключевым словом ref.
wdDoNotSaveChanges — не сохранять,
wdPromptToSaveChanges — выдать запрос перед сохранением,
В нашем примере мы еще не создали документ и, поэтому, любая константа пока не оказывает влияние на выход. Однако, если в уже в запущенном Word создать документ (обычным для Word способом) и что-то в него записать, то при закрытии Word будет выдан запрос — сохранять или нет документ.
wdOriginalDocumentFormat — в оригинальном формате документа (не изменяя его),
wdPromptUser — по выбору пользователя (актуально при открытии документа, а не при его создании, при создании и сохранении окно сохранения документа всегда присутствует при отсутствии заданного имени документа).
RouteDocument — необязательный параметр. При true документ направляется следующему получателю, если документ является attached документом.
Параграф 2. Создание документов
Как отмечалось, основным в иерархии объектов Word.Application является объект Document. Информация об объектах Document хранится в виде ссылок на открытые документы в свойстве Documents. Книга в приложение может быть добавлена только через добавление ссылки в совокупность Workbooks, а ссылка на открытый документ может быть получена различным образом (по имени, номеру, как ссылка на активный документ).
Рассмотрим создание двух рабочих документов. Для доступа к документам определим глобально объекты Word.Documents и Word.Document.
Изменим код в case 1:
О параметрах, передаваемых в методы Word.Application мы уже говорили, остановимся на методе Add.
Template — имя шаблона, по которому создается новый документ. Если значение не указано, то используется шаблон Normal.dot.
NewTemplate — при true новый документ открывается как шаблон. Значение по умолчанию — False.
wdNewBlankDocument — документ Word (по умолчанию);
wdNewEmailMessage — электронное сообщение;
В качестве параметра Template методу Add можно определить имя существующего документа или полное имя шаблона. Во втором случае мы привязываемся к пути, по которому инсталлированы приложения MicrosoftOffice, и в этом нет необходимости — тот же эффект достигается если используется параметр по умолчанию (напомним, Type — класс декларации типов, Type.Missing — отсутствие значения или значение по умолчанию. Кроме того, некоторые методы принимают необязательные параметры, которые не поддерживаются в C#, и в этом случае также используется Type.Missing, который является ссылочным типом — reference type). Использование в качестве Template имя существующего файла полезно когда потребуется добавлять какие либо данные в уже существующий документ или бланк.
В приведенном примере мы использовали для получения доступа к документу возвращаемое значение метода Add. Доступ можно получить и через его номер в массиве Documents:
В данном примере, при работе в Visual Studio 2003 активным становился (как не странно) первый, а не второй из созданных документов (нумерация от 1 в обратном порядке создания). Видно поэтому разработчики ввели метод get_Item(ref number). Но, все же, более удобнее использовать доступ к документу по имени, как показано в следующем примере:
Параграф 3. Сохранение документов
Документы Word можно сохранить программно и обычным для Word способом. В любом случае перед выходом из Word необходимо вызвать метод Quit. Если свойство Word.Application DisplayAlerts имеет значение true, Word предложит сохранить данные в том случае, когда после старта в документ были внесены какие либо изменения.
Для сохранения документа можно использовать методы Save(), SaveAs() и SaveAs2000. Метод Save() не имеет параметров и при его вызове будет отображено диалоговое окно Word «Сохранение документа». Напротив, метод SaveAs имеет множество параметров, большинство из которых можно не указывать, а использовать как параметры по умолчанию.
Рассмотрим параметры методов SaveAs() и SaveAs2000() (они имеют одинаковые параметры, однако при использовании на компьютере Office 2000 или OfficeXP рекомендуется использовать метод SaveAs2000, пример использования см. ниже):
Добавим к предыдущему примеру функцию сохранения и сохраним открытый документ a1.doc с именем a2.doc (на данном этапе можно поэксперементировать и с сохранением документа как текстового файла для формата wdFormatText и уяснить на примерах действие последних из рассмотренных параметров, которые в принципе повторяют установки окна преобразования файла см. Рис.4).
Рис.4. Окно преобразования файла.
Того же эффекта можно было добиться задав значения всех параметров (кроме имени файла) как Type.Missing.
Параграф 4. Открытие существующего документа
Для открытия существующего документа основными методом является метод Open (Open2000, Open2002).
Рассмотрим параметры метода Open. Методы Open2000, Open2002 имеют те же параметры, что и метод Open, но параметров у данных методов меньше. Этими методами рекомендуется пользоваться только при разработке приложений под конкретную версию Office (например, при использовании на компьютере Office 2000 или OfficeXP рекомендуется использовать метод Open2000).
В методе все параметры кроме имени файла могут иметь значение Type.Missing, то есть иметь значения по умолчанию, соответствующее открытию простого документа Word.
Пример открытия существующего документа:
Параграф 5. Вывод текстовой информации — объект и свойство Range
Создадим или откроем документ Word, например как в предыдущем примере. Объект worddocument на данном этапе соответствует объекту открытого документа и необходим нам для того, чтобы еще ниже опуститься по иерархии объектов Word. Следующим в иерархии объектов рассмотрим Word.Paragraph — параграф. Текстовая информация помещается и хранится в параграфах документа Word (объект Paragraph это один абзац текста).
Для манипуляции с параграфами определим глобально соответствующие объекты:
Любой документ Word всегда имеет хотя бы один параграф, даже если он пуст, то всегда присутствует курсор ввода. Убедиться в этом можно с помощью следующего фрагмента кода (как всегда для простоты мы выводим текстовую информацию в заголовок формы), который добавим к коду предыдущего примера:
Вывод текста выполняется не просто в параграф, а в диапазон параграфа — объект Range. Объект Range — это непрерывная область документа, включающая позицию начального и конечного символов. Для пустого параграфа или если начальная и конечная позиции диапазона совпадают — Range представляет курсор ввода.
В документе можно определить диапазон, вызовом метода Range с передачей ему начального и конечного значений позиций символов (при определении позиции номера символов считаются от 0 и включают все символы, в том числе и не печатные). Выделенный диапазон можно «подсветить», используя метод Select().
Есть возможность выделить весь текст всего документа следующим образом:
Объект Range позволяет выполнить вывод текста в выделенный участок и изменить параметры текста (шрифт, цвет, подчеркивание. ).
Помимо этого имеется возможность программно создать свой стиль и применить его либо ко всему документу, либо к объекту Range. Все возможности форматирования текста можно посмотреть в меню Word «Формат» пункт «Стили и форматирование». Изменять можно шрифт документа, параметры абзаца, параметры табуляции, границы, язык, рамки, нумерацию, сочетание клавиш. Они все доступны и программно за исключением возможности сохранять в шаблоне Word свой созданный стиль.
Принцип работы со стилями демонстрирует следующий фрагмент кода:
Шрифт и другие параметры форматирования текста можно задать как параметры по умолчанию для всего документа аналогично заданию стиля:
Свойства Content объекта Document определяет основную область документа (document’s main story), т. е. его содержимое без колонтитулов и других вспомогательных элементов.
Кроме объекта Range документа каждый параграф также имеет свойство Range, которое позволяет выводить текст и устанавливать его характеристики для параграфа.
Следующий пример демонстрирует возможности вывода текста используя свойство Range параграфа и объект Range документа (для демонстрации лучше код примера добавлять начиная с вывода 1го абзаца и т.д.):
Рис.5. Вывод текстовой информации.
Обратим внимание, что при использовании объекта Range вставляемый в выделенную область текст заменяет выделенный фрагмент и переносится в него полностью — не зависимо от величины диапазона.
Параграф 6. Вывод текстовой информации — объект Selection
Объект Selection представляет собой текущую выделенную область документа. Все действия, связанные с внесением в документ каких либо частных изменений в Word выполняются применительно к выделенным фрагментам. Программно это выполняется через объект Selection. Аналогично, как и при обычной работе с Word, при программном выводе необходимо вначале выделить фрагмент и, далее, выполнить требуемые действия над данным фрагментом (изменить шрифт, форматирование, напечатать и т.д.).
Объект Selection всегда присутствует в документе. Если ничего не выделено, он представляет курсор ввода (insertion point).
Для демонстрации использования объекта Selection сохраним в файле C:\a1.doc результаты выполнения кода предыдущего примера (Рис.4.).
Объект Selection может представлять блок, строку или столбец таблицы, курсор ввода, рисунок, фрейм, выделенный текст, или некоторую комбинацию объектов, которые можно определить через свойство Type объекта Selection (wdSelectionBlock, wdSelectionRow, wdSelectionColumn, wdSelectionIP, wdSelectionShape, wdSelectionInlineShape, wdSelectionNormal). Посколько свойства и методы у различных типов объектов Selection различны, то при применении того или иного метода или использования свойства рекомендуется выполнить проверку типа объекта Selection, например:
MoveLeft([Unit], [Count], [Extend]);
MoveRight([Unit], [Count], [Extend]);
MoveUp([Unit], [Count], [Extend]);
Рассмотрим эти методы.
6.1. Методы HomeKey и EndKey
WdLine — переход в начало (конец) строки. Значение по умолчанию.
WdStory — переход в начало (конец) документа.
WdColumn — переход в начало (конец) столбца таблицы.
WdMove — только переместить курсор. При использовании с wdLine перемещает курсор ввода в начало или конец строки, а при использовании с wdStory — в начало или конец документа.
Пример использования методов HomeKey и EndKey для выделения всего текста.
6.2. Методы MoveLeft и MoveRight
Методы позволяют перемещать курсор на указанное число и выделять в тексте указанное число символов, слов, ячеек или предложений.
wdCharacter — инкремент в единицах символов.
wdWord — инкремент в единицах слов.
wdCell — инкремент в единицах ячеек таблицы.
WdMove — только переместить курсор. При использовании с wdLine перемещает курсор ввода в начало или конец строки, а при использовании с wdStory — в начало или конец документа.
Пример использования методов MoveLeft и MoveRight (фрагмент кода вставлен в предыдущей пример после комментария «Непосредственно работа с объектом Selection»). Результат выполнения кода показан на Рис.6.
Рис.6. Пример использования методов MoveLeft и MoveRight
6.3. Методы MoveUp и MoveDown
Методы позволяют перемещать курсор на указанное число и выделять в тексте указанное число строк или абзацев.
wdLine — инкремент в единицах строк.
wdParagraph — инкремент в абзацах.
wdWindow — инкремент в единицах окна.
Остальные параметры аналогичны методам MoveLeft и MoveRight.
Пример выделения всего текста
6.4. Метод Move
Метод Move сворачивает выделение к последнему выделенному символу и перемещает курсор ввода на заданное параметром Count число символов, слов, строк, абзацев.
wdCharacter — инкремент в единицах символов.
wdWord — инкремент в единицах слов.
wdLine — инкремент в единицах строк.
6.5. Вывод текста
Для вывода текста с использованием объекта Selection используется его свойство Text или метод объекта TypeText. Метод, в зависимости от того, установлен в Word режим «вставка» или «замена» вставляет или пишет новый текст поверх имеющегося.
Примеры вывода текста приводятся ниже. В данных примерах сначала создаются и заполняются одинаковым шрифтом четыре абзаца, а затем в режиме вставки вставляется текст с использованием метода TypeText и свойства Text. Пример наглядно демонстрирует (Рис.7.) одинаковый характер действия при применении метода и свойства когда объект Selection представляет выделенный участок текста (строки 1 и 2) и различие при применении если он является курсором ввода (строки 3 и 4).
Рис.7. Пример вывода текста
Параграф 7. Работа с таблицами
7.1. Создание таблиц
Информация об объектах Table хранится в виде ссылок на таблицы документа в свойстве Tables. Набор ссылок Tables доступен из объектов Document, Selection и Range и даже из объекта Word.Table. Это значит, что и создавать таблицы можно с использованием любого из этих объектов (в любом — применительно к таблицам). Для создания таблиц используется метод Add:
Пример создания таблицы:
После того как таблица добавлена в документ, объект, соответствующий добавленной таблице можно получить по ее номеру в порядке добавления:
После того как мы добавили таблицу и получили объект Word.Table мы можем изменить стиль таблицы, например:
Пример создания двух таблиц (Рис.8.). В примере не только показано как можно создать таблицу, но и как разместить ее в пределах документа Word. Пример, как и ранее, выполняется при открытии чистого документа C:\a.doc, и, поэтому, здесь опускается часть кода, приводимая выше по открытию документа:
Рис.8. Пример создания таблиц
Обратим внимание на то, что курсор остался в первой ячейки последней добавленной таблицы. Это может понадобиться для начала вывода информации.
7.2. Вывод информации в ячейки таблиц
Таблица или Объект Word.Tableт состоит из ячеек — объектов Cell, ссылки на которые хранятся в наборе Cells данной таблицы. Ячейки таблицы считаются упорядоченными по координатам X и Y, нумерация с 1. Следующие строки кода ссылаются на ячейку, расположенную в первой строке и во втором столбце:
Для вывода текста в ячейку достаточно свойству Text объекта Word.Range присвоить значение типа string.
Добавим к приведенному выше коду примера следующие строки и продемонстрируем вывод текста в ячейки (Рис.9.):
Рис.9. Вывод текста в ячейки таблиц
В таблице и отдельно в ячейках можно изменять как параметры шрифта, так и любые другие параметры, которые можно изменить непосредственно из Word. Эти изменения доступны из свойства Range, которое имеется как у всей таблицы, так и у каждой ее ячейки. Например, вставив в предыдущем примере перед последней строчкой кода следующую строку, мы, тем самым, выполним вывод информации шрифтом, размер которого возрастает при возрастании номера строки:
Задать цвет шрифта можно следующими способами:
Аналогично можно задать и обводку ячеек:
Не составляет труда и выполнить заливку:
И последнее, что может понадобиться — выравнивание, которое выполняется аналогично выравниванию текста для параграфа текста:
7.3. Объединение ячеек таблиц
Параграф добавлен по просьбе назвавшего себя pasportbez1:
В примере использованы Reference:
Текст программы практически полностью без того, что создает студия:
Рис.10. Объединение ячеек
Параграф 8. Защита документа
Материал прислан Андреем Ковалевым и отредактирован автором.
Для защиты документа необходимо установить пароль. В качестве пароля используется любая переменная тира Object.
Соответственно функция в Microsoft.Office.Interop.Word.dll, определена как:
wdNoProtection — не применять защиту к документу;
wdAllowOnlyRevisions — только запись исправлений;
wdAllowOnlyComments — позволяет только добавлять коментарии к документу;
wdAllowOnlyFormFields — позволяет добавлять содержание через поля формы;
noReset — значение false, чтобы сбросить поля формы к значениям по умолчанию. Значение true, чтобы сохранить текущие значения полей формы, если документ защищен. Если Type не является wdAllowOnlyFormFields, аргумент NoReset игнорируется.
useIRM — определяет, следует ли использовать механизм управления правами на доступ к данным (IRM) для защиты документа от изменений.
Приведенный выше код, соответственно, разрешает только чтение документа и позволяет сохранить текущие значения полей формы.
Для снятия защиты используем:
Параграф 9. Диалоговые окна
Материал прислан Павлом Матрениным и отредактирован автором.
Многие действия в Word сопровождаются вызовом диалоговых окон (создание, открытие, сохранение и печать документа, изменение параметров шрифта, текста и т.д.).
Допустим, необходимо предоставить пользователю возможность отправить документ на печать прямо из вашего приложения. Можно использовать функцю Document.PrintOut, но у нее «устрашающее» количество параметров, как и у большинства функций связи C# с COM объектами. Мы это видели в функциях, использованных выше, см., например, функцию SaveAs и другие.
Решением этой проблемы может быть использование стандартных диалоговых окон Word, привычных пользователю и позволяющих сократить разработчику программный код.
Перейдем к примеру вызова диалогового окна печати документа:
Dialogs — это коллекция диалоговых окон Word (всего 296 элементов класса Microsoft.Office.Interop.Word.Dialog). Для того, что бы посмотреть все доступные диалоговые окна, кликните правой кнопкой мышки на WdWordDialog и выберите пункт меню «Go To Defination».
Элементы доступны по своим именам и имеют понятные названия, например:
Определение функции Dialog.Show в Microsoft.Office.Interop.Word.dll:
Параметр: «TimeOut» — время в миллисекундах, через которое окно будет закрыто автоматически. Практика показывет, что некоторые виды окон игнорируют это время и сами не закрываются. Это относится к сохранению, открытию, созданию нового документа. Окна для указания параметров «печати, параметров страницы, шрифта. » закрываются по истечении указанного времени.
Значение, которое возвращает функция Show, зависит от вида окна и от того, как оно было закрыто. Если вышло время, или пользователь нажал «OK», или «Закрыть», то «-1», если «Отмена», то «0», если «По умолчанию», то «1». А вот если окно закрыто крестиком, то может быть «-1» или «-2», зависит от самого окна. Других значений в своих опытах я не встречал (П.Матренин).
Обратите внимане на то, что попытка вызвать диалоговое окно, которое не может быть вызвано (например, сохранение, если пока не создано ни одного документа) приводит к возникновению исключительной ситуации.
Заметим также, что при Object time = 0 диалоговое окно появляется на не установленное время, иначе, программа будет ожидать действий пользователя с диалоговым окном.
Параграф 10. Основы анализа документов Microsoft Word
Материал прислан Сергеем Бабичевым , переведен на C# и отредактирован автором.
Очень часто людям приходится заниматься так называемой “обезьяньей работой”, нудным и совершенно нетворческим делом. Например, форматировать документы по некоторому образцу или проверять ссылки в разных документах друг на друга, искать соответствие товаров в разных накладных и ведомостях…. При этом очень легко ошибиться, что-то пропустить и на все это уходит много времени и сил.
Что бы автоматизировать труд и избавиться от лишней работы необходимо знать основы анализа и преобразования формы документов. В данном параграфе приводятся лишь некоторые возможности, предоставляемые OLE сервером Word, которые могут быть использованы при анализе и преобразования документов.
За основу примеров взяты наработки автора сайта из этого раздела. Имена переменных соответствуют именам, используемым в данной главе.
После написания кода запуска OLE сервера Word и открытия документа можно приступить к его анализу.
Если нужно просмотреть весь документ по параграфам, то можно сделать это таким циклом:
В функции vTestText мы можем просто выполнить анализ текста, например, на поиск некоторых слов (наименований товара. ).
Если мы поместим абзац в переменную wordrange, объявленную как
, то мы можем работать не только с текстом параграфа документа, но и определить, что представляет собой параграф и видоизменять его.
Функция vTestRange определим как:
Выделить нужный абзац для дальнейших его преобразований или анализа:
Выбрать текст абзаца в некоторую строковую переменную:
Определить номер страницы, на которой расположен абзац:
Другая константа — wdActiveEndAdjustedPageNumber, позволяет определить номер листа документа, когда нумерация в нем начинается не с первого номера, то есть используется своя нумерацию страниц документа.
Если абзац в таблице, то можно узнать номер строки и столбца таблицы:
Также этот код позволяет определить число строк и столбцов в таблице (методом перебора по максимальному числу номера строки и столбца).
узнать жирный (наклонный, подчеркнутый. ) или нет шрифт:
«-1» — жирный, «0» — нет;
Использовать объект Case, который предназначен для преобразования текста параграфов, но может быть использован и для анализа. Объект имеет множество свойств (даже для изменения текстов написанного иероглифами). Для нас могут быть интересны значения: wdLowerCase; wdUpperCase; wdNextCase; wdToggleCase; wdTitleWord; wdTitleSentence. Эти свойства позволяют соответственно перевести текст параграфа в нижний, верхний регистры, менять регистр, преобразовать первую букву каждого слова или предложения в верхний регистр.
для преобразования может быть использован код: F:\My_Site\C_Sharp\comword.html
Использовать объект PageSetup для анализа и форматирования документа:
Другие интересные свойства PageSetup, которые могут быть использованы для анализа и форматирования VerticalAlignment, RightMargin, TopMargin, LinesPage(строк на странице).
Использовать объект Fields для анализа документа и поиска в документе:
Определение принадлежности к оглавлению:
Первый и последний абзац его содержат в свойстве Fields ссылки на все остальные абзацы оглавления.
В первом поле содержится текст «TOC»
Встречаются документы, в которых абзац, не принадлежащий оглавлению содержит в себе поле с текстом «_Toc» (загадка). Поэтому проверять на вхождение абзаца в оглавление лучше по слову «PAGEREF». Кроме того, пустой абзац после оглавления, содержащий всего лишь символ перехода на новую строку (\r), также может содержать «PAGEREF». И, для того чтобы отличить текст оглавления от его завершения, может понадобиться проверка текста абзаца на наличие только «\r».
Определение наличия гиперссылки в абзаце.
Здесь надо обратить внимание на то, что пункты оглавления, как и абзацы, просто содержащие гиперссылку, имеют поле «HYPERLINK». Поэтому, если мы хотим найти гиперссылку в тексте, то нам придется проверить параграф на то, что он не относится к оглавлению.
Определение принадлежности абзаца к списку.
Для определения принадлежности абзаца к списку можно использовать свойство ListString. Правда это потребует знания кодов типов маркеров и нумерации списка, так как не все признаки линии в списках (некоторые черточки, например) приводятся к читаемым строковым символам, и кроме того, пользователь может назначить свои маркеры и нумерацию. Поэтому этот способ не всегда оправдан, разве что, для поиска в документе конкретных типов маркеров или конкретной нумерации.
wdListBullet = 0xF0B7,0xF0A7, 0x6А, 0xF076, F0D8,F0FC. ;
wdListSimpleNumbering = 0x2E, 0x29.
В заключение параграфа надо сказать, что формат документа Word не очень непростой и содержит огромное количество полей и свойств. Назначение многих из них непонятны, я думаю, даже программистам Microsoft (Сергеей Бабичев).
Параграф 11. Некоторые другие возможности по работе с Word
Из приложения, управляя сервером Word, можно делать практически все, что и из автономно запущенного Word. Однако, во всех этих, потрясающих объемом доступных действий и возможностей, обычно нет необходимости. Здесь приведены лишь несколько возможностей, которые помимо приведенного выше материала, когда либо потребовались автору в его практической работе.
Сменить ориентацию листа и установить поля.
Аналогично установить все другие параметры, доступные из меню Файл / Параметры страницы, а именно: Orientation, TopMargin, BottomMargin, LeftMargin, RightMargin, Gutter, HeaderDistance, FooterDistance, PageWidth, PageHeight, FirstPageTray, OtherPagesTray, SectionStart, OddAndEvenPagesHeaderFooter, DifferentFirstPageHeaderFooter, VerticalAlignment, SuppressEndnotes, MirrorMargins, TwoPagesOnOne, BookFoldPrinting, BookFoldRevPrinting, BookFoldPrintingSheets, GutterPos.
Скопировать любой документ или его выделенный диапазон (объект Ramge) в буфер обмена (например диаграмму Excel) или вставить в документ что-то из буфера обмена:
Изменить масштаб документа:
Изменить размер отображения окна:
Отменить любое количество выполненных из приложения действий:
Управлять параметрами окна Word, и прочитать установочные данные:
Поиск в документе:
Документ открыт и есть объект документа:
Вставка картинок в приложение (решение прислала Оксанчик):
Вставка разрава — на другой лист
Заключение. О богатстве возможностей
Еще раз повторим, сказанное в Главе 2, богатства возможностей по управлению запущенным приложением потрясающе. Практически, все, что можно сделать в автономно запущенном приложении доступно и из приложения на C#. Каждый может достаточно легко выполнить то или иное действия из приложения, если создаст макрос для этого действия, а, затем, «переведет» код VBA в коды C#.
И, в тоже время, по мнению автора, для начального этапа практической работы по формированию документов из приложения, изложенного материала вполне достаточно. Но, несмотря на это, раздел будет пополняться по мере возникновения какой либо проблемы в практической работе и ее разрешении. Если у Вас есть интересные находки — присылайте — с указанием авторства они будут включены в данный материал.