Проблема с комбобоксом

Автор eeigor, 9 февраля 2023, 13:34

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

eeigor

com.sun.star.form.OComboBoxModel
Как его правильно запрограммировать?
Вот на это событие реагирует: com.sun.star.awt.TextEvent
А, к примеру, вот на это нет: com.sun.star.awt.ActionEvent
Короче, реагирует не на все события, присутствующие в списке свойств формы для настройки элемента управления (пробовал только на "Выполнить действие" и "Перед обновлением" - не работает; "Текст изменен" - работает). Изменение текста происходит при выборе нового значения из раскрывающегося списка. А мне надо на beforeUpdate ("Перед обновлением"), да так, чтобы была возможность отмены события. В списке событий оно есть (см. скриншот). Но нет реакции.

В списке методов Xray показывает разные слушатели. Неужели для требуемого мне события надо его подключать??

  • addActionListener ( l as object ) com.sun.star.awt.XComboBox
  • addEventListener ( xListener as object ) com.sun.star.lang.XComponent
  • addFocusListener ( xListener as object ) com.sun.star.awt.XWindow
  • addItem ( aItem as string, nPos as integer ) com.sun.star.awt.XComboBox
  • addItemListener ( l as object ) com.sun.star.awt.XComboBox
  • addItems ( aItems as []string, nPos as integer ) com.sun.star.awt.XComboBox
  • addKeyListener ( xListener as object ) com.sun.star.awt.XWindow
  • addModeChangeApproveListener ( rxListener as object ) com.sun.star.util.XModeChangeBroadcaster
  • addModeChangeListener ( rxListener as object ) com.sun.star.util.XModeChangeBroadcaster
  • addMouseListener ( xListener as object ) com.sun.star.awt.XWindow
  • addMouseMotionListener ( xListener as object ) com.sun.star.awt.XWindow
  • addPaintListener ( xListener as object ) com.sun.star.awt.XWindow
  • addTextListener ( l as object ) com.sun.star.awt.XTextComponent
  • addWindowListener ( xListener as object ) com.sun.star.awt.XWindow

Если да, то как? Пример бы. Особенно с отменой действия.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

sokol92

#1
Эксперимент показывает следующее.
1. Если установить обработчик события "Text Modified" ("Текст изменен"), то обработчик вызывается как при смене значения Combobox путем выбора из выпадающего списка, так и при непосредственном вводе (каждого символа) с клавиатуры. Обработчик события "Item Status Changed" ("Состояние изменено") при установленном обработчике "Text Modified" не вызывается.
2. Если установить обработчик события "Item Status Changed" (не устанавливая обработчика "Text Modified"), то обработчик будет вызываться, если Вы меняете текст Combobox путем выбора из выпадающего списка.

В любом случае, контроль за значением поля Combobox остается за программистом. Можно это делать на событии нажатия кнопки OK и, дополнительно, на событии потери фокуса Combobox.
Владимир.

sokol92

#2
На всякий случай, для читателей темы (и себе для памяти  :) )поясню, что в LO есть два визуально не отличимых элемента управления вида "Поле со стрелкой":

1. List box, имеет модель UnoControlListBoxModel, свойство которой Dropdown имеет значение True.
2. Combo box, имеет модель UnoControlComboBoxModel.

Разница в поведении - в Combo box мы можем выбрать значение из выпадающего списка (как случае List box), а также ввести произвольное значение (в отличие от List box).

Для справки: в Microsoft Office Forms варианты 1 и 2 представлены элементом управления ComboBox в зависимости от значения свойства Style: для варианта 1 это fmStyleDropDownList, варианта 2 - fmStyleDropDownCombo.
Владимир.

eeigor

#3
Владимир, у меня combobox на листе, а не в диалоговом окне. Надо поймать отмену события, чтобы вернуть прежнее значение в поле combobox'а. Отмены нет, OldValue нет... Зато есть список событий (см. скриншот в стартовом сообщении), который непонятно зачем.

Почему здесь использую combobox вместо ячейки с проверкой – отдельная тема. Хотя, как правило, обхожусь функцией проверки данных для выбора значения из списка.
Таки поясню. Ячейку надо отслеживать по событию листа. У меня частенько «зависает» курсор и начинается выделение экрана вослед за мышкой без всяких кнопок. Двойным щелчком ЛКМ останавливаю зловредный процесс. Ошибка давняя. Баг? Но тут файл передаётся пользователям, и я хочу этого избежать. Вторя причина – отмена изменения.

Всё-таки предложите решение. В Excel это делается просто по событию BeforeUpdate. Не может быть, чтобы решения не было.

И это должно быть очень просто, потому как задачка типовая.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

sokol92

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

sokol92

Цитата: eeigor от  9 февраля 2023, 19:49У меня частенько «зависает» курсор и начинается выделение экрана вослед за мышкой без всяких кнопок.
Честно говоря, понятнее не стало. Файл с примером и описанием действий, вероятно, помог бы.

Ситуация с некорректным выделением экрана может быть связана с неправильными кодами возврата (или их отсутствием в случае процедур, а не функций) слушателей событий мыши в тех случаях, когда код возврата предполагается.
Владимир.

eeigor

#6
Цитата: sokol92 от  9 февраля 2023, 19:55Из Вашего контекста не ясно, о какой отмене события идет речь. Опишите, пожалуйста, всю последовательность действий.
Пользователь выбирает новый месяц календаря, при этом выводится сообщение с предупреждением, что данные о посещаемости за прежний месяц будут очищены, и предложение продолжить (Да/Нет). Если пользователь решает остановиться, то в поле элемента управления остаётся новое  значение, а информация о старом отсутствует (если не "городить огород" для сохранения этого значения в глобальной переменной). Появляется рассогласование в данных. Когда я использовал обычную ячейку с проверкой, то отмена (".uno:Undo") не работала, пока выполнялся код по событию листа. Использовать менеджер отмены не хочу, потому что он отслеживает всё и вся (лишняя нагрузка на процессор). Нужен простой аналог событию Combobox_BeforeUpdate(Cancel As Boolean). Помнится, в Excel я часто его использовал. Как же без этого обходятся другие?
Готов подключить слушатель к элементу управления. Это всё равно лучше, чем реагировать на изменения содержания ячеек всего листа в поисках отслеживаемой ячейки. Нужен пример.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

Narrnika

События "Перед обновлением/восстановлением", "После обновления/восстановления" скорее всего подразумевают связь с источником данных; например, когда у вас страница формы связана с записью в БД.

События типа "Перед изменением текста" скорей всего нет - сталкивался с этим много где (правда, в основном в игровых движках). Почему-то в событии изменения текста элемента UI, будь то comboBox или lineEdit, в качестве параметра можно пощупать только то, что уже наклацал юзер, а вот что было до этого - шиш. Решается ручным кэшированием - т.е. в данном случае на событие "Текст изменён" вешаем что-то вроде
sub textChange(event as Object)
    with event.source
        if len(.Text) > 5 then ' тут своя проверка
            .Text = .getModel().tag
        else
            .getModel().tag = .Text
        end if
    end with
end sub
tag - это свойство элемента "Дополнительная информация", так что если оно занято - ну, можно в глобальную переменную вывести, хоть мне это и не нравится.

Единственное "но" - событие "Текст изменён" не будет срабатывать при программном изменении текста. Т.е. ввод с клавиатуры, копипаста или выбор из списка - да, а если установить значение из макроса - то нет.
Ubuntu 20.04 / LibreOffice 6.4.7.2 / OpenOffice 4.1.7

eeigor

#8
Решение проблемы в самом низу.

@Narrnika, со свойством Tag, пожалуй лучше будет, чем с глобальной переменной: переменную надо инициализировать при открытии файла, а в указанном свойстве можно сохранить текущее значение постоянно, которое при смене значения в поле станет прежним (для возврата к нему при отмене действия пользователем). Одним ненужным шагом меньше, и так надёжнее. Похоже на заплатку.
Признаюсь, для меня было полной неожиданностью, что список событий, приведённый в стартовом сообщении (см. скриншот) - это "обман зрения".

Мы гадаем на кофейной гуще. Может, Михаил (@mikekaganski) развеет наши сомнения?..

Только это и работает:
Sub cboMonth_textChanged(oEvent As com.sun.star.awt.TextEvent)
End Sub

UPD.
Цитата: Narrnika от 10 февраля 2023, 00:21Единственное "но" - событие "Текст изменён" не будет срабатывать при программном изменении текста. Т.е. ввод с клавиатуры, копипаста или выбор из списка - да, а если установить значение из макроса - то нет.
Однако ничего не выходит: при обратном присвоении прежнего значения в поле комбобокса вызывается то же событие (Текст изменен) и действие зацикливается.
Номер типа (так делал в Excel)
  Static bInHere As Boolean  'Я тута
не проходит: вызов следует один за другим, если жать "Нет".
.Text = .Model.Tag  'зацикливает процедуру
Загружаются ли все вызовы в стек или событийная процедура выполняется, а затем вызывается заново - не знаю. Стек не проверял.

Ниже оставлен рабочий вариант в соответствии с найденным решением.

Sub cboMonth_textChanged(oEvent As com.sun.star.awt.TextEvent)
''' Called by: <cboMonth> (Label: None) button on the sheet "Табель".

Rem Xray oEvent
Rem Stop
' Static bInHere As Boolean: If bInHere Then Exit Sub

If MsgBox("Данные о посещаемости за прежний месяц будут очищены." & Chr(10) _
& Chr(10) & "Чтобы сохранить прежние данные, сначала переименуйте этот файл" _
& " и только потом смените месяц." & Chr(10) _
& Chr(10) & "Продолжить?" _
, MB_ICONQUESTION + MB_YESNO + MB_DEFBUTTON2, "Смена месяца") = IDNO Then

Call UnoUndo

' bInHere = True
' With oEvent.Source: .Text = .Model.Tag: End With
' bInHere = False
Else
' With oEvent.Source: .Model.Tag = .Text: End With
Rem Call ClearContents
End If
End Sub

Sub UnoUndo()
Dim document As Object, dispatcher As Object
document = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
dispatcher.executeDispatch(document, ".uno:Undo", "", 0, Array())
End Sub

Sub ClearContents()
''' Clear the calendar when you choose the next (new) month.
''' Called by: cboMonth_textChanged

On Local Error GoTo HandleErrors
Dim oRange As Object
Dim nCellFlags&

oRange = ThisComponent.NamedRanges.getByName("Табель").ReferredCells
oRange = oRange.getCellRangeByPosition(6, 0, 36, oRange.Rows.Count - 1)  'calendar
Rem ThisComponent.CurrentController.select(oRange)
With com.sun.star.sheet.CellFlags
nCellFlags = .VALUE + .STRING
End With
oRange.clearContents nCellFlags
Exit Sub

HandleErrors:
Msgbox "Ошибка " & Err & " в строке " & Erl & ": " & Error _
, MB_ICONSTOP, "macro:ClearContents"
End Sub

Простейший вопрос. Что-то здесь не то... Ау?
В конце концов меня уже устраивает любой вариант с отменой события.

UPD. Стоп! Решение найдено. Вызов метода диспетчера для отмены изменений не работал с отслеживаемой ячейкой (со списком значений функции валидации) при обработке события листа
  Sub Sheet_OnChange(oTarget As Object)
однако прекрасно работает с комбобоксом, который дополнительно связан с ячейкой листа (той, что под ним). Метод .uno:undo отменяет изменение в ячейке листа, а комбобокс синхронно возвращается к прежнему значению, не зацикливаясь. Свойство Tag не понадобилось.
Работает идеально. И не похоже на заплатку.

Но другие варианты использования событий с отменой приветствуются. Тема не закрыта. Ликбез продолжается...
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

eeigor

#9
Попробуйте реализовать то же самое (с отменой) с обычной ячейкой со списком значений проверки (валидации). Было бы не лишним. Поверять при событии листа OnChange.

Дело в том, что использование разных подходов нарушает единство стиля в интерфейсе пользователя. То кнопка справа от ячейки, то внутри ячейки, а в Linux, в отличие от Windows, она ещё и совершенно по-другому выглядит: в виде вертикальной палочки, а не кнопки с треугольником.

Тонкий момент. В примере выше отмена (undo) возникает в ячейке, а комбобокс синхронизируется с ней без запуска события изменения текста в своём поле. При работе с обычной ячейкой после отмены значения будет паразитный эффект: запустится всё та же процедура, что и при первом вводе нового значения. И как можно было видеть, я не смог этого побороть даже использованием статической переменной bInHere ("Я уже здесь"). В Excel это обычная практика, чаще при использовании рекурсии.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

sokol92

Цитата: eeigor от 10 февраля 2023, 00:05Пользователь выбирает новый месяц календаря
Для этого действия гораздо лучше подходит диалог, где произойдет выбор нового расчетного периода с учетом всех предупреждений. Если пользователь передумает, то ничего откатывать не нужно.
Владимир.

Narrnika

Цитата: eeigor от 10 февраля 2023, 01:08Однако ничего не выходит...
Да, я ошибся. Писал по опыту работы с числовым полем, а там есть setText(5) и setValue(5). Первое вызывает срабатывание события changeText (или как оно там правильно зовётся), второе - нет. Удобно, кстати, можно выбирать, что нужнее.
Ubuntu 20.04 / LibreOffice 6.4.7.2 / OpenOffice 4.1.7

Narrnika

Цитата: eeigor от 10 февраля 2023, 01:08не проходит: вызов следует один за другим, если жать "Нет".

Вполне решаемо, правда пришлось внимательно прочитать условия задачи (можно было раньше, но так не интересно).

Код вставлять и пояснять лениво, вот файл:
Ubuntu 20.04 / LibreOffice 6.4.7.2 / OpenOffice 4.1.7

eeigor

@Narrnika, да, я увидел выход из процедуры при возврате к прежнему значению, так как значения в поле и Tag равны.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community