LibreOffice - Basic.

Uwaga: przeglądasz tę stronę na urządzeniu o niewielkim ekranie (szerokość < 640px). Niektóre zamieszczone w artykule ilustracje i animacje mogą stać się nieczytelne po dopasowaniu ich do rozdzielczości tego ekranu.

Yestok.pl

Jerzy Moruś

© Wszystkie prawa zastrzeżone. Wykorzystanie całości serwisu lub jego fragmentów bez pisemnej zgody autora zabronione.

Basic w arkuszu Calc.



Opracowanie powstało z wykorzystaniem wersji oprogramowania istniejącej w trakcie jego pisania. Było to LibreOffice 7.1.5.


Wiele osób używających oprogramowania Apache OpenOffice i LibreOffice dopytuje jak napisać makra wspomagające działanie arkusza kalkulacyjnego. W języku polskim prawie nie ma dokumentacji na ten temat. Sam opis języka Basic jest dość dobrze udokumentowany w samej pomocy pakietu Apache OpenOffice i LibreOffice. Na podstawie tej wiedzy można spokojnie napisać własne funkcje dedykowane arkuszowi. Napisałem o tym oddzielne opracowanie, które można przeczytać tutaj: https://yestok.pl/lbo/y61.php. Brakuje natomiast informacji, w jaki sposób „dostać” się do zawartości arkusza po to, aby wymusić zaplanowane działania na jego zawartości. Dokumentacja na ten temat jest bardzo obszerna, ale dostęp do niej jest trudny i zniechęcający. To opracowanie ma na celu przedstawienie podstawowych wiadomości jak tego dokonać przy założeniu, że czytelnik potrafi już programować w języku Basic.

API

Programowanie w języku Basic nie umożliwia bezpośredniego działania na dokumentach pakietów biurowych LibreOffice i Apache OpenOffice (w dalszej części opracowania będę się posługiwał jednym określeniem: OpenOffice).

Dokumenty tego oprogramowania mają strukturę obiektową. OpenOffice udostępnia interfejs programistyczny w postaci Universal Network Objects (UNO), który OpenOffice dzieli na różne obiekty, te ze swojej strony pozwalają na kontrolowany przez programistów dostęp do pakietu Office.

Obiekty OpenOffice, takie jak akapity, arkusze kalkulacyjne czy czcionki, są dostępne w Basicu za pośrednictwem interfejsu API bazującego na wspomnianym powyżej UNO. Dzięki API można tworzyć, otwierać, modyfikować i drukować dokumenty.

Basic jest proceduralnym językiem programowania więc aby umożliwić mu korzystanie z właściwości obiektowych, wprowadzono do niego kilka nowych konstrukcji.

Najważniejsza to deklaracja zmiennej dla powiązanego z nią obiektu. Deklaracja dokonywana jest za pomocą standardowej instrukcji Dim.

Dim oNazwa As Object

Ten zapis deklaruje zmienną obiektową o nazwie oNazwa. Następnym krokiem jest zainicjowanie takiej zmiennej.

oNazwa = createUnoService("com.sun.star.frame.Desktop")

Powyższa instrukcja przypisuje zmiennej oNazwa odniesienie do nowo utworzonego obiektu com.sun.star.frame.Desktop. Przypomina to typ obiektu, jednak w terminologii UNO nazywa się usługą, a nie typem. Zgodnie z filozofią UNO obiekt oNazwa jest opisywany jako odniesienie do obiektu obsługującego usługę com.sun.star.frame.Desktop. Termin usługi używany w OpenOffice Basic odpowiada zatem terminom: typ i klasa używanym w innych językach programowania.

W Basicu zdefiniowano specjalną, zmienną globalną o nazwie ThisComponent, która umożliwia dostęp do tego dokumentu, w którym uruchomiono makro. ThisComponent będzie zatem odpowiadał całemu skoroszytowi w Calc-u, dokumentowi tekstowemu we Writerze, prezentacji w Impress-ie, czy grafice w Draw. Użycie tej zmiennej w instrukcji podstawienia zastępuje konieczność inicjowania zmiennej obiektowej poprzez UNO. To od tej zmiennej globalnej rozpoczyna się docieranie do wszystkich niezbędnych komponentów dokumentu.

Osobom, które do tej pory nie miały do czynienia z modelem obiektowym, należy się następujące wyjaśnienie. Z każdym obiektem związany jest pewien zestaw metod (methods) i właściwości (properties) a sam obiekt może składać się z innych obiektów, które także mogą składać się z kolejnych obiektów. Metody możemy utożsamiać z działaniami, które są związane z obiektem albo funkcjami, które taki obiekt wywołują do wykonania jakiegoś działania. Metody, tak jak funkcje, mogą wymagać parametrów i mogą zwracać wynik działania. Właściwości można interpretować jako cechy obiektu, które możemy przypisywać, zmieniać albo je odczytać.

Tutaj mały przykład takiego podejścia całkowicie odbiegający od problemów programowania. Niech obiektem będzie samochód. Jego właściwościami będą np.: „rodzaj pojazdu” (osobowy, terenowy, dostawczy, ciężarowy), „kolor nadwozia” czy „liczba drzwi”. Metodami są np.: „jedź do przodu”, „jedź w tył”, „stop”, „skręć w lewo”, „skręć w prawo”. Metoda „skręć” może wymagać parametru określającego, jaki ma być kąt skrętu. Samochód będzie się składał z innych obiektów np. „napęd”, „karoseria”, „układ jezdny”. Te obiekty mają swoje własne właściwości i metody. Np. „napęd” może mieć wśród wielu innych metodę „włącz bieg” (z parametrem jaki to ma być bieg) i właściwość „liczba obrotów” a „karoseria” może np. mieć metodę „otwórz bagażnik” i związaną z nią „zamknij bagażnik” oraz właściwość „przyciemniane szyby” (przyjmującą wartość logiczną „tak”, gdy szyby są przyciemniane i „nie”, gdy nie są).

To opracowanie jest poświęcone programowaniu w zakresie arkusza kalkulacyjnego, więc ThisComponent odpowiada całemu dokumentowi, nazywanemu też skoroszytem. Dokument (skoroszyt) składa się z podrzędnych obiektów: arkuszy (Sheets) albo zakresów (Ranges), te z kolei z kolejnych obiektów: – komórek (Cells), wierszy (Rows) czy kolumn (Columns). Dlatego bardzo często w definiowaniu kolejnych zmiennych obiektowych zobaczymy następującą sekwencję:

oDoc=ThisComponent ' oDoc opisuje cały dokument.
oSheet=oDoc.Sheets(0) 'oSheet opisuje pierwszy arkusz w obiekcie oDoc.
oCell=oSheet.getCellByPosition(2,4) 'oCell jest obiektem opisującym komórkę C5 w obiekcie oSheet.

W powyższej notacji każdy obiekt opisuje siebie jako element obiektu nadrzędnego. Taki zapis można zastąpić następująco:

oCell=ThisComponent.Sheets(0).getCellByPosition(2,4).

W tym zapisie poszczególne obiekty składowe dokumentu są rozdzielone znakiem kropki i nie wymagają deklarowania obiektów pośrednich. W rzeczywistości te zapisy przedstawiają użycie odpowiednich metod w stosunku do obiektu nadrzędnego.

Zmienne w Basicu.

Przeglądając różnego rodzaju przykłady programów, zauważysz czytelniku, że programiści nazwę zmiennej bardzo często poprzedzają jedną z małych liter. To nie jest przypadek. Litera ta ma informować osobę analizującą kod programu, z jakiego typu zmienną ma do czynienia. Ta litera nie deklaruje typu zmiennej, ma charakter opisowy i zwyczajowo jest stosowana następująco (chociaż tak naprawdę może zależeć od indywidualnych preferencji programisty):

Do rzeczywistego zdefiniowania typu zmiennej służy instrukcja Dim, w której po nazwie zmiennej pojawi się deklaracja typu w postaci: As typ zmiennej. Jeżeli w instrukcji Dim w jednym wierszu deklarujemy więcej zmiennych, to typ zmiennej należy podawać po nazwie każdej zmiennej. Nazwa zmiennej bez zadeklarowanego typu automatycznie przyjmuje typ Variant i status zmiennej niezainicjowanej (Empty). Taka zmienna może zostać zainicjowana dowolną wartością i przedstawia się dynamicznie przypisanym typem zależnym od wprowadzonej wartości.

Zapis: Dim nNazwa1, nNazwa2, nNazwa3 As Integer spowoduje, że typ Integer otrzyma tylko zmienna nNazwa3.

Typ zmiennej można określić bez użycia zapisu „As typ”, umieszczając na końcu nazwy znak deklaracji. Basic przewiduje możliwość zastosowania następujących znaków:

Znak deklaracji nie jest częścią nazwy zmiennej, dlatego nie można takiej samej nazwy zakończyć różnymi znakami deklaracji. Basic nie wymaga jawnego deklarowania zmiennych, wobec czego znak deklaracji może zostać dopisany przy pierwszym użyciu nazwy. W dalszej treści programu używa się nazwy bez znaku deklaracji, chociaż powtarzanie go nie jest błędem.

Natomiast próba użycia w kodzie programu nazw np.: nazwa% i nazwa$ skończy się komunikatem o błędzie: „Symbol nazwa został już inaczej zdefiniowany”, gdyż z punktu widzenia Basica tylko nazwa jest nazwą zmiennej, a nie można takiej samej nazwy zdefiniować wielokrotnie.

W końcu jest jeszcze jeden sposób deklarowania typu zmiennej. W Basicu, poza treścią podprogramu (Sub – End Sub) i funkcji (Function – End Function) a przed tymi strukturami (najlepiej na początku modułu) można umieścić instrukcje DefXxx, w której Xxx określa typ zmiennej.

W instrukcji można podać zakresy liter, które – jeśli będą pierwszą literą nazwy zmiennej – zadecydują o jej typie. Tymi instrukcjami są DefBool (Boolean), DefDate (Date), DefDbl (Double), DefInt (Integer), DefLng (Long), DefObj (Object) i DefVar (Variant).

Przykład: Instrukcja DefInt i-k, n spowoduje, że zmienne, których nazwa rozpoczyna się literami i, j, k oraz n, będą zmiennymi typu Integer. Typ zmiennej zgodny z zastosowaną funkcją DefXxx zostanie przypisany tylko wówczas, gdy zmienna nie zostanie zadeklarowana w sposób jawny, instrukcją Dim lub nie zostanie podana ze znakiem deklaracji.

Zatem przykładowo, jeśli użyto instrukcji DefInt i a następnie w podprogramie lub funkcji wpisano instrukcję podstawienia iNazwa="tekst" to wartością zmiennej będzie 0, gdyż zmiennej Integer przypisano wartość tekstową. Jeśli jednak wpisano iNazwa$="tekst", to zmienna iNazwa będzie typu String (przy użyciu nazwy dodano znak deklaracji typu), gdyby w module umieszczono deklarację zmiennej w postaci Dim iNazwa, to typem tak zadeklarowanej zmiennej będzie Variant, bo nie podano składnika As typ..

Współczesne języki programowania najczęściej nie wymagają deklarowania nazw zmiennych. Zmienna tworzona jest automatycznie w momencie jej pierwszego użycia. Może to powodować pewne problemy. Gdy programista w kodzie programu użyje nazwy zmiennej, w której nieopatrznie przestawił litery, zostanie ona potraktowana jako nowa zmienna. To z kolei najprawdopodobniej spowoduje błędne działanie algorytmu. Znalezienie błędu, szczególnie w dużych programach, jest zazwyczaj kłopotliwe. Basic umożliwia wymuszenie konieczności deklarowania zmiennych. Gdy w module pierwszą instrukcją jest Option Explicit, to każda zmienna musi zostać wcześniej zadeklarowana za pomocą instrukcji Dim. Dzięki temu, przy próbie wykonania makra, programista zostanie zawsze poinformowany o tym, że użył zmiennej, która nie została zadeklarowana.

Komunikat informujacy o niezadeklarowanej zmiennej.
Rys. 1: Komunikat o niezadeklarowanej zmiennej.

Działania na dokumencie.

Zobacz też working with documents
Poniższa część opracowania jest w zasadzie tłumaczeniem zawartości strony https://wiki.openoffice.org/wiki/Spreadsheet_common, uzupełnionej o dodatkowe informacje, które uznałem za przydatne.

Bieżący dokument.

Uzyskanie dostępu do bieżąco otwartego dokumentu zapewnia poniższa sekwencja:

Dim oDoc As Object
oDoc = ThisComponent

Jeżeli zmiennej oDoc przypisano obiekt ThisComponent, to każde użycie zmiennej obiektowej oDoc jest równoważne użyciu tej zmiennej globalnej.

Wczytanie dokumentu z dysku.

Wczytanie z dysku wybranego pliku i uzyskanie do niego dostępu.

Dim oDoc As Object
oDoc = StarDesktop.loadComponentFromURL(ConvertToURL("/home/robert/abc.xls"), "_blank",0,Array())

Sprawdzenie, czy dokument jest plikiem Calc-a. Funkcja może być przydatna, jeżeli chcemy zapewnić to, że makro wykona się tylko wtedy gdy zostanie wywołane w arkuszu kalkulacyjnym. Np. w kontekście wyżej pokazanego wczytania dokumentu.

Funkcja zwróci wartość True, gdy dokument jest arkuszem kalkulacyjnym.

Function CzyToCalc(oDoc As Object) As Boolean
CzyToCalc = oDoc.supportsService("com.sun.star.sheet.SpreadsheetDocument")
End Function

Odpowiedni kod programu może wyglądać np. tak:

If Not CzyToCalc(oDoc) Then Print "To nie jest dokument Calc" : Stop

Utworzenie nowego dokumentu.

Nowy dokument Calc utworzy instrukcja:

oDoc = StarDesktop.loadComponentFromURL("private:factory/scalc", "_blank", 0, Array())

Działania na arkuszu.

Uwaga ogólna. Jeżeli w dokumencie odwołujemy się do arkusza, komórki lub obszaru komórek, przez wskazanie ich numeru w obrębie obiektu, to musimy pamiętać o tym, że wszystkie indeksy zaczynają się od 0, a współrzędne są podawane w kolejności: kolumna, wiersz. Dlatego np. komórka C5 ma współrzędne (2,4)

Wybór arkusza aktywnego.

Gdy musimy wykonać działania na arkuszu, który jest w tym momencie arkuszem aktywnym:

oSheet=ThisComponent.getCurrentController.getActiveSheet

Ilość arkuszy w skoroszycie.

Informacja o tym, ile jest arkuszy w bieżącym skoroszycie.

i&=ThisComponent.getSheets.getCount

Należy pamiętać, że numerowanie arkuszy rozpoczyna się od 0.

Wybór arkusza poprzez indeks.

Konkretny arkusz można udostępnić przez indeks, czyli jego położenie w kolekcji arkuszy.

Dim oSheet As Object
oSheet = ThisComponent.getSheets.getByIndex(0)

Tu warto być może pokazać, jak to wszystko funkcjonuje w modelu obiektowym. W skoroszycie obiekt ThisComponent zawiera metodę getSheets, która po wykonaniu, jako wynik zwraca obiekt będący kolekcją wszystkich arkuszy. Ten zwrócony obiekt zawiera swoją metodę getByIndex, która zwraca jako obiekt arkusz wskazany w argumencie tej metody. W efekcie zmienna obiektowa oSheet reprezentuje konkretny arkusz skoroszytu.

Innym, uproszczonym sposobem realizacji tego zapisu jest:

oSheet = ThisComponent.Sheets(0) 

Indeks 0 jest przypisany pierwszemu arkuszowi w kolekcji bez względu na to, jaki arkusz na tym miejscu się znajduje. Kolejność arkuszy wyznaczają zakładki z ich nazwami, liczone od lewej do prawej, uwzględniając także arkusze ukryte.

Wybór arkusza przez nazwę.

Dim oSheet As Object
oSheet = ThisComponent.getSheets.getByName("Arkusz1")

Ta postać instrukcji nie ma wersji skróconej, chociaż można napisać takie przypisanie konkretnego arkusza:

oSheet=ThisComponent.Sheets.Arkusz1

Jednak ta postać przypisuje obiektowi oSheet wybrany arkusz statycznie. To znaczy że zmienna oSheet zachowa to przypisanie przynajmniej do końca wykonywania kodu w procedurze lub ponownej instrukcji przypisania innego obiektu. W rzeczywistości zależeć to będzie od sposobu zadeklarowania tej zmiennej. W przypadku pierwszego sposobu metoda getByName wymaga parametru podającego nazwę arkusza, więc można jako parametr podać zmienną zawierającą tę nazwę.

Uaktywnienie wybranego arkusza

Wybrany arkusz można uaktywnić na kilka sposobów.

Jeżeli oSheet jest już określony jako obiekt, to:

oDoc=ThisComponent
oSheet=...
oDoc.CurrentController.setActiveSheet(oSheet)

Można także napisac taki kod:

oDoc.CurrentController.setActiveSheet(oDoc.Sheets.getByName("Arkusz1"))

albo

oDoc.CurrentController.setActiveSheet(oDoc.Sheets(0))

Te instrukcje spowodują, że użytkownik zobaczy na ekranie wybrany arkusz.

Przechodzenie poprzez wszystkie arkusze.

Procedura pozwala przechodzić przez wszystkie kolejne arkusze skoroszytu. Wykorzystana tu została zmienna enumeratora.

Dim oSheet As Object
Dim eSheets As Object
eSheets = oDoc.getSheets.createEnumeration
While eSheets.hasMoreElements
	oSheet = eSheets.nextElement()
	MsgBox "Jesteś w arkuszu " & oSheet.Name & "."

	' Tu można wykonać działania w arkuszu.
Wend 

To samo działanie można oczywiście wykonać i w inny sposób, wykorzystując podaną przed chwilą metodę otrzymania liczby arkuszy oraz instrukcje FOR ... NEXT:

Dim oSheet As Object, oDoc As Object
oDoc=ThisComponent
For i&=0 to oDoc.Sheets.getCount-1
	oSheet=oDoc.Sheets(i)
	MsgBox "Jesteś w arkuszu " & oSheet.Name & "."

	' Tu można wykonać działania w arkuszu.
Next i

Utwórz nowy arkusz

Tworzenie nowego arkusza w skoroszycie.

oDoc = ThisComponent
oSheet = oDoc.createInstance("com.sun.star.sheet.Spreadsheet")
oDoc.Sheets.insertByName("Nowy arkusz",oSheet) 

Wiersze i kolumny.

Wybór wiersza lub kolumny.

Numery wierszy i kolumn są indeksowane od 0.

Dim oRow As Object
Dim oColumn As Object

' Wybierz pierwszy wiersz.
oRow = oSheet.getRows.getByIndex(0)

' Wybierz kolumnę B.
oColumn = oSheet.getColumns.getByIndex(1)

Właściwości

Dostęp do właściwości obiektu możliwy jest na dwa sposoby. W specyfikacji API najpierw umożliwiono dostęp przez dwie metody, metodę Get, do odczytania jej wartości i metodę Set do jej przypisania. Ogólna postać tych metod to getPropertyValue("Nazwa właściwości") do odczytania wartości i setPropertyValue("Nazwa właściwości",wartość) do nadania nowej wartości.

Drugi sposób to bezpośrednie odczytanie i nadanie wartości. Ogólny zapis tego sposobu to: x=oObiekt.Nazwa_właściwości do odczytanie wartości i oObiekt.Nazwa_właściwości=wartość, do nadania wartości.

W OpenOffice wszystkie właściwości odpowiadające mierzeniu, a więc takie jak np. szerokość, grubość, odległość, podawane są w setnych częściach milimetra. Same właściwości mogą być wartościami liczbowymi, logicznymi lub tekstowymi.

Właściwości kolumn.

Ustawienie szerokości kolumny na 1 cm.

oColumn.setPropertyValue("Width",1000)
oColumn.Width=1000

Pobranie wartości.

nszer&=oColumn.getPropertyValue("Width")
nszer&=oColumn.Width

Przypisanie kolumnie optymalnej szerokości.

oColumn.setPropertyValue("OptimalWidth",True)
oColumn.OptimalWidth=True

Ukrycie kolumny (False) lub pokazanie (True).

oColumn.setPropertyValue("IsVisible",False)
oColumn.IsVisible=True

Sprawdzenie, jaki jest status kolumny.

bStan=oColumn.getPropertyValue("IsVisible")
bStan=oColumn.IsVisible

Uwidocznienie ukrytej kolumny.

If Not oColumn.IsVisible Then oColumn.IsVisible=True

Wymuszenie pojawienia się kolumny na nowej stronie.

oColumn.setPropertyValue("IsStartOfNewPage",True)
oColumn.IsStartOfNewPage=True
Właściwości wiersza.

Ustawienie wysokości wiersza na 1 mm.

oRow.setPropertyValue("Height",100)
oRow.Height=100

Odczyt wysokości.

nwys&=oRow.setPropertyValue("Height")
nwys&=oRow.Height

Przypisanie do wiersza wysokości optymalnej.

oRow.setPropertyValue("OptimalHeight",True)
oRow.OptimalHeight=True

Ukrycie wiersza (False) lub widoczny (True).

oRow.setPropertyValue("IsVisible",False)
oRow.IsVisible=False

Wymuszenie pojawienia się wiersza na nowej stronie.

oRow.setPropertyValue("IsStartOfNewPage",True)
oRow.IsStartOfNewPage=True 

Wstaw, usuń wiersz.

Wstawienie 2 nowych wierszy po 4. wierszu (wiersze liczone są od 0)

oSheet.getRows.insertByIndex(3,2)

Usunięcie 3 wierszy. Pierwszym usuwanym jest wiersz 7.

oSheet.getRows.removeByIndex(6,3)

Wstaw, usuń kolumnę.

Wstawienie 2 kolumn przed kolumną czwartą (D).

oSheet.getColumns.insertByIndex(3,2)

Usunięcie 3 kolumn od kolumny G.

oSheet.getColumns.removeByIndex(6,3)

Działania na komórkach.

Wybór komórki.

Dim oCell As Object

Wybierz komórkę A1 (pierwszy indeks = kolumna, drugi indeks = wiersz)

oCell = oSheet.getCellByPosition(0,0)

Wybierz komórkę, podając jej adres w arkuszu.

oCell = oSheet.getCellRangeByName("A1")

Albo przez przypisaną komórce nazwę, w tym przykładzie „Dane”.

oCell = oSheet.getCellRangeByName("Dane")

Odwoływanie się do komórki poprzez nadaną jej indywidualną nazwę, pozwala na „dotarcie” do niej bez względu na to, czy jakieś wiersze lub kolumny zostały dodane do arkusza, lub z niego usunięte, z wyjątkiem usunięcia nazwanego elementu.

Pobierz komórkę aktualnie aktywną. Komórka nie musi być „podświetlona”.

oCell = ThisComponent.getCurrentSelection

Metoda getCurrentSelection tworzy obiekt obsługiwany przez trzy różne usługi, zależne od tego, co zostało zaznaczone w arkuszu. A więc, czy zaznaczono tylko jedną komórkę lub niczego nie zaznaczono (wówczas jako komórka zaznaczona jest traktowana komórka aktywna), czy zaznaczono pewien obszar komórek lub, czy zaznaczonych jest kilka obszarów. Zweryfikowanie tego, co jest zaznaczone w arkuszu, pokazałem w ostatnim przykładzie zamieszczonym w tym opracowaniu. Aby upewnić się, że zaznaczona jest tylko jedna komórka, należy wykonać instrukcję:

oCell.supportsService("com.sun.star.sheet.SheetCell")

Zwróci ona wartość True, gdy obiekt oCell może być obsłużony przez tę usługę. Jeżeli może, to oznacza to, że tylko jedna komórka jest zaznaczona.

Pozostałe przypadki wymagają sprawdzenia dwóch innych usług:

oCell.supportsService("com.sun.star.table.CellRange")

Zwróci ona wartość True jeśli zaznaczony jest tylko jeden obszar komórek. Kolejna usługa to:

oCell.supportsService("com.sun.star.sheet.SheetCellRanges")

Ta zwróci wartość True gdy w arkuszu zaznaczonych jest kilka obszarów a wówczas właściwość Count tego obiektu będzie mówiła o tym, ile jest zaznaczonych obszarów.

Jeśli komórka ma przypisaną indywidualną nazwę „Dane” (jak w przykładzie powyżej), to można ją pobrać bez wcześniejszego określania arkusza, w poniższy sposób:

oCell = ThisComponent.NamedRanges.getByName("Dane").getReferredCells

Zawartość komorki.

Każda komórka może zawierać tekst, liczbę albo formułę:

Wstawienie tekstu.

oCell.String = "To jest tekst"
oCell.setPropertyValue("String","To jest tekst")

Wstawienie liczby.

oCell.Value = 100
oCell.setPropertyValue("Value", 100)

Wstawienie do komórki bieżącej daty i bieżącego czasu (sposób prezentacji danych zależy od formatowania komórki, a „Now” jest nazwą funkcji daty i czasu w Basicu).

oCell.Value = Now
oCell.setPropertyValue("Value", Now)

Wstawienie formuły.

oCell.Formula = "=A2+A3"
oCell.setPropertyValue("Formula", "=A2+A3")

Określenie zawartości komórki.

Poniższa procedura pozwoli określić jaki rodzaj informacji znajduje się w komórce.

Select Case oCell.Type
	Case com.sun.star.table.CellContentType.EMPTY
		MsgBox "Komórka jest pusta."
	Case com.sun.star.table.CellContentType.VALUE
		MsgBox "Komórka zawiera liczbę."
	Case com.sun.star.table.CellContentType.TEXT
		MsgBox "Komórka zawiera tekst."
	Case com.sun.star.table.CellContentType.FORMULA
		MsgBox "Komórka zawiera formułę."
End Select 

Wygodniejszą, bo niewymagającą wielokrotnego przepisywania długiego ciągu com.sun.star.table.CellContentType, postacią kodu jest przykład pokazany poniżej:

With com.sun.star.table.CellContentType
	Select Case oCell.Type
		Case .EMPTY
			MsgBox "Komórka jest pusta."
		Case .VALUE
			MsgBox "Komórka zawiera liczbę."
		Case .TEXT
			MsgBox "Komórka zawiera tekst."
		Case .FORMULA
			MsgBox "Komórka zawiera formułę."
	End Select
End With

W tym miejscu zwracam uwagę na to, że nazwy typów muszą być wpisane wielkimi literami.

Zakres komórek.

Zakres komórek opisuje struktura CellRangeAddress, która składa się z pięciu elementów:

CellRangeAddress służy tylko do określenia zakresu komórek, które zostaną wykorzystane w operacjach dodawania, usuwania, łączenia, przenoszenia lub kopiowania komórek w arkuszu.

W tym miejscu wspomnę o tym, że interfejs API korzysta ze wstępnie zdefiniowanych struktur. Są to wyspecjalizowane struktury UNO. Należy do nich także wspomniana struktura CellRangeAddress. Jest ona czymś w rodzaju szablonu, który posłuży do utworzenia własnej zmiennej. Tę zmienna tworzymy na podstawie tego szablonu następująco:

Dim aRange As New com.sun.star.table.CellRangeAddress

Fraza As New wskazuje, na podstawie jakiego szablonu utworzona zostanie zmienna, w tym przypadku aRange. Oczywiście raz zadeklarowana zmienna może być wykorzystywana w różnych działaniach, wymaga jedynie wprowadzenie właściwych danych do elementów tej struktury.

Wstawianie komórek

Poniższy przykład wstawia komórki w obszarze B2:C3. Wszystkie znajdujące się w tym obszarze dane zostaną przesunięte w dół. Obiekt oSheet wykorzystuje metodę insertCells, która wymaga dwóch parametrów. Zakresu komórek, który musi być w pełni określony i stałej określonej przez com.sun.star.sheet.CellInsertMode informującej o tym, co ma się stać z otaczającymi komórkami.

Dim aRange As New com.sun.star.table.CellRangeAddress
' Pierwszy arkusz, obszar: B2:C3.
aRange.Sheet = 0
aRange.StartColumn = 1
aRange.StartRow = 1
aRange.EndColumn = 2
aRange.EndRow = 2
oSheet.insertCells(aRange,com.sun.star.sheet.CellInsertMode.DOWN)

Dopuszczalnymi stałymi com.sun.star.sheet.CellInsertMode są:

Usuwanie komórek

Poniższy przykład usuwa komórki z obszaru B2:C3. Wszystkie dane leżące poniżej tego obszaru zostaną przesunięte w górę, na zwolnione miejsce. W tym przykładzie obiekt oSheet wykorzystuje metodę removeRange.

Dim aRange As New com.sun.star.table.CellRangeAddress
' Pierwszy arkusz, obszar: B2:C3.
aRange.Sheet = 0
aRange.StartColumn = 1
aRange.StartRow = 1
aRange.EndColumn = 2
aRange.EndRow = 2
oSheet.removeRange(aRange,com.sun.star.sheet.CellDeleteMode.UP)

Dopuszczalnymi stałymi com.sun.star.sheet.CellDeleteMode są:

Przenoszenie i kopiowanie komórek.

Ta operacja wymaga określenia, jaki obszar ma zostać skopiowany lub przeniesiony oraz adresu, pod którym ma się znaleźć lewy górny róg kopiowanego lub przenoszonego obszaru. Kopiowany lub przenoszony obszar jest opisywany wspomnianą już wcześniej strukturą CellRangeAddress. Miejsce kopiowania lub przenoszenia określa struktura CellAddress. Opisuje ona komórkę docelową i zawiera trzy elementy:

Operacje te wymagają podania dwóch argumentów: miejsca docelowego, jako pierwszego argumentu i zakresu komórek, jako argumentu drugiego.

Poniższy przykład przenosi komórki z obszaru A2:B3 w pierwszym arkuszu do komórki A1 w drugim arkuszu. Obiekt oSheet wykorzystuje jedną z metod: moveRange, do przeniesienia komórek z zakresu lub copyRange, do skopiowania komórek z zakresu.

Dim aRange As New com.sun.star.table.CellRangeAddress
' A2:B3 w pierwszym arkuszu
aRange.Sheet = 0
aRange.StartColumn = 1
aRange.StartRow = 1
aRange.EndColumn = 2
aRange.EndRow = 2

Dim aAddress As New com.sun.star.table.CellAddress

' A1 w drugim arkuszu
aAddress.Sheet = 1
aAddress.Column = 0
aAddress.Row = 0
' Przesuń obszar wskazany w CellRangeAddress do nowego położenia.
oSheet.moveRange(aAddress,aRange)
' Skopiuj obszar wskazany w CellRangeAddress do nowego położenia.
oSheet.copyRange(aAddress,aRange) 

Obszar komórek.

Obszar komórek ma inne znaczenie niż zakres komórek. O ile zakres zawiera wyłącznie informacje o położeniu komórki lub komórek początkowej i końcowej, to obszar jest obiektem zawierającym właściwości i metody analogiczne do właściwości i metod arkusza, tyle że ograniczonymi do zdefiniowanego obszaru.

Obszar określany przez nazwę.

Można utworzyć obiekt złożony z wielu komórek, korzystając z naturalnych nazw komórek.

oRange = oSheet.getCellRangeByName("B2:C4")

Albo poprzez wykorzystanie nazwy obszaru nadanego w arkuszu.

oRange = oSheet.getCellRangeByName("Nazwa")

Obszar określany przez pozycje.

Obszar jest okreslany przez koordynaty komórek: lewej górnej i prawej dolnej, podając ich położenie w arkuszu w kolejności: kolumna, wiersz. Wiersze i kolumny są liczone od 0. Przykład dla zakresu B2:C4

oRange=oSheet.getCellRangeByPosition(1,1,2,3)

Dostęp do komórek obszaru.

Dostęp do poszczególnych komórek w obszarze jest analogiczny do wybierania ich z arkusza. Poniższy przykład udostępnia komórkę C4 znajdującą się w w obszarze B2:C4.

oCell = oRange.getCellByPosition(1,2)

Pozycja komórki w obszarze jest określana względem lewego górnego rogu tego obszaru i zaczyna się także od 0.

Jeżeli tak określony obszar jest potrzebny do wykonania działań omówionych w rozdziale „Zakres komórek”, to obiekt CellRangeAddress potrzebny w tych działaniach można otrzymać bezpośrednio z określonego już obszaru, poprzez właściwość RangeAddress:

oZakres=oRange.RangeAddress

Usuwanie wybranych treści.

Do wyczyszczenia komórek z różnych przypisanych im właściwości służy metoda clearContents. Wymaga ona jednego parametru określającego jakiego rodzaju właściwości mają zostać wyczyszczone. Tym parametrem są stałe com.sun.star.sheet.CellFlags określające te rodzaje. Poniższy przykład czyści komórki obszaru z tekstów i (lub) przypisanych im stylów formatowania warunkowego.

Dim nFlags As Long
oRange = oSheet.getCellRangeByName("B2:C3")
nFlags = com.sun.star.sheet.CellFlags.STRING + _
com.sun.star.sheet.CellFlags.STYLES
oRange.clearContents(nFlags) 

Można wykorzystać następujące stałe (stałe muszą być podane wielkimi literami):

Stałe można łączyć (dodawać) jak w przykładzie powyżej, tworząc złożony zestaw treści do usunięcia.

Każdej stałej jest przypisana wartość liczbowa, którą podałem w nawiasie na końcu opisu. Wartości te można wykorzystać i zamiast wpisywania złożonego kodu, zapisać to zadanie następująco:

oRange = oSheet.getCellRangeByName("B2:C3")
oRange.clearContents(68) 

Wartość 68 wynika z sumowania 4 (STRING) i 64 (STYLES). Tę instrukcję można też zapisać tak:

oRange.clearContents(64+4)

Formatowanie

Poniższe przykłady mogą być wykorzystane zarówno do formatowania pojedynczej komórki, jak i obszaru komórek.

Odwiedź stronę dotyczącą stylów w dokumentacji aby uzyskać informacje na temat zmiany formatów liczb.

Kolor tła i cienie.

Cień jest definiowany w strukturze com.sun.star.table.ShadowFormat, która ma cztery elementy.

Location opisują następujące stałe com.sun.star.table.ShadowLocation:

Kolor tła komórki.

Kolor jako parametr jest liczbą całkowitą długą. Wartość tej liczby zależy od wartości trzech bazowych składowych tworzących ten kolor. Tymi składowymi są kolory: czerwony (Red), zielony (Green) i niebieski (Blue), a każda z nich może przyjąć wartość od 0 do 255 (w systemie szesnastkowym od 00 do FF). Kolor można określić przy pomocy istniejącej w Basicu funkcji RGB(czerwony, zielony, niebieski) albo bezpośrednio jako wynik liczbowy. Kolor odpowiada liczbie szesnastkowej o strukturze RRGGBB, w której RR to szesnastkowa wartość dla składowej czerwonej, GG – dla składowej zielonej i BB – dla składowej niebieskiej. W Basicu każdą liczbę można podać w systemie szesnastkowym, zapisując ją z prefiksem „&h”. Przyjęło się, że wartości szesnastkowe zapisuje się z parzystą liczbą cyfr. Tak więc zapis &hC8FF00 odpowiada kolorowi, w którym składowa czerwona ma wartość &hC8 (200), składowa zielona: &hFF (255) a składowa niebieska: &h00 (0). Taka liczba odpowiada wartości dziesiętnej 13172480. Każdy z poniższych przykładów wstawi do komórki taki sam kolor tła. Ten przykładowy tekst znajduje się na tle koloru wymienionego powyżej.

oCell.setPropertyValue("CellBackColor",RGB(200,255,0))
oCell.CellBackColor=RGB(200,255,0)
oCell.CellBackColor=&hC8FF00
oCell.CellBackColor= 13172480

Nadanie koloru tła obszarowi opisanego obiektem oRange jest analogiczne.

oRange.setPropertyValue("CellBackColor",RGB(&hc8,&hff,0))
oRange.CellBackColor=RGB(200,200,200)

Przezroczystość tła. True – tło jest przezroczyste, czyli go nie widać, False – tło jest widoczne.

oCell.setPropertyValue("IsCellBackgroundTransparent",False)
oCell.IsCellBackgroundTransparent=False

Cień komórki.

Dim aShadow As New com.sun.star.table.ShadowFormat
aShadow.Location = com.sun.star.table.ShadowLocation.BOTTOM_RIGHT
aShadow.Color = RGB(255,255,0)
aShadow.ShadowWidth = 50
aShadow.IsTransparent = False
oCell.setPropertyValue("ShadowFormat",aShadow) 

Wyrównanie w poziomie.

Każda komórka zawiera właściwość HoriJustify, która może przyjąć następujące wartości stałej com.sun.star.table.CellHoriJustify:

Poziome wyrównanie do prawej

oCell.setPropertyValue("HoriJustify",com.sun.star.table.CellHoriJustify.RIGHT)
oCell.HoriJustify=com.sun.star.table.CellHoriJustify.RIGHT

Wyrównanie w pionie.

Każda komórka zawiera właściwość VertJustify, która może przyjąć następujące wartości stałej com.sun.star.table.CellVertJustify:

oCell.setPropertyValue("VertJustify",com.sun.star.table.CellVertJustify.CENTER)
oCell.VertJustify=com.sun.star.table.CellVertJustify.CENTER)

Kierunek tekstu.

Każda komórka zawiera właściwość Orientation, która może przyjąć następujące wartości stałej com.sun.star.table.CellOrientation:

oCell.setPropertyValue("Orientation",com.sun.star.table.CellOrientation.STACKED)
oCell.Orientation=com.sun.star.table.CellOrientation.STACKED)

Zawijanie tekstu, obrót tekstu.

Każda komórka zawiera właściwości: IsTextWrapped (Boolean) – wartość True oznacza, że tekst w komórce może być zawijany, i RotateAngle (Long) – wartość wyrażona w stopniach określa obrót tekstu.

Włączenie zawijania tekstu.

oCell.setPropertyValue("IsTextWrapped",True)
oCell.IsTextWrapped=True

Wartość obrotu. Jeśli liczba jest dodatnia, pierwszy znak tekstu jest punktem obrotu, a tekst obraca się w kierunku przeciwnym do ruchu wskazówek zegara (w lewo). Jeśli wartość jest ujemna, ostatni znak tekstu jest punktem obrotu, a tekst obraca się zgodnie z ruchem zegara (w prawo). Poniższy przykład obraca tekst o 90 st. w lewo (stopnie są także wyrażane w setnych częściach jednostki).

oCell.setPropertyValue("RotateAngle",9000)
oCell.RotateAngle=9000

Obracanie tekstu w komórce nie jest jednak dobrze oprogramowane. Teksty obrócone, właściwie prezentują się tylko w pozycjach 90° i 270° oraz -90° i – 270°, gdyż zachowują wyrównanie wewnątrz komórki i mieszczą się całkowicie wewnątrz nich.

Znajdź oraz Znajdź i zamień.

Podobnie jak w dokumentach tekstowych, także w arkuszach można wykonywać operacje wyszukiwania oraz wyszukiwania z zamianą zawartości komórek. Te działania można wykonać na całych arkuszach lub obszarach znajdujących się wewnątrz arkuszy.

Do wykonania tych operacji należy najpierw zdefiniować zmienną obiektową, nazywaną deskryptorem wyszukiwania i zamiany. Zmienną taką tworzą dwie metody, obsługujące arkusz albo obszar, są to: CreateReplaceDescriptor albo CreateSearchDescriptor. Niezależnie od użytej metody zmienna ma taką samą budowę i może zostać użyta zarówno jako argument wyszukiwania, jak i wyszukiwania z zamianą. W tej zmiennej obiektowej należy określić odpowiednie właściwości, decydujące o sposobie wyszukiwania i zamieniania. Tymi właściwościami są (wymieniam tu tylko moim zdaniem najważniejsze):

Za wykonanie operacji wyszukiwania i zamiany odpowiada metoda ReplaceAll. Wymaga ona podania jednego parametru, deskryptora. Metoda zwraca wynik, liczbę całkowitą, informującą ile operacji zamiany zostało wykonanych.

Do samego wyszukiwania służą trzy metody: FindAll, wyszukująca wszystkie dopasowane komórki, FindFirst, odnajdująca pierwsze wystąpienie szukanego tekstu, i metoda FindNext, wyszukująca następne wystąpienie tekstu. Te metody zwracają wynik w postaci obiektu zawierającego znaleziony tekst. Właściwość AbsoluteName tego obiektu zawiera pełny adres komórki. W przypadku metody FindAll właściwość AbsoluteName zawiera wykaz wszystkich adresów zawierających wyszukiwany tekst. Właściwość Count zawiera liczbę znalezionych wystąpień. Jeśli dopasowanie nie zostało znalezione, obiekt ma wartość Null.

Metody: FindAll i FindFirst wymagają jednego parametru – wspomnianego już deskryptora. Metoda FindNext wymaga dwóch argumentów, pierwszy to zmienna obiektowa, która określa pozycję, od której ma rozpocząć się dalsze wyszukiwanie. Drugim parametrem jest deskryptor. Pierwszym argumentem tej metody może być wynik otrzymany po wykonaniu FindFirst lub wynik poprzedniego wykonania FindNext.

Poniższy przykład wykonuje dwa działania: wyszukanie wybranego ciągu tekstowego w obszarze A1:E20 oraz znalezienie i zamianę na inny określonego tekstu we wszystkich arkuszach skoroszytu.

Sub Main
oDoc=ThisComponent
oSheet=oDoc.Sheets(0)
oRange=osheet.getCellRangeByName("A1:E20")
oDeskryptor=oRange.createSearchDescriptor
With oDeskryptor
	.SearchRegularExpression=False
	.SearchCaseSensitive=False
	.SearchWords=False
	.SearchString="xxx"
	.ReplaceString="&"
End With
k = 0
oWynik=oRange.findAll(oDeskryptor)
If IsNull(oWynik) Then GoTo BezDopasowania
Print oWynik.Count, oWynik.AbsoluteName
oWynik=oRange.findFirst(oDeskryptor)
If IsNull(oWynik) Then GoTo BezDopasowania
Print "Pierwszy adres " & oWynik.AbsoluteName
k = k+1
Do While not IsNull(oWynik)
	oWynik=oRange.FindNext(oWynik,oDeskryptor)
	If IsNull(oWynik) Then Exit Do
	k = k+1
	print k &"  " & oWynik.AbsoluteName
Loop
BezDopasowania:
Print k
With oDeskryptor
	.SearchRegularExpression=True
	.SearchWords=True
	.SearchString="ju.*"
	.ReplaceString="&"
	.SearchWords=True
End With
j = 0
For i=0 to oDoc.Sheets.Count -1
	oSheet=oDoc.Sheets(i)
	j=j+ oSheet.ReplaceAll(oDeskryptor)
Next i
Print j
End Sub

Wyszukiwany będzie ciąg znakowy „xxx”, bez względu na wielkość liter (SearchCaseSensitive=False) i bez względu na jego umiejscowienie w komórce (SearchWord=False). Ponadto nie będą rozpoznawane wyrażenia regularne (SearchRegularExpression=False). W deskryptorze określono element ReplaceString, chociaż, ze względu na zastosowana metodę Find, nie będzie on wykorzystany. W drugiej części tego przykładu, użyte zostaną wyrażenia regularne (SearchRegularExpression=True) a zgodna z wyszukiwaną treścią ma być zawartość całej komórki (SearchWords=True). Wyszukiwany ciąg to „ju.*”, czyli znajdowane będą komórki, których zawartość zaczyna się od znaków „ju”. Ponieważ zastosowana została metoda ReplaceAll to znaczenie ma ciąg zamiennika. Znak „&” przy włączonych wyrażeniach regularnych oznacza zastąp tym, co znalazłeś.

Funkcja identyfikująca rodzaj dokumentu.

Czasami przydatna jest możliwość identyfikacji, w jakim dokumencie zostało uruchomione makro. Pozwala to bowiem różnicować działanie makra w zależności od typu dokumentu. Funkcja została przedstawiona w dokumentacji Apache OpenOffice. Zamieszczam ją w tym opracowaniu, (po przetłumaczeniu na polski tekstów zawartych w oryginale) gdyż może się komuś przydać.

W tej funkcji HasUnoInterfaces sprawdza, czy dokument obsługuje interfejs com.sun.star.lang.XserviceInfo, bo tylko wówczas można wykorzystać metodę supportService do zweryfikowania typu dokumentu.

function fnWhichComponent(oDoc) as string
if HasUnoInterfaces(oDoc, "com.sun.star.lang.XServiceInfo") then
	if oDoc.supportsService _
		("com.sun.star.text.GenericTextDocument") then
		fnWhichComponent = "To jest dokument Writera."
	elseif oDoc.supportsService _
		("com.sun.star.sheet.SpreadsheetDocument") then
		fnWhichComponent = "To jest dokument Calca."
	elseif oDoc.supportsService _
		("com.sun.star.presentation.PresentationDocument") then
		fnWhichComponent = "To jest dokument Impressa."
	elseif oDoc.supportsService _
		("com.sun.star.drawing.GenericDrawingDocument") then
		fnWhichComponent = "To jest dokument Draw."
	else
		fnWhichComponent = "Oops To jakiś inny dokument."
	end if
else
	fnWhichComponent = "Nie ma żadnego dokumentu"
end if
End function

Użycie funkcji programu Calc w Basicu.

Czasami chcemy wykonać funkcję wbudowaną do programu Calc, gdyż jej wykonanie jest prostsze niż napisanie całego algorytmu. Jest to możliwe i realizuje to serwis "com.sun.star.sheet.FunctionAccess".

Tworzymy zmienną obiektową oFC (skrót od Funkcja Calca)
oFC = createUnoService( "com.sun.star.sheet.FunctionAccess" )

i na tym obiekcie wykorzystamy metodę CallFunction. Metoda wymaga dwóch parametrów, pierwszy, tekstowy, to nazwa funkcji. Ta nazwa musi być podana w wersji angielskiej. Drugim argumentem jest tablica zawierająca kolejne parametry wymagane przez żądaną funkcję Calca.

Zaprezentuję to na przykładzie wywołania funkcji: WYSZUKAJ.PIONOWO, angielską nazwą tej funkcji jest VLOOKUP. W Calc-u funkcja wymaga czterech argumentów.

Function FunkcjaCalc(poz)
oTabelka = ThisComponent.NamedRanges.getByName("aaaa").getReferredCells
oFA = createUnoService( "com.sun.star.sheet.FunctionAccess" )
vWynik = oFA.callFunction( "vlookup", array(poz,oTabelka,3,0) )
FunkcjaCalc = Iif(isEmpty(vWynik),”Nie ma pozycji”,vWynik)
End Function

W tym przykładzie wyszukiwana jest wartość „poz” w zakresie, który w arkuszu ma przypisaną nazwę „aaaa”. Zwracana jest zawartość trzeciej kolumny, czwarty parametr ma wartość 0, czyli dopasowanie musi być zgodne. Jeśli pozycja nie zostanie znaleziona, zmienna vWynik będzie miała wartość Empty i wynikiem będzie tekst „Nie ma pozycji”.

Zmienna oTabelka może zostać zadeklarowana przez każdy inny sposób określania obszaru, a więc i tak:

oTabelka = ThisComponent.Sheets(0).getCellRangeByName("aaaa")
oTabelka = ThisComponent.Sheets(0).getCellRangeByName("A1:C4")
oTabelka = ThisComponent.Sheets(0).getCellRangeByPosition(0,0,2,3).

Przykład: Wstawianie do aktualnie zaznaczonych komórek bieżącej daty i czasu.

Ten przykład zmodyfikowałem w stosunku do oryginału znajdującego się na wspomnianej już stronie internetowej. Przykład nie jest może szczególnie prosty dla osób początkujących, jednak ukazuje kilka bardzo interesujących rozwiązań.

Przede wszystkim przedstawia rozwiązanie całkowicie ogólne, a więc takie, które wypełni komórki niezależnie od tego, czy zaznaczono tylko jedną komórkę, jeden obszar komórek czy kilka takich obszarów. Za wstawienie daty do pojedynczej komórki odpowiada podprogram subSetCell. Ma on jeden argument: komórkę, do której należy wprowadzić datę. Do komórki wprowadzana jest wartość zmiennej vValue, i sposób formatowania, opisany zmienną nFormat. Obie te zmienne są zadeklarowane jako zmienne Public, gdyż ich deklaracje Dim znajdują się przed wywołaniem procedur. Zmienne Public, z aktualnymi wartościami są dostępne we wszystkich modułach biblioteki. Wartość daty, odpowiadającą bieżącemu momentowi, podaje funkcja Now. W Basicu za format wyświetlanych danych liczbowych odpowiada specyficzny kod, ten kod wyznacza specjalnie przygotowana funkcja fnGetNumberFormatId. Funkcja wymaga dwóch parametrów, dokumentu, w którym ma zostać zastosowany format i sposobu formatowania. Sama funkcja najpierw sprawdza, czy wymagany format nie jest już zdefiniowany w dokumencie. Jeśli tego formatu nie ma – tworzy go.

Procedura najpierw wstawia do zmiennej vValue wartość daty i czasu. Funkcja wyznaczająca kod formatu liczbowego jest wywołana w dalszej linijce kodu i do zmiennej nFormat wstawia ten kod. Obie te zmienne są dostępne z tymi wartościami we wszystkich podprogramach tej biblioteki. Dopiero teraz można omówić główny program subSetDate. Do rozpoznania co zostało zaznaczone w arkuszu, służą odpowiednie serwisy.

Pierwszy test dotyczy tego, czy w arkuszu jest zaznaczony tylko jedna komórka (komórka aktywna jest traktowana jak zaznaczona). Gdy test ma wartość True, wywołany zostanie moduł subSetCell. Wstawia on do przekazanej parametrem komórki wartość vValue i ustala formatowanie tej komórki.

Drugi test sprawdza, czy w arkuszu jest zaznaczony tylko jeden obszar wielokomórkowy. Jeśli wynikiem testu jest True, wywoływany jest podprogram subSetRange. Ten podprogram, dla każdej komórki należącej do takiego zakresu wywołuje podprogram subSetCell, który jak już wspomniano, wstawi do każdej z nich wartość i ustali jej formatowanie.

Trzeci i ostatni test sprawdza, czy w arkuszu jest zaznaczonych więcej niż jeden obszarów komórek. Jeśli wynikiem jest True, wywołany zostaje podprogram subSetRanges. Ten podprogram dla każdego zaznaczonego obszaru wywołuje podprogram subSetRange, który w efekcie wywoła subSetCell, wypełniając wszystkie zaznaczone komórki.

Dim vValue
Dim nFormat
Dim sFormat
sub subSetDate
vValue = Now
sFormat="DD/MM/YY"
oDoc = thisComponent
oSelection = oDoc.getCurrentSelection
nFormat = fnGetNumberFormatId(oDoc,sFormat)
if oSelection.supportsService("com.sun.star.sheet.SheetCell") then
'	 Sprawdzenie, czy zmienna oSelection dotyczy pojedynczej komórki.
	subSetCell(oSelection)
elseif oSelection.supportsService("com.sun.star.table.CellRange") then
'	 Sprawdzenie, czy zmienna oSelection dotyczy tylko jednego zakresu.
		subSetRange(oSelection)
elseif oSelection.supportsService("com.sun.star.sheet.SheetCellRanges") then
'	Sprawdzenie, czy zmienna oSelection dotyczy więcej niż jednego zakresu.
	subSetRanges(oSelection)
end if
end sub


sub subSetRanges(oRanges)
for i = 0 to oRanges.Count-1
	subSetRange(oRanges.getByIndex(i))
next i
end sub


sub subSetRange(oRange)
for i = 0 to oRange.getColumns.getCount - 1
	for j = 0 to oRange.getRows.getCount - 1
		subSetCell(oRange.getCellByPosition(i,j))
	next
next
end sub


sub subSetCell(oCell)
oCell.value = vValue
oCell.setPropertyValue("NumberFormat",nFormat)
end sub


function fnGetNumberFormatId(oDoc,sNumberFormat)
sCharLocale = oDoc.getPropertyValue("CharLocale")
nFormatId = oDoc.getNumberFormats.queryKey(sNumberFormat,sCharLocale,false)
if nFormatId = - 1 then 'Format nie został jeszcze zdefiniowany.
	nFormatId = oDoc.getNumberFormats.addNew(sNumberFormat,sCharLocale)
end if
fnGetNumberFormatId = nFormatId
end function 

Do powyższego kodu chciałbym dodać uwagę. Nie dotyczy ona wykonania kodu, lecz algorytmu. Omawiany wcześniej temat obszaru komórek mówił, że obszarowi można niektóre właściwości nadać od razu w całości. Do takich właściwości należą np. kolor tła, ale także i formatowanie. Zatem, zamiast w każdej komórce wykonywać instrukcję oCell.setPropertyValue("NumberFormat",nFormat) można po wywołaniu funkcji fnGetNumberFormatId dodać instrukcję: oSelection.NumberFormat=nFormat, która ustawi zadeklarowany format we wszystkich zaznaczonych komórkach. Nie można jednak w ten sposób nadawać wartości lub formuł, dlatego trzeba to wykonać dla każdej komórki oddzielnie.

Właściwości, metody, interfejsy.

Każdy obiekt UNO ma związane z nim właściwości, metody i interfejsy. Programista czasami chciałby wiedzieć jakie. Dlatego każdy z tych obiektów ma właściwości DBG, które dostarczają listę tych elementów. W Basicu można uzyskać te listy za pomocą następujących instrukcji (w przykładzie wykorzystałem obiekt arkusz).

Sub lista()
oDoc=ThisComponent
oSheets=ThisComponent.Sheets
oArkusz=oSheets.getByName("Arkusz1")
msgBox oArkusz.DBG_Properties
msgBox oArkusz.DBG_Methods
msgBox oArkusz.DBG_SupportedInterfaces
End Sub

Lista jest jednak mało „przyjazna” dla użytkownika, gdyż właściwości i metody są wypisane jako ciąg znaków rozdzielony średnikami, co znacznie utrudnia czytanie takiego komunikatu.

Na potrzeby analizy metod i właściwości powstały dwa niezależne narzędzia XrayTool60 oraz MRI, pozwalające wyświetlić wszystkie dostępne informacje o obiekcie. Przy spełnieniu opisanych w dokumentacji warunków instalacji możliwy jest także dostęp do odpowiedniej dokumentacji API. Xray jest przygotowane w postaci pliku tekstowego zawierającego opis tego narzędzia z możliwością zainstalowania go we własnym pakiecie Office. To narzędzie można pobrać z tego miejsca: link do XrayTool60 [Dostęp 26.08.2021 15:00].

Oprogramowanie XrayTool60 opracował francuski programista Bernard Marcelly. Jego strona internetowa widoczna jest w podanym linku. Strona ta wydaje się być nieaktywna od dłuższego czasu. Ostatnie datowane materiały pochodzą z maja 2016 roku. W tym serwisie znajduje się informacja o polskiej instrukcji do tego narzędzia. Niestety próba jej pobrania kończy się niepowodzeniem. Udało mi się skontaktować z autorem tłumaczenia. Dla niego jest to stara sprawa, z 2012 roku, ale obiecał mi, że przeszuka swoje zasoby i jeśli znajdzie materiały, które można upublicznić, prześle mi je. Gdy je otrzymam zamieszczę je w serwisie.

MRI jest przygotowane w postaci pliku rozszerzenia oxt, do pobrania z repozytorium rozszerzeń. Instaluje się, dodając dwa nowe polecenia w menu „Narzędzia → Dodatki”. W repozytorium nie ma jednak najnowszej wersji 1.3.4. Znajduje się ona pod tym adresem: kliknij aby pobrać to rozszerzenie [Dostęp 26.08.2021 15:40].

O ile aby otrzymać informacje z XrayTool trzeba wprowadzić odpowiedni kod do swojego makra (a po wykorzystaniu informacji, kod ten usunąć), to MRI działa dynamicznie i pozwala podczas normalnej pracy uzyskiwać niezbędne informacje.