Заключение
В статье описываются классы, которые могут быть полезны разработчику на платформе .NET. Некоторые из них, такие как AcedRipeMD, AcedDeflator, являются аналогами классов, добавленных в .NET 2.0. Однако, реализация этих алгоритмов в библиотеке AcedUtils.NET представляется все же более эффективной.
Взаимодействие Microsoft Excel с приложениями .NET. Позднее связывание.
Гасанов Ровшан Закариевич
ведущий .NET-разработчик компании PFSoft
Microsoft Certified Application Developer
Содержание:
Вступление
Запуск и завершение работы Excel.
Управление книгами и страницами.
Работа со страницами. Объект Range. Использование записи макросов для автоматизации Excel.
Перехват событий Excel.
Заключение.
Литература
Примеры классов.
1. Вступление.
Многим разработчикам рано или поздно приходится сталкиваться с задачами, которые подразумевают использование Microsoft Excel (далее по тексту просто Excel) в своей работе. Не будем перечислять подобные задачи, думаю, читатель сам уже с этим столкнулся. Многие вопросы покажутся Вам очень знакомыми, кое-кто скажет, а зачем такие сложности? Ведь можно применить утилиту tlbimp.exe, импортировать библиотеку типов, создать RCW сборку, добавить на нее ссылку и вам станет доступно пространство имен Excel, со всеми RCW классами, которые отображают в себя "внутренности" Excel. Или еще проще, просто добавить ссылку на COM-объекты Excel в Visual Studio, и она сделает все сама. Все это хорошо, конечно. И просто. Но иногда возникают условия, когда описанное вкратце "раннее связывание" неприемлемо. И тогда на помощь приходит т.н. "позднее связывание", когда типы становятся известными не на этапе компиляции, а на этапе выполнения.
Описывать позднее связывание в этой статье нет смысла, в литературе, как и в Интернете достаточно материала по этой теме. По поводу языка, то все примеры приведены с использованием C#, но, надеюсь программисты, использующие в своей работе другие .NET языки, смогут разобраться в коде без особого труда.
2. Запуск и завершение работы Excel.
Запуск Excel и его корректное завершение - это самая первая задача, которую нужно решить программисту, если он собрался использовать Excel в своем приложении. Возможно, Excel уже запущен, и операция запуска уже не нужна, достаточно получить на него ссылку, и начать с ним работу. В получении ссылки на уже работающую копию Excel кроется один неприятный момент, связанный с ошибкой в самом приложении Excel (которую вроде бы исправлена в MSOffice 2003)[2]. Эта ситуация подробно описана в конце этой главы.
А сейчас по порядку.
В первую очередь Вы должны подключить к своему приложению два пространства имен:
using System.Runtime.InteropServices;
using System.Reflection;
Типы, которые необходимы для организации позднего связывания, описаны в этих пространствах имен. Один из них: класс Marshal, который предоставляет богатые возможности для организации взаимодействия между кодом с автоматически управляемой памятью (managed code), и объектами "неуправляемым кодом" (unmanaged code).
Для получения ссылки на процесс Excel, нужно знать GUID Excel. Однако можно поступить намного проще, зная программный идентификатор Excel: "Excel.Application".
Для получения ссылки на работающий Excel, воспользуйтесь статическим методом GetActiveObject(), класса Marshal:
string sAppProgID = "Excel.Application";
object oExcel = Marshal.GetActiveObject(sAppProgID);
Если Excel уже запущен (COM-объект Excel присутствует), то вызов данного метода вернет ссылку на объект-отображение Excel в .NET, которые Вы сможете использовать для дальнейшей работы. Если Excel не запущен, то возникнет исключение.
Для запуска Excel необходимо воспользоваться классом Activator, описанным в пространстве имен System.
string sAppProgID = "Excel.Application";
// Получаем ссылку на интерфейс IDispatch
Type tExcelObj = Type.GetTypeFromProgID(sAppProgID);
// Запускаем Excel
object oExcel = Activator.CreateInstance(tExcelObj);
После того, как Вы получили ссылку на работающее приложение Excel, или же запустили его, Вам становится доступно вся объектная модель Excel. С точки зрения программиста она выглядит так:
Рис. 1. Объектная модель Excel
Нам для работы необходимо получить вместе с объектом Excel, ссылку на его коллекцию книг, и с ее помощью мы можем получить доступ к любой книге. У каждой книги есть коллекция страниц, ссылку на которую мы также должны получить для доступа к конкретной странице. Хочу сразу заметить, что доступ к книгам и к страницам мы можем получить как по их имени, так и по их порядковому номеру, причем, что самое важное: нумерация книг и страниц в коллекции начинается с единицы, а не с нуля (как принято нумеровать массивы в .NET). Отмечу, что хотя в Visual Basic for Excel есть директива Option Base, на порядок нумерации в коллекциях в наше случае он не влияет.
Для того, чтобы корректно завершить работу с приложением Excel, для всех объектов, которые мы получаем поздним связыванием, нам необходимо применить метод ReleaseComObject класса Marshal:
// Уничтожение объекта Excel.
Marshal.ReleaseComObject(oExcel);
// Вызываем сборщик мусора для немедленной очистки памяти
GC.GetTotalMemory(true);
Отмечу сразу, что если вызов GC.Collect() не помогает, то попробуйте очистку памяти этим способом. Если проигнорировать эту операцию, то в памяти останутся объекты Excel, которые будут существовать даже после того, как Вы завершите свое приложение и Excel. Если после этого запустить приложение NET и попытаться получить ссылку на работающий Excel, то мы без проблем ее получим. Но если мы заходим сделать Excel видимым (Установив ему свойство Visible в true), то при наличии MSExcel версии ранней, чем 2003, основное окно Excel прорисовывалось не полностью. На экране присутствовали только панели инструментов и окантовка основного окна. В MS Excel 2003 вроде бы такого не наблюдается.
Но, тем не менее, если Ваша программа получает ссылки на какие-либо объекты Excel, Вы обязательно должны вызвать для них ReleaseComObject() класса Marshal.
А перед завершением работы с Excel обязательно произведите очистку памяти:
GC.GetTotalMemory(true);
3. Управление книгами и страницами.
Позднее связывание подразумевает, что нам неизвестен тип объекта, с которым мы хотим работать, а это значит, что мы не можем применять непосредственно обращаться к его методам и полям, используя оператор ".". Поэтому для вызова метода, нам необходимо знать его название и список формальных параметров, которые он принимает. Для вызова метода в классе Type предусмотрен метод InvokeMember(). Поэтому нам достаточно получить ссылку на экземпляр класса Type, описывающий тип объекта, с которым мы устанавливаем позднее связывание, и вызвать метод InvokeMember()/ Я не буду останавливаться подробно на этом методе, он достаточно хорошо описан в технической документации. Отмечу только самое необходимое, с которым мы будем непосредственно работать.
Метод InvokeMember() перегружен, и имеет три модификации.
public object InvokeMember(string name, BindingFlags flags, Binder binder, object target, object[] args);
public object InvokeMember(string name, BindingFlags flags, Binder binder, object target, object[] args, CultureInfo info);
public abstract object InvokeMember(string name, BindingFlags flags, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo info, string[] namedParameters);
В нашей работе мы будем использовать только первую модификацию метода. В качестве первого параметра метод получает строковое название метода, поля, свойства того объекта, с которым мы устанавливаем связь. При этом в названии не должно быть пробелов или лишних символов, кроме того, этот параметр чувствителен к регистру.
Второй параметр принимает на вход флаги, характеризующие связывание. Нам понадобятся только следующие флаги:
BindingFlags.InvokeMethod - Найти метод, определить его точку входа, и выполнить передав ему массив фактических параметров.
BindingFlags.GetProperty - Установить свойство
BindingFlags.SetProperty - Получить значение свойства.
Третий параметр - binder - мы устанавливаем в null - он нам не нужен.
Через четвертый параметр - target - мы передаем ссылку на объект, к методу которого мы хотим обратиться.
Пятый параметр - args - это массив с параметрами, который принимает на вход вызываемый поздним связыванием метод, или массив, который содержит один элемент - значение свойство, которое мы устанавливаем.
Метод InvokeMember() возвращает результат выполнения метода или значение свойства.
Для управления книгами и страницами в первую очередь нужно получить ссылку на их коллекции. Для получения ссылки на коллекцию книг необходимо выполнить следующий код (считается, что ссылка на oExcel успешно получена):
object oWorkbooks = oExcel.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, oExcel, null);
Объект oWorkbooks и есть managed-ссылка на коллекцию книг.
Для получения доступа к конкретной книге выполняем следующий код, используя коллекцию книг:
// Доступ к книге по ее порядковому номеру
// Создаем массив параметров
object[] args = new object[1];
// Мы хотим получить доступ к первой книге Excel
args[0] = 1;
// Получаем ссылку на первую книгу в коллекции Excel
object oWorkbook = oWorkbooks.GetType().InvokeMember("Item", BindingFlags.GetProperty, null, oWorkbooks, args);
// Доступ к книге по ее названию
// (обратите внимание, что расширение в
// названии не указывается)
object[] args = new object[1];
// Указываем название книги, к которой мы хотим получить доступ
args[0] = "Книга1";
// Получаем ссылку на первую книгу в коллекции Excel