| Интеграция CA-Visual Objects с Microsoft Word и Excel | 2/4 |
Использование DDE
Есть три решения как использовать Dynamic Data Exchange для интеграции CA-Visul Objects с MS-Excel - Excel является клиентом, сервером, клиентом и сервером. В большинстве ситуаций Вы захотите иметь в качестве сервера Excel, в качестве клиента свое приложение. Ваше приложение делает запросы - Excel возвращает данные по этим запросам.
Когда два приложения соединены, то говорят, что между ними "conversation" (беседа). Для инициализации "conversation" (давайте я это обзову конвисэйшн) (также как Вы набираете номер телефона для разговора) необходимо вызвать Excel сервер с именем "Excel", когда Вы конструируете IPC клиента.
oExcelDDEClient := IPCClient{"EXCEL"}
К сожалению, если Excel еще не загружен в память, то появиться сообщение об ошибке, так что сперва Вам следует удостовериться о его наличии в памяти. Функция Windows API GetModuleHandle() может определить активен ли Excel в данный момент. Следующая приведенная функция демонстрирует трюк проверки наличия Excel в памяти, и если нет, то загрузку его в память.
FUNCTION LoadExcel()
LOCAL nPtr AS PTR
LOCAL nResult AS INT
LOCAL lLoadOK AS LOGIC
nPtr := GetModuleHandle(String2PSZ("EXCEL.EXE"))
IF nPtr == 0
nResult := ;
WinExec(String2PSZ("C:\MSOFFICE\EXCEL\EXCEL.EXE"),;
SW_SHOWMINNOACTIVE)
IF nResult > 32
lLoadOK := TRUE
ELSE // Ошибка!
lLoadOK := FALSE
ENDIF
ENDIF
RETURN lLoadOK
Перед установкой связи Вам следует убедиться в наличие Excel в памяти.
Допустим Вы установили связь без особых проблем. Что делать дальше? Дальше
надо запускать на выполнение процедуры Excel, требовать с него данные.
Как это сделать? Для этого в классе IPCClient
предусмотрены методы Execute()
и RequestData(). Они имеют следующий
синтаксис:
<oIpcClient>:Execute(<cTopic>, <cItem>, <cCommand>) --->
NIL
<cTopic> Имя IPC topic для которого сервер выполняет команду.
<cItem> Имя IPC item для которого сервер выполняет команду.
<cCommand> Команда, которую должен выполнить сервер.
<oIpcClient>:RequestData(<oIpcTopic>, [<lContinuous>]) --->
NIL
<oIpcTopic> IPC topic данные которого требуются клиенту.
<lContinuous> Если установлен в TRUE, сервер посылает данные
клиенту каждый раз как эти данные на сервере изменяются; Если установлен
в FALSE, сервер посылает данные клиенту только по этому требованию. По
умолчанию FALSE.
Как все усложнилось, подумаете Вы, но для Вас, напротив, все очень просто.
Execute() метод требует для своей работы
некий Topic (но это так же естественно как Вы по телефону говорите
о политике или о погоде). Topic обычно определяет конкретную страницу
или специальный системный Topic. Item не используетс
в Microsoft Windows и его можно проигнорировать здесь (передайте пустую
строку). Command это конкретная команда, требующая выполнения, така
как, например, печать страницы или занесение данных в ячейку.
При запросе данных Вы должны определить реально существующий в DDE
сервере Topic. Реально существующие Topic-и в DDE серверах
определяются из документации к этим серверам. Также при запросе данных
Вы должны определить какой вид связи вы желаете иметь - горячий или холодный.
Макро язык Excel и Visual Basic для приложений. (Excel Macro
Language (XLM) и Visual Basic for Applications (VBA))
Одно из неудобств использования DDE это тот печальный факт, что разработчику в распоряжение дается ограниченный набор методов ведение конвисэйшн, в то время как он, разработчик, требует по возможности все внутренние методы. Но нет никаких препятствий, для того чтобы написать весь коммуникационный код на языке Visual Objects.
Не надо быть экспертом в Excel, чтобы понять, что все, что может Excel, может его внутренний Basic, подобный макроязыку (Excel Macro Language (XLM)) или Visual Basic for Applications (VBA). Excel версии 4.0 снабжен XLM, версии 5.0 VBA - аналогом популярного продукта Microsoft.
Как применить указанное свойство Excel в нашем случае? Есть метод, который
называется RUN и который может быть вызван через DDE. Этот метод
выполняет макрос или вызывает функцию. Это может быть использовано дл
запуска на выполнение макроса, написанного на VBA, макроязыка Microsoft
Excel 4.0, или даже функции в .DLL. Для примера давайте разработаем небольшую
программку для импорта данных ASCII файла в страницу Excel. На ум сразу
приходит решение - просканировать ASCII файл низкоуровневыми функциями
доступа CA-Visual Objects и отправить эти данные в таблицу Excel использу
механизм DDE. Однако VBA Excel имеет собственные функции низкого уровн
доступа к файлам, поэтому решение может выглядеть следующем образом.
Пишем макрос на языке VBA:
Sub LoadCurrentTable()
nCount = 1
Open "TESTFILE" For Input As #1 ' Открытие файла
Do While Not EOF(1) ' Цикл до конца файла
Input #1, cData ' Читаем данные в переменную
Worksheets("Лист1").Cells(nCount, nCount).Value = VAL(cData)
If Worksheets("Лист1").Cells(1, nCount).Value > 100 Then
With Worksheets("Лист1").Cells(nCount, nCount).Font ' Изменим фонт
.Size = 14
.Bold = True
.Italic = True
End With
End If
nCount = nCount + 1
Loop
Close #1 ' Закрыть файл
End Sub
На языке CA-Visual Objects подобный код мог бы выглядеть гораздо сложнее,
так как VO ничего не знает о клетке страницы таблицы Excel. Однако сконструировав
его мы можем вызвать процедуру LoadCurrentTable() из VO использу
DDE технологию, просто набрав:
oIPCClient:Execute("Книга1", "", `[Application.Run("LoadCurrentTable()")]')
Отметьте, пожалуйста, как расставлены символы "", `' и [] - это достаточно важно при реализации DDE в CA-Visual Objects. Если Вы хотите выполнить макрофункцию на языке XLM Вы должны указать страницу, в которой этот макрос находиться:
oIPCClient:Execute("Книга1",; `',`[Application.Run("Модуль1.XLM!LoadCurrentTable()")]')
Использование внутренних языков Excel XLM и/или VBA наиболее легкий
путь интеграции Excel с приложениями CA-Visual Objects.
Разница между версиями Excel 2-5, 5.x и более поздними.
К сожалению от версии Excel сильно зависит то как Вы будете интегрировать свои приложения. В ранних версиях Excel не поддерживалась последная спецификаци OLE 2.0 и полная поддержка расширенных системных Topic , которые были добавлены, начиная с версии 2.0. Вот еще некоторые различия:
DDE имеет несколько ограничений, наименьшее из которых убогий интерфейс
доступа к данным. Слишком много операций следует предпринять, чтобы манипулировать
с данными. Хороший пример с тем, что Вы хотите извлечь данные из клетки.
С DDE Вы не можете просто вытащить содержимое клетки, метод Execute не
возвращает никакого результата. Для этой цели Вы вынуждены использовать
метод RequestData
Через механизм OLE, однако доступно гораздо больше опций. Если опци
не доступна в Excel или в его аналоге через OCX, по крайней мере от Microsoft,
Вы вправе использовать компоненты других фирм. Наиболее известной компонентой
является компонента "Formula One" от "Visual Component". Это очень сильна
вещь со многих позиций. Во первых она удобней, по ней написан огромный
HELP, чего нельзя сказать про Excel. Во вторых она меньше ест ресурсов
и быстрей работает
Иерархия OLE объектов в Exel.
Excel имеет несколько типов объектов, доступных через механизм OLE (самих объектов более чем 128). В то время как другие сервера автоматизации имеют один объект APPLICATION, Excel имеет три:
APPLICATION
AddIn
AutoCorrect
Debug
Dialog
Name
MenuBar
Toolbar
Window
Workbook
Worksheet
DialogSheet
Chart
Module
DocumentProperty
Style
Window
Name
RoutineSlip
Как Вы видите объект класса APPLICATION является контейнером для объектов
класса WORKBOOK, которые, в свою очередь, являются контейнерами для объектов
класса WORKSHEET, CHAR и Style. С подобной схемой становиться более понятной
логическая схема работы Excel.
В CA-Visual Object Вы можете сделать объект класса APPLICATION, заполнить
его объектами WORKBOOK, которые могут содержать несколько объектов WORKSHEET.
Начало работы через OLE c Excel.
Есть два наиболее приемлемых пути для определения объекта Excel.
Первое - Вы можете использовать OleAutoObject класс, который создает
OLE объект времени выполнения связанный с сервером автоматизации OLE объектов
(Excel). Это очень удобно, но имеет свои отрицательные стороны:
Создаем новое приложение. Обратите внимание, что при создании приложени CA Visual Object спрашивает Вас об использовании OLE классов. Ответьте утвердительно:
.
Наберите программу:
METHOD Start() CLASS App
LOCAL oW AS ShellWindow
LOCAL oAO AS OLEAutoObject
oW := ShellWindow{SELF}
oW:Show(SHOWCENTERED)
oAO := OLEAutoObject{"EXCEL.APPLICATION.5"}
IF !oAO:fInit
TextBox{oW, "Все паршиво!"}:Show()
ELSE
TextBox{oW, "Все удачно!"}:Show()
END IF
SELF:Exec()И убедитесь ... что она не работает. Точнее не создается объект oAO. Поскольку не работает основное, далее можно не продолжать. При попытке создать класс на основе интерфейса автоматизации, выдается сообщение об ошибки и ничего не генерится:
Связь с Excel через OLE OCX компоненту
Для использования OCX компоненты, ее надо установить на машину. Это делает стандартный инсталлятор, и если следовать инструкциям Вы наверняка получите то, что хотите. Помимо того, что инсталлирование переписывает файлы на Ваш диск, оно еще занимается регистрацией установленной компоненты в файл регистратор системы - Register. Это необходимое требование для нормальной работы компонента.
CLASS VCIFormulaOneWorkbook INHERIT OleControl
Обратите внимание от чего он наследуется. Если Вы посмотрите на методы сгенеренного класса, то наверняка обратите внимание на то, что все они похожи друг на друга как близнецы:
METHOD Read(;
pPathName,; // AS STRING
pFileType,; // AS PTR
) CLASS VCIFormulaOneWorkbook
// Считывает рабочую книгу или рабочий лист с диска
LOCAL a[OFD_LEN] AS ARRAY
LOCAL pParamDesc AS VOOLEARGDESC
LOCAL pRetDesc IS VOOLEARGDESC
LOCAL uRetValue AS USUAL
a[OFD_NAME] := String2Symbol("Read")
a[OFD_MEMBERID] := 202
a[OFD_INVOKE] := INVOKE_METHOD
a[OFD_PARAMS] := 2
a[OFD_OPTPARAMS] := 0
a[OFD_NAMEDARGS] := .T.
pParamDesc := oAuto:__AllocArgDescs(2)
a[OFD_PARAMDESC] := pParamDesc
pParamDesc.dwIDLFlag := 0
pParamDesc.dwVarType := VT_BSTR
pParamDesc.atomArgName := #pPathName
pParamDesc += 1
pParamDesc.dwIDLFlag := 0
pParamDesc.dwVarType := VT_PTR
pParamDesc.dwArrPtrType := VT_I2
pParamDesc.atomArgName := #pFileTypeЭ
pParamDesc += 1
pRetDesc.dwIDLFlag := 0
pRetDesc.dwVarType := VT_VOID
a[OFD_RETDESC] := @pRetDesc
uRetValue := oAuto:__InvokeMethod(a,DWORD(_bp+16),PCount())
MemFree(pParamDesc)
RETURN(uRetValue)
ACCESS SheetName(; nSheet,; // AS LONG ) CLASS VCIFormulaOneWorkbook ASSIGN SheetName(; nSheet,; // AS LONG uParam002,; // AS STRING ) CLASS VCIFormulaOneWorkbook METHOD SheetNameACCESS(; nSheet,; // AS LONG ) CLASS VCIFormulaOneWorkbook METHOD SheetNameASSIGN(; nSheet,; // AS LONG uParam002,; // AS STRING ) CLASS VCIFormulaOneWorkbook
CLASS FrmOneControl INHERIT DATAWINDOW EXPORT oDCF1 AS VCIFORMULAONEWORKBOOK
METHOD Init(oWindow,iCtlID,oServer,uExtra) CLASS FrmOneControl
SELF:PreInit(oWindow,iCtlID,oServer,uExtra)
SUPER:Init(oWindow,ResourceID{"FrmOneControl",_GetInst()},iCtlID)
oDCF1 := VCIFORMULAONEWORKBOOK{SELF,ResourceID{FRMONECONTROL_F1,_GetInst()}}
oDCF1:HyperLabel := HyperLabel{#F1,NULL_STRING,NULL_STRING,NULL_STRING}
oDCF1:CreateFromAppDocStorage(OLEAppDocStorage{"OLE200", "FrmOneControl", "F1"})
SELF:Caption := "DataWindow Caption"
SELF:HyperLabel := HyperLabel{#FrmOneControl,"DataWindow Caption",;
NULL_STRING,NULL_STRING}
SELF:PostInit(oWindow,iCtlID,oServer,uExtra)
RETURN SELF
METHOD Test() CLASS ShellWindow
LOCAL oF1c AS FrmOneControl
LOCAL oP := 4 AS INT
LOCAL kL, nL, L1, L2 AS LONG
LOCAL lP AS LOGIC //Флаг удачного поиска нужной страницы
LOCAL oServ AS DBserver
LOCAL sDBF, sXLS AS STRING
LOCAL m1, m2 AS USUAL
//
sXLS := "d:\St\Tehnicon97\Использование Excel и Word в CA-Visual Objects 2.0\Ole1.xls"
sDBF := "d:\St\Tehnicon97\Использование Excel и WORD в CA-Visual Objects 2.0\t1.dbf"
IF File(sDBF)
DeleteFile(AsPsz(sDBF))
END IF
DBCREATE(sDBF, {{"F1", "N", 26, 8}, {"F2", "C", 20, 0}})
oServ := dbserver{sDBF, DBEXCLUSIVE, DBREADWRITE}
//
oF1c := FrmOneControl{SELF}
oF1c:oDCF1:Read(sXLS, @oP)
//Выбираем нужную странице в файле Platts
kL := oF1c:oDCF1:NumSheets
lP := FALSE
FOR nL := 1 UPTO kL
IF "ПРИМЕР" == Upper(AllTrim(StrTran(oF1c:oDCF1:SheetNameAccess(nL)," ",;"")))
oF1c:oDCF1:Sheet := nL
lP := TRUE
EXIT
END IF
NEXT
IF lP //Если мы ее нашли
L1 := oF1c:oDCF1:LastRow
FOR L2 := 1 UPTO L1
oServ:append()
m1 := Val(oF1c:oDCF1:EntryRC(L2, 1))
m2 := oF1c:oDCF1:EntryRC(L2, 2)
oServ:FIELDPUT(#F1, m1)
oServ:FIELDPUT(#F2, m2)
NEXT
TextBox{SELF, "Все класно!"}:Show()
ELSE
TextBox{SELF, "Все не очень класно!"}:Show()
END IF
//