Взаимодействие Microsoft Excel с приложениями .NET - позднее связываниеИсточник: rsdn Гасанов Ровшан Закариевич
Вступление.Многим разработчикам рано или поздно приходится сталкиваться с задачами, которые подразумевают использование Microsoft Excel (далее по тексту просто Excel). В этих случаях можно применить утилиту tlbimp.exe, импортировать библиотеку типов, добавить ссылку на нее, и вам станет доступно пространство имен Excel, со всеми классами-обертками, вокруг "внутренностей" Excel. Или еще проще, можно просто добавить ссылку на COM-объекты Excel в Visual Studio, и она сделает все сама. Все это хорошо, конечно. И просто. Но иногда возникают ситуации, когда описанное вкратце "раннее связывание" неприемлемо. Например, ваше приложение будет выложено на сайт, следовательно, нужно уменьшить его размер. Раннее связывание порождает две сопутствующие сборки - Microsoft.Office.Interop.Excel.dll и Microsoft.Vbe.Interop.dll, которые содержат классы-обертки. Суммарный размер этих сборок превышает мегабайт, и, несмотря на то, что сборки .NET хорошо сжимаются архиваторами, "тащить" за собой все классы-обертки Excel ради использования двух-трех методов явно нецелесообразно. И тогда на помощь приходит т.н. "позднее связывание", когда не нужно никаких сопутствующих сборок. Описывать позднее связывание в этой статье нет смысла, в литературе, как и в Интернете, достаточно материала по этой теме. Все примеры выполнены на C#, но, надеюсь, программисты, использующие в своей работе другие .NET-языки, смогут разобраться в коде без особого труда. Запуск и завершение работы Excel.Запуск Excel и его корректное завершение - это самая первая задача, которую нужно решить программисту, если он собрался использовать Excel в своем приложении. Возможно, Excel уже запущен, и операция запуска уже не нужна, достаточно получить ссылку на Excel и начать работу с ним. В получении ссылки на уже работающую копию Excel кроется один неприятный момент, связанный с ошибкой в самом приложении Excel (которая, вроде бы, исправлена в MSOffice 2003)[2]. Эта ситуация подробно описана в конце этого раздела. А теперь по порядку. В первую очередь нужно подключить к приложению два пространства имен:
В этих пространствах имен описаны типы, необходимые для организации позднего связывания. Один из них - класс Marshal, который предоставляет богатые возможности для организации взаимодействия между managed и unmanaged-объектами. Для получения ссылки на процесс Excel нужно знать GUID Excel. Однако можно поступить намного проще - использовать программный идентификатор Excel: "Excel.Application". Для получения ссылки на работающий Excel можно воспользоваться статическим методом GetActiveObject() класса Marshal:
Если Excel уже запущен (COM-объект Excel присутствует), то вызов данного метода вернет ссылку на объект-отображение Excel в .NET, который можно использовать для дальнейшей работы. Если Excel не запущен, то возникнет исключение. Для запуска Excel необходимо воспользоваться классом Activator, описанным в пространстве имен System.
После получения ссылки или же запуска становится доступна вся объектная модель Excel. С точки зрения программиста она выглядит так:
Для работы вместе с объектом Excel необходимо получить ссылку на его коллекцию книг. С ее помощью можно получить доступ к любой книге. У каждой книги есть коллекция страниц, ссылку на которую также нужно получить для доступа к конкретной странице. Хочу сразу заметить, что доступ к книгам и к страницам можно получить как по их имени, так и по их порядковому номеру.
Чтобы корректно завершить работу с приложением Excel, для всех объектов, которые полученных с помощью позднего связывания, необходимо применить метод ReleaseComObject класса Marshal:
Если вызов GC.Collect() не помогает, попробуйте очистку памяти этим способом. Если проигнорировать эту операцию, то в памяти останутся объекты Excel, которые будут существовать даже после того, как вы завершите свое приложение. При этом, даже если пользователь закрыл Excel, в списке запущенных процессов Диспетчера Задач Windows остается процесс Excel. В этом случае, если запустить .NET-приложение и попытаться получить ссылку на работающий Excel, это удастся без проблем. Но, скорее всего, в этом случае, при установке свойства Visible в true, главное окно Excel будет прорисовано не полностью. На экране будут присутствовать только панели инструментов и окантовка главного окна. Эта ситуация нередко наблюдалась с Excel 2000 и Excel XP. В Excel 2003 подобных явлений не замечено. Но, тем не менее, если ваша программа получает ссылки на какие-либо объекты Excel, вы обязательно должны вызвать для них ReleaseComObject() класса Marshal. А перед завершением работы с Excel обязательно произведите очистку памяти:
Управление книгами и страницами.Позднее связывание подразумевает, что тип объекта, с которым планируется работать, неизвестен, а это значит, что нельзя непосредственно обращаться к его методам и полям, используя оператор ".". Поэтому для вызова метода необходимо знать его название и список формальных параметров, которые он принимает. Для вызова метода в классе Type предусмотрен метод InvokeMember(). Поэтому достаточно получить ссылку на экземпляр класса Type, описывающий тип объекта, с которым устанавливается позднее связывание, и вызвать метод InvokeMember(). Я не буду останавливаться подробно на этом методе, он достаточно хорошо описан в технической документации. Отмечу только самое необходимое. Метод InvokeMember() перегружен, и имеет три модификации.
Здесь будет использоваться только первая модификация метода. В качестве первого параметра метод получает строковое название метода (поля, свойства) того объекта, с которым устанавливается связь. При этом в названии не должно быть пробелов или лишних символов, кроме того, этот параметр чувствителен к регистру. Второй параметр принимает на вход флаги, характеризующие связывание. В данном случае понадобятся только следующие флаги:
Третий параметр - binder - устанавливается в null. Через четвертый параметр - target - передается ссылка на объект, к методу которого нужно обратиться. Пятый параметр - args - это массив с параметрами, который принимает на вход вызываемый поздним связыванием метод или массив, который содержит один элемент - устанавливаемое значение свойства. Метод InvokeMember() возвращает результат выполнения метода или значение свойства. Для управления книгами и страницами в первую очередь нужно получить ссылку на их коллекции. Для получения ссылки на коллекцию книг необходимо выполнить следующий код (считается, что ссылка на excel успешно получена):
Объект workbooks и есть managed-ссылка на коллекцию книг. Для получения доступа к конкретной книге выполняется следующий код, использующий коллекцию книг:
Если книги с указанным названием не существует, генерируется исключение. Чтобы открыть, закрыть или создать книгу, воспользуемся соответствующими методами коллекции книг workbooks, ссылка на которую уже успешно получена. Для создания новой книги у коллекции workbooks есть несколько модификаций метода Add. Если вызвать этот метод без параметров, будет создана новая книга с именем по умолчанию, содержащая количество страниц, также принятое по умолчанию.
Для создания книги на основе шаблона достаточно передать полное имя файла, содержащего этот шаблон:
Для открытия файла с книгой используется метод Open объекта workbooks:
Закрытие книги возможно с помощью метода Close объекта workbook. При этом он принимает несколько необязательных параметров. Рассмотрим два варианта (обратите внимание, что мы вызываем метод Close книги, а не коллекции книг, и целевым объектом у нас выступает workbook, а не workbooks):
Нужно отметить, что сохранение произойдет только в том случае, если в рабочей книге произведены какие-либо изменения. Если создать рабочую книгу и попробовать ее сразу же закрыть, сохранив под другим именем - ничего не выйдет. Excel просто закроет книгу и все. Чтобы просто сохранить изменения в книге, достаточно вызвать для нее метод Save или SaveAs, передав последнему в качестве параметра имя файла, под которым нужно сохранить книгу.
Для работы со страницами необходимо получить доступ к их коллекции. Естественно, к этому моменту необходимо иметь ссылку на рабочую книгу. Для получения ссылки на коллекцию страниц нужно вызвать свойство Worksheets рабочей книги:
Объект oWorksheets - это managed-ссылка на коллекцию страниц текущей книги. Зная ссылку на эту коллекцию, можно получить доступ к конкретной странице по ее имени или порядковому номеру (аналогично коллекции рабочих книг):
Работа со страницами. Объект Range. Использование записи макросов для автоматизации Excel.Страница имеет ссылку на объект Range, который по сути представляет собой диапазон ячеек. Через объект Range можно получить доступ к любой ячейке и ее свойствам. Но объект Range содержит массу методов, и для позднего связывания нужно знать не только формат передаваемых им формальных параметров, но и точное название метода (или свойства, которое по сути дела является комбинацией методов). Иными словами, чтобы успешно вызвать метод через позднее связывание, нужно знать его сигнатуру. До сих пор мы использовали простые методы типа Open, Close, Save, с которыми, в принципе, все понятно. У них немного параметров, и список этих параметров интуитивно ясен. Чтобы узнать, какие методы поддерживает объект Range, можно воспользоваться утилитой OleView, предназначенной специально для этого. Она поставляется с Visual Studio и лежит в [путь к Visual Studio]\Common7\Tools. Если вы не установили эту утилиту при инсталляции VS, ее можно найти в Интернет. Кроме того, можно, импортировав с помощью утилиты tlbimp.exe библиотеку типов Excel, открыть и изучить ее с помощью дизассемблера/декомпилятора (например, ildasm.exe, Reflector или Anakrino). Дизассемблер покажет объект Range и все его методы. Но есть более простой способ, который позволит существенно сэкономить время. Это сам Excel, а точнее его запись макросов. Допустим, что нужно отформатировать ячейки определенным образом, например так, как показано на рисунке 2:
Для этого нужно открыть Excel, включить запись макросов и отформатировать указанные ячейки. Полученный макрос будет выглядеть следующим образом:
Как видно, здесь очень часто используется вызов метода Select у объекта Range. Но ведь с ячейками можно работать напрямую, минуя их выделение. Метод Select просто переопределяет ссылки, которые будут возвращаться объектом Selection. Объект Selection - это тот же самый Range. Таким образом, задача существенно упрощается. Нужно просто получить ссылки на соответствующие объекты Range, получить доступ к их внутренним объектам и произвести вызов соответствующих методов или свойств, используя метод InvokeMember(). Например, следующий участок кода окрашивает цвет фона ячейки B3 в оранжевый (причем заливка ячейки - сплошная), а цвет текста ячейки C3 устанавливает в красный:
Попробуем реализовать этот участок в приложении. Допустим, что мы успешно получили ссылки на нужную книгу и страницу. Ссылка на страницу хранится в переменной worksheet.
Чтобы задать текст, можно использовать свойство Value объекта Range.
Далее разбирать код я не буду, советую читателям самим поэкспериментировать с установкой свойств Excel из приложений .NET, по аналогии с приведенными здесь примерами. А сейчас перейдем к событиям Excel и их перехвату с использованием позднего связывания. Перехват событий ExcelПерехват событий Excel позволяет отслеживать его состояние и контролировать некоторые действия. Например, можно отследить закрытие рабочей книги и корректно отключиться от Excel, произведя очистку памяти и прочие завершающие процедуры. Чтобы объяснить, как перехватывать события, проведу небольшой экскурс в события COM-объектов. В этом отступлении я предполагаю, что читатель немного знаком с архитектурой COM. Если объект хочет получать события другого COM-объекта, то он должен уведомить об этом источник событий, зарегистрировав себя в списке объектов-получателей уведомлений о событиях. Для этого СОМ предоставляет интерфейс IConnectionPointContainer, содержащий метод FindConnectionPoint. С помощью вызова метода FindConnectionPoint объект-получатель события получает "точку подключения", интерфейс IConnectionPoint, и регистрирует c помощью метода Advise свою реализацию интерфейса IDispatch, методы которого будут вызываться при возникновении тех или иных событий. Excel определяет интерфейс, который должен реализовываться классом-приемником событий.
Таким образом, класс-приемник событий должен реализовывать этот интерфейс и регистрировать себя, используяIConnectionPointContainer и IConnectionPoint. Библиотека базовых классов .NET уже определяет managed-версии интерфейсов: дляIConnectionPointContainer это UCOMIConnectionPointContainer, а для IConnectionPoint - UCOMIConnectionPoint, которые определены в пространстве имен System.Runtime.InteropServices. Регистрация класса-приемника событий будет выглядеть так:
Для отключения от событий достаточно вызвать метод Unadvise(), и передать ему в качестве параметра идентификатор cookie, полученный при регистрации класса-приемника событий методом Advise:
Заключение.В статье на примере работы с MS Excel рассматривалось взаимодействие COM и NET с использованием позднего связывания. Используя аналогичный подход, можно организовать управление любым COM-сервером. В приложенном к данной статье файле находится класс, с помощью которого можно организовать обработку событий Excel в любом приложении .NET. Литература
|