Шаг 398 - Автоматизация приложений MS Office. Часть 5.

Шаг прислал Yegor A. Blackheel (blackheel@rlt.ru)

Продолжение темы автоматизации MS Excel. Работа с диапазонами.

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

5.1. Запись данных из массива в диапазон ячеек.

Основным классом для работы с ячейками является класс CRange (соответствующий объекту Range в MS Excel). Мы уже использовали этот класс в предыдущей части. Хочу ещё раз отметить, что этот класс используется для доступа не только к диапазону ячеек, но и к одиночной ячейке. Чтобы выбрать значения ячеек в диапазон используется созданный для нас ClassWizard-ом метод LPDISPATCH get_Range(VARIANT Cell1, VARIANT Cell2 ). В качестве параметров он принимает номер первой (верхней-левой) и последней (нижней-правой) ячеек диапазона. Если в передать один и тот же номер, то получим диапазон из одной ячейки.

Для примера попробуем передать в Excel двумерный массив (матрицу) вещественных чисел. Работать будем с диапазоном A10:J19.

CRange oRange1;
// получить диапазон
oRange1 = oSheet.get_Range(COleVariant(_T("A10")),COleVariant(_T("J19")));

На VBA подобный фокус делается с пол-пинка, примерно так:

Sub Range_test() 
 Dim arr(10, 10) 
 Dim rng As Range 
 With 
  ThisWorkbook.Worksheets(1) 
  For i = 0 To 9 
   For j = 0 To 9 
    arr(i, j) = i * j + 0.1
   Next j 
  Next i 
 Set rng = Range(Cells(10, 1), Cells(20, 10)) 
 rng = arr 
 End With 
End Sub

Однако у нас все будет несколько сложнее. Во-первых, мы будем использовать класс MFC COleSafeArray, очень грубо говоря, представляющий собой обертку над структурой SAFEARRAY из VARIANT , т.е. массивом для использования в COM (в действительности этот класс пронаследован непосредственно от VARIANT, просто он "заточен" для работы с массивами произвольной размерности, а также с байт-векторами, и использует в работе функции из "VARIANT API"). Во-вторых, приём из VBA по присвоению массива диапазону у нас не пройдет, так как не существует никакого способа привести типы к "единому знаменателю". К счастью, у Range есть свойство (property), которое называется Value (и ещё Value2). Это свойство и представляет собой значение диапазона. Оно открыто как на чтение, так и на запись, и ClassWizard уже создал нам 2 метода для доступа к нему:

Для нашего примера мы будем использовать Value2 (о разнице между Value и Value2 можно прочесть в MSDN).

Итак, приступим...

COleSafeArray saMatrixToExcel;
DWORD numElements[] = {10, 10};

// создать вариантный массив, тип double (64-bit), размерность 2, число элементов 10*10
saMatrixToExcel.Create(VT_R8, 2, numElements);
ASSERT(saMatrixToExcel.GetDim() == 2);

// заполнить его какими-нибудь данными
long index[2];
double val;
for(index[0]= 0;index[0]<10; index[0]++)
{
    for(index[1]= 0;index[1]<10; index[1]++)
    {
	val = index[0] + index[1]*10 + 0.1;		
	saMatrixToExcel.PutElement(index, &val);
    }
}

// поместить данные массива в желаемый диапазон ячеек
oRange1.put_Value2(saMatrixToExcel); 

Если на этом этапе переключиться в Excel , мы увидим примерно следующую картину:

398_1.png (62052 b)

5.2. Получение данных из диапазона ячеек.

Следующей задачей будет научиться получать данные из диапазона. Для этого нам снова понадобится COleSafeArray. Однако теперь создавать его для нас будет сам Excel. Здесь есть одна хитрость. Поскольку заранее нельзя сказать, какие данные находятся в ячейках, то Excel передаст нам массив, каждый элемент которого будет типа VARIANT. Сравните это с передачей данных в Excel, когда мы задавали тип элементов самостоятельно. Преимуществом такого подхода является то, что мы можем обрабатывать любой тип данных из ячеек.

Для иллюстрации заменим в последней ячейке (J19) цифровое значение 99,1 на слово "Тест".

oRange1 = oSheet.get_Range(COleVariant(_T("J19")),COleVariant(_T("J19"))); 
oRange1.put_Value2(COleVariant(_T("Тест")));

398_2.png (63303 b)

После этого получим массив из диапазона, и выведем дамп в окно отладчика (для простоты):

// get_Value|get_Value2 вернет нам либо 1 значение (если диапазон содержит тольrо одну ячейку),
// либо SAFEARRAY, если oRange1 представляет собой прямоугольную область
VARIANT va;
va = oRange1.get_Value2();
COleSafeArray saMatrixFromExcel(va); // в нашем примере будет массив
VARIANT el;
long row_start, col_start, row_count, col_count;

ASSERT(saMatrixFromExcel.GetDim() == 2); // размерность должна быть 2 (матрица)
saMatrixFromExcel.GetLBound(1,&row_start);
saMatrixFromExcel.GetLBound(2,&col_start);
saMatrixFromExcel.GetUBound(1,&row_count);
saMatrixFromExcel.GetUBound(2,&col_count);

// обойдем массив и сбросим каждый элемент в дамп в соответсвии с его типом
for(index[0]=row_start; index[0]<=row_count; index[0]++)
{
    for(index[1]=col_start; index[1]<=col_count; index[1]++)
    {
	// содержимое saMatrixFromExcel представляет собой матрицу
	// элементов типа VARIANT, именно так ее передает нам Excel
	saMatrixFromExcel.GetElement(index,&el);
	// кто в теремочке живёт?
	switch (el.vt)
	{
	case VT_R8:				
	    TRACE("%g ", el.dblVal);
	    break;
	case VT_BSTR: 
	    {
		CString s(el);
		TRACE("%s ", s);
	    }
	    break;
	default: // прочие типы в этом примере не используются
	    break;
	}
    }
    TRACE("\n", el.bstrVal);
}

Если все сделано правильно, то в окне Output появится наша матрица:

398_3.png (29645 b)

Иcходники

Здесь вы можете загрузить иллюстрирующий проект.
Скачать исходники - архив ZIP,~70 Кб

Шаг прислал Yegor A. Blackheel (blackheel@rlt.ru)


Предыдущий Шаг | Оглавление
Автор Каев Артем - 08.04.2008