Как подключить Menu Event Listener ?

Автор eeigor, 26 ноября 2021, 07:46

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

eeigor

Как подключить прослушиватель событий меню для отслеживания изменения конкретного параметра:
Tools - Options - LibreOffice Calc - Calculate - Search criteria = and <> must apply to whole cells

Пои открытии документа я могу синхронизировать значение параметра с флажком на листе, но если пользователь изменяет в меню этот параметр в ходе работы, то как обновить флажок на листе?

С указанным параметром непосредственно связано свойство ThisComponent.MatchWholeCell и свойство SearchCriteria в файле реестра.

P.S. У дескриптора фильтра есть только два свойства, которые я могу задать сам перед фильтрацией согласно установкам пользователя: Case sensitive и Regular expressions (на листе есть также два одноимённых флажка). Это удобно. Но указанный выше параметр влияет на поиск с использованием регулярных выражений, и надо синхронизировать состояние третьего одноимённого флажка на листе.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

mikekaganski

#1
Что-то вроде

Sub StartListening
 Dim oManager As Object, oConfigProvider As Object, oConfigAccess As Object, oListener As Object
 Dim aArg(0 to 0) As New com.sun.star.beans.PropertyValue
 oManager = GetProcessServiceManager()
 oConfigProvider = oManager.createInstance("com.sun.star.configuration.ConfigurationProvider")
 aArg(0).Name = "nodepath"
 aArg(0).Value = "/org.openoffice.Office.Calc/Calculate/Other"
 oConfigAccess = oConfigProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", aArg)
 oListener = CreateUnoListener("SearchCriteriaListener_", "com.sun.star.beans.XPropertyChangeListener")
 oConfigAccess.addPropertyChangeListener("", oListener)
End Sub

Sub SearchCriteriaListener_propertyChange(oEvent)
 If oEvent.PropertyName = "SearchCriteria" Then
   MsgBox "New value: " & oEvent.Source.SearchCriteria
 End If
End Sub


Или лучше вместо

oConfigAccess.addPropertyChangeListener("", oListener)

использовать

oConfigAccess.addPropertyChangeListener("SearchCriteria", oListener)

и упростить обработчик:

Sub SearchCriteriaListener_propertyChange(oEvent)
 MsgBox "New value: " & oEvent.Source.SearchCriteria
End Sub
С уважением,
Михаил Каганский

mikekaganski

Цитата: eeigor от 26 ноября 2021, 07:46Но указанный выше параметр влияет на поиск с использованием регулярных выражений

И не только с использованием регулярных выражений (см. https://forumooo.ru/index.php/topic,8469.45/msg,60298.html)
С уважением,
Михаил Каганский

eeigor

#3
Михаил, большое спасибо за отклик. Опробую только в воскресенье и отпишусь.
Смутило, конечно, что решение дано через:
oConfigAccess.addPropertyChangeListener("SearchCriteria", oListener)

Мне казалось, что через меню Calc'а как-то тоже можно было и проще... Но я этого никогда не делал.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

mikekaganski

#4
Любые настройки программы проще и правильнее всего отслеживать через доступ к конфигурации (com.sun.star.configuration.ConfigurationAccess). Я не имею представления, что это за способ через меню, но даже если он есть, он был бы совершенно неправильным решением, т.к. эту настройку можно изменить кучей способов помимо "меню". Именно неиспользование правильного отслеживания настроек ответственно за баги типа tdf#132145 "com.sun.star.sheet.GlobalSheetSettings" properties, "org.openoffice.Office.Math" and "org.openoffice.Office.Calc/Input/" entries are not synchronized и иже с ним.
С уважением,
Михаил Каганский

eeigor

Спасибо. Я обязательно попробую, как представится возможность.
Надеюсь, что в этот раз :) я достаточно обстоятельно и в то же время кратко описал проблему...
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

eeigor

Михаил, код работает. Благодарю за помощь.
Мы пока не можем (можем, конечно) изменить значение некоторых параметров через реестр, поскольку требуется перезагрузка приложения (известная проблема, поднятая ещё А.Питоньяком давным давно). Зато можем отследить изменение параметра пользователем. И в моём случае это оказалось важно.

Замечена некоторая погрешность, с темой этого поста не связанная.
Если я изменяю значение рассмотренного здесь параметра (сбрасываю или устанавливаю флажок), а затем, не закрывая окна диалога, нажимаю кнопку Apply (Применить), то значения параметра визуально отменяется (устанавливается обратно или сбрасывается соответственно), но после закрытия/открытия окна диалога параметр всё равно установлен верно, в соответствии со значением реестра.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

eeigor

#7
Михаил, я несколько развернул код, но заметил странность:
глобальная переменная не устанавливается (Is Null), соответственно остановить слушатель нельзя.
Впрочем, мне так и надо, чтобы он работал на всём протяжении работы приложения.
Но, вроде как, я должен обозначить процедуру:
Sub SearchCriteriaListener_disposing(oEvent): End Sub

В Вашем примере локальная переменная oListener запускает слушатель, и он работает, пока открыт файл:
oListener = CreateUnoListener("SearchCriteriaListener_", "com.sun.star.beans.XPropertyChangeListener")

Вопрос носит чисто теоретический характер: что не так с обработчиком com.sun.star.beans.XPropertyChangeListener ?

Приведу пример своей версии кода (ключевой блок, написанный Вами, остался прежним).
'
'''''''''''''''''''' SearchCriteria PropertyChangeListener '''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Global oConfigAccess As Object
Global oSearchCriteriaListener As Object

Sub StartSearchCriteriaListener()
If Not IsNull(oSearchCriteriaListener) Then Exit Sub  'just to be sure it doesn't start twice

Dim oManager As Object, oConfigProvider As Object  ', oConfigAccess As Object
Dim aArg(0) As New com.sun.star.beans.PropertyValue

oManager = GetProcessServiceManager()
oConfigProvider = oManager.createInstance("com.sun.star.configuration.ConfigurationProvider")
aArg(0).Name = "nodepath"
aArg(0).Value = "/org.openoffice.Office.Calc/Calculate/Other"
oConfigAccess = oConfigProvider _
.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", aArg)

oSearchCriteriaListener = CreateUnoListener("SearchCriteriaListener_", "com.sun.star.beans.XPropertyChangeListener")
oConfigAccess.addPropertyChangeListener("SearchCriteria", oSearchCriteriaListener)
End Sub

Sub StopSearchCriteriaListener()
If IsNull(oSearchCriteriaListener) Then Exit Sub  'only if still running

oConfigAccess.removePropertyChangeListener("SearchCriteria", oSearchCriteriaListener)
oSearchCriteriaListener = Nothing  'to know later the listener has stopt
End Sub

'Sub SearchCriteriaListener_disposing(oEvent)
''' Remarks: In the case of configuration listeners, disposing will never be called,
''' because Basic will be unloaded earlier than the configuration.
''' So it's not needed here.
' Call StopSearchCriteriaListener
'End Sub

Sub SearchCriteriaListener_propertyChange(oEvent)
MsgBox "New value: " & oEvent.Source.SearchCriteria
End Sub


Таким образом, Михаил, Ваш код оказывается необходимым и достаточным, а всё, что я добавил, ничего не дало. Но что-то здесь не так...
UPDATED: Код выше исправлен.

Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

mikekaganski

Цитата: eeigor от 27 ноября 2021, 22:56Но, вроде как, я должен обозначить процедуру:
Sub SearchCriteriaListener_disposing(oEvent): End Sub

Не должны. Если соответствующий метод не найден, он не вызывается.
В случае слушателей конфигурации disposing никогда не вызовется, потому что Basic выгрузится раньше, чем конфигурация. Так что здесь он не нужен.

Цитата: eeigor от 27 ноября 2021, 22:56В Вашем примере локальная переменная oListener запускает слушатель

oListener не "запускает" слушатель, это и есть сам слушатель (точнее - count-referenced ссылка на него). "Запускает" слушатель вызов addPropertyChangeListener.

Выгружать слушатель по идее желательно (но по факту, если не выгружать - он сам выгрузится при последующей попытке его вызова). Но дело в том, что его надо выгружать вызовом removePropertyChangeListener на том же экземпляре ConfigurationAccess, что использовался для активации слушателя. Для этого тот исходный экземпляр сам должен сохраняться в глобальной переменной.
С уважением,
Михаил Каганский

eeigor

#9
Михаил, спасибо ещё раз. Я поправил код. Действительно Global oConfigAccess выгружает тот же экземпляр слушателя. Всё работает. Но можно этого и не делать.
Думаю, что это решение ещё не раз пригодится. На других форумах я подобных решений не нашёл...
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

sokol92

Добрый день!

Михаил, спасибо за, как всегда, интересные разъяснения!

Для меня один вопрос остается не понятным. Добавляем код из #1 в документ, запускаем слушатель. Закрываем документ и при следующей попытке корректировки соответствующего параметра LO ошибка не возникает (вопреки моим ожиданиям и аналогичным ситуациям в прошлом).

Вы пишите, что слушатель "сам выгрузится при последующей попытке его вызова". Это относится к данному конкретному случаю (слушателю) или есть какие-то общие правила?
Владимир.

eeigor

@sokol92, Ваш вопрос лично мне не совсем понятен. О какой ошибке идёт речь, если ошибка не возникает? В сообщение #7 показано, как выгрузить слушатель явно.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

sokol92

#12
@eeigor, постараюсь сам понять свой вопрос. :)

Я представлял процесс функционирования Listener (слушателя) в Basic следующим образом:

 1. Создаем слушателя путем вызова функции createUnoListener. Функция возвращает объект, поддерживающий соответствующий интерфейс. При вызове методов этого объекта (соответствующих событиям) слушатель вызывает макрос Basic с определенным именем.
 Для каждого события, которое может возникнуть, необходимо создать соответствующим макрос, иначе при возникновении события (после действия в пункте 2) возникнет ошибка времени выполнения.

 2. Добавляем слушатель к вещателю (Broadcaster) путем вызова соответствующего метода (обычно Add...Listener). С этого момента слушатель получает уведомление о каждом событии. Один и тот же слушатель может добавляться к вещателям несколько раз. Например, если мы повторно добавим слушателя к тому же самому вещателю, то наши макросы будут дважды вызываться при каждом событии.

 3. Если модуль Basic, в котором находится слушатель, принадлежит библиотеке документа, то необходимо перед закрытием документа отключиться от всех вещателей, к которым добавлялся слушатель. Для обеспечения этой возможности на шаге 2 нужно запомнить (в глобальные переменные) объекты (ссылки на объекты) слушателя и вещателя.

 Похоже, необходимость (выделенная в пункте 3 жирным шрифтом) отсутствует.

Владимир.

eeigor

#13
Цитата: mikekaganski от 28 ноября 2021, 12:53Выгружать слушатель по идее желательно (но по факту, если не выгружать - он сам выгрузится при последующей попытке его вызова).
Цитата: mikekaganski от 28 ноября 2021, 12:53В случае слушателей конфигурации disposing никогда не вызовется, потому что Basic выгрузится раньше, чем конфигурация. Так что здесь он не нужен.
@sokol92, вы всё пишите правильно. Но нельзя однозначно утверждать, что ваш пункт 3 не нужен. Он, как сказал Михаил, не нужен в моём конкретном случае.
А если я запускаю слушатель ещё раз, то количество запущенных слушателей увеличивается с каждым разом. Я полагаю, слушатель сам выгрузится не при попытке его вызова, а при закрытии приложения.

Но у меня с данной задачей всё получилось идеально.

P.S. А при вызове StopSearchCriteriaListener() метод SearchCriteriaListener_disposing(oEvent) всё равно не вызывается. С другими слушателями это не так.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

mikekaganski

Цитата: sokol92 от 28 ноября 2021, 16:34
Для меня один вопрос остается не понятным. Добавляем код из #1 в документ, запускаем слушатель. Закрываем документ и при следующей попытке корректировки соответствующего параметра LO ошибка не возникает (вопреки моим ожиданиям и аналогичным ситуациям в прошлом).

Вы пишите, что слушатель "сам выгрузится при последующей попытке его вызова". Это относится к данному конкретному случаю (слушателю) или есть какие-то общие правила?

Правильный вопрос, и мне потребовался отладчик, чтобы ответить.
Я не совсем прав, слушатель никогда не выгрузится, если это не сделать вручную. Он просто не будет вызываться, поскольку у соответствующего объекта не будет родительского контейнера (см. BasicAllListener_Impl::firing_impl, где есть проверка while( pP->GetParent() )). Обнуление родителя происходит в момент выгрузки библиотеки (при выгрузке документа), а сам слушатель, поскольку имеет активные ссылки, не выгружается.

В принципе полагаться на такое правильное поведение опасно; с другой стороны, если в аналогичных случаях возникают краши, это однозначный баг (я надеюсь, что именно из-за исправления багов Вы видите теперь другое, правильное поведение). Ошибка Basic была бы разумной альтернативой (может, Вы видели именно такие ошибки?) - не знаю, возникнет ли она у слушателей других объектов, но судя по тому, что я вижу в коде, у любого слушателя из библиотек документов должен обнуляться родитель при выгрузке.

Цитата: sokol92 от 28 ноября 2021, 20:27
Я представлял процесс функционирования Listener (слушателя) в Basic следующим образом:

  1. Создаем слушателя путем вызова функции createUnoListener. Функция возвращает объект, поддерживающий соответствующий интерфейс. При вызове методов этого объекта (соответствующих событиям) слушатель вызывает макрос Basic с определенным именем.
  Для каждого события, которое может возникнуть, необходимо создать соответствующим макрос, иначе при возникновении события (после действия в пункте 2) возникнет ошибка времени выполнения.

Да. Именно при возникновении события. В случае слушателей конфигурации такого возникнуть не может.

Цитата: sokol92 от 28 ноября 2021, 20:27
  3. Если модуль Basic, в котором находится слушатель, принадлежит библиотеке документа, то необходимо перед закрытием документа отключиться от всех вещателей, к которым добавлялся слушатель. Для обеспечения этой возможности на шаге 2 нужно запомнить (в глобальные переменные) объекты (ссылки на объекты) слушателя и вещателя.

См. выше :)

Цитата: eeigor от 28 ноября 2021, 21:41
P.S. А при вызове StopSearchCriteriaListener() метод SearchCriteriaListener_disposing(oEvent) всё равно не вызывается. С другими слушателями это не так.

Он и не должен вызываться при отсоединении слушателя. См. XEventListener, где описан метод disposing:

Цитироватьgets called when the broadcaster is about to be disposed.

Он не имеет отношения к отсоединению слушателя, а только к уничтожению бродкастера (источника событий). В данном случае бродкастером является подсистема конфигурации. Она не уничтожается, пока Basic активен. К моменту её уничтожения все слушатели Basic уже сами будут уничтожены.

И у других слушателей должно быть то же самое.
С уважением,
Михаил Каганский