[Решено] Что есть "ActiveCell" в LO Calc?

Автор eeigor, 13 марта 2021, 09:55

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

eeigor

Вопрос носит частично теоретический характер, но имеет практическое значение в принципе.

Было бы неплохо дать возможность пользователю просто выделить весь столбец (несколько столбцов), ведь строк с данными ниже может быть очень много, а затем "отсечь" текущую область (current region) и уже потом выполнить какой-то макрос.
Вот эта задача, к примеру, тоже несёт в себе рассматриваемую проблему.

И это несложно сделать с использованием метода UNO, а уже потом выполнить что-то своё.
Sub UnoSelectData
   Dim document As Object
   Dim dispatcher As Object

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


Штатный метод по команде меню - Edit/Select/Select Data Area (Ctrl+*) - по-видимому, вызывает тот же "uno:SelectData" и работает следующим образом.
Кстати, у меня в Linux эта штука Ctrl+* не работает. Кто подскажет, почему?

При выборе одного или нескольких столбцов мышью "активной" становится первая ячейка видимой области листа (то есть совсем не в первой строке самого листа) и в том столбце, который был выделен последним. Курсор идёт как бы следом за указателем мыши в первой видимой строке. И вот от этой самой активной ячейки (на скриншоте - в черной рамке) Calc вычисляет текущую область и выделяет её, то есть расширяет в стороны от активной ячейки.

Чтобы реализовать подобное, нужно приблизиться к концепции "активной ячейки", которая здесь, как я это понимаю, не реализована или представлена в неявном виде (в отличие от Excel "ActiveCell").

Процедура ниже (GetCurrentRegion) приведена для примера, и она возвращает нам CurrentRegion, если была выбрана одна ячейка (возможно, что и несколько, но только не весь столбец). Если выбран один или несколько столбцов, то возвращается весь исходный диапазон. Это не то, что нам нужно. Однако метод "uno:SelectData" работает правильно, и он, как было сказано, связан с командой меню, описанной выше.
Function GetCurrentRegion(oRange As Object)
   Dim oCursor As Object

   oCursor = oRange.Spreadsheet.createCursorByRange(oRange)
   ' Расширяем курсор на содержащую ячейки область,
   ' в которую курсор в настоящее время указывает.
   ' "Region" - это диапазон ячеек, ограниченный пустыми ячейками.
  oCursor.collapseToCurrentRegion()
  GetCurrentRegion = oCursor  '.getCellRangeByName(oCursor.AbsoluteName)
End Function

Пусть не смущает термин "collapse" (означает "сжиматься, сокращаться"): речь идёт именно о расширении. Оставим эти "неясности" с выбором терминологии на совести разработчиков (ссылка).

Чтобы реализовать одноимённую с методом UNO процедуру SelectData, нужно получить ссылку на активную ячейку.
Псевдокод
Sub SelectData
   oActiveCell = GetActiveCell(oSelection)
   oRange = GetCurrentRegion(oActiveCell)
   ThisComponent.CurrentController.select(oRange)
End Sub


Решение этой "задачи" сопряжено с разбором структуры ThisComponent.CurrentController.ViewData
Кто может предложить реализацию процедуры GetActiveCell, возвращающую ссылку на активную ячейку (в рассматриваемом случае - выбираемую Calc'ом самостоятельно в видимой области листа).


UPD1:
Вот эта процедура решает поставленную задачу, но мне не всё здесь понятно. Начнём с того, что я не могу найти подробное описание структуры ViewData.
Кто может прокомментировать решение или предложить своё? И насколько я прав по поводу активной ячейки в Calc'е?
Sub Test_getActiveCell
   Dim oCell As Object

   oCell =  getActiveCell(ThisComponent.CurrentController)
   MsgBox "Active Cell: " & oCell.AbsoluteName
End Sub

Function getActiveCell(oView)
Dim as1(), lSheet&,lCol&,lRow$, sDum as String,bErr as Boolean
   as1()  = Split(oView.ViewData, ";")
   lSheet = CLng(as1(1))
   sDum = as1(lSheet +3)
   as1() = Split(sDum, "/")
   on error goto errSlash
       lCol = CLng(as1(0))
       lRow = CLng(as1(1))
   on error goto 0
   getActiveCell = oView.Model.getSheets.getByIndex(lSheet).getcellByPosition(lCol,lRow)
   Exit Function

errSlash:
   if NOT(bErr) then
       bErr = True
       as1() = Split(sDum, "+")
       resume
   endif
End Function


И в каком случае bErr будет равно True ?

UPD2:
Загружено 2 скриншота (состояние до вызова "Select Data Area" (выделен весь столбец, первая видимая строка - 40) и после выделения).
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

economist

Цитата: eeigor от 13 марта 2021, 09:55Кстати, у меня в Linux эта штука Ctrl+* не работает. Кто подскажет, почему?

Работает в Ubuntu 20.04, 20.10, Raspbian OS. Ищите утилиту DE и удаляйте в ней глобальные хоткеи, которыми кишит каждый пингвиновый дистр.
Руб. за сто, что Питоньяк
Любит водку и коньяк!
Потому что мне, без оных, -
Не понять его никак...

eeigor

#2
ThisComponent.CurrentController.ViewData = "10026;308351;100;0;300192;21972;309924;0;1"
Скриншот 1 со свойствами (возможно, это не то, что нужно).

Код от Питоньяка, который частенько выдает ошибку (скриншоты 2 и 3). Свойства разные. Пока не ясно...
Разобрался: код Питьньяка (ниже) работает для Writer и скриншот 2 с его свойствами, для Calc набор свойств существенно шире (скриншот 4) и код Питьньяка выдает ошибку.
Но, похоже, нас интересует исходная строка: "10026;308351;100;0;300192;21972;309924;0;1"

Sub GetViewData
   Dim vViewData 'View data object
   Dim i%  'Index variable
   Dim j%  'Index variable
   Dim s$  'General string
   Dim vtemp  'View data for one object

   vViewData = ThisComponent.getViewData()
   REM For each view of the data
   For i = 0 To vViewData.getCount() - 1
       vtemp = vViewData.getByIndex(i)
       For j = 0 To UBound(vtemp)
           s = s & vtemp(j).Name & " = " & CStr(vtemp(j).Value) & CHR$(10)
       Next
       MsgBox s, 0, "View Data"
   Next
End Sub

Скриншот 2 под коду от Питоньяка.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

eeigor

#3
Цитата: eeigor от 13 марта 2021, 12:19.ViewData = "10026;308351;100;0;300192;21972;309924;0;1"
Где это найти?
Но в функции, которую я прошу пояснить (решение в стартовом сообщении), мы работаем с такой строкой:
100/60/0;0;tw:1832;1/39/0/0/0/0/2/0/0/0/39
где 0 - это индекс листа;
1 - это индекс столбца текущей ячейки выбранного листа;
39 - это индекс строки текущей ячейки выбранного листа.
В этом примере документ содержит только один лист с выбранной ячейкой ($Sheet1.$B$40).

Пример с выбранной ячейкой на втором листе ($Sheet2.$C$4):
100/60/0;1;tw:1832;1/47/0/0/0/0/2/0/0/0/39;2/3/0/0/0/0/2/0/0/0/0

Что означают другие значения, неизвестно...
Просьба пояснить работающее решение, ибо мой вопрос связан с отсутствием human readable документации.

as1() = Split(sDum, "/"): on error goto errSlash
Когда может произойти ошибка?
И что может означать "+" в строке с параметрами? Ответ: означает "сделано криво".
as1() = Split(sDum, "+")

UPD:
В общем, в чём-то разобрался сам, в чём-то помогли "соседи"...
Кому интересно, обратите внимание на разделитель параметров второго листа:
100/60/0;1;tw:1832;1/39/0/0/0/0/2/0/0/0/39;0+8192+0+0+0+0+2+0+0+0+8173
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

sokol92

Добрый день! Структура строки ViewData контроллера документа Calc описана здесь (строки 3158-3164). В чаcтности указано:

when rows bigger than 8192, "+" instead of "/"
Владимир.

eeigor

#5
Спасибо. Теперь, в принципе, всё ясно. И с плюсом разобрались. Но и я уже склонен был считать это не багом, а специфической особенностью, которой 15 больше лет. Это как-то связано исторически с увеличением количества строк на листе. Теперь это неважно. Процедура, которую я привел выше, отрабатывает эту "деталь" и используется очень давно.

Приведу строки, на которые вы сослались, для удобства другим (nTab - зд. индекс листа, "tab" значит "ярлычок"):
3158     // nZoom (until 364v) or nZoom/nPageZoom/bPageMode (from 364w)
3159     // nTab
3160     // Tab control width
3161     // per sheet:
3162     // CursorX/CursorY/HSplitMode/VSplitMode/HSplitPos/VSplitPos/SplitActive/
3163     // PosX[left]/PosX[right]/PosY[top]/PosY[bottom]

3164     // when rows bigger than 8192, "+" instead of "/"


Нас, соответственно, интересовали параметры: nTab активного листа и координаты активной (в фокусе) ячейки: CursorX, CursorY

Примечание. Я уже высказывался о необходимости "сбора" полезного кода. Вот эта функция (getActiveCell) - первый кандидат, а содержание этого поста - необходимый комментарий к ней.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

sokol92

#6
Цитата: eeigor от 14 марта 2021, 18:13Вот эта функция (getActiveCell) - первый кандидат
У указанной функции достаточно бедный интерфейс. Следующая функция выдает текущую (активную) ячейку любого листа из любого загруженного документа Calc. Без указания параметров она работает так же, как указанная выше.

Option Explicit
Option Compatible
' Возвращает активную (текущую ячейкy) документа (или листа).
' Параметры:
' oDoc  документ Calc. Если параметр опущен, то выбирается ThisComponent.
' sheet лист документа. Может быть объектом (Spreadsheet), числом (номер листа) или строкой (имя листа).
'       Если не задан, или равен пустой строке, то выбирается активный (текущий) лист документа.
'
' В случае ошибки возвращает Nothing.
Function Doc_ActiveCell(Optional ByVal oDoc As Variant, Optional ByVal sheet As Variant) As Object
  Dim arr, arr2, nSheet As Long, s As String, oSheet As Object
  On Local Error GoTo ErrLabel
  If IsMissing(oDoc) Then oDoc=ThisComponent
  arr=Split(oDoc.CurrentController.ViewData, ";")
 
  nSheet=-1
  If IsMissing(sheet) Then
    nSheet=arr(1)
  ElseIf IsObject(sheet) Then
    oSheet=sheet
    nSheet=oSheet.RangeAddress.Sheet
  ElseIf VarType(sheet)=V_STRING Then
    If sheet="" Then 
      nSheet=arr(1)
    Else
      nSheet=oDoc.Sheets.getByName(sheet).RangeAddress.Sheet
    End If
  ElseIf Vartype(sheet)=V_EMPTY Or Vartype(sheet)=V_NULL Then
     nSheet=arr(1)
  Else  ' числовой тип   
    nSheet=sheet
  End If
 
  If nsheet>=0 Then
    s=arr(3+nsheet)           
    arr2=Split(s, IIf(Instr(1, s, "+")>0, "+", "/"))
    Doc_ActiveCell=oDoc.Sheets(nSheet).getCellByPosition(CLng(arr2(0)), CLng(arr2(1)))
  End If 
ErrLabel:
End Function
Владимир.