Viser opslag med etiketten programmering. Vis alle opslag
Viser opslag med etiketten programmering. Vis alle opslag

søndag den 28. april 2019

Programmering med Python

Programmeringssproget Python vinder stor udbredelse for tiden, hvilket måske hænger sammen med at det er et programmeringssprog som er open source, og som ikke er afhængig af et bestemt operativsystem. Python er, lige som Java, helt platformsuafhængig. Til gengæld forudsætter afvikling af programmerne, at brugeren har et afviklingsmiljø installeret.

Python bruges i øvrigt som makrosprog i mange open source programmer, f.eks. Inkscape, Gimp og LibreOffice.

Når du downloader og installerer LibreOffice, får du helt automatisk et Python afviklingsmiljø med i pakken.

Python i LibreOffice

Hvis du programmerer makroer i LibreOffice Basic, benytter du den indbyggede IDE (integrated Development Environment), men til Python må du selv vælge hvilket udviklingsmiljø du gerne vil bruge. Der er mange at vælge imellem. Geany, PyCharm og Eclipse er bare eksempler.

Der er tre ting du skal være opmærksom på, inden du går i gang:
  1. Pythonfilen skal være placeret i dit brugerkatalog under user/Scripts/python/ eller en folder derunder. På min PC er det her: /home/leif/.config/libreoffice/4/user/Scripts/python
  2. Den makro (Python-funktion) du gerne vil stille til rådighed i LibreOffice brugergrænsefladen skal "udstilles" ved at tilføje navnet til Tuple-variablen g_exportedScripts.
  3. For at tilgå dokumentobjekter i LibreOffice skal du benytte XSCRIPTCONTEXT ved at kalde en af funktionerne getDocument (), getInvocationContext (), getDesktop (), getComponentContext (). Når først du har gjort det, kan du tilgå LibreOffice ad bagvejen, altså programmere makroer.
Her er et klassisk eksempel (Hello world):

# -*- coding: utf-8 -*-
# !/usr/bin/env python

import uno

def HelloWorld():
    """Write Helo World in Writer"""

    try:
        ctx = remote_ctx                   # use in development
    except:
        ctx = uno.getComponentContext()    # use in production

    # get desktop
    desktop = ctx.getByName("/singletons/com.sun.star.frame.theDesktop")
    # get document
    document = desktop.getCurrentComponent()
    # access the document's text property
    text = document.Text
    # create a cursor
    cursor = text.createTextCursor()
    # insert the text into the document
    text.insertString(cursor, "Hello World", 0)

HelloWorld()

g_exportedScripts = HelloWorld,


Du kan læse mere om Python i LibreOffice her: https://help.libreoffice.org/6.3/en-US/text/sbasic/python/main0000.html

Du kan læse mere om XSCRIPTCONTEXT her: https://wiki.openoffice.org/wiki/Python/Transfer_from_Basic_to_Python

Der er masser af muligheder på internettet for at lære programmering med Python. Du kan f.eks. tage et komplet kursus på Massachusetss Institute of Technology (MIT) helt gratis: https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/

lørdag den 10. december 2016

Mere om fejlhåndtering

Jeg har tidligere skrevet om fejlhåndtering i arktiklen "Håndtering af fejl" her: http://libreofficedk.blogspot.dk/2016/12/handtering-af-fejl.html

I den tidligere artikle beskrev jeg eksempler på håndtering af konkrete fejl, simpelthen ved at undersøge om en "ulovlig" situation er opstået, umiddelbart før en kritisk handling udføres. I denne artikel vil jeg komme ind på nogle af de indbyggede funktioner LibreOffice har til fejlhåndtering:
  • Erl: Linjenummer hvor seneste fejl opstod
  • Err: Fejlnummer for senestre opståede fejl
  • Error eller Error(error_number): Fejlbesked (tekst) for seneste fejl eller for et specifikt fejlnummer
  • On Error:
  • On Error Resume Next: Ignorer fejl og fortsæt makroen.
  • On Error GoTo 0: Nulstil den aktuelle error handler.
  • On Error GoTo LabelName: Ved fejl gå til ...[etikket]
  • CVErr: Konverterer et udtryk til et fejlobjekt

Lad os se på et par eksempler:

Sub test1
  On Error Goto BadError

Print UBound(Array1)

BadError:
  If Err <> 0 Then
    Print err
    Print erl
    Print Error(err)
End if
End sub


Der vil opstå en fejl, da variablen Array1 ikke eksistrer og derfor ikke har et UBound. Linjen Print UBound(Array1) fejler.

BadError svarer:
  • 91 (Fejlnummer)
  • 36 (Linjenummer)
  • Objektvariabel ikke initialiseret. (Fejlbesked som tekst) .
Her er et lidt mere omfattende eksempel:

Sub ExampleError
  On Error Goto BadError        'Sæt error handleren op
  Print 1/ CInt(0.2)            'Udfør noget ulovligt

BadError:                       'Error handler starter her
  Dim s As String               'Akkumuler beskeden
  Dim oldError As Integer       'Fejlnummeret
  Dim lineNum As Integer        'Linjenummeret
  If Err <> 0 Then              'Tjek Err
    oldError = Err              'Gem fejlnummeret
    lineNum = Erl               'Gem linjenummeret
    s = "Før nulstillet error handler " & CHR$(10) &_
        "Fejlnummer " & Err & " opstod ved linjenummer " & Erl & CHR$(10) &_
        "Fjlbesked: " & Error() & CHR$(10)
  End If
  On Error Goto 0               'Nulstil error handler
 
  REM Nu er der ingen ting at vise
  s = s & CHR$(10) & "Efter nulstillet error handler:" & CHR$(10) &_
      "Fejlnummer " & Err & " opstod ved linjenummer " & Erl & CHR$(10)
     
  REM Use the saved information
  s = s & CHR$(10) & "Fejlinformation blev gemt i variable " & CHR$(10) & "så vi kan finde dem frem senere:" & CHR$(10) &_
      "Fejlnummer " & oldError & " opstod ved linjenummer " &_
      lineNum & CHR$(10) & "Fjlbesked: " & Error(oldError)
  MsgBox s, 0, "Error Handling"
End Sub



Håndtering af fejl

I de tidligere artikler har jeg ikke gjort meget ud af at håndtere eventuelle fejlsituationer. I denne artikle ser vi på håndtering af meget konkrete fejlsituationer, som vi kan forudse.

En typisk situation som vi ALTID skal tage stilling til, er hver gang vi skal udføre matematisk division. Det kan nemlig meget let gå galt, hvis vi ikke først lige undersøger om vi har 0 (nul) i nævneren (det er forbudt at dele med nul). Et overordnet råd er derfor ALTID kontrolle nævneren før du udfører division.

Et andet eksempel er i artiklen Tekstdokumenters titel (læs artiklen her: https://libreofficedk.blogspot.dk/2016/12/tekstdokumenters-titel.html ), hvor jeg lavede følgende meget simple program:

Sub TitelFraFelt
oMaster = thisComponent.getTextFieldMasters().getByName("com.sun.star.text.fieldmaster.User.Titel")
thisComponent.DocumentProperties.Title = oMaster.Content
End Sub


Der er umiddelbart to situationer som vi bør tage stilling til:
  • Hvad nu hvis dokumentet ikke er et tekstdokument?
  • Hvad nu hvis feltet "Titel" ikke eksisterer?
Det første punkt bør vi altid kontrollere, når vi arbejder med makroer specifikke for tekstdokuemnter.

Denne lille rutine kontrollerer for om dokumentet er et tekstdokument:

If NOT ThisComponent.supportsService("com.sun.star.text.TextDocument") Then
  MsgBox "Det aktuelle dokument er ikke et Writer tekstdokument", 48, "Fejl"
  Exit Sub
End If


Det andet punkt kan vi løse vha. denne linje:

If thisComponent.getTextFieldMasters().hasByName("com.sun.star.text.fieldmaster.User.Titel") then

Makroen i artiklen kunne f.eks. se sådan ud:

Sub TitelFraFelt
rem Kontroller om det er et tekstdokument
If NOT ThisComponent.supportsService("com.sun.star.text.TextDocument") Then
  MsgBox "Det aktuelle dokument er ikke et Writer tekstdokument", 48, "Fejl"
  Exit Sub
End If
rem Kontroller om der findes et felt med navnet Titel
If thisComponent.getTextFieldMasters().hasByName("com.sun.star.text.fieldmaster.User.Titel") then
    oMaster = thisComponent.getTextFieldMasters().getByName("com.sun.star.text.fieldmaster.User.Titel")
    thisComponent.DocumentProperties.Title = oMaster.Content
    Else
      MsgBox "Feltet Titel findes ikke", 48, "Fejl"
    End if
End Sub




tirsdag den 29. november 2016

Månedens makrokursus

Også i denne måned har jeg skrevet en række artikler, som forhåbenlig kan hjælpe nogle flere i gang med makroprogrammering. Artiklerne er en skøn blanding af teori og praktiske eksempler:

Calc makro: Kopiere indhold: http://libreofficedk.blogspot.dk/2016/11/calc-makro-kopiere-indhold.html
Fortløbende nummerering: http://libreofficedk.blogspot.dk/2016/11/fortlbende-nummerering.html
Fortløbende nummerering (fortsat): http://libreofficedk.blogspot.dk/2016/11/fortlbende-nummerering-fortsat_8.html
Arrays: http://libreofficedk.blogspot.dk/2016/11/makroe-med-arrays.html
UNO-objekter: http://libreofficedk.blogspot.dk/2016/11/uno-objekter.html
Hvad-nu-hvis: http://libreofficedk.blogspot.dk/2016/11/hvad-nu-hvis.html
Dialog med brugeren: http://libreofficedk.blogspot.dk/2016/11/dialog-med-brugeren.html
Dialog med brugeren (fortsat): http://libreofficedk.blogspot.dk/2016/11/dialoger-og-makroer.html
Aflæse indstillinger med makro: http://libreofficedk.blogspot.dk/2016/11/afls-indstillinger-med-makro.html
Writer: Aflæse markeret tekst: http://libreofficedk.blogspot.dk/2016/11/undersge-markeret-tekst-i-writer-med.html

Du kan finde alle artiklerne her: http://libreofficedk.blogspot.dk/search/label/makro

Artiklerne er tænkt som inspiration til dig, så du selv får lyst til at gå videre med at programmere.

    Er der interesse for flere artikler eller lektioner?
    Synes du artiklerne er udformet på en forståelig måde?
    Er der konkrete emner du gerne vil have jeg tager op (jeg lover ikke noget)?

Send mig en mail eller skriv dit spørgsmål ind i vores forum her: http://forum.liboforum.dk/viewforum.php?f=7&sid=1cae4b6593c9eb783083d615cd986335

lørdag den 19. november 2016

Makroer med arrays

Jeg har i flere artikler benyttet arrays til at håndtere data. En variabel er en midlertidig erstatning for en værdi, hvor et array er en midlertidig erstatning for en liste af værdier.

I eksemplerne tildeles variable og arrays værdier i koden, men i virkeligheden vil værdierne være generiske, og f.eks. komme fra celler i et regneark, en InputBox eller blive indlæst fra en fil.

Name="John"    'Variabel
Names()=Array("Peter", "Poul", "Birthe", "Anne") 'Array


Jeg navngiver array'et Names() men kunne lige så godt have skrevet Names.

Vi kan undersøge om en variabel indeholder et array:

IsArray (Names())        'True

Indholdet af et array kan vi finde frem med et indeks:

Print Names(1)            '"Poul"

Som standard er et array indeks-nul, hvilket betyder at det første element i array'et har indeks 0. Det er muligt at anvende indeks 1, men det er upraktisk, og i resten af artiklen antager vi at vi benytter indeks-nul.

LBound(Names()) udtrykker den nederst grænse, hvilket normalt er 0.

UBound (Names()) udtrykker den øverste grænse, altså hvor mange elementer der er (men husk at lægge en til, for at kompensere for indeks-nul).

UBound af et tomt array returnerer -1:

NavneArray=Array()
Print UBound(NavneArray)     '-1 hvis array er tom


Et lidt større eksempel:

Sub SimpleArray
    Names=Array("Peter", "Poul", "Birthe", "Anne")
    Print IsArray(Names)    'True
    Print Names(1)        'Poul
    Print LBound(Names)    '0
    Print UBound(Names)    '3
End Sub


Arrays er tollerante for datatyper:

Names=Array("Peter", "Poul", "Birthe", "Anne", 15)
Print UBound(Names)    '4
Print Names(4)    '15


Vi kan også tildele elementer på en anden måde:

Dim Persons(4)
Persons(0) = "Peter" : Persons(1) = "Poul": Persons(2) = "Birthe" : Persons(3) = "Anne" : Persons(4) = 15


Hvis du ønsker at tilføje et nyt element til et eksisterende array, skal du først gøre plads (redimensionere):

ReDim Persons(5)
Persons(5) = "John"


Et array kan også indeholde flere dimensioner:

Sub ComplexArray
Dim Persons(3,2)
Persons(0,0)="Peter":Persons(1,0)="Poul":Persons(2,0)="Birthe":Persons(3,0)="Anne"
Persons(0,1)=17:Persons(1,1)=21:Persons(2,1)=45:Persons(3,1)=12
Persons(0,2)="Mand":Persons(1,2)="Mand":Persons(2,2)="Kvinde":Persons(3,2)="Kvinde"

For i=0 to UBound(Persons(),1)          'Øvre grænse første dimension
    v=""                                'Nulstil
    For j = 0 to UBound(Persons(),2)    'Øvre grænse anden dimension
        v= v+Persons(i,j) + chr(13)     'chr(13) = linjeskift
    Next j
    MsgBox(v)
Next i
End Sub

Vi kan konvertere tekst til arrray og omvendt. 
Split opdeler en tekststreng, og returnerer et array.
Join samler elementerne fra et array og returnerer en tekststreng.

Eksempel:

Sub Split_Join
Navneliste="Peter, Poul, Birthe, Anne"    'Tekststreng
Print IsArray(Navneliste)
NavneArray()=Split(Navneliste,",")
Print IsArray(NavneArray)
For i = 0 to UBound(NavneArray)
    NavneArray(i) = Trim(NavneArray(i))   'Fjern mellemrum
Next i
NyNavneListe()= Join(NavneArray,"#")      'Ny tekststreng
Print NyNavneListe                        'Peter#Poul#Birthe#Anne
End Sub

tirsdag den 15. november 2016

Dialoger og makroer

Jeg har skrevet om dialog med brugeren i en tidligere artikel her: http://libreofficedk.blogspot.dk/2016/11/dialog-med-brugeren.html

Men den vigtigste og mest avancerede form for dialog med brugeren er...dialoger.

Når vi skal arbejde med dialoger i LibreOffice makroer, skal vi igennem en række trin:

1) Designe dialogen
2) Programmere eksekvering af dialogen
3) Programmere eksekvering af OK og aflæse brugerens valg
4) Programmere Annuller
5) Knytte makroerne til (OK og Annuller) knapper i dialogen

Vi starter fra toppen og hen ad vejen lærer vi begreberne lidt hen ad vejen.

1) Oprette og designe dialog

Du kan oprette en dialog fra LibreOffice med Funktioner - Makroer - Administrer dialoger... eller fra Funktioner - Makroer - Administrer makroer...

Fra makroeditoren ser du i bunden en omvendt fane, med navnet "Module1" Der er fordi du har et enkelt makromodul med navnet Module1. Højreklik og vælg Indsæt - BASIC-dialog.

Nu er der endnu en omvendt fane, sandsynligvis med navnet "Dialog1". Samtidig ser du i det store vindue en dialog-editor. Nederst i skærmbilledet ser du en palette af værktøj, som du kan bruge til at designe din dialog. Her finder du også de kontrolelementer, som du kan indsætte i dialogen. På værktøjslinjen er også et ikon med navnet Forhåndsvisningsdialog, som du kan bruge til at vise hvordan dialogen ser ud når den eksekveres.


Hvis du markerer dialogen, ser du i venstre side af skærmbilledet et område, hvod du kan indtaste forskellige egenskaber om dialogen, blandt andet give den et nyt navn (ikke synligt i dialogen) eller tildele en titel (synligt i dialogen).

Nu kan du tilføje kontrolelementer til dialogen, blandt andet felter og knapper. Eksperimenter med forskellige typer af kontrolelementer.

Rullelisten fodrer du med valgmuligheder i egenskaben Listeelementer - en valgmulighed per linje.




2) Eksekvere af dialogen

Dialogen kan vi eksekvere fra programkode i BASIC. Der er to forskellige måder:

Sub Dialog1Show
    BasicLibraries.LoadLibrary("Tools")
    oDialog1 = LoadDialog("Standard", "Dialog1")
    oDialog1.Execute()
End Sub

Hvis dialogen opbevares i et dokument, skal du anvende GlobalScope.BasicLibraries.LoadLibrary("Tools") i den første linje.

Uden brug af "LoadDialog" kan du kalde koden som følger:

Sub Dialog1Show
    DialogLibraries.LoadLibrary("Standard")
    oDialog1 = CreateUnoDialog( DialogLibraries.Standard.Dialog1 )
    oDialog1.Execute()
End Sub

Sidstnævnte eksempel er sikrest og lettest at få til at fungere, specielt hvis makroen benytter dokumentet som makrobibliotek.

3) Programmere eksekvering af OK og aflæse brugerens valg

Eksekvering af OK-knappen skal igangsætte en række begivenheder, som skal foregå i den rigtige rækkefølge.

Først skal vi finde data i de forskellige kontrolelementer for til sidst at lukke dialogen.

Når vi skal aflæse hvad brugeren har udfyldt i formularen, skal vi kende lidt til hvordan LibreOffice "tænker". Dialogen har nogle egenskaber, som vi kan aflæse af variablen oDialog1. F.eks. er oDialog.Title= "Min helt egen dialog" og oDialog1.Size.Height=235.

Dialogens indhold derimod, finder vi i dialogens model.

oModel= oDialog.Model

F.eks. kan vi ændre dialogens baggrundsfarve sådan:

oModel.BackgroundColor = rgb(255,255,255)

De enkelte kontrolelementer kan vi få fat i med deres navn:

oModel.getByName("TextField1")

Det vi skal nå frem til er at aflæse indholdet af de forskellige kontrolelementer. Vi skal oprette en ny subrutine, som eksekveres når OK-knappen trykkes på. Fordi variablen oDialog1 blev dimensioneret udenfor subrutinen, er den global og eksisterer altså stadig når vi kommer ud af den subrutine som åbner dialogen.

Sub OK_trykket
    oModel= oDialog1.Model
    Navn= oModel.getByName("TextField1").Text

    Print Navn

    oDialog1.endExecute()
End sub

Det skal vi gøre for hver enkelt felt brugeren kan udfylde. For tekstfeltet (TextField1) skal vi hente indformationen med .text, men for de øvrige felttyper er det lidt anderledes.

Radioknapperne håndterer vi ved for hver enkelt at aflæse .State:

    Linux= oModel.getByName("OptionButton1").State
    Mac= oModel.getByName("OptionButton2").State
    Windows= oModel.getByName("OptionButton3").State

Listboksen (Region) er lidt speciel, fordi vi skal håndtere at der ikke behøver at være noget vlagt. Heldigvis er det forholdsvis let. Desuden får vi i første omgang kun oplysninger om hvilket element (nummer i rækken) der er valgt (RegionNumber). Det skal vi så efterfølgende oversætte til noget brugbar tekst. Det gør vi sådan:

    Region= oModel.getByName("ListBox1").StringItemList(RegionNumber)

Tjekboksen Nyhedsbrev aflæser vi også med .State

Til sidst skal vi så aflevere informationerne, hvilket jeg af praktiske årsager går ved at vise en MsgBox. Måske skulle vi indsætte resultatet i celler i et regneark? Det må blive en anden dag.

Hele makroen (som starter med den subrutine som eksekverer dialogen) tilknyttes OK-knappen. I dialog-editoren vælger du OK-knappen og kigger i venstre side. Her ser du egenskaber for knappen. Vælg fanen "Hændelser" og find hændelsen "Museknap trykket". Tilknyt makroen til hændelsen.

4) Programmere Annuller-knappen

Programmet som vi skal bruge til at lukke dialogen uden videre er temmelig meget mere overskuelig:

Sub CloseDialog
  oDialog1.endExecute()
End Sub

Denne makro skal så tilknyttes til Annuller-knappen.

Hele makroen


Herunder er hele makroen:

REM  *****  BASIC  *****


Dim oDialog1 as Object

Sub Dialog1Show
    DialogLibraries.LoadLibrary("Standard")
    oDialog1 = CreateUnoDialog( DialogLibraries.Standard.Dialog1 )   
    oModel= oDialog1.Model
    oModel.BackgroundColor = rgb(255,255,255)
    oDialog1.Execute()
End Sub

Sub CloseDialog
  oDialog1.endExecute()
End Sub

Sub OK_trykket
    oModel= oDialog1.Model
    Navn= oModel.getByName("TextField1").Text
    Linux= oModel.getByName("OptionButton1").State
    Mac= oModel.getByName("OptionButton2").State
    Windows= oModel.getByName("OptionButton3").State

    If ubound (oModel.getByName("ListBox1").SelectedItems) >-1 then
        RegionNumber=oModel.getByName("ListBox1").SelectedItems(0)
        Region= oModel.getByName("ListBox1").StringItemList(RegionNumber)
    End if

    Nyhedsbrev=oModel.getByName("CheckBox1").State

    oDialog1.endExecute()

    MsgBox("Navn: " & Navn & Chr(13) & "Linux: " & Linux & Chr(13) & "Mac: " & Mac  & Chr(13) & "Windows: " & Windows  & Chr(13) & "Region: " & Region & Chr(13) & "Nyhedsbrev: " & Nyhedsbrev)

End sub




søndag den 13. november 2016

Dialog med brugeren

Når vi programmerer makroer i LibreOffice, vil vi fra tid til anden, gerne involvere brugeren. Resultatet af brugerens valg kan vi så anvende til at regne videre eller dirigere programmet hen i den rigtige retning.

Vi har flere forskellige metoder:

Print

Print-funktionen er den simpleste måde at kontakte brugeren på. Du sender en besked i form at en tekst, men får ikke noget resultat tilbage.


Print-funktionen printer enkeltlinjer som særskilte dialoger. Hver af følgende vil derfor resultere i to små dialoger:

Print "expression1" & chr(13) & "expression2"    'chr(13) svarer til "Enter" (Carriage return)
Print "expression1" & chr(10) & "expression2"    'chr(10) svarer til "Skift" + "Enter" (Line feed)


Print bruges ofte til at vise resultatet af en variabel:

Print date(now)

Print kan håndtere alle dataformater, så det er ligemeget om du printer en dato, en tekst eller et tal. Dog er det ikke muligt at kombinere variable med forskellige datatyper. Derfor skal du selv konvertere f.eks. en dato, før du kan kombinere den med en tekst.

Selv bruger jeg kun Print til debugging, hvor jeg indsætter en masse print-sætnigner i koden, som udskriver variabel-værdier.

Der er flere parametre for Print-sætnignen som jeg ikke kommer mere ind på. Du kan selv kigge i hjælpen for mere information.

MsgBox

MsgBox er mere egnet for egentlig bruger-dialog. Dialogen består i at du kan lytte til hvad brugeren svarer tilbage (hvilekt vlag brugeren gjorde).



Syntaks:

MsgBox (Text As String [,Type As Integer [,DialogTitle As String]])

Resultatet af brugerens valg kan du få fat i ved at tilknytte MsgBox til en variabel:

Result= MsgBox ("Tekst", type "Overskrift")

Teksten kan, til forskel fra Print, godt indeholde linjeskift, hvilket er praktisk, hvis du f.eks. skal vise en liste over værdier. Men det mest interessante er parameteren 'Type', som giver dig kontrol over, hvordan dialogen ser ud.

Ikoner og knapper som brugeren kan trykke på kontrolleres sådan:

0 : Vis kun knappen OK.
1 : Vis knapperne OK og Annuller.
2 : Vis knapperne Annuller, Prøv igen og Ignorer.
3 : Vis knapperne Ja, Nej og Annuller.
4 : Vis knapperne Ja og Nej.
5 : Vis knapperne Prøv igen og Annuller.
16 : Føj stop-ikonet til dialogen.
32 : Føj spørgsmåls-ikonet til dialogen.
48 : Føj udråbs-ikonet til dialogen.
64 : Føj informations-ikonet til dialogen.
128 : Første knap i dialogen som standardknap.
256 : Anden knap i dialogen som standardknap.
512 : Tredje knap i dialogen som standardknap.

Hvis du ønsker at kombinere valgmulighederne, skal du lægge værdierne sammen, f.eks. 3+64=67 svarer til "Vis knapperne Ja, Nej og Annuller" og "Føj informations-ikonet til dialogen".

Returværdier (returneres til dig i variablen):
1 : OK
2 : Annuller
3 : Afbryd
4 : Prøv igen
5 : Ignorer
6 : Ja
7 : Nej

Om du angiver parameteren Type som et tal eller et regnestykker er det samme:

Result= MsgBox ("Tekst", 67+512, "Overskrift")
Result= MsgBox ("Tekst", 579, "Overskrift")


Resultatet vil dog altiv være 6 (Ja), 7 (Nej) eller 2 (Annuller).

MsgBox returnerer altid resultater fra en afgrænset mængde af muligheder, og Select...Case er dermed en oplagt måde at håndtere resultatet på. Eksempel:

REM  *****  BASIC  *****

Sub Main
Resultat = MsgBox("Tryk på en tast", 67, "Tastetryk")

Select Case Resultat

Case 6
    Print "Du trykkede på Ja"
Case 7
    Print "Du trykkede på Nej"
Case 2
    Print "Du trykkede på Annuller"

end Select
End Sub


InputBox

Med InputBox kan du bede brugeren om at indtaste en tekst, som du så modtager retur. Syntaksen er:

InputBox (Msg As String[, Title As String[, Default As String[, x_pos As Integer, y_pos As Integer]]]])

Returværdien er altid en tekststreng, også selvom brugeren skriver et tal. Hvis du beder brugeren om at indtaste et tal, skal du selv konvertere tekststrengen til et tal.

Et eksempel:

    sText = InputBox ("Indtast en sætning:","Kære bruger","Jeg gætter på")
    Print sText




Læg mærke til at resultatet kan aflæses af variablen sText.

Endnu et eksempel:
   
Sub InputBoxTest
    Resultat = InputBox("Indtast et tal","Tal", 8)
    ResultatTal = Val(Resultat)
    Print Resultat + 12        '812
    Print ResultatTal + 12    '20
End Sub



Jeg bruger InputBox i eksemplet i artiklen "Regneark i Calc (Årskalender)", som du kan læse her: http://libreofficedk.blogspot.dk/2016/10/regneark-i-calc-arskalender.html

this_year = InputBox ("Indtast årstal mellem 1583 og 3000", "Vælg årstal", year(date))
If (1583 > val(this_year) OR val(this_year) > 3000) then
    MsgBox("Årstallet skal være mellem 1583 og 3000", 48, "Fejl")
Stop
End If


I eksemplet bruger jeg year(date) som standardværdi (indeværende år).

Dialog

Det er muligt at designe sine helt egne dialoger i LibreOffice. Det emne er lidt mere kompliceret, og det vil jeg behandle i en særskilt artikel senere.

Hvad nu hvis?

Programkontrol er noget af det centrale i al programmering. Programmet skal gerne udføre handlinger afhængigt af forskellige betingelser. Betingelserne kan være noget brugeren indtaster, noget der opstår som følge af tid, eller noget der kommer fra data. Under alle omstændigheder er det altid noget vi kan måle og veje.

IF-sætning

Den mest brugte måde at styre programmet på, er med IF-sætninger. If...then...else. HVIS en bestemt betingelse er opfyldt SÅ skal der ske noget bestemt ELLERS skal der ske noget andet.

If betingelse=true Then
  programudtryk
[ElseIf betingelse=true Then]
  programudtryk
[Else]
  programudtryk
End If


  • Betingelsen skal altid resultere i en boolean (True/False).
  • Programudtryk er et stykke program som skal udføres.
Det er et krav at blokken starter med IF og slutter med END IF, men der behøver ikke være andet end en enkelt betingelse og et enkelt programudtryk. Resten er valgfrit, men jeg vil anbefale altid at anvende en ELSE-blok, bare for at være sikker på at tage hånd om fejl og undtagelser.

Et eksempel:

If x < 0 Then
  MsgBox "Tallet er negativt"
ElseIf x > 0 Then
  MsgBox "Tallet er positivt"
Else
  MsgBox "Tallet er nul"
End If


Det er muligt at indbygge IF-sætninger i IF-sætninger, hvilket ikke er usædvanligt at se.

If...Then...Else ses også ofte brugt som manuel fejlhåndtering, f.eks. til at kontrollere for helt konkrete fejlsituationer. Men det er en helt anden historie.

IIF-sætning

En mere simpel løsning er en IIF-sætning:

IIf (betingelse, SandUdtryk, FalskUdtryk)

Eksempel:

hoejeste_alder = IIf(johns_alder > peters_alder, johns_alder, peters_alder)

Samme blok kunne vi have skrevet med en IF-sætning sådan:

If johns_alder > peters_alder Then
  hoejeste_alder= johns_alder
Else
  hoejeste_alder= peters_alder
End If


IIF-sætningen egner sig bedst til små og korte betingelses-blokke.

CHOOSE-sætning

Choose-sætningen giver dig muighed for at vælge fra en liste, baseret på et indeks.

Choose (Indeks, Valg_1[, Valg_2, ... [,Valg__n]])

Hvis Indeks er 1 returneres Valg_1, hvis Indeks er 2 returneres Vlag_2 osv.

FOR...NEXT-loop

Denne funktion gentager en blok et bestemt antal gange.

For taeller=start To slut [Step trin]
  programudtryk
  [Exit For]
  programudtryk
Next [taeller]


Det er muligt at angive hvordan loopet skal tælle ("Step trin"), men denne parameter udelades ofte, hvilket også gør sig gældende for "Exit For".

Et eksempel:

  For n = LBound(oPrinter) To UBound(oPrinter)
    sMsg = sMsg + oPrinter(n).Name + Chr(13)
  Next n


I eksemplet herover bruger vi UBound(...) som aflutning, hvilket er særdeles praktisk. UBound angiver øvre grænse eller største indeksværdi i et array. På den måde kan for..next bruges til at gennemløbe et array trin for trin.

I min tidligere artikel om årskalender bruger jeg for...next loop rigtig meget: http://libreofficedk.blogspot.dk/2016/10/regneark-i-calc-arskalender.html


DO WHILE loop

Så længe en betingelse er opfyldt, fortsæt med at udføre ...

Do While betingelse
  programudtryk
Loop


Alternativ syntakt:
Indtil en betingelse er opfyldt, fortsæt med at udføre ...

Do Until betingelse
  programudtryk
Loop


Et eksempel fra min artikel om fortløbende nummerering:

Do While NOT EOF(n)          
    Input #n, s                
    If result="" then
        result = s
    Else
        result = result + chr(13) + s
    end if
Loop


Hele artiklen kan læses her: https://libreofficedk.blogspot.dk/2016/11/fortlbende-nummerering-fortsat_8.html

Vær meget opmærksom på at Do...While...Loop er lidt farlig, fordi den kan ende med et af enhver programmørs værste mareridt, nemlig et endeløst loop. Du skal være 100% sikker på at du faktsik når frem til betingelsen. Ellers vil din makro fortsætte uendeligt (eller til en bruger vælger at afbryde den).

SELECT...CASE-sætning

Vælg fra en liste baseret på et valg.

Syntaks:

Select Case betingelses_udtryk
  Case tilfælde_1
    programudtryk_1
  Case tilfælde_2
    programudtryk_2
  Case Else
    programudtryk_3
End Select


Select...Case bruges ofte i tilfælde, hvor antallet af valgmuligheder er fastlagt af omstændighederne. Et godt eksempel er ugedagene, som der er syv af, og det er temmelig fastsat på forhånd.

Funktionen Weekday(dato) returnerer et heltal mellem 1 og 7, hvor 1 er søndag.

    Select Case WeekDay( Now )
        Case 1
            sDay="Søndag"
        Case 2
            sDay="Mandag"
        Case 3
            sDay="Tirsdag"
        Case 4
            sDay="Onsdag"
        Case 5
            sDay="Torsdag"
        Case 6
            sDay="Fredag"
        Case 7
            sDay="Lørdag"
    End Select
    Msgbox sDay,64,"I dag er det"

torsdag den 10. november 2016

Undersøge markeret tekst i Writer (med makro)


Vi kan med en makro aflæse hvad der er markeret i et tekstdokument.

Problemet er at vi ikke på forhånd kan vide om der overhovedet er markeret noget, og om markeringen dækker et enkelt eller flere områder. Flere markerede områder er hvis du holder CTRL-tasten nede mens du markerer flere områder.

Vi kommer til at anvende to centrale begreber: Selection og Cursor
Selection er en liste med alle markerede områder:

oSelections = ThisComponent.getCurrentSelection()

Resultatet kan f.eks. vises sådan:

Print oSelections.getCount

For at udlede noget af det markerede, må vi håndtere hver enkelt markeret område et ad gangen. Her aflæser vi helt konkret hvad der står i hver enkelt markering:

Sub Main
oSelections = ThisComponent.getCurrentSelection()
For i = 0 To oSelections.getCount() - 1
oSel = oSelections.getByIndex(i)
Print oSel.String
Next
End Sub

Der findes to typer cursor i LibreOffice makroer:
  • ViewCursor ved, hvordan dataene vises, men ikke noget om data. 
  • TextCursor (non-view cursor), ved en masse om data, men meget lidt om, hvordan de vises. 
For eksempel kender ViewCursor ikke hvilke ord der er markeret, og TextCursor kender intet til linjer, skærme, eller sider.

Vi skal bruge en TextCursor ud fra hver enkelt af vores markeringer:

oCursor = oDoc.Text.CreateTextCursorByRange(oSel)

Nu kan vi kontrollere om noget er markeret, hvilket lyder enkelt, men tro om igen. Hvis antallet er markeringer er 0, så er intet markeret. Men der er altid en markør i dokumentet, så antallet af markeringer er aldrig 0. Principielt kan vi komme ud for at der ingen markering er, hvis LibreOffice afvikles på en server, uden brugergrænsefladen vises.

Hvis længden af markeringen er 0, er intet markeret. Længden skal måles på hver enkelt markering, hvilket er lidt farligt. Det er fordi en tekststreng ikke kan/må være længere end 64k, så brug ikke den metode. Du risikerer at LibreOffice crasher.

Den bedste måde er at skabe en tekstcursor fra den aktuelle markering, og så undersøge om start og slutpunktet er det samme. Kontrol af om der er noget markeret er altså relativt kompliceret men dog muligt. Vi kan lave denne funktion til formålet:

Function IsAnythingSelected(oDoc As Object) As Boolean
Dim oSelections 'Contains all of the selections
Dim oSel 'Contains one specific selection
Dim oCursor 'Text cursor to check for a collapsed range
REM Assume nothing is selected
IsAnythingSelected = False
If IsNull(oDoc) Then Exit Function
' The current selection in the current controller.
'If there is no current controller, it returns NULL.
oSelections = oDoc.getCurrentSelection()
If IsNull(oSelections) Then Exit Function
If oSelections.getCount() = 0 Then Exit Function
If oSelections.getCount() > 1 Then
REM There is more than one selection so return True
IsAnythingSelected = True
Else
REM There is only one selection so obtain the first selection
oSel = oSelections.getByIndex(0)
REM Create a text cursor that covers the range and then see if it is
REM collapsed.
oCursor = oDoc.Text.CreateTextCursorByRange(oSel)
If Not oCursor.IsCollapsed() Then IsAnythingSelected = True

REM You can also compare these to see if the selection starts and ends at
REM the same location.
REM If oDoc.Text.compareRegionStarts(oSel.getStart(),_
REM oSel.getEnd()) <> 0 Then
REM IsAnythingSelected = True
REM End If
End If
End Function

Funktionen IsAnythingSelected er fra bogen OpenOffice.org Macros Explained af Andrew D. Pitonyak.