Как вызвать функцию в ячейке из собственной библиотеки модулей?

Автор paulsmith, 10 ноября 2011, 15:39

0 Пользователи и 1 гость просматривают эту тему.

paulsmith

Помогите пожалуйста.

Имею следующее..

В Oo 3.3 под Windows XP_Pro SP3 создаем библиотеку myLib
- в ней создаем модуль myModule
- в нем пишем функцию myFunc

Function myFunc(param as string)
myFunc = param
end function

в листе Calc в любой ячейке набираем =myFunc("Привет Мир") и вместо "Привет Мир" получаем #ЗНАЧЕНИЕ!
Если переместить модуль myModule в библиотеку Standard, то все работает - пишет в ячейке "Привет Мир"

Проверяем загрузку библиотеки...

В модуль myModule добавляем еще одну функцию

Sub Main
oLibs = GlobalScope.BasicLibraries
LibName="myLib"
If oLibs.HasByName (LibName) and (Not oLibs.isLibraryLoaded(LibName)) Then
oLibs.LoadLibrary(LibName)
end if
End Sub

Ставим ее на событие (запуск приложения) и на всякий случай (открытие документа)

Запускаем приложение и открываем документ

Условие if  не выполняется, из чего можно сделать вывод, что библиотека myLib действительно есть и она уже загружена.  Однако результат все тот-же #ЗНАЧЕНИЕ!

Принудительно oLibs.LoadLibrary(LibName) тоже не помогает

Как добиться вызова функции myFunc из библиотеки myLib?

JohnSUN

Добро пожаловать на форум!
Чтобы подготовить ответ, потребуется немного времени. Подождешь?
А пока одно соображение:
Цитата: paulsmith от 10 ноября 2011, 15:39
If oLibs.HasByName (LibName) and (Not oLibs.isLibraryLoaded(LibName)) Then
...
Условие if  не выполняется, из чего можно сделать вывод, что библиотека myLib действительно есть и она уже загружена. 
Там в If два условия. Может быть не выполняется первое из них?
Нужно поставить пару экспериментов... Какой Oo 3.3?
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

Рыбка Рио

Функции на Basic в Calc работают кажется только из библиотеки Standard.
ubuntu 12.04 + LibO3.6.0

neft

Функцию листа из библиотеки вызвать нельзя.

В библиотеке myFunc(param) - функция Basic.

Её нужно переопределить в документе.
Function myFuncSh(arg)
If (Not GlobalScope.BasicLibraries.isLibraryLoaded("myLib")) Then GlobalScope.BasicLibraries.LoadLibrary("myLib")
myFuncSh=myFunc(arg)
End Function

и в ячейке писать переопределенную функцию
=MYFUNCSH(A1)

И всё работает.

JohnSUN

Увы, но Клио прав. Это проблема не бэйсика, а самого Калка. Причем проблема старая.
Значит, придется или действительно держать myFunc в Standard или как CyrillicTools с помощью какой-нибудь CopyModuleToDoc сохранять нужные функции в каждом документе, которому они могут понадобиться.
Или как сказал neft
Цитата: neft от 10 ноября 2011, 18:04
Её нужно переопределить в документе.
В смысле, затолкать в Standard документа...
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

JohnSUN

Вау! Как я рад, что мы ошиблись! Есть обходной путь!
Как говорил дедушка Мичурин, мы не можем ждать милостей...
Первоначальный вариант:
REM Вызов пользовательской функции из произвольной библиотеки
REM Параметры: libName - строка - имя библиотеки
REM funcName - строка - имя пользовательской функции
REM Param1..Param5 - до пяти параметров, передаваемых в функцию
REM В случае ошибки (нет библиотеки, не найдена функция, ошибка в самой функции)
REM возвращает Nothing (оставляет ячейку пустой). Иначе - результат выполнения
REM пользовательской функции с указанными параметрами.
REM Автор: Владислав Орлов aka JohnSUN, Украина, Киев, 2011
Function runMacro(libName As String, funcName As String, _
Optional Param1, Optional Param2, Optional Param3, _
Optional Param4, Optional Param5)
Dim oScriptProvider As Object ' Доступ к макросам как к исполняемым объектам
Dim oLib As Object ' Библиотека макросов
Dim oScript As Object ' Макрос как объект, который имеет метод invoke
Dim oModuleNames As Object ' Массив имен модулей библиотеки
Dim i% ' Переменная цикла
Dim inpParams ' Массив передаваемых параметров
Dim aOutParamIndex() ' Два пустых массива для invoke,
Dim aOutParam() ' при вызове функций в процессе не участвуют
Dim macroName As String ' Полное имя искомой функции, ее URI
Const prefix="vnd.sun.star.script:" ' Первая часть URI
Const sufix="?language=Basic&location=application" ' Окончание URI
REM Эту константу можно менять на свой вкус:
REM первая часть - language - может иметь значения Basic, Java, Python или BeanShell
REM вторая часть - location - application или document
runMacro = Nothing ' Уверены в плачевном результате
If NOT isLibraryLoaded(libName) Then Exit Function ' Библиотека не найдена

oScriptProvider = ThisComponent.ScriptProvider
REM Преобразовать цепочку Param1..Param5 в массив
If IsMissing(Param1) Then
inpParams = Array()
ElseIf IsMissing(Param2) Then
inpParams = Array(Param1)
ElseIf IsMissing(Param3) Then
inpParams = Array(Param1, Param2)
ElseIf IsMissing(Param4) Then
inpParams = Array(Param1, Param2, Param3)
ElseIf IsMissing(Param5) Then
inpParams = Array(Param1, Param2, Param3, Param4) ' Так можно продолжать долго
Else
inpParams = Array(Param1, Param2, Param3, Param4, Param5)
EndIf

oLib = GlobalScope.BasicLibraries.getByName(libName)
oModuleNames = oLib.getElementNames()
On Local Error GOTO NextIteration
For i = LBound(oModuleNames) To UBound(oModuleNames)
macroName = prefix & libName & "." & oModuleNames(i) & "." & funcName & sufix
oScript = oScriptProvider.getScript(macroName)
REM Если в этом модуле такого скрипта не нашли - перейти к следующему
REM Иначе попробовать выполнить найденный скрипт:
runMacro = oScript.invoke(inpParams, aOutParamIndex, aOutParam)
Exit Function
NextIteration:
Next i
' Цикл закончился, а мы все еще здесь. Значит результат функции все тот же Nothing
End Function

Текст isLibraryLoaded и других сопутствующих пока не привожу.
Значит, в двух словах, что эта штука делает... Для библиотеки с указанным именем libName перебирает все модули и из каждого пытается выдернуть скрипт с именем funcName. Обычно это заканчивается ошибкой - не в каждом модуле у нас есть такая функция. Но это не страшно все эти ::com::sun::star::provider::ScriptFrameworkErrorException обрабатывает On Local Error GOTO
А если не ошибка, то через invoke выполняем найденный макрос. Результат invoke и есть результат нашей функции...

Почему пока не привожу все тексты? Хочу еще усилить алгоритм - убрать имя библиотеки и перебрать их все... Благо, GlobalScope.BasicLibraries тоже имеет метод .getElementNames()

Кстати, коллеги, попутно вопрос - сколько по вашему мнению нужно определять этих самых "опциональных параметров"?

ЗЫ. Подменил код - добавил комментариев, чтобы хоть попонятнее стало
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

neft

Не, я без поллитры не разберусь.

А что в ячейке-то писать?

JohnSUN

Ну, для той функции, что в качестве примера давалась вызов будет таким:
=RUNMACRO("MyLib";"MyFunc";"Привет Мир")
То есть, первым параметром - имя библиотеки, вторым - имя функции, а дальше от нуля до пяти параметров этой функции...

Что-то задержался я с перебором библиотек... Чтобы не простаивали, вот код недостающей в прошлом листинге функции: REM Проверяет загружена ли библиотека макросов с указанным именем
REM (по умолчанию "Tools"). Если еще не загружена, то пытается загрузить.
Function isLibraryLoaded(Optional LibName As String) As Boolean
Dim oLibs As Object
If IsMissing(LibName) Then LibName="Tools"
oLibs = GlobalScope.BasicLibraries
If oLibs.HasByName (LibName) Then
If (Not oLibs.isLibraryLoaded(LibName)) Then
oLibs.LoadLibrary(LibName)
End If
If (Not oLibs.isLibraryLoaded(LibName)) Then
MsgBox("Библиотека с именем """ + LibName + """ не загружена!" + Chr(13) + _
"Проверьте правильность установки офиса." + Chr(13) + _
"Некоторые макросы будут недоступны!", 48, "Внимание! Некоторые макросы будут недоступны!")
isLibraryLoaded = False
Else
isLibraryLoaded = True
End If
Else
MsgBox("Библиотека с именем """ + LibName + """ отсутствует!" + Chr(13) + _
"Проверьте правильность установки офиса." + Chr(13) + _
"Некоторые макросы будут недоступны!", 48, "Внимание! Некоторые макросы будут недоступны!")
isLibraryLoaded = False
End If
End Function
и, на всякий пожарный, процедуры:
Sub loadSomeLibs
isLibraryLoaded() ' Без параметра - загрузить стандартную Tools
isLibraryLoaded("MRILib") ' Нужна часто
isLibraryLoaded("MyLib1") ' А это уже для тестирования
isLibraryLoaded("MyLib") ' И это для тестирования
REM Здесь же можно добавить и остальные часто загружаемые прибамбасы.
REM Например, prepareXray...
End Sub

Я просто не успел проверить, будет ли RUNMACRO работать с незагруженной библиотекой. У меня в запуске офиса уже стоит loadSomeLibs...
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

neft

"Нормальные герои всегда идут в обход.
В обходе, если честно, не очень-то легко,
Не очень-то приятно и очень далеко."

JohnSUN, пока только ошибки.

Можешь выложить работающий пример?

JohnSUN

Нет, не могу... Там фишка в чем - в файле, который я выложу будет просто ячейка с формулой =RUNMACRO("MyLib";"MyFunc";"Привет Мир")
А все эти тексты нужно распихать по модулям:
процедуру loadSomeLibs и функции isLibraryLoaded и саму runMacro - в Мои макросы-Standard
myFunc - в какой-нибудь модуль (у меня она в Module1) в библиотеке MyLib.
Ну и в Сервис-Настройка-События на "Запуск приложения" подвесить вызов loadSomeLibs (там внизу - Сохранить в - для приложения, а не для документа. У меня LibreOffice).
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

JohnSUN

Н-да... Затянулась модификация.
Не так трудно кодировать, как сообразить общую последовательность просмотра библиотек, обработку ошибок и прочие мелочи, на которых не стоило бы, наверное, и сосредотачиваться. Опять же, отладка первоначального варианта отвлекла.
Цитата: neft от 10 ноября 2011, 21:49
JohnSUN, пока только ошибки.
Теперь я понял, о чем ты говорил - при загрузке документа пересчет ячеек начинается еще до того, как ThisComponent получил все свои свойства-методы и отдал их макросу. Ну, старая история, в общем. Так что сразу после загрузки ячейки, в которых вызывается runMacro, останутся пустыми. Чтобы увидеть значения нужно будет нажать Ctrl+Shif+F9.
В прикрепленном ZIP'е - полный текст модуля runAnyFunction. Его место в библиотеке Standard.

[вложение удалено Администратором]
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

paulsmith

Огромное всем СПАСИБО за помощь и отзывчивость :beer:

Идеи замечательные. В каждом предложении есть и ДА и НЕТ

Идея neft вызывать макрос вызова  формулы в Standard документа хороша. Но у меня пользователь сам-себе делает
с нуля Calc-форму и вставлять в нее код запуска формулы он не будет. Тут либо писать еще один макрос в своей библиотеке, который
внедряет код в Standard новой формы и запускать его вручную, сказав пользователю - "Сделал форму, нажми кнопку!",
либо поставить запуск этого макроса на событие создания нового документа O.o. В первом варианте пользователь
обязательно забудет нажать на кнопку, а во втором - все нужные и ненужные документы будут с макросом запуска.

Распространять макрос через библиотеку Standard O.o очень не хотелось бы, т.к. придется это делать для каждого пользователя и каждый раз при обновлении кода формулы.
Слишком частое у меня обновление кода формулы.  Из ответов JohnSUN, Клио, neft ясно что вызов функции листа вне Standard - это проблема.
(до недавнего времени хотелось верить, что это не так  :-\ и что я где-то чего-то недочитал  :))

Идея JohnSUN тоже хороша и понятна. Конечно  опять не удастся уйти от вставки чего-либо в Standard, но прелесть в том,
что эта вставка универсальна и обновлять ее код не придется. Помучился один раз, и порядок. Дальше только свою
библиотеку можно обновлять через надстройку.

Спасибо! Буду пробовать

   

paulsmith

Ура! Решение JohnSUN runAnyFunction - РАБОТАЕТ!

Правда пришлось добавить еще одну функцию к своей библиотеке и поставить ее на
событие (открытие документа). Это функция перерасчета всех формул на листе чтобы не жать каждый раз Ctrl+Shif+F9

Sub doFullCalculate()
  Dim oDocument   As Object
  Dim oDispatcher As Object
  oDispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
  oDocument = ThisComponent.CurrentController.Frame
  oDispatcher.executeDispatch(oDocument, ".uno:CalculateHard", "", 0, Array())     
End Sub

JohnSUN

Ты стреляешь быстрее! Я её как раз дописывал, эту процедуру, а тут раз - твое сообщение  :beer:

PS. Только у меня это выглядело так:
Sub reCalcAll()
  ThisComponent.calculateAll()
End Sub
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

dr.Faust

Свобода информации - свобода личности!