[Решено] Calc: Как сделать видимым начало диапазона после фильтрации?

Автор eeigor, 3 января 2021, 16:58

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

eeigor

День добрый и с Новым годом!
Есть диапазон условий (CriteriaRange) расширенного фильтра (AdvancedFilter) на листе и диапазон базы данных (DatabaseRange), фильтруемый макросом после изменения условий фильтрации. Строка заголовков столбцов данных закреплена. Проблема в том, что после наложения фильтра данные могут отображаться не с начала. А надо, чтобы с начала, с первой строки вновь отфильтрованных данных. Как прокрутить диапазон отфильтрованных данных в начало без изменения активной ячейки в диапазоне условий расширенного фильтра?
Код ниже решает задачу отображения отфильтрованных данных с первой видимой строки (.uno:GoToCell, .uno:GoDown), но теряется активная ячейка (напомню, что пользователь вводит условия в диапазоне условий). Вопрос касается исключительно удобства ввода данных в диапазоне условий.
Sub EnsureVisible()
  dim document as object
  dim dispatcher as object

  document   = ThisComponent.CurrentController.Frame
  dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")

  ' Suppose $A$9 is the upper left corner of the database range.
  dim args1(0) as new com.sun.star.beans.PropertyValue
  args1(0).Name = "ToPoint"
  args1(0).Value = "$A$9"
  dispatcher.executeDispatch(document, ".uno:GoToCell", "", 0, args1())
 
  dim args2(1) as new com.sun.star.beans.PropertyValue
  args2(0).Name = "By"
  args2(0).Value = 1
  args2(1).Name = "Sel"
  args2(1).Value = false
  dispatcher.executeDispatch(document, ".uno:GoDown", "", 0, args2())
end sub


Можно ли вообще прокрутить диапазон отфильтрованных данных к началу, не мешая работе пользователя, который последовательно вводит условия фильтра в диапазоне условий?
Данные фильтруются по событию листа  "Содержимое изменено" (OnChange Event).
Сейчас, если, к примеру, в результате наложения нового фильтра все видимые в данный момент строки будут скрыты, то можно вообще увидеть только зафиксированную строку заголовков столбцов. И ничего больше. Требуется прокрутить диапазон данных вверх.

Предположим, заголовки столбцов диапазона базы данных начинаются со строки 9.
Повторная фиксация этой строки вызывает прокрутку диапазона данных к началу, хотя и занимает некоторое время. При этом активная ячейка в диапазоне условий не меняется. Возможно, есть другой способ.
ThisComponent.CurrentController.freezeAtPosition (0, 9)  'ByName("A10")
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

rami

ThisComponent.CurrentController.FirstVisibleRow = 50   'номер первой видимой строки

eeigor

#2
Цитата: rami от  3 января 2021, 20:46'номер первой видимой строки
Это не работает, поскольку на листе есть закреплённая строка: "Строка заголовков столбцов данных закреплена".
Во всяком случае я не знаю, как этим воспользоваться.

Строки 1-5 отведены для диапазона условий расширенного фильтра с заголовками в строке 1;
строка 7 - итоги по базе данных (SUBTOTAL);
строка 9 - заголовки полей базы данных, эта строка закреплена;
со строки 10 (индекс строки 9) идут сами данные диапазона базы данных.
Строки 6 и 8 оставлены пустыми.

Сработала, как писал выше, такая строка:
   ThisComponent.CurrentController.freezeAtPosition(0, 9)
То есть повторное закрепление строки заголовков вызывает прокрутку отфильтрованных данных к началу диапазона.
Но будет ли такой способ оптимальным, rami?

Диапазон отфильтрованных на скриншоте данных начинается со строки 57. Но так как диапазон данных в ходе предыдущей работы с ним был прокручен к концу, то после наложения нового фильтра (полей вправо много, и условий фильтра не видно) отображена первая строка 1910, а надо, разумеется, прокрутить к началу - строке 57. Если использовать метод 'select', то теряется активная ячейка в диапазоне условий, и это неудобно.

А пользователь, работая с диапазоном условий, может нажать как TAB (переход вправо), так и ENTER (переход вниз). В общем, активизация ячеек в диапазоне базы данных (DatabaseRange) мешает работе пользователя, ибо активной ячейкой в момент срабатывания события является исходная, а к какой вернуться (перейти) - зависит от ранее нажатой клавиши... При этом не хочется усложнять код.

UPD: Некоторые замечания по сравнению с работой расширенного фильтра Excel и о самом LO Calc AdvancedFilter
Условия в диапазоне условий в Excel должны вводится только подряд, и там я использовал .CurrentRegion для уточнения размера диапазона условий перед его передачей методу диапазона .AdvancedFilter. В LO Calc это не имеет значения. Можно вводить условия в любую строку, хоть в 5-ю, то есть не подряд. Странно однако... Зато удобно!

Кроме того, обнаружена ошибка (возможно, баг).
В LO Calc диапазон базы данных (DatabaseRange), в отличие от обычного именованного диапазона, уже имеет свой дескриптор фильтра (FilterDescriptor) и, соответственно, не имеет метода filter, зато у него имеется метод refresh. Однако к диапазону базы данных можно обратиться и как к обычному диапазону по имени. Далее создать независимый дескриптор фильтра, связать его с диапазоном условий и передать диапазону данных. В этом случае у диапазона данных будет метод filter. Этот способ рассматривается здесь на форме (пример от rami для AdvancedFilter).

Другой вариант сложнее: использовать встроенный дескриптор фильтра объекта базы данных (DatabaseRange.FilterDescriptor), примеров нет ни здесь, ни у Питоньяка. Дело усложняется и тем, что некоторые объекты предоставляют копии своих свойств, и значения этим свойствам нельзя устанавливать напрямую. В результате процесс инициализации свойств усложняется и непонятен.

Вот так работает (зд. имена переменных говорят сами за себя):
   oDescriptor = oDBRange.FilterDescriptor
   oFields = oCriteriaRange.createFilterDescriptorByObject(oDBRange.ReferredCells).FilterFields
   oDescriptor.FilterFields = oFields

А вот так у меня не работает:
   oFields = oCriteriaRange.createFilterDescriptorByObject(oDBRange.ReferredCells).FilterFields
   oDBRange.FilterDescriptor.FilterFields = oFields
Вот не смог я установить .FilterDescriptor.FilterFields напрямую!? Могу ошибаться. Увы, я не программист и не понимаю смысл этой "концепции".

Однако использование встроенного дескриптора фильтра выглядит логичнее, не правда ли?
В первом случае мы вызываем метод filter, во втором - метод refresh, поскольку у объекта DatabaseRange нет метода filter.

А теперь о баге. В первом случае в диапазоне данных могут отображаться кнопки автофильтра, и он не мешает работе расширенного фильтра. Во втором случае мешает, расширенный фильтр не работает через вызов метода refresh, но работает-таки через меню расширенного фильтра LO Calc (Данные/Ещё фильтры/Расширенный фильтр...).
Лечится просто:
   If oDBRange.AutoFilter Then oDBRange.AutoFilter = False
Вот только локализовать ошибку удалось с большим трудом и не сразу! Ибо Calc "молчит, как партизан".

Два способа обратиться к диапазону базы данных, где "Database" есть имя диапазона:
   oDataRange = ThisComponent.CurrentController.ActiveSheet.getCellRangeByName("Database")  '<<< oDataRange.filter()
   oDBRange = ThisComponent.DatabaseRanges.getByName("Database")  '<<< oDBRange.refresh()
При этом заметьте, что в окне "Управление именами" нет имени "Database". А если вы его создадите (продублируете и тут), то у вас не будут работать структурные ссылки (см. ниже).

UPD2: А зачем вообще работать с диапазонами базы данных?
Лично мне периодически приходится обрабатывать "сырые" данные, и использование структурных ссылок (с использованием квадратных скобок, кто знает) при обращении к полям диапазона базы данных позволяет не создавать временных именованных диапазонов. Это очень наглядно и легко. Однако после повторного открытия файла структурные ссылки будет заменены на абсолютные. Но в моём случае это неважно.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

sokol92

Цитата: eeigor от  3 января 2021, 21:50не понимаю смысл этой "концепции".
Добрый день!
Книга Питоньяка OOME_4_0.odt, комментарии к листингам 196 и 411. Понять, действительно, не просто. :)
Владимир.

eeigor

#4
Цитата: sokol92 от  4 января 2021, 12:42Понять, действительно, не просто.
sokol92, спасибо за примеры; я увидел. Выше, как оказалось, я поступил с объектом Filelds дескриптора фильтра по аналогии с примером у Питоньяка:
Dim aLocale
aLocale = oCursor.CharLocale  'Or use a copy
aLocale.Language = "fr"  'Set Locale to use the French language
aLocale.Country = "CH"  'Set Locale to use Switzerland as the country
oCursor.CharLocale = aLocale  'Assign the value back

Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

eeigor

#5
Цитата: rami от  3 января 2021, 20:46ThisComponent.CurrentController.FirstVisibleRow = 50   'номер первой видимой строки
Да, это верный метод, только требуется уточнить окно ("ScViewPaneObj" object), в котором это делается:

   ' Зд. строка с индексом 9 идет сразу под зафиксированной строкой заголовков и относится к окну 2 (с индексом 1).
   ThisComponent.CurrentController.getByIndex(1).setFirstVisibleRow(9)  '.FirstVisibleRow = 9

Но так тоже работает:
   ThisComponent.CurrentController.freezeAtPosition(0, 9)  'повторно фиксирует зафиксированную строку

Хотя первый способ более осмысленный.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community