LibreOfficeLogo

Příručka aplikace Base 7.3

Kapitola 9
Makra

 

Autorská práva

Tento dokument je chráněn autorskými právy © 2022 týmem pro dokumentaci LibreOffice. Přispěvatelé jsou uvedeni níže. Dokument lze šířit nebo upravovat za podmínek licence GNU General Public License (https://www.gnu.org/licenses/gpl.html), verze 3 nebo novější, nebo the Creative Commons Attribution License (https://creativecommons.org/licenses/by/4.0/), verze 4.0 nebo novější.

Všechny ochranné známky uvedené v této příručce patří jejich vlastníkům.

Přispěvatelé

Pro toto vydání

flywire

Jean-Pierre Ledure

Jean Hollis Weber

Steve Fanning

Olivier Hallot

 

Pro předchozí vydání

Robert Großkopf

Pulkit Krishna

Jost Lange

Jean-Pierre Ledure

Andrew Pitonyak

Alain Romedenne

Hazel Russman

Jochen Schiffers

Jean Hollis Weber

Zpětná vazba

Jakékoli připomínky nebo návrhy k tomuto dokumentu prosím směřujte do fóra dokumentačního týmu na adrese https://community.documentfoundation.org/c/documentation/loguides/ (registrace je nutná) nebo pošlete e-mail na adresu: loguides@community.documentfoundation.org.

Poznámka

Vše, co napíšete do fóra, včetně vaší e-mailové adresy a dalších osobních údajů, které jsou ve zprávě napsány, je veřejně archivováno a nemůže být smazáno. E-maily zaslané do fóra jsou moderovány.

Datum vydání a verze programu

Vydáno Srpen 2022. Založeno na LibreOffice 7.3 Community.
Jiné verze LibreOffice se mohou lišit vzhledem a funkčností.

Používání LibreOffice na systému macOS

Některé klávesové zkratky a položky nabídek jsou v systému macOS jiné než v systémech Windows a Linux. V následující tabulce jsou uvedeny nejdůležitější rozdíly, které se týkají informací v této knize. Podrobnější seznam se nachází v nápovědě aplikace.

Windows nebo Linux

Ekvivalent pro macOS

Akce

Výběr v nabídce Nástroje > Možnosti

LibreOffice > Předvolby

Otevřou se možnosti nastavení.

Klepnutí pravým tlačítkem

Ctrl + klepnutí a/nebo klepnutí pravým tlačítkem v závislosti na operačním systému počítače

Otevře se místní nabídka.

Ctrl (Control)

(Command)

Používá se také s dalšími klávesami.

Alt

⌥ (Option)

Používá se také s dalšími klávesami.

Ctrl+Q

+ Q

Ukončí LibreOffice

 

Obecné poznámky k makrům

Databázi v systému Base lze v zásadě spravovat bez maker. Někdy však mohou být nezbytná pro:

Sami se musíme rozhodnout, jak intenzivně chceme makra v Base používat. Makra mohou zlepšit použitelnost, ale vždy jsou spojena s malým snížením rychlosti programu a někdy i s větším (při špatném kódování). Z dlouhodobého hlediska může také způsobit problémy s údržbou kódu. Vždy je lepší začít plným využitím možností databáze a nastavením konfigurace formulářů, než se pokusíme zajistit další funkce pomocí maker. Makra by se měla vždy testovat na větších databázích, aby se zjistil jejich vliv na výkon.

Makra jsou vytvořena pomocí Nástroje > Makra > Správce maker > Základní. Zobrazí se okno s přístupem ke všem makrům. V případě Base odpovídá důležitá oblast názvu souboru Base.

graphics1

Obrázek 1: Dialogové okno Nový modul

Tlačítko Nový v dialogovém okně makra LibreOffice Basic otevře dialogové okno Nový modul, ve kterém je požadován název modulu (složka, ve které bude makro uloženo). Název lze v případě potřeby později změnit.

Jakmile je zadán, zobrazí se editor maker. Jeho vstupní oblast již obsahuje klauzule Start a End for podprogramu:

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

 

Sub Main

 

End Sub

Pokud chceme použít makra, je nutné provést následující kroky:

Některé základní zásady pro použití kódu Basic v LibreOffice:

   End Sub

Další podrobnosti nalezneme v kapitole 13, Začínáme s makry, v příručce Začínáme s LibreOffice.

Poznámka

Makra ve verzích PDF a ODT této kapitoly jsou barevně odlišena podle pravidel editoru maker LibreOffice:

Popis makra
Komentář makra
Operátor makra
Rezervovaný výraz makra
Číslo makra
Řetězec znaků makra

Poznámka

V této kapitole je několik odkazů na Odkazy na LibreOffice API. V elektronické verzi kapitoly budou tyto odkazy hypertextové. Pro ty, kteří čtou tištěnou verzi, je odkaz na rozhraní API k dispozici na adrese https://api.libreoffice.org/docs/idl/ref/.

Makra v Base

Používání maker

„Přímý způsob“ pomocí Nástroje > Makra > Spustit makro je možný, ale pro základní makra není obvyklý. Makro je obvykle přiřazeno k události a spouští se, když tato událost nastane. Použití maker je na následující:

„Přímá cesta“ není možná, a to ani pro testování, když má být používán jeden z objektů thisComponent (viz „Přístup k formulářům” na straně 1) nebo oEvent (viz „Přístup k prvkům formuláře” na straně 1).

Přiřazení maker

Má-li být makro spuštěno událostí, musí být nejprve definováno. Poté jej lze přiřadit k události. K těmto událostem lze přistupovat na dvou místech.

Události, které nastanou ve formuláři při otevření nebo zavření okna.

Akce, které se provádějí při otevření nebo zavření formuláře, se zaznamenávají následujícím způsobem:

Grafik2

Obrázek 2: Dialogové okno Přizpůsobení, karta Události

  1. Při návrhu formuláře otevřeme kartu Události v Nástroje > Přizpůsobit.

  2. Vybereme příslušnou událost. Některá makra lze spustit pouze při zvolení události Pohled vytvořen. Jiná makra, například pro vytvoření celoobrazovkového formuláře, by měla být spuštěna příkazem Otevřít dokument.

  3. Pomocí tlačítka Makro vyhledáme požadované makro a potvrdíme výběr.

  4. V části Uložit do zadáme název formuláře.

  5. Potvrdíme pomocí OK.

Události ve formuláři v otevřeném okně

Po otevření okna, které zobrazuje celkový obsah formuláře, lze přistupovat k jednotlivým prvkům formuláře. To zahrnuje prvky, které jsme formuláři přiřadili.

K prvkům formuláře lze přistupovat pomocí Navigátoru formulářem, jak je znázorněno na obrázku níže. Stejně dobře k nim lze přistupovat pomocí místních nabídek jednotlivých ovládacích prvků v rozhraní formuláře.

Všechny události uvedené pod Vlastnosti formuláře > Události se odehrávají při otevření okna formuláře. Lze je nastavit samostatně pro každý formulář nebo podformulář v okně formuláře.

Bild11

Obrázek 3: Dialogové okno Vlastnosti formuláře, karta Události

Poznámka

Base používá slovo „formulář“ jak pro okno otevřené pro zadávání dat, tak pro prvky v tomto okně, které jsou vázány na konkrétní zdroj dat (tabulku nebo dotaz).

Jedno okno formuláře může obsahovat několik formulářů s různými zdroji dat. V Navigátoru formulářem se vždy nejprve zobrazí termín Formuláře, který v případě jednoduchého formuláře obsahuje pouze jednu podřízenou položku.

Události ve formuláři

Všechna ostatní makra se registrují pomocí vlastností podformulářů a ovládacích prvků na kartě Události.

  1. Otevřeme okno vlastností ovládacího prvku (pokud jsme tak ještě neučinili).

  2. Na kartě Události vybereme vhodnou událost.

  3. K úpravě zdroje dat použijeme události, které odkazují na položku Záznam, Aktualizovat nebo Obnovit.

  1. Kliknutím na tlačítko ... vpravo otevřeme dialogové okno Přiřadit akci.

  2. Kliknutím na tlačítko Makro vybereme makro definované pro danou akci.

  3. Kliknutím na OK potvrdíme přiřazení.

Grafik3

Obrázek 4: Dialogové okno Přiřadit akci

Součásti maker

V této části jsou vysvětleny některé makrojazyky, které se v Base běžně používají, zejména ve formulářích. Pokud je to možné (a rozumné), jsou příklady uvedeny ve všech následujících oddílech.

„Framework“ makra

Definice makra začíná jeho typem – Sub nebo Function  – a končí End Sub nebo End Function.  Makro, které je přiřazeno události, může přijímat argumenty (hodnoty); jediný užitečný je argument oEvent. Všechny ostatní rutiny, které by mohly být takovým makrem volány, mohou být definovány s návratovou hodnotou nebo bez ní, v závislosti na jejich účelu, a v případě potřeby opatřeny argumenty.

Sub update_loan

End Sub

Sub from_Form_to_Form(oEvent As Object)

End Sub

Function confirm_delete(oEvent As Object) As Boolean

    confirm_delete = False

End Function

Je užitečné si tento framework ihned zapsat a následně do něj vložit obsah. Nezapomeňte přidat komentáře k vysvětlení makra a pamatujte na pravidlo „Tolik, kolik je třeba, co nejméně je možné“. Jazyk Basic navíc nerozlišuje mezi velkými a malými písmeny. Obvykle se pevně stanovené pojmy jako SUB píšou přednostně velkými písmeny, ostatní pojmy smíšenými.

Definování proměnných

V dalším kroku, na začátku rutiny, se pomocí příkazu Dim definují proměnné, které se budou v rutině vyskytovat, každá s příslušným datovým typem. Samotný Basic to nevyžaduje; akceptuje všechny nové proměnné, které se v programu vyskytnou. Programový kód je však „bezpečnější“, pokud jsou proměnné, zejména jejich datové typy, deklarovány. Mnozí programátoři to vyžadují a při psaní modulu používají volbu Explicit jazyka Basic. To znamená „Neuznávej žádnou starou proměnnou, ale pouze tu, kterou jsem předem deklaroval“.

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm As Object

   Dim sName As String

   Dim bOKEnabled As Boolean

   Dim iCounter As Integer

   Dim dBirthday As Date

V názvech proměnných lze používat pouze abecední znaky (A-Z nebo a-z), čísla a znak podtržení „_“. Žádné speciální znaky nejsou povoleny. Mezery jsou za určitých podmínek povoleny, ale je lepší se jim vyhnout. První znak musí být abecední.

Běžnou praxí je uvádět datový typ v prvním znaku. Pak ji lze rozpoznat všude, kde se proměnná v kódu vyskytuje. Doporučují se také „expresivní názvy“, aby byl význam proměnné zřejmý z jejího názvu.

Poznámka

Pokud je to možné, měli bychom to upřesnit, protože pouze jedno písmeno neumožňuje rozlišit mezi datovými typy „Double“ a „Date“ nebo „Single“ a „String“.

Seznam možných datových typů v jazyce Star Basic najdeme v příloze A této knihy. Na různých místech se liší od typů v databázi a v rozhraní API (Application Programming Interface) LibreOffice. Tyto změny jsou jasně uvedeny v příkladech.

Definování polí

Zejména u databází je důležité sestavení několika proměnných do záznamu. Pokud je několik proměnných uloženo společně na jednom společném místě, nazývá se pole. Před zápisem dat do pole musí být pole definováno.

Dim arData()

vytvoří prázdné pole.

arData = Array("Lisa","Schmidt")

vytvoří pole určité velikosti (2 prvky) a předá mu hodnoty.

Použití

Print arData(0), arData(1)

způsobí, že se na obrazovce zobrazí dva definované prvky. Počet prvků začíná číslem 0.

Dim arData(2)

arData(0) = "Lisa"

arData(1) = "Schmidt"

arData(2) = "Cologne"

Tím se vytvoří pole, do kterého lze uložit tři prvky libovolného typu, například záznam pro "Lisa""Schmidt""Cologne". Do tohoto pole nelze vložit více než tři prvky. Pokud chceme uložit více prvků, musíme pole zvětšit. Pokud je však velikost pole předefinováno za běhu makra, je pole zpočátku prázdné, stejně jako nové pole.

ReDim Preserve arData(3)

arData(3) = "18.07.2003"

Přidáním Preserve se zachovávají předchozí údaje, takže pole je skutečně rozšířeno o položku data (zde ve formě textu).

Výše uvedené pole může uchovávat pouze jeden záznam. Pokud chceme uložit několik záznamů, jako je tomu u tabulky v databázi, musíme definovat dvourozměrné pole.

Dim arData(2,1)

arData(0,0) = "Lisa"

arData(1,0) = "Schmidt"

arData(2,0) = "Cologne"

arData(0,1) = "Egon"

arData(1,1) = "Müller"

arData(2,1) = "Hamburg"

I zde je možné rozšířit dříve definované pole a zachovat stávající obsah pomocí Preserve.

Přístup k formulářům

Formulář leží v aktuálně aktivním dokumentu. Oblast, která je zde reprezentována, se nazývá drawpage. Kontejner, ve kterém jsou uloženy všechny formuláře, se nazývá forms; v Navigátoru formulářů se zobrazuje jako primární záhlaví s připojenými jednotlivými formuláři. Výše uvedené proměnné získávají své hodnoty takto:

   oDoc = thisComponent

   oDrawpage = oDoc.drawpage

   oForm = oDrawpage.forms.getByName("Filter")

Formulář, ke kterému se má přistupovat, se nazývá Filter. Jedná se o název, který je viditelný v nejvyšší úrovni Navigátoru formulářem (ve výchozím nastavení se první formulář jmenuje MainForm). Dílčí formuláře jsou v rámci hlavního formuláře hierarchicky uspořádány a lze k nim přistupovat postupně:

   Dim oSubForm As Object

   Dim oSubSubForm As Object

   oSubForm = oForm.getByName("Readerselect")

   oSubSubForm = oSubForm.getByName("Readerdisplay")

Namísto použití přechodných proměnných můžeme přejít přímo na konkrétní formulář. Přechodný objekt, který může být použit více než jednou, je třeba deklarovat a přiřadit mu samostatnou hodnotu. V následujícím příkladu se oSubForm již nepoužívá.

   oForm = thisComponent.drawpage.forms.getByName("Filter")

   oSubSubForm = oForm.getByName("readerselect").getByName("readerdisplay")

Poznámka

Pokud se název skládá pouze z písmen ascii a čísel bez mezer a speciálních znaků, lze jej použít přímo v příkazu přiřazení.

   oForm = thisComponent.drawpage.forms.Filter
  oSubSubForm
= oForm.readerselect.readerdisplay

Na rozdíl od běžného používání jazyka Basic je nutné tyto názvy psát se správnými velkými a malými písmeny.

Jiný způsob přístupu k formuláři poskytuje událost, která makro spouští.

Pokud je makro spuštěno z události formuláře, například Vlastnosti formuláře > Před záznamem, lze se k samotnému formuláři dostat následujícím způsobem:

Sub MacroexampleCalc(oEvent As Object)

   oForm = oEvent.Source

   ...

End Sub

Pokud je makro spuštěno z události na ovládacím prvku formuláře, například Textové pole > Při ztrátě zaměření, zpřístupní se formulář i pole:

Sub MacroexampleCalc(oEvent As Object)

   oField = oEvent.Source.Model

   oForm = oField.Parent

   ...

End Sub

Přístup k událostem má tu výhodu, že se nemusíme starat o to, zda se jedná o hlavní nebo podformulář. Také název formuláře nemá pro fungování makra žádný význam.

Přístup k prvkům formuláře

K prvkům ve formulářích se přistupuje podobným způsobem: deklarujeme vhodnou proměnnou jako objekt a vyhledáme příslušný ovládací prvek ve formuláři:

   Dim btnOK As Object  ' Button »OK»

   btnOK = oSubSubForm.getByName("button 1")   ' z formuláře readerdisplay

Tato metoda funguje vždy, když víme, se kterým prvkem má makro pracovat. Pokud je však prvním krokem určení, která událost makro spustila, je užitečná výše uvedená metoda oEvent. Proměnná je deklarována v rámci makra „framework“ a při spuštění makra je jí přiřazena hodnota. Vlastnost Source vždy uvádí prvek, který makro spustil, zatímco vlastnost Model podrobně popisuje ovládací prvek:

Sub confirm_choice(oEvent As Object)

   Dim btnOK As Object

   btnOK = oEvent.Source.Model

End Sub

Pokud chceme, můžeme s objektem získaným touto metodou provádět další akce.

Upozorňujeme, že podformuláře se počítají jako součásti formuláře.

Přístup do databáze

Přístup k databázi se obvykle řídí pomocí formulářů, dotazů, sestav nebo funkce hromadné korespondence (mailmerge), jak bylo popsáno v předchozích kapitolách. Pokud se tyto možnosti ukážou jako nedostatečné, může makro přistupovat k databázi několika způsoby.

Připojení k databázi

Nejjednodušší metoda používá stejné připojení jako formulář. oForm se určí podle výše uvedeného.

Dim oConnection As Object

oConnection = oForm.activeConnection()

Nebo můžeme zdroj dat (tj. databázi) načíst prostřednictvím dokumentu a pro makro použít jeho existující připojení:

Dim oDatasource As Object

Dim oConnection As Object

oDatasource = thisComponent.Parent.dataSource

oConnection = oDatasource.getConnection("","")

Další způsob umožňuje vytvořit připojení k databázi za běhu:

Dim oDatasource As Object

Dim oConnection As Object

oDatasource = thisComponent.Parent.CurrentController

If Not (oDatasource.isConnected()) Then oDatasource.connect()

oConnection = oDatasource.ActiveConnection()

Podmínka If řídí pouze jeden řádek, takže End If není nutná.

Pokud má být makro spuštěno prostřednictvím uživatelského rozhraní, a ne z události ve formuláři, je vhodná následující varianta:

Dim oDatasource As Object

Dim oConnection As Object

oDatasource = thisDatabaseDocument.CurrentController

If Not (oDatasource.isConnected()) Then oDatasource.connect()

oConnection = oDatasource.ActiveConnection()

Přístup k databázím mimo aktuální databázi je možný následujícím způsobem:

Dim oDatabaseContext As Object

Dim oDatasource As Object

Dim oConnection As Object

oDatabaseContext = createUnoService("com.sun.star.sdb.DatabaseContext")

oDatasource = oDatabaseContext.getByName("registered name of Database in LO")

oConnection = oDatasource.GetConnection("","")

Připojení k databázím, které nejsou registrovány v LibreOffice, jsou také možná. V takových případech musí být místo registrovaného názvu uvedena cesta k databázi jako file:///....../database.odb.

Rozšířené pokyny pro připojení k databáze jsou uvedeny v části „Vytvoření připojení k databázi“ (strana 1).

Příkazy SQL

S databází pracujeme pomocí příkazů SQL. Ty je třeba vytvořit a odeslat do databáze; výsledek se určí podle typu příkazu a výsledky lze dále zpracovávat. Direktiva createStatement vytvoří k tomuto účelu vhodný objekt.

Dim oSQL_Statement As Object ' objekt, který provede SQL příkaz

Dim stSql As String          ' Text aktuálního SQL příkazu

Dim oResult As Object        ' výsledek executeQuery

Dim iResult As Integer       ' výsledek executeUpdate

oSQL_Statement = oConnection.createStatement()

Pro dotaz na data zavoláte metodu executeQuery; výsledek se pak vyhodnotí. Názvy tabulek a polí se obvykle uvádějí s dvojitými uvozovkami. Makro je musí maskovat dalšími dvojitými uvozovkami, aby se v příkazu objevily.

stSql = "SELECT * FROM ""Table1"""

oResult = oSQL_Statement.executeQuery(stSql)

Chceme-li upravit data – tedy INSERT, UPDATE nebo DELETE – nebo ovlivnit strukturu databáze, zavoláme metodu executeUpdate. V závislosti na příkazu a databázi se buď nezjistí nic užitečného (nula), nebo se zjistí počet změněných záznamů.

stSql = "DROP TABLE ""Suchtmp"" IF EXISTS"

iResult = oSQL_Statement.executeUpdate(stSql)

Pro úplnost je třeba zmínit ještě jeden speciální případ: má-li být oSQL_Statement používán různými způsoby pro SELECT nebo pro jiné účely, je k dispozici ještě jedna metoda, a to execute. Nebudeme ji zde používat. Další informace nalezneme v části Odkazy API.

Předpřipravené příkazy SQL s parametry

Ve všech případech, kdy je třeba do příkazu SQL převést ruční zadání uživatele, je jednodušší a bezpečnější nevytvářet příkaz jako dlouhý řetězec znaků, ale připravit si jej předem a použít jej s parametry. To usnadňuje formátování čísel, dat a řetězců (zmizí neustálé dvojité uvozovky) a zabraňuje ztrátě dat při nekorektním vstupu.

Při použití této metody se vytvoří a připraví objekt pro konkrétní příkaz SQL:

Dim oSQL_Statement As Object   'objekt, který provede SQL příkaz

Dim stSql As String            ' Text aktuálního SQL příkazu

stSql = "UPDATE author " _

      & "SET lastname = ?, firstname = ?" _

      & "WHERE ID = ?"

oSQL_Statement = oConnection.prepareStatement(stSql)

Objekt je vytvořen pomocí prepareStatement, takže příkaz SQL je předem znám. Každý otazník označuje pozici, která později – před provedením příkazu – získá skutečnou hodnotu. Protože je příkaz připraven předem, databáze ví, jaký typ záznamu – v tomto případě dva řetězce a číslo – se očekává. Jednotlivé pozice jsou rozlišeny číslem (počítáno od 1).

Poté se hodnoty přenesou pomocí vhodných příkazů a provede se příkaz SQL. Zde jsou hodnoty převzaty z ovládacích prvků formuláře, ale mohou pocházet i z jiných maker nebo mohou být zadány jako prostý text:

oSQL_Statement.setString(1, oTextfeld1.Text)   ' Text pro příjmení (surname)

oSQL_Statement.setString(2, oTextfeld2.Text)   ' Text pro křestní jmeno (first name)

oSQL_Statement.setLong(3, oZahlenfeld1.Value)  ' hodnota pro odpovídající ID

iResult = oSQL_Statement.executeUpdate

Úplný seznam přiřazení je uveden v části "Parametry pro připravené příkazy SQL" (strana 1).

Další informace o výhodách této metody nalezneme níže (externí odkazy):

Čtení a používání záznamů

V závislosti na požadavcích existuje několik způsobů, jak přenést informace z databáze do makra, aby mohly být dále zpracovány.

Upozornění: odkazy na formulář zahrnují i podformuláře. Tím je myšlen formulář nebo část formuláře, která je vázána na určitý zdroj dat.

Používání formulářů

Aktuální záznam a jeho data jsou vždy k dispozici prostřednictvím formuláře, který zobrazuje příslušná data (tabulka, dotaz, SELECT). Existuje několik metod typu getdata_, jako např.:

Dim ID As Long

Dim sName As String

Dim dValue AS Currency

Dim dEntry As New com.sun.star.util.Date

ID = oForm.getLong(1)

sName = oForm.getString(2)

dValue = oForm.getDouble(4)

dEntry = oForm.getDate(7)

Všechny tyto metody vyžadují číslo sloupce ze zdroje dat; počítadlo začíná od 1.

Poznámka

U všech metod, které pracují s databázemi, začíná počítání od hodnoty 1. To platí pro sloupce i řádky.

Pokud pro práci s podkladovým zdrojem dat (tabulkou, dotazem, pohledem) dáváme přednost použití názvů sloupců před jejich čísly, lze číslo sloupce určit pomocí findColumn. Zde je příklad pro vyhledání sloupce s názvem Name.

Dim sName As String

nName = oForm.findColumn("Name")

sName = oForm.getString(nName)

Typ vracené hodnoty vždy odpovídá typu metody, je však třeba upozornit na následující zvláštní případy:

Úplný seznam těchto metod najdeme v části "Úprava řádků dat" (strana 1).

Tip

Pokud mají být hodnoty z formuláře použity přímo pro další zpracování v SQL (například pro vstup do jiné tabulky), je mnohem jednodušší nedotazovat se na typ pole.

Následující makro, které je vázáno na Vlastnosti: Tlačítko > Události > Provést akci načte první pole ve formuláři nezávisle na typu, který je nutný pro další zpracování v Basicu.

SUB ReadValues(oEvent As Object)
  Dim
oForm As Object
  Dim
stFeld1 As String
  
oForm = oEvent.Source.Model.Parent
  stFeld1 = oForm.getString(1)
End Sub

Pokud jsou pole načtena pomocí getString(), je zachováno veškeré formátování potřebné pro další zpracování SQL. Datum, které se zobrazí jako 08.03.19, se načte ve formátu 2019-03-08 a lze jej použít přímo v SQL.

Čtení ve formátu odpovídající typu je povinné pouze tehdy, má-li být hodnota dále zpracována v rámci makra, například při výpočtu.

Výsledek dotazu

Stejným způsobem lze použít i sadu výsledků dotazu. V sekci Příkazy SQL najdeme pro tuto sadu výsledků proměnnou oResult, která se obvykle čte takto:

While oResult.next         ' jeden záznam za druhým
  REM převádí výsledek do proměnných
  stVar = oResult.getString(1)
  inVar = oResult.getLong(2)
  boVar = oResult.getBoolean(3)
  REM udělá něco s těmito hodnotami

Wend

Podle typu SQL příkazu, očekávaného výsledku a jeho účelu lze smyčku WHILE zkrátit nebo zcela vypustit. V zásadě lze ale vždy takto vyhodnotit množinu výsledků.

Pokud se má vyhodnotit pouze první záznam.

oResult.next

přistupuje k řádku tohoto záznamu a pomocí

stVar = oResult.getString(1)

přečte obsah prvního pole. Zde smyčka končí.

Dotaz pro výše uvedený příklad má v prvním sloupci text, ve druhém celé číslo (Integer v databázi odpovídá Long v jazyce Basic) a ve třetím pole Ano/Ne. K polím se přistupuje pomocí indexu pole, který na rozdíl od indexu pole začíná od 1.

Navigace přes takový výsledek není možná. Povoleny jsou pouze jednotlivé kroky k dalšímu záznamu. Aby bylo možné se v záznamu pohybovat, musí být při vytváření dotazu znám ResultSetType. K tomu se přistupuje pomocí

oSQL_Result.ResultSetType = 1004

nebo

oSQL_Result.ResultSetType = 1005

Typ 1004 – SCROLL_INTENSIVE umožňuje volný pohyb, ale nezachycuje změny v původních datech. Typ 1005 – SCROLL_SENSITIVE rozpozná změny v původních datech , které mohou ovlivnit výsledek dotazu.

Celkový počet řádků v sadě výsledků lze určit až po zadání číselného typu výsledku. Provádí se takto:

Dim iResult As Long

If oResult.last                ' přejde na poslední záznam, pokud je to možné

   iResult = oResult.getRow    ' průběžné číslo je součet

Else

   iResult = 0

End If

Použití ovládacího prvku

Pokud je ovládací prvek vázán na zdroj dat, lze hodnotu načíst přímo, jak je popsáno v následující části. To však může vést k problémům. Bezpečnější je použít postup popsaný v kapitole „Používání formulářů“ (strana 1) nebo následující metodu, která je uvedena pro několik různých typů ovládání:

sValue = oTextField.BoundField.Text       ' příklad pro Textové pole

nValue = oNumericField.BoundField.Value   ' příklad číselného pole

dValue = oDateField.BoundField.Date       ' příklad pole pro datum

BoundField představuje spojení mezi viditelným ovládacím prvkem a skutečným obsahem datové sady.

Navigace v datové sadě

V předposledním příkladu byla použita metoda Next pro přechod z jednoho řádku výsledkové sady na další. Existují další podobné metody a testy, které lze použít jak pro data ve formuláři – reprezentovaná proměnnou oForm –, tak pro množinu výsledků. Například pomocí metody popsané v části „Automatická aktualizace formulářů“ (strana 1) lze znovu vybrat předchozí záznam:

Dim loRow As Long

loRow = oForm.getRow()   ' uloží aktuální číslo řádku

oForm.reload()           ' znovu načte množinu záznamů

oForm.absolute(loRow)    ' vrátí se zpět ke stejnému řádku

V části „Automatická aktualizace formulářů“ jsou uvedeny všechny metody, které jsou k tomu vhodné.

Poznámka

Existuje chyba, která ovlivňuje formuláře. Při změně dat ve formuláři nastaví aktuální číslo řádku na hodnotu „0“. Viz https://bugs.documentfoundation.org/show_bug.cgi?id=82591. Chceme-li získat správné číslo aktuálního řádku, přiřadíme k události Formulář > Vlastnosti > Události > Po změně záznamu následující makro.

Global loRow As Long
Sub
RowCounter(oEvent As Object)
  
loRow = oEvent.Source.Row
End Sub

Nové číslo řádku se načte a přiřadí do globální proměnné loRow. Tato proměnná má být umístěna na začátku všech modulů a zachová si svůj obsah, dokud neukončíme aplikaci Base nebo nezměníme hodnotu opětovným voláním RowCounter.

Editace záznamů – přidávání, úprava, mazání

Aby bylo možné záznamy upravovat, musí spolupracovat několik věcí:

Při provádění tohoto postupu pomocí makra je třeba vzít v úvahu všechny tyto dílčí kroky. Pokud některý z nich chybí nebo je proveden chybně, změny se ztratí a v databázi se neobjeví. Především se změna nesmí týkat zobrazené hodnoty ovládacího prvku, ale samotné sady dat. Proto je zbytečné měnit vlastnost Text ovládacího prvku.

Upozorňujeme, že tabulky jsou jediné datové sady, které lze měnit, aniž by to způsobilo problémy. U ostatních datových sad jsou úpravy možné pouze za zvláštních okolností.

Změna obsahu ovládacího prvku

Pokud chceme změnit pouze jednu hodnotu, lze použít vlastnost BoundField ovládacího prvku s vhodnou metodou. Poté musí být změna přenesena do databáze. Zde je příklad pole pro datum, do kterého se zadává skutečné datum:

Dim unoDate As New com.sun.star.util.Date

unoDate.Year = Year(Date)

unoDate.Month = Month(Date)

unoDate.Day = Day(Date)

oDateField.BoundField.updateDate( unoDate )

oForm.updateRow()       ' změna je přenesena do databáze

Pro BoundField se použije metoda updateXxx, která odpovídá datovému typu pole. V tomto příkladu se jedná o pole Date. Jako argument se předává nová hodnota – v tomto případě aktuální datum převedené do formátu, který makro vyžaduje.

Změna řádků v datové sadě

Předchozí metoda je nevhodná, pokud je třeba změnit několik hodnot v řádku.  Pro každé pole by totiž musel existovat ve formuláři ovládací prvek, což často není žádoucí ani užitečné. Pro každé pole musí být také načten objekt. Jednoduchý a přímý způsob používá tento formulář:

Dim unoDate As New com.sun.star.util.Date

unoDate.Year = Year(Date)

unoDate.Month = Month(Date)

unoDate.Day = Day(Date)

oForm.updateDate(3, unoDate )

oForm.updateString(4, "ein Text")

oForm.updateDouble(6, 3.14)

oForm.updateInt(7, 16)

oForm.updateRow()

Pro každý sloupec v datové sadě se zavolá metoda updateXxx odpovídající jeho typu. Argumenty jsou číslo sloupce (počítáno od 1) a požadovaná hodnota. Poté se změny přenesou do databáze.

Vytváření, úprava a odstraňování řádků

Pojmenované změny se vztahují k aktuálnímu řádku datové sady, která je základem formuláře. Za určitých okolností je nutné zavolat metodu z kapitoly „Navigace v datové sadě“ (strana 1). Je třeba provést následující kroky:

  1. Vybereme aktuální záznam.

  2. Změníme hodnoty podle popisu v předchozí části.

  3. Změnu potvrdíme následujícím příkazem:
    oForm.updateRow()

  4. Ve zvláštních případech je možné zrušit a vrátit se do předchozího stavu:
    oForm.cancelRowUpdates()

Pro nový záznam existuje speciální metoda srovnatelná s přechodem na nový řádek v ovládacím prvku tabulky. To se provádí následujícím způsobem:

  1. Připravíme se na nový záznam:
    oForm.moveToInsertRow()

  2. Zadáme všechny potřebné/požadované hodnoty. To se provádí pomocí metod updateXxx, jak je uvedeno v předchozí části.

  3. Nová data potvrdíme následujícím příkazem:
    oForm.insertRow()

  4. Nový záznam nelze snadno vrátit zpět. Místo toho budeme muset nový záznam odstranit.

Pro odstranění záznamu existuje jednoduchý příkaz; postupujeme takto:

  1. Zvolíme požadovaný záznam a provedeme jeho aktualizaci, stejně jako v případě změny.

  2. K jeho odstranění použijeme následující příkaz:
    oForm.deleteRow()

Tip

Aby bylo zajištěno, že se změny přenesou do databáze, je třeba je explicitně potvrdit pomocí updateRow nebo insertRow. Zatímco při stisknutí tlačítka Uložit se automaticky použije příslušná funkce, u makra je třeba před uložením určit, zda se jedná o nový záznam (Insert) nebo o úpravu stávajícího (Update).

If oForm.isNew Then
  oForm.insertRow()
Else
  
oForm.updateRow()
End If

Testování a změna ovládacích prvků

Kromě obsahu datové sady lze z kontroly vyčíst, upravit a modifikovat mnohem více informací. To platí zejména pro vlastnosti, jak je popsáno v kapitole 4, Formuláře.

Několik příkladů v části „Zlepšení použitelnosti“ (strana 1) používá doplňkové informace v poli:

Dim stTag As String

stTag = oEvent.Source.Model.Tag

Jak bylo uvedeno v předchozí části, vlastnost Text lze užitečně měnit pouze tehdy, pokud ovládací prvek není vázán na datovou sadu. Existují však i další vlastnosti, které jsou určeny jako součást definice formuláře, ale lze je upravit za běhu. Například popisek může mít jinou barvu textu, pokud představuje spíše varování než informaci:

Sub showWarning(oField As Object, iType As Integer)

   Select Case iType

      Case 1

         oField.TextColor = RGB(0,0,255)   ' 1 = blue

      Case 2

         oField.TextColor = RGB(255,0,0)   ' 2 = red

      Case Else

         oField.TextColor = RGB(0,255,0)   ' 0 = green (neither 1 nor 2)

   End Select

End Sub

Anglické názvy v makrech

Zatímco návrhář formuláře může pro vlastnosti a přístup k datům používat označení v rodném jazyce, v jazyce Basic lze používat pouze anglické názvy. Ty jsou uvedeny v následujícím přehledu.

Vlastnosti, které se obvykle nastavují pouze v definici formuláře, zde nejsou obsaženy. Stejně tak metody (funkce a/nebo procedury), které se používají jen zřídka nebo jsou vyžadovány pouze u složitějších deklarací.

Přehled obsahuje následující informace:

Další informace nalezneme v Přehledu API po vyhledání anglického názvu ovládacího prvku. Existuje užitečný nástroj Xray, který umožňuje zjistit, které vlastnosti a metody jsou pro daný prvek k dispozici.

Sub Main(oEvent)

   Xray(oEvent)

End Sub

Tím se spustí rozšíření Xray pro daný argument.

Vlastnosti formulářů a ovládacích prvků

Model ovládacího prvku popisuje jeho vlastnosti. Podle situace může být hodnota vlastnosti přístupná pouze pro čtení nebo pouze pro zápis. Pořadí je stejné jako v seznamech „Vlastnosti řídicích polí“ v kapitole 4, Formuláře.

Písmo

V každém ovládacím prvku, který zobrazuje text, lze přizpůsobit vlastnosti písma.

Tabulka 1: Vlastnosti písma

Název

Datový typ

R/W

Vlastnost

FontName

string

R+W

 Název písma

FontHeight

single

R+W

 Velikost písma

FontWeight

single

R+W

 Tučné nebo normální

FontSlant

integer

R+W

 Kurzíva nebo latinka

FontUnderline

integer

R+W

 Podtržené či nikoliv

FontStrikeout

integer

R+W

 Přeškrtnuté či nikoliv

Vzorec

Anglický termín: Form

Tabulka 2: Vlastnosti vzorce

Název

Datový typ

R/W

Vlastnost

ApplyFilter

boolean

R+W

Použitý filtr

Filtr

string

R+W

Aktuální filtr pro záznam

FetchSize

long

R+W

Počet záznamů načtených najednou

Row

long

R

Aktuální číslo řádku

RowCount

long

R

Počet záznamů

Tyto vlastnosti platí pro všechny ovládací prvky

Ovládací prvek – viz také FormComponent

Tabulka 3: Vlastnosti platné pro všechny ovládací prvky

Název

Datový typ

R/W

Vlastnost

Název

string

R+(W)

Název pole

Enabled

boolean

R+W

Aktivní: Pole lze vybrat.

EnableVisible

boolean

R+W

Zobrazí se pole.

ReadOnly

boolean

R+W

Obsah pole nelze měnit.

TabStop

boolean

R+W

Do pole se dostaneme pomocí klávesy Tab

Zarovnání

integer

R+W

Vodorovné zarovnání:
0 = vlevo, 1 = na střed, 2 = vpravo

BackgroundColor

long

R+W

Barva pozadí

Tag

string

R+W

Další informace

HelpText

string

R+W

Text nápovědy jako Tip zobrazený po najetí

Ty se vztahují na mnoho typů ovládacích prvků

Tabulka 4: Vlastnosti platné pro mnoho ovládacích prvků

Název

Datový typ

R/W

Vlastnost

Text

string

(R+W)

Zobrazený obsah pole. V textových polích je lze přečíst a dále zpracovat, ale u jiných typů to obvykle nefunguje.

Spin

boolean

R+W

Číselník začleněný do formátovaného pole.

TextColor

long

R+W

Barva textu (popředí).

DataField

string

R

Název pole v datové sadě.

BoundField

objekt

R

Objekt představující připojení k datové sadě a poskytující přístup k obsahu pole.

Textové pole – další vlastnosti (TextField)

Tabulka 5: Další vlastnosti textového pole

Název

Datový typ

R/W

Vlastnost

String

string

R+W

Zobrazený obsah pole.

MaxTextLen

integer

R+W

Maximální délka textu.

DefaultText

string

R+W

Výchozí text.

MultiLine

boolean

R+W

Označuje, zda existuje více než jeden řádek.

EchoChar

(integer)

R+W

Znak zobrazený při zadávání hesla.

Číselné pole (NumericField)

Tabulka 6: Vlastnosti číselného pole

Název

Datový typ

R/W

Vlastnost

ValueMin

double

R+W

Minimální přijatelná vstupní hodnota

ValueMax

double

R+W

Maximální přijatelná vstupní hodnota

Hodnota

double

R+(W)

Aktuální hodnota
(Nepoužívejte pro hodnoty ze souboru dat).

ValueStep

double

R+W

Interval odpovídající jednomu kliknutí kolečkem myši nebo spinboxem.

DefaultValue

double

R+W

Výchozí hodnota.

DecimalAccuracy

integer

R+W

Počet desetinných míst.

ShowThousandsSeparator

boolean

R+W

Zobrazení oddělovače tisíců podle nastavení národního prostředí.

Pole pro datum (DateField)

Hodnoty data jsou definovány datovým typem long a zobrazují se ve formátu ISO: RRRRMMDD, například 20190304 pro 04. března 2019. Chceme-li použít tento typ s funkcí getDateupdateDate, a s typem com.sun.star.util.Date, podíváme se na příklady.

Tabulka 7: Vlastnosti pole Datum

Název

Datový typ

Datový typ od LO 4.1.1

R/W

Vlastnost

DateMin

long

com.sun.star.util.Date

R+W

Minimální akceptované zadané datum.

DateMax

long

com.sun.star.util.Date

R+W

Maximální akceptované zadané datum.

Datum

long

com.sun.star.util.Date

R+(W)

Aktuální hodnota
(Nepoužívejte pro hodnoty ze souboru dat).

DateFormat

integer

 

R+W

Formát data specifický pro operační systém:
0 = krátké datum  (jednoduché)
1 = krátké Datum
dd.mm.yy (rok zobrazen pomocí dvou číslic)
2 = krátké Datum
dd.mm.yyyy (čtyřmístný rok)
3 = dlouhé Datum (obsahuje název dne v týdnu a měsíce)
Další možnosti nalezneme v definici formuláře nebo v 
Popisu API.

DefaultDate

long

com.sun.star.util.Date

R+W

Výchozí hodnota.

DropDown

boolean

 

R+W

Zobrazení rozevíracího měsíčního kalendáře

Pole pro čas (TimeField)

Hodnoty času jsou také typu long.

Tabulka 8: Vlastnosti pole pro čas

Název

Datový typ

Datový typ z LO 4.1.1

R/W

Vlastnost

TimeMin

long

com.sun.star.util.Time

R+W

Minimální přijatelná vstupní hodnota.

TimeMax

long

com.sun.star.util.Time

R+W

Maximální přijatelná vstupní hodnota.

Čas

long

com.sun.star.util.Time

R+(W)

Aktuální hodnota
(Nepoužívejte pro hodnoty ze souboru dat).

TimeFormat

integer

 

R+W

Formát času:
0 = krátký, formát
hh:mm (hodiny, minuty, 24hodinové hodiny)
1 = dlouhý, formát
h:mm:ss (totéž s vteřinami, 24hodinové hodiny)
2 = krátký, formát
h:mm (12hodinové hodiny s AM/PM)
3 = dlouhý, formát
h:mm:ss (12hodinové hodiny s AM/PM)
4 = krátké zadání na dobu trvání
5 = dlouhý záznam pro časové období

DefaultTime

long

com.sun.star.util.Time

R+W

Výchozí hodnota.

Měnové pole (CurrencyField)

Měnové pole je číselné pole s následujícími dalšími možnostmi.

Tabulka 9: Vlastnosti měnového pole

Název

Datový typ

R/W

Vlastnost

CurrencySymbol

string

R+W

Symbol měny pouze pro zobrazení.

PrependCurrencySymbol

boolean

R+W

Před číslem se zobrazí symbol.

Formátované pole (FormattedControl)

Formátovaný ovládací prvek lze použít podle potřeby pro čísla, měnu nebo datum/čas. Platí zde velmi mnoho již popsaných vlastností, ale s jinými názvy.

Tabulka 10: Vlastnosti formátovaného pole

Název

Datový typ

L/S

Vlastnost

CurrentValue

varianta

R

Aktuální hodnota obsahu. Skutečný typ dat závisí na jejich obsahu a formátu.

EffectiveValue

R+(W)

EffectiveMin

double

R+W

Minimální přijatelná vstupní hodnota.

EffectiveMax

double

R+W

Maximální přijatelná vstupní hodnota.

EffectiveDefault

varianta

R+W

Výchozí hodnota.

FormatKey

long

R+(W)

Formát pro zobrazení a zadání. Pomocí makra jej nelze jednoduše změnit.

EnforceFormat

boolean

R+W

Formát se testuje při zadávání. Povoleny jsou pouze určité znaky a kombinace.

Pole se seznamem (ListBox)

Přístup ke čtení a zápisu hodnoty ležící za vybraným řádkem je poněkud komplikovaný, ale možný.

Tabulka 11: Vlastnosti seznamu

Název

Datový typ

R/W

Vlastnost

ListSource

pole řetězců

R+W

Zdroj dat: Zdroj obsahu seznamu nebo název datové sady, která poskytuje viditelnou položku.

ListSourceType

integer

R+W

Typ zdroje dat:
0 = Seznam hodnot
1 = Tabulka
2 = Dotaz
3 = Sada výsledků z SQL příkazu
4 = Výsledek databázového příkazu
5 = Názvy polí z tabulky databáze

StringItemList

pole řetězců

R

Seznam položek, které lze vybrat.

ItemCount

integer

R

Počet dostupných položek seznamu

ValueItemList

pole řetězců

R

Seznam hodnot, které mají být předány z formuláře do tabulky.

DropDown

boolean

R+W

Rozevírací seznam.

LineCount

integer

R+W

Celkový počet zobrazených řádků při úplném rozbalení.

MultiSelection

boolean

R+W

Určeno pro vícenásobný výběr.

SelectedItems

pole celých čísel

R+W

Seznam vybraných položek jako seznam pozic v celkovém seznamu položek.

První vybraný prvek z pole seznamu se získá takto:

oControl = oForm.getByName("Name of the Listbox")

sEintrag = oControl.ValueItemList( oControl.SelectedItems(0) )

Poznámka

Od verze LibreOffice 4.1 lze hodnotu předanou do databáze určit přímo.

oControl = oForm.getByName("Name of the Listbox")
iD = oControl.getCurrentValue()

getCurrentValue() vrací hodnotu, která bude uložena v databázové tabulce. U polí se seznamem to závisí na poli, ke kterému jsou vázány ( BoundField ).

Až do verze LibreOffice 4.0 včetně vracela tato funkce zobrazený obsah, nikoli základní hodnotu v tabulce.

Vezměme prosím na vědomí, že položka je „pole řetězců“, pokud se dotaz na pole seznamu vymění za omezení možnosti výběru:

Sub Listenfeldfilter

   Dim stSql(0) As String

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm As Object

   Dim oFeld As Object

   oDoc = thisComponent

   oDrawpage = oDoc.drawpage

   oForm = oDrawpage.forms.getByName("MainForm")

   oFeld = oForm.getByname("Listenfeld")

   stSql(0) = "SELECT ""Name"", ""ID"" FROM ""Filter_Name"" ORDER BY ""Name"""

   oFeld.ListSource = stSql

   oFeld.refresh

End Sub

Pole se seznamem (ComboBox)

Přestože mají podobné funkce jako seznamy, vlastnosti polí se seznamem se poněkud liší. Viz příklad „Pole se seznamem jako seznamy s možností zadání“ na straně 1.

Tabulka 12: Vlastnosti pole se seznamem

Název

Datový typ

R/W

Vlastnost

Automatické dokončování

boolean

R+W

Vyplňuje se automaticky.

StringItemList

pole řetězců

R+W

Položky seznamu, které jsou k dispozici k použití.

ItemCount

integer

R

Počet dostupných položek seznamu.

DropDown

boolean

R+W

Rozevírací seznam.

LineCount

integer

R+W

Počet řádků zobrazených při rozbalení.

Text

string

R+W

Aktuálně zobrazený text.

DefaultText

string

R+W

Výchozí položka.

ListSource

string

R+W

Název zdroje dat, který poskytuje položky seznamu.

ListSourceType

integer

R+W

Typ zdroje dat. Stejné možnosti jako u polí se seznamem (pouze výběr seznamu hodnot je ignorován).

Zaškrtávací políčka (CheckBox) a přepínače (RadioButton)

Lze použít také tlačítka volby.

Tabulka 13: Vlastnosti zaškrtávacích políček a přepínačů

Název

Datový typ

R/W

Vlastnost

Popisek

string

R+W

Název (štítek)

Stav

short

R+W

Stav
0 = není vybráno
1 = vybráno
2 = nedefinováno

MultiLine

boolean

R+W

Zalomení řádků pro dlouhý text.

Pole vzoru (PatternField)

Kromě vlastností pro jednoduchý text jsou zajímavé následující:

Tabulka 14: Vlastnosti pole vzorku

Název

Datový typ

R/W

Vlastnost

EditMask

string

R+W

Vstupní maska.

LiteralMask

string

R+W

Maska znaků.

StrictFormat

boolean

R+W

Testování formátu při zadávání.

Ovládání prvek tabulky (GridControl)

Tabulka 15: Vlastnosti prvku tabulky

Název

Datový typ

R/W

Vlastnost

Počet

long

R

Počet sloupců.

ElementNames

pole řetězců

R

Seznam názvů sloupců.

HasNavigationBar

boolean

R+W

K dispozici je navigační panel.

RowHeight

long

R+W

Výška řádku.

FixedText – také se nazývá Label

Tabulka 16: Vlastnosti pevného textu / popisku

Název

Datový typ

R/W

Vlastnost

Popisek

string

R+W

Zobrazený text.

MultiLine

boolean

R+W

Zalomení řádků pro dlouhý text.

Skupinové rámečky (GroupBox)

Pro skupinové rámečky, které se běžně zpracovávají pomocí maker, nejsou k dispozici žádné vlastnosti. Důležitý je stav jednotlivých polí možností.

Tlačítka

CommandButton nebo ImageButton

Tabulka 17: Vlastnosti příkazového a obrazového tlačítka

Název

Datový typ

R/W

Vlastnost

Popisek

string

R+W

Nadpis – Text popisku.

Stav

short

R+W

Výchozí stav vybraný pro přepínání.

MultiLine

boolean

R+W

Zalomení řádků pro dlouhý text.

DefaultButton

boolean

R+W

Zda se jedná o výchozí tlačítko.

Lišta navigace (NavigationBar)

Další vlastnosti a metody spojené s navigací – například filtry a změna ukazatele záznamu – se ovládají pomocí formuláře.

Tabulka 18: Vlastnosti lišty navigace

Název

Datový typ

R/W

Vlastnost

IconSize

short

R+W

Velikost ikon.

ShowPosition

boolean

R+W

Polohu lze zadat a zobrazí se.

ShowNavigation

boolean

R+W

Umožňuje navigaci.

ShowRecordActions

boolean

R+W

Umožňuje provádět záznamové akce.

ShowFilterSort

boolean

R+W

Umožňuje třídění podle filtru.

Metody pro formuláře a ovládací prvky

Datový typ parametru je označen zkratkou:

Navigace v datové sadě

Tyto metody fungují jak ve formulářích, tak v sadě výsledků dotazu.

„Kurzor“ v popisu znamená ukazatel záznamu.

Tabulka 19: Navigační metody: Testování polohy kurzoru

Název

Datový typ

Popis

isBeforeFirst

boolean

Kurzor je před prvním záznamem. To platí v případě, že po zadání ještě nebyl vynulován.

isFirst

boolean

Zobrazí, zda je kurzor na prvním záznamu.

isLast

boolean

Zobrazí, zda je kurzor na posledním záznamu.

isAfterLast

boolean

Kurzor je po přesunutí na další řádek za posledním řádkem.

getRow

long

Aktuální číslo řádku.

Tabulka 20: Navigační metody: Nastavení kurzoru

Pro logické datové typy znamená True, že navigace proběhla úspěšně.

Název

Datový typ

Popis

beforeFirst

Přesune se před první řádek.

first

boolean

Přesune se na první řádek.

previous

boolean

Vrací se o jeden řádek zpět.

next

boolean

Přesune se o jeden řádek dopředu.

last

boolean

Přejde na poslední záznam.

afterLast

Přejde po posledním záznamu.

absolute(n)

boolean

Přejde na řádek se zadaným číslem řádku.

relative(n)

boolean

Přejde zpět nebo dopředu o zadanou hodnotu: dopředu kladná hodnota a dozadu pro záporná hodnota argumentu.

Tabulka 21: Navigační metody: Metody ovlivňující aktuální stav záznamu

Název

Datový typ

Popis

refreshRow

 Zpětně načte původní hodnoty řádku.

rowInserted

boolean

 Označuje, zda se jedná o nový řádek.

rowUpdated

boolean

 Ukazuje, zda byl aktuální řádek změněn.

rowDeleted

boolean

 Ukazuje, zda byl aktuální řádek smazán.

Úprava řádků dat

Metody používané pro čtení jsou k dispozici pro jakýkoli formulář nebo soubor dat. Metody pro změnu a ukládání lze použít pouze pro upravitelné datové sady (obvykle tabulky, nikoli dotazy).

Tabulka 22: Metody pro úpravu řádků dat: Metody pro celý řádek

Název

Datový typ

Popis

insertRow

Uloží nový řádek.

updateRow

Potvrdí změnu aktuálního řádku.

deleteRow

Odstraní aktuální řádek.

cancelRowUpdates

Obrátí změny v aktuálním řádku.

moveToInsertRow

Přesune kurzor na řádek odpovídající novému záznamu.

moveToCurrentRow

Po zadání nového záznamu vrátí kurzor na předchozí pozici.

Tabulka 23: Metody pro úpravu řádků dat: Čtení hodnot

Název

Datový typ

Popis

getString(c)

string

Předává obsah sloupce jako řetězec znaků.

getBoolean(c)

boolean

Předává obsah sloupce jako logickou hodnotu.

getByte(c)

byte

Udává obsah sloupce jako jeden bajt.

getShort(c)

short

Udává obsah sloupce jako celé číslo.

getInt(c)

integer

Udává obsah sloupce jako celé číslo.

getLong(c)

long

Udává obsah sloupce jako celé číslo.

getFloat(c)

float

Udává obsah sloupce jako desetinné číslo s jednoduchou přesností.

getDouble(c)

double

Udává obsah sloupce jako desetinné číslo s dvojnásobnou přesností. Díky automatickým převodům prováděným Basicem je tento typ vhodný pro desetinná a měnová pole.

getBytes(c)

pole bajtů

Předává obsah sloupce jako pole jednotlivých bajtů.

getDate(c)

Datum

Předává obsah sloupce jako datum.

getTime(c)

Čas

Předává obsah sloupce jako časovou hodnotu.

getTimestamp(c)

DateTime

Předává obsah sloupce jako časové razítko (datum a čas).

 

wasNull

boolean

Ukazuje, zda hodnota naposledy načteného sloupce byla NULL.

Poznámka

V samotném jazyce Basic mají hodnoty data a času typ DATE. Pro přístup k datům v datových sadách jsou k dispozici různé typy: com.sun.star.util.Date pro datum, com.sun.star.util.Time pro čas a com.sun.star.util.DateTime pro časovou značku.

Tabulka 24: Metody ukládání hodnot

Název

Datový typ

Popis

updateNull(c)

Nastaví obsah sloupce na hodnotu NULL.

updateBoolean(c,b)

Změní obsah sloupce c na logickou hodnotu b.

updateByte(c,x)

Uloží bajt x do sloupce c.

updateShort(c,n)

Uloží celé číslo n do sloupce c.

updateInt(c,n)

Uloží celé číslo n do sloupce c.

updateLong(c,n)

Uloží celé číslo n do sloupce c.

updateFloat(c,n)

Uloží desetinné číslo n do sloupce c.

updateDouble(c,n)

Uloží desetinné číslo n do sloupce c.

updateString(c,s)

Uloží řetězec s do sloupce c.

updateBytes(c,x)

Uloží pole bajtů x do sloupce c.

updateDate(c,d)

Uloží datum d do sloupce c.

updateTime(c,d)

Uloží čas d do sloupce c.

updateTimestamp(c,d)

Uloží časové razítko d do sloupce c.

Úprava jednotlivých hodnot

Tato metoda používá vlastnost BoundField ovládacího prvku ke čtení nebo úpravě obsahu příslušného sloupce. Odpovídá téměř přesně metodě popsané v předchozí části s tím rozdílem, že se neuvádí číslo sloupce.

Tabulka 25: Metody úprav jednotlivých hodnot: čtení hodnot

Název

Datový typ

Popis

getString

string

Předává obsah pole jako řetězec znaků.

getBoolean

boolean

Předává obsah pole jako logickou hodnotu.

getByte

byte

Předává obsah pole jako jeden bajt.

getShort

short

Udává obsah pole jako celé číslo.

getInt

integer

Udává obsah pole jako celé číslo.

getLong

long

Udává obsah pole jako celé číslo.

getFloat

float

Udává obsah pole jako desetinnou hodnotu s přesností na jedno desetinné místo.

getDouble

double

Udává obsah pole jako desetinné číslo s přesností na dvě desetinná místa. Díky automatickým převodům prováděným Basicem je tento typ vhodný pro desetinná a měnová pole.

getBytes

pole bajtů

Předává obsah pole jako pole bajtů.

getDate

Datum

Udává obsah pole jako datum.

getTime

Čas

Udává obsah pole jako čas.

getTimestamp

DateTime

Udává obsah pole jako časové razítko.

wasNull

boolean

Ukazuje, zda hodnota naposledy načteného sloupce byla NULL.

Poznámka

V samotném jazyce Basic mají hodnoty data a času typ DATE. Pro přístup k datům v datových sadách jsou k dispozici různé typy: com.sun.star.util.Date pro datum, com.sun.star.util.Time pro čas a com.sun.star.util.DateTime pro časovou značku.

Tabulka 26: Metody úprav jednotlivých hodnot: ukládání hodnot

Název

Datový typ

Popis

updateNull

Nastaví obsah sloupce na hodnotu NULL.

updateBoolean(b)

Nastaví obsah sloupce na logickou hodnotu b.

updateByte(x)

Uloží bajt x do sloupce.

updateShort(n)

Uloží celé číslo n do sloupce.

updateInt(n)

Uloží celé číslo n do sloupce.

updateLong(n)

Uloží celé číslo n do sloupce.

updateFloat(n)

Uloží do sloupce desetinné číslo n.

updateDouble(n)

Uloží do sloupce desetinné číslo n.

updateString(s)

Uloží řetězec znaků s do sloupce.

updateBytes(x)

Uloží pole bajtů x do sloupce.

updateDate(d)

Uloží datum d do sloupce.

updateTime(d)

Uloží čas d do sloupce.

updateTimestamp(d)

Uloží časové razítko d do sloupce.

Parametry pro připravené příkazy SQL

Metody, které přenášejí hodnotu předpřipraveného příkazu SQL (viz „Předpřipravené příkazy SQL s parametry“ na straně 1), jsou podobné jako v předchozí části. První parametr (označený I) je číslovaná pozice v příkazu SQL.

Tabulka 27: Metody přenosu hodnoty předpřipraveného příkazu SQL

Název

Datový typ

Popis

setNull(i, n)

Nastaví obsah sloupce na hodnotu NULL. N je datový typ SQL, jak je uveden v Popisu API.

setBoolean(i, b)

Vloží do příkazu SQL zadanou logickou hodnotu b.

setByte(i, x)

Vloží zadaný bajt x do příkazu SQL.

setShort(i, n)

Vloží do příkazu SQL zadané celé číslo n.

setInt(i, n)

Vloží do příkazu SQL zadané celé číslo n.

setLong(i, n)

Vloží do příkazu SQL zadané celé číslo n.

setFloat(i, n)

Vloží zadané desetinné číslo do příkazu SQL.

setDouble(i, n)

setString(i, s)

Vloží zadaný řetězec znaků do příkazu SQL.

setBytes(i, x)

Vloží zadané pole bajtů x do příkazu SQL.

setDate(i, d)

Vloží zadané datum d do příkazu SQL.

setTime(i, d)

Vloží do příkazu SQL zadaný čas d.

setTimestamp(i, d)

Vloží do příkazu SQL zadané časové razítko d.

clearParameters

Odstraní předchozí hodnoty všech parametrů příkazu SQL.

Zlepšení použitelnosti

U této první kategorie použití maker si ukážeme různé možnosti, jak zlepšit použitelnost formulářů aplikace Base.

Automatická aktualizace formulářů

Často se ve formuláři něco změní a tato změna se musí objevit v druhém formuláři na stejné stránce. Následující úryvek kódu zavolá metodu Reload druhého formuláře a způsobí jeho obnovení.

Sub Update

Nejprve je makro pojmenováno. Výchozí označení makra je Sub. Může být napsáno velkými nebo malými písmeny. Sub umožňuje spuštění podprogramu bez vrácení hodnoty. Dále je naopak popsána funkce, která vrací hodnotu.

Makro má název Update. Proměnné nemusíme deklarovat, protože LibreOffice Basic automaticky vytváří proměnné při jejich použití. Pokud proměnnou napíšeme špatně, LibreOffice Basic vytvoří novou proměnnou, aniž by si stěžoval. Použijeme Option Explicit chceme-li LibreOffice Basicu zabránit v automatickém vytváření proměnných; většina programátorů tento postup doporučuje.

Proto obvykle začínáme deklarací proměnných. Všechny zde deklarované proměnné jsou objekty (nikoli čísla nebo text), proto na konec deklarace přidáme As Object. Abychom si později připomněli typ proměnných, uvádíme jejich názvy s písmenem "o". V zásadě si však můžeme zvolit téměř libovolné názvy proměnných.

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm As Object

Formulář leží v aktuálně aktivním dokumentu. Kontejner, ve kterém jsou uloženy všechny formuláře, se jmenuje drawpage. V navigátoru formulářem se jedná o koncept nejvyšší úrovně, kterému jsou všechny formuláře podřízeny.

V tomto příkladu je formulář, ke kterému se má přistupovat, pojmenován Display. Display je název viditelný v navigátoru formulářem. Tak například první formulář se ve výchozím nastavení jmenuje Form1.

   oDoc = thisComponent

   oDrawpage = oDoc.drawpage

   oForm = oDrawpage.forms.getByName("Display")

Protože je nyní formulář zpřístupněn a bod, ve kterém je přístupný, je uložen v proměnné oForm, je nyní znovu načten (obnoven) příkazem reload().

   oForm.reload()

End Sub

Podprogram začíná SUB, takže musí končit End Sub.

Toto makro lze nyní vybrat ke spuštění při uložení jiného formuláře. Například v pokladně, pokud se do jednoho formuláře zadá celkový počet prodaných položek a jejich skladová čísla (načtená snímačem čárových kódů), může se v jiném formuláři ve stejném otevřeném okně zobrazit název všech položek a celková cena, jakmile se formulář uloží.

Filtrování záznamů

Samotný filtr může bez problémů fungovat v podobě popsané v kapitole 8, Úlohy databáze. Níže uvedená varianta nahrazuje tlačítko Uložit a znovu načítá seznamy, takže zvolený filtr z jednoho seznamu může omezit možnosti dostupné v druhém seznamu.

Poznámka

Viz také databáze Example_Search_and_Filter.odb související s touto knihou.

Sub Filter

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm1 As Object

   Dim oForm2 As Object

   Dim oFieldList1 As Object

   Dim oFieldList2 As Object

   oDoc = thisComponent

   oDrawpage = oDoc.drawpage

Nejprve jsou definovány a nastaveny proměnné pro přístup k sadě formulářů. Tato sada obsahuje dva formuláře "Filter" a "Display". Pole seznamu jsou ve formuláři "Filter" a mají názvy "List_1" a "List_2".

   oForm1 = oDrawpage.forms.getByName("filter")

   oForm2 = oDrawpage.forms.getByName("display")

   oFieldList1 = oForm1.getByName("listbox1")

   oFieldList2 = oForm1.getByName("listbox2")

Nejprve se obsah polí se seznamem přenese do základního formuláře pomocí commit(). Přenos je nutný, protože jinak by se změna v poli se seznamem při ukládání neprojevila. Instrukci commit() je třeba použít pouze na poli se seznamem, ke kterému byl právě uskutečněn přístup. Poté se záznam uloží pomocí updateRow(). Naše filtrační tabulka obsahuje v zásadě pouze jeden záznam, který je zapsán jednou na začátku. Tento záznam je proto průběžně přepisován pomocí příkazu update.

   oFieldList1.commit()

   oFieldList2.commit()

   oForm1.updateRow()

Pole se seznamem se mají navzájem ovlivňovat. Pokud například jedno pole se seznamem slouží k omezení zobrazených médií na CD, druhé pole se seznamem by nemělo zahrnovat všechny autory knih ve svém seznamu autorů. Výběr v druhém seznamu by pak příliš často vedl k prázdnému filtru. To je důvod, proč je třeba seznamy znovu načíst. Přesně řečeno, příkaz refresh() je třeba provést pouze na seznamu, který nebyl otevřen.

Poté se znovu načte form2, který má zobrazit filtrovaný obsah.

   oFieldList1.refresh()

   oFieldList2.refresh()

   oForm2.reload()

End Sub

Pole se seznamem, která mají být touto metodou ovlivněna, mohou být doplněna pomocí různých dotazů.

Nejjednodušší variantou je, že pole seznamu přebírá svůj obsah z výsledků filtru. Jediný filtr pak určuje, který obsah dat bude dále filtrován.

SELECT "Field_1" || ' - ' || "Count" AS "Display", "Field_1"

FROM ( SELECT COUNT( "ID" ) AS "Count", "Field_1" FROM "searchtable" GROUP BY "Field_1" )

ORDER BY "Field_1"

Zobrazí se obsah pole a počet nálezů. K získání počtu shod se používá dílčí dotaz. To je nezbytné, protože jinak se v seznamu zobrazí pouze počet shod bez dalších informací z pole.

Makro touto akcí poměrně rychle vytvoří seznamy, které jsou vyplněny pouze jednou hodnotou. Pokud pole se seznamem není NULL, bere se při filtrování v úvahu. Po aktivaci druhého pole se seznamem jsou v obou polích se seznamem k dispozici pouze prázdná pole a jedna zobrazená hodnota. To se může zdát praktické pro omezené vyhledávání. Ale co když knihovní katalog jasně ukazuje zařazení položky, ale neukazuje jednoznačně, zda se jedná o knihu, CD nebo DVD? Pokud je nejprve vybrána klasifikace a poté je druhé pole seznamu nastaveno na "CD", musí být pro následné vyhledávání zahrnující knihy nastaveno na NULL. Bylo by praktičtější, kdyby se v druhém seznamu přímo zobrazovaly různé dostupné typy médií s odpovídajícím počtem položek.

K dosažení tohoto cíle je sestaven následující dotaz, který již nevychází přímo z výsledků filtru. Počet odpovídajících položek je třeba získat jiným způsobem.

SELECT

IFNULL( "Field_1" || ' - ' || "Count", 'empty - ' || "Count" ) AS "Display",

"Field_1"

FROM

   ( SELECT COUNT( "ID" ) AS "Count", "Field_1" FROM "Table"

      WHERE "ID" IN

      ( SELECT "Table"."ID" FROM "Filter", "Table"

         WHERE "Table"."Field_2" = IFNULL( "Filter"."Filter_2",

         "Table"."Field_2" ) )

   GROUP BY "Field_1" )

ORDER BY "Field_1"

Tento velmi složitý dotaz lze rozdělit. V praxi se běžně používá VIEW pro poddotaz. Pole se seznamem získává svůj obsah z dotazu vztahujícího se k tomuto VIEW.

Podrobný dotaz: Dotaz obsahuje dva sloupce. První sloupec obsahuje zobrazení, které vidí osoba s otevřeným formulářem. Tento pohled ukazuje obsah pole a shody pro obsah tohoto pole oddělené pomlčkou. Druhý sloupec přenáší svůj obsah do základní tabulky formuláře. Zde máme k dispozici pouze obsah pole. Pole se seznamem tak čerpají svůj obsah z dotazu, který je ve formuláři prezentován jako výsledek filtru. Pouze tato pole jsou k dispozici pro další filtrování.

Tabulka, ze které se tyto informace čerpají, je vlastně dotaz. V tomto dotazu se počítají pole primárního klíče (SELECT COUNT("ID" ) AS "Count"). Ta se pak seskupí podle hledaného výrazu v poli (GROUP BY "Field_1"). Tento dotaz zobrazuje termín v samotném poli jako druhý sloupec. Tento dotaz je zase založen na dalším poddotazu:

SELECT "Table"."ID" FROM "Filter", "Table"

WHERE "Table"."Field_2" =

   IFNULL( "Filter"."Filter_2", "Table"."Field_2" )

Tento poddotaz se zabývá dalším filtrovaným polem. Toto další pole musí v zásadě také odpovídat primárnímu klíči. Pokud existují další filtry, lze tento dotaz rozšířit:

SELECT "Table"."ID" FROM "Filter", "Table" WHERE

"Table"."Field_2" = IFNULL( "Filter"."Filter_2", "Table"."Field_2" )

AND

"Table"."Field_3" = IFNULL( "Filter"."Filter_3", "Table"."Field_3" )

To umožňuje, aby všechna další pole, která mají být filtrována, kontrolovala, co se nakonec zobrazí v seznamu prvního pole "Field_1".

Nakonec je celý dotaz seřazen podle základního pole.

Jak vlastně vypadá výsledný dotaz, který je základem zobrazeného formuláře, se dozvíme v kapitole 8, Úlohy databáze.

Následující makro může prostřednictvím pole se seznamem řídit, která pole se seznamem se musí uložit a která se musí znovu načíst.

Následující podprogram předpokládá, že vlastnost Další informace pro každé pole se seznamem obsahuje čárkou oddělený seznam všech názvů polí se seznamem bez mezer. První název v seznamu musí být název tohoto pole se seznamem.

Sub Filter_more_info(oEvent As Object)

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm1 As Object

   Dim oForm2 As Object

   Dim sTag As String

   sTag = oEvent.Source.Model.Tag

Vytvoří se pole (kolekce dat přístupná pomocí indexového čísla) a naplní se názvy polí se seznamem. První název v seznamu je název pole seznamu spojeného s událostí.

   aList() = Split(sTag, ",")

   oDoc = thisComponent

   oDrawpage = oDoc.drawpage

   oForm1 = oDrawpage.forms.getByName("filter")

   oForm2 = oDrawpage.forms.getByName("display")

Pole je procházeno od své dolní hranice ('Lbound()') k horní hranici ('Ubound()') v jedné smyčce. Všechny hodnoty, které byly v doplňkových informacích odděleny čárkami, se nyní přenášejí postupně.

   For = LBound(aList()) To UBound(aList())

      If = 0 Then

Pole se seznamem, které makro spustilo, musí být uloženo. Nachází se v proměnné aList(0). Nejprve se informace pro pole seznamu přenesou do základní tabulky a poté se záznam uloží.

         oForm1.getByName(aList(i)).commit()

         oForm1.updateRow()

      Else

Ostatní seznamy je třeba obnovit, protože nyní obsahují různé hodnoty v závislosti na prvním seznamu.

         oForm1.getByName(aList(i)).refresh()

      End If

   Next

   oForm2.reload()

End Sub

Dotazy pro toto použitelnější makro jsou přirozeně stejné jako ty, které již byly uvedeny v předchozí části.

Příprava dat z textových polí tak, aby odpovídala konvencím SQL

Při ukládání dat do příkazu SQL mohou apostrofy v názvech, například „O'Connor“, způsobit problémy. Je to proto, že jednoduché uvozovky ('') se používají k uzavření textu, který má být vložen do záznamů. V takových případech potřebujeme zprostředkující funkci, která data vhodně připraví.

Function String_to_SQL(st As StringString)

   If InStr(st,"'") Then

      st = Join(Split(st,"'"),"''")

   End If

   String_to_SQL = st

End Function

Všimneme si, že se jedná o funkci, nikoli o podřízený prvek. Funkce přijímá jako argument hodnotu a poté vrací hodnotu.

V textu, který se má přenést, se nejprve vyhledá, zda neobsahuje apostrof. V takovém případě se text v tomto místě rozdělí – apostrof je sám o sobě oddělovačem – a opět se spojí dvěma apostrofy. Tím se zakryje kód SQL. Výsledek funkce se získá následujícím voláním:

stTextnew = String_to_SQL(stTextold)

To jednoduše znamená, že proměnná stTextold se přepracuje a výsledek se uloží do proměnné stTextnew. Tyto dvě proměnné nemusí mít ve skutečnosti odlišné názvy. Volání lze provést pomocí:

stText = String_to_SQL(stText)

Tato funkce se opakovaně používá v následujících makrech, aby bylo možné apostrofy ukládat také pomocí příkazů SQL.

Výpočet hodnot ve formuláři předem

Hodnoty, které lze vypočítat pomocí databázových funkcí, nejsou v databázi uloženy samostatně. Výpočet neprobíhá během zadávání do formuláře, ale až po uložení záznamu. Pokud se formulář skládá pouze z jednoho ovládacího prvku tabulky, je to jen malý rozdíl. Vypočtenou hodnotu lze odečíst ihned po zadání údajů. Pokud však formuláře obsahují sadu různých jednotlivých polí, nemusí být předchozí záznam viditelný. V takových případech má smysl, aby se hodnoty, které se jinak počítají uvnitř databáze, zobrazovaly v příslušných polích.

Poznámka

Viz databáze Example_direct_Calculation_Form.odb spojená s touto knihou.

Následující tři makra ukazují, jak lze takovou věc v zásadě provést. Obě makra jsou spojena s výstupem z daného pole.  To také umožňuje zohlednit skutečnost, že hodnota v existujícím poli může být následně změněna.

Sub Calculation_without_Tax(oEvent As Object)

   Dim oForm As Object

   Dim oField As Object

   Dim oField2 As Object

   oField = oEvent.Source.Model

   oForm = oField.Parent

   oField2 = oForm.getByName("price_without_tax")

   oField2.BoundField.UpdateDouble(oField.getCurrentValue / 1.19)

   If Not IsEmpty(oForm.getByName("quantity").getCurrentValue()) Then

      total_calc2(oForm.getByName("quantity"))

   End If

End Sub

Pokud je do pole Price zadána hodnota, makro se spustí při opuštění tohoto pole. Ve stejném formuláři jako pole Price je pole nazvané price_without_tax. Pro toto pole se použije BoundField.UpdateDouble pro výpočet ceny bez DPH. Datové pole je odvozeno z dotazu, který v zásadě provádí stejný výpočet, ale s použitím uložených dat. Tímto způsobem je vypočtená hodnota viditelná při zadávání dat a také později při procházení záznamu, aniž by byla uložena.

Pokud pole quantity obsahuje hodnotu, provede se další výpočet pro pole, která jsou s ním svázána.

Sub Calculation_Total(oEvent As Object)

   oField = oEvent.Source.Model

   Calculation_Total2(oField)

End Sub

Tento krátký postup slouží pouze k přenosu řešení následujícího postupu při opuštění pole quantity na formuláři.

Sub Calculation_Total2(oFeld As Object)

   Dim oForm As Object

   Dim oField2 As Object

   Dim oField3 As Object

   Dim oField4 As Object

   oForm = oFeld.Parent

   oField2 = oForm.getByName("price")

   oField3 = oForm.getByName("total")

   oField4 = oForm.getByName("tax_total")

   oField3.BoundField.UpdateDouble(oField.getCurrentValue * oField2.getCurrentValue)

   oField4.BoundField.UpdateDouble(oField.getCurrentValue * oField2.getCurrentValue -  

      oField.getCurrentValue * oField2.getCurrentValue / 1.19)

End Sub

Tento postup je pouze způsob, jak ovlivnit několik polí najednou. Postup se spouští z jednoho pole quantity, které obsahuje počet nakoupených položek. Pomocí tohoto pole a pole price se vypočítá celková částka a tax_total (daň celkem, pozn. překl.) a přenese se do příslušných polí.

Tyto postupy a dotazy mají jeden nedostatek: sazba DPH je v programu zakódována napevno. Bylo by lepší použít argument související s cenou, protože DPH se může lišit a nemusí být u všech výrobků stejná. V takových případech by bylo třeba příslušnou hodnotu DPH vyčíst z pole formuláře.

Poskytnutí aktuální verze LibreOffice

LibreOffice verze 4.1 přinesl některé změny v polích se seznamem a hodnotách data, kvůli kterým je nutné při spouštění maker v těchto oblastech určit aktuální verzi. K tomuto účelu slouží následující kód:

Function OfficeVersion()

   Dim aSettings, aConfigProvider

   Dim aParams2(0) As New com.sun.star.beans.PropertyValue

   Dim sProvider$, sAccess$

   sProvider = "com.sun.star.configuration.ConfigurationProvider"

   sAccess = "com.sun.star.configuration.ConfigurationAccess"

   aConfigProvider = createUnoService(sProvider)

   aParams2(0).Name = "nodepath"

   aParams2(0).Value = "/org.openoffice.Setup/Product"

   aSettings = aConfigProvider.createInstanceWithArguments(sAccess, aParams2())

   OfficeVersion() = Array(aSettings.ooName,aSettings.ooSetupVersionAboutBox)

End Function

Tato funkce vrací pole, jehož prvním prvkem je LibreOffice a druhým úplné číslo verze, například 4.1.5.2.

Vrácení hodnoty polí seznamu

Od verze LibreOffice 4.1 se do databáze ukládá hodnota vrácená polem se seznamem do pole CurrentValue. V předchozích verzích, ani v OpenOffice nebo Apache OpenOffice tomu tak nebylo. Výpočet provede následující funkce. Je třeba zkontrolovat, zda verze LibreOffice není novější než LibreOffice 4.0.

Function ID_Determination(oField As Object) As Integer

   a() = OfficeVersion()

   If a(0) = "LibreOffice" And (LEFT(a(1),1) = 4 And RIGHT(LEFT(a(1),3),1) > 0) Or       LEFT(a(1),1) > 4 Then

      stContent = oField.currentValue

   Else

Před verzí LibreOffice 4.1 se předávaná hodnota načítala ze seznamu hodnot pole se seznamem. Viditelně vybraný záznam je SelectedItems(0). '0', protože v seznamu lze vybrat několik dalších hodnot.

      stContent = oField.ValueItemList(oField.SelectedItems(0))

   End If

   If IsEmpty(stContent) Then

-1 je hodnota, která se nepoužívá jako automatická hodnota, a proto nebude existovat ve většině tabulek jako cizí klíč.

      ID_Determination = -1

   Else

      ID_Determination = Cint(stContent)

Převod na celé číslo

   End If

End Function

Funkce přenáší hodnotu jako celé číslo. Většina primárních klíčů jsou automaticky se zvětšující celá čísla. Pokud cizí klíč toto kritérium nesplňuje, musí být návratová hodnota upravena na příslušný typ.

Zobrazenou hodnotu pole se seznamem lze dále určit pomocí vlastnosti zobrazení pole.

Sub Listfielddisplay

   Dim oDoc As Object

   Dim oForm As Object

   Dim oListbox As Object

   Dim oController As Object

   Dim oView As Object

   oDoc = thisComponent

   oForm = oDoc.Drawpage.Forms(0)

   oListbox = oForm.getByName("Listbox")

   oController = oDoc.getCurrentController()

   oView = oController.getControl(oListbox)

   print "Displayed content: " & oView.SelectedItem

End Sub

Kontrolér slouží k přístupu k zobrazení formuláře. To určuje, co se zobrazí ve vizuálním rozhraní. Vybraná hodnota je SelectedItem.

Omezení seznamů zadáním počátečních písmen

Někdy se může stát, že obsah polí se seznamem naroste do příliš velkých rozměrů. V takových případech je pro urychlení vyhledávání vhodné omezit obsah pole seznamu na hodnoty uvedené zadáním jednoho nebo více počátečních znaků. Samotné pole se seznamem je opatřeno příkazem SQL, který slouží jako zástupný příkaz. Může to být:

SELECT "Name", "ID" FROM "Table" ORDER BY "Name" LIMIT 5

Tím se zabrání tomu, aby aplikace Base musela při otevření formuláře načítat obrovský seznam hodnot.

Následující makro je propojeno s Vlastnosti: Vlastnosti: Pole se seznamem > Události > Klávesa uvolněna.

Global stListStart As String

Global lTime As Long

Nejprve se vytvoří globální proměnné. Tyto proměnné jsou nezbytné k tomu, aby bylo možné vyhledávat nejen jednotlivá písmena, ale po stisknutí dalších kláves také kombinace písmen.

Zadaná písmena se postupně ukládají do globální proměnné stListStart.

Globální proměnná lTime slouží k uložení aktuálního času v sekundách. Pokud je mezi jednotlivými stisky kláves dlouhá pauza, měla by se proměnná stListStart vynulovat. Z tohoto důvodu je dotazován časový rozdíl mezi po sobě následujícími záznamy.

Sub ListFilter(oEvent As Object)

   oField = oEvent.Source.Model

   If oEvent.KeyCode < 538 Then

Makro se spouští stiskem klávesy. V rámci rozhraní API má každý klíč číselný kód, který lze vyhledat v com::sun::star::awt::Key (skupiny konstant části rozhraní API).Speciální znaky jako ä, ö, a ü má kód 0. Všechna ostatní písmena a číslice mají kód KeyCode menší než 538.

Je důležité zkontrolovat KeyCode, protože stisknutí klávesy Tab pro přechod do jiného pole spustí také makro. KeyCode pro klávesu Tab je 1282, takže další kód v makru nebude proveden.

      Dim stSql(0) As String

Kód SQL pro pole se seznamem je uložen v poli. Příkazy SQL se však počítají jako jednotlivé datové prvky, takže pole je dimenzováno jako stSql(0).

Při čtení kódu SQL z pole se seznamem si uvědomíme, že kód SQL není přímo přístupný jako text. Místo toho je kód k dispozici jako jeden prvek pole: oField.ListSource(0).

Po deklaraci proměnných pro budoucí použití se příkaz SQL rozdělí. Abychom získali pole, které má být filtrováno, rozdělíme kód na první čárku. Pole proto musí být v příkazu uvedeno jako první. Poté je tento kód opět rozdělen na první znak uvozovek, který zavádí název pole. Zde je to provedeno pomocí jednoduchých polí. Proměnná stField musí mít na začátku uvozovky. Kromě toho se používá Rtrim, aby se na konci výrazu nevyskytovala mezera.

      Dim stText As String

      Dim stField As String

      Dim stQuery As String

      Dim ar0()

      Dim ar1()

      ar0() = Split(oField.ListSource(0),",", 2)

      ar1() = Split(ar0(0),"""", 2)

      stFeld = """" & Rtrim(ar1(1))

Dále se v kódu SQL očekává instrukce třídění. Příkazy v jazyce SQL však mohou být ve velkých, malých nebo smíšených písmenech, proto se k nalezení řetězce znaků ORDER používá funkce inStr místo funkce Split. Poslední parametr této funkce je 1, což znamená, že vyhledávání by nemělo rozlišovat velká a malá písmena. Vše nalevo od řetězce ORDER se použije pro konstrukci nového kódu SQL. Tím je zajištěno, že kód může obsluhovat i pole se seznamem, která pocházejí z různých tabulek nebo byla definována v kódu SQL pomocí dalších podmínek.

   stQuery = Left(oField.ListSource(0), inStr(1,oField.ListSource(0), "ORDER",1)-1)

   If inStr(stQuery, "LOWER") > 0 Then

      stQuery = Left(stQuery, inStr(stQuery, "LOWER")-1)

   ElseIf inStr(1,stQuery, "WHERE",1) > 0 Then

      stQuery = stQuery & " AND "

   Else

      stQuery = stQuery & " WHERE "

   End If

Pokud dotaz obsahuje výraz LOWER, znamená to, že byl vytvořen pomocí této procedury ListFilter. Proto při konstrukci nového dotazu musíme jít pouze do této pozice.

Pokud tomu tak není a dotaz již obsahuje výraz WHERE (velkými nebo malými písmeny), je třeba k dalším podmínkám dotazu připojit AND.

Pokud není splněna ani jedna z těchto podmínek, připojí se ke stávajícímu kódu WHERE.

      If lTime > 0 And Timer() - lTime < 5 Then

         stListStart = stListStart & oEvent.KeyChar

      Else

         stListStart = oEvent.KeyChar

      End If

      lTime = Timer()

Pokud je v globální proměnné uložena hodnota času a rozdíl mezi ní a aktuálním časem je menší než 5 sekund, připojí se zadané písmeno k předchozímu. V opačném případě je písmeno považováno za nový jednopísmenný záznam. Pole se seznamem bude poté znovu filtrováno podle této položky. Poté se aktuální čas uloží do lTime.

      stText = LCase( stListStart & "%")

      stSql(0) = stQuery + "LOWER("+stField+") LIKE '"+stText+"' ORDER BY "+stField+""

      oFeld.ListSource = stSql

      oField.refresh

   End If

End Sub

Kód SQL je konečně sestaven. Verze obsahu pole s malými písmeny se porovná s verzí zadaného písmene (písmen) s malými písmeny. Kód se vloží do pole se seznamem a pole se aktualizuje tak, aby bylo možné vyhledat pouze filtrovaný obsah.

Převod dat z formuláře do proměnné data

Function DateValue(oField As Object) As Date

   a() = OfficeVersion()

   If a(0) = "LibreOffice" And (LEFT(a(1),1) = 4 And RIGHT(LEFT(a(1),3),1) > 0)

      Or LEFT(a(1),1) > 4 Then

Zde jsou zachyceny všechny verze LibreOffice od verze 4.1. Za tímto účelem se číslo verze rozdělí na jednotlivé prvky a zkontroluje se hlavní a vedlejší číslo verze. Tato funkce bude fungovat až do verze LibreOffice 9.

      Dim stMonth As String

      Dim stDay As String

      stMonth = Right(Str(0) & Str(oField.CurrentValue.Month),2)

      stDay =  Right(Str(0) & Str(oField.CurrentValue.Day),2)

      Datumswert = CDateFromIso(oField.CurrentValue.Year & stMonth & stDay)

   Else

      DateValue = CDateFromIso(oField.CurrentValue)

   End If

End Function

Od verze LibreOffice 4.1.2 jsou data v ovládacích prvcích formuláře ukládána jako pole. To znamená, že aktuální hodnotu ovládacího prvku nelze použít k přístupu k samotnému datu. Má-li být datum dále použito v makrech, je třeba jej znovu vytvořit ze dne, měsíce a roku.

Vyhledávání datových záznamů

Záznamy v databázi můžeme prohledávat bez použití makra. Odpovídající dotaz, který je třeba nastavit, však může být velmi složitý. Makro může tento problém vyřešit pomocí smyčky.

Následující podprogram načte pole v tabulce, interně vytvoří dotaz, a nakonec vypíše seznam čísel primárních klíčů záznamů v tabulce, které jsou vyhledány pomocí tohoto vyhledávacího výrazu. V následujícím popisu je tabulka s názvem Searchtmp, která se skládá z pole primárního klíče s automatickým nárůstem (ID) a pole s názvem Nr., které obsahuje všechny primární klíče získané z prohledávané tabulky. Název tabulky je podprogramu zpočátku předáván jako proměnná.

Chceme-li získat správný výsledek, musí tabulka obsahovat hledaný obsah jako text, nikoli jako cizí klíče. V případě potřeby můžeme vytvořit VIEW, které makro použije.

Poznámka

Viz databáze Example_Search_and_Filter.odb související s touto knihou.

Sub Searching(stTable As String)

   Dim oDataSource As Object

   Dim oConnection As Object

   Dim oSQL_Command As Object

   Dim stSql As String

   Dim oResult As Object

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm As Object

   Dim oForm2 As Object

   Dim oField As Object

   Dim stContent As String

   Dim arContent() As String

   Dim inI As Integer

   Dim inK As Integer

   oDoc = thisComponent

   oDrawpage = oDoc.drawpage

   oForm = oDrawpage.forms.getByName("searchform")

   oField = oForm.getByName("searchtext")

   stContent = oField.getCurrentValue()

   stContent = LCase(stContent)

Obsah vyhledávacího textového pole je zpočátku převeden na malá písmena, takže následná vyhledávací funkce musí porovnávat pouze malá písmena.

   oDataSource = ThisComponent.Parent.DataSource

   oConnection = oDataSource.GetConnection("","")

   oSQL_Command = oConnection.createStatement()

Nejprve je třeba zjistit, zda byl vyhledávací výraz skutečně zadán. Pokud je pole prázdné, předpokládá se, že vyhledávání není vyžadováno. Všechny záznamy se zobrazí bez dalšího vyhledávání.

Pokud byl zadán hledaný výraz, načtou se názvy sloupců z prohledávané tabulky, aby mohl dotaz přistupovat k polím.

If stContent <> "" Then

   stSql = "SELECT ""COLUMN_NAME"" FROM ""INFORMATION_SCHEMA"".""SYSTEM_COLUMNS"" WHERE ""TABLE_NAME"" = '" + stTable + "' ORDER BY ""ORDINAL_POSITION"""

   oResult = oSQL_Statement.executeQuery(stSql)

Poznámka

Vzorce SQL v makrech musí být nejprve umístěny do dvojitých uvozovek jako běžné znakové řetězce. Názvy polí a tabulek jsou uvnitř vzorce SQL již ve dvojitých uvozovkách. Pro vytvoření konečného kódu, který správně přenáší dvojité uvozovky, musí být názvy polí a tabulek opatřeny dvěma sadami těchto uvozovek.

stSql = "SELECT ""Name"" FROM ""Table"";"

se při zobrazení příkazem MsgBox stSql,
.
SELECT "Name" FROM "Table";

Index pole, do kterého se zapisují názvy polí, je zpočátku nastaven na 0. Poté se začne číst dotaz. Protože velikost pole není známa, je třeba ji průběžně upravovat. Proto smyčka začíná 'ReDim Preserve arContent(inI)', aby se nastavila velikost pole a zároveň se zachoval jeho stávající obsah. Poté se načtou pole a index pole se zvýší o 1. Poté se pole znovu dimenzuje a lze do něj uložit další hodnotu.

      InI = 0

      While oResult.next

         ReDim Preserve arContent(inI)

         arContent(inI) = oResult.getString(1)

         inI = inI + 1

      Wend

      stSql = "DROP TABLE ""searchtmp"" IF EXISTS"

      oSQL_Command.executeUpdate (stSql)

Nyní je dotaz sestaven v rámci smyčky a následně aplikován na tabulku definovanou na začátku. Jsou povoleny všechny kombinace malých a velkých písmen, protože obsah pole v dotazu je převeden na malá písmena.

Dotaz je sestaven tak, aby výsledky skončily v tabulce "searchtmp". Předpokládá se, že primárním klíčem je první pole tabulky (arContent(0)).

   stSql = "SELECT """+arContent(0)+""" INTO ""searchtmp"" FROM """ + stTable
   
+ """ WHERE "

   For inK = 0 To (inI - 1)

      stSql = stSql+"LCase("""+arContent(inK)+""") LIKE '%"+stContent+"%'"

      If inK < (inI - 1) Then

         stSql = stSql+" OR "

      End If

   Next

   oSQL_Command.executeQuery(stSql)

Else

   stSql = "DELETE FROM ""searchtmp"""

   oSQL_Command.executeUpdate (stSql)

End If

Zobrazovací formulář je třeba znovu načíst. Jeho zdrojem dat je dotaz, v tomto případě Searchquery.

   oForm2 = oDrawpage.forms.getByName("display")

   oForm2.reload()

End Sub

Tím se vytvoří tabulka, která bude vyhodnocena dotazem. Pokud je to možné, dotaz by měl být sestaven tak, aby jej bylo možné následně upravovat. Je zobrazen vzorový dotaz:

SELECT * FROM "searchtable" WHERE "Nr." IN ( SELECT "Nr." FROM "searchtmp" ) OR "Nr." = CASE WHEN ( SELECT COUNT( "Nr." ) FROM "searchtmp" ) > 0 THEN '0' ELSE "Nr." END

Jsou zahrnuty všechny prvky searchtable včetně primárního klíče. V přímém dotazu se neobjevuje žádná jiná tabulka, proto není potřeba žádný primární klíč z jiné tabulky a výsledek dotazu zůstává editovatelný.

Primární klíč je v tomto příkladu uložen pod názvem Nr. Makro načte právě toto pole. Provede se počáteční kontrola, zda se obsah pole Nr. objeví v tabulce searchtmp. Operátor IN je kompatibilní s více hodnotami. Dílčí dotaz může poskytnout i několik záznamů.

Při větším množství dat se přiřazování hodnot pomocí operátoru IN rychle zpomaluje. Proto není dobré použít prázdné vyhledávací pole, abychom jednoduše přenesli všechna pole primárního klíče z tabulky searchtable do tabulky searchtmp a poté data zobrazovali stejným způsobem. Místo toho prázdné vyhledávací pole vytvoří prázdnou tabulku searchtmp, takže nejsou k dispozici žádné záznamy. To je účelem druhé poloviny podmínky:

OR "Nr." = CASE WHEN ( SELECT COUNT( "Nr." ) FROM "searchtmp" ) > 0 THEN '-1' ELSE "Nr." END

Pokud je v tabulce Searchtmp nalezen záznam, znamená to, že výsledek prvního dotazu je větší než 0. V tomto případě: "Nr." = '-1' (zde potřebujeme číslo, které se nemůže vyskytovat jako primární klíč, takže "-1 "je dobrá hodnota). Pokud je výsledkem dotazu přesně 0 (což je případ, kdy nejsou přítomny žádné záznamy), pak "Nr." = "Nr.".  Tímto se vypíše každý záznam, který má Nr.Nr. je primární klíč, což znamená všechny záznamy.

Zvýraznění vyhledávacích výrazů ve formulářích a výsledcích

U rozsáhlého textového pole často není jasné, kde se vyskytují shody s hledaným výrazem. Bylo by hezké, kdyby formulář uměl zvýraznit zápasy. Mělo by to vypadat jako na obrázku 5.

Bild2

Obrázek 5: Formulář pro zvýraznění shody při vyhledávání

K tomu, aby formulář takto fungoval, potřebujeme několik dalších položek v naší sadě triků.

Poznámka

Viz databáze Example_Autotext_Searchmarkin_Spelling.odb související s touto knihou.

Fungování takového vyhledávacího pole již bylo vysvětleno. Je vytvořena tabulka filtrů a formulář slouží k zápisu aktuálních hodnot jednoho záznamu do této tabulky.  Hlavní formulář je opatřen obsahem pomocí dotazu, který vypadá takto:

SELECT "ID", "memo"

FROM "table"

WHERE LOWER ( "memo" ) LIKE '%' || LOWER (

   ( SELECT "searchtext" FROM "filter" WHERE "ID" = TRUE ) ) || '%'

Po zadání hledaného textu se zobrazí všechny záznamy v tabulce „Table“, které mají hledaný text v poli „memo“. Při vyhledávání se nerozlišují velká a malá písmena.

Pokud není zadán žádný vyhledávací text, zobrazí se všechny záznamy v tabulce. Jelikož je primární klíč této tabulky obsažen v dotazu, lze jej upravovat.

Bild6

Obrázek 6: Nastavení typu textu na víceřádkový s formátováním

Ve formuláři je kromě pole ID pro primární klíč také pole s názvem MemoFormat, které bylo nakonfigurováno (pomocí Vlastnosti > Obecné > Typ textu > Víceřádkový s formátováním) tak, aby zobrazovalo barevný i černý text. Při pozorném zkoumání vlastností textového pole zjistíme, že karta Data nyní zmizela. Je to proto, že do pole nelze zadávat data s dodatečným formátováním, které databáze sama o sobě nedokáže uložit. Přesto je možné do tohoto pole dostat text, označit jej a po aktualizaci jej přenést pomocí makra.

Procedura ContentRead slouží k přenosu obsahu databázového pole „memo“ do formátovaného textového pole MemoFormat a k jeho formátování tak, aby byl zvýrazněn text odpovídající textu ve vyhledávacím poli.

Postup je vázán na Formulář > Události > Po změně záznamu.

Sub ContentRead(oEvent As Object)   

   Dim inMemo As Integer

   Dim oField As Object

   Dim stSearchtext As String

   Dim oCursor As Object

   Dim inSearch As Integer

   Dim inSearchOld As Integer

   Dim inLen As Integer

   oForm = oEvent.Source

   inMemo = oForm.findColumn("memo")

   oField = oForm.getByName("MemoFormat")

   oField.Text = oForm.getString(inMemo)

Nejprve jsou definovány proměnné. Poté se z formuláře vyhledá pole tabulky „memo“ a pomocí funkce getString() se načte text z očíslovaného sloupce. Ten se přenáší do pole, které lze formátovat, ale které nemá vazbu na databázi: MemoFormat.

První testy ukázaly, že se formulář otevřel, ale nástrojová lišta formuláře v dolní části se již nevytvořila. Proto byla do systému zabudována velmi krátká čekací doba 5/1000 sekund. Poté se zobrazený obsah načte z formuláře FormFilter (který je v hierarchii formulářů paralelní s formulářem Form).

   Wait 5

   stSearchtext = oForm.Parent.getByName("FormFilter").getByName("Search").Text

Aby bylo možné formátovat text, musí být v poli, které obsahuje text, vytvořen (neviditelný) TextCursor. Výchozí zobrazení textu používá 12bodové písmo serif, které se nemusí vyskytovat v jiných částech formuláře a nelze jej přímo přizpůsobit pomocí vlastností ovládacího prvku formuláře. Při tomto postupu se hned na začátku nastaví požadovaný vzhled textu. Pokud se tak nestane, mohou rozdíly ve formátování způsobit odříznutí horního okraje textu v poli. Při prvních testech byly čitelné pouze 2/3 prvního řádku.

Aby neviditelný kurzor označil text, nastaví se nejprve na začátek pole a poté na jeho konec. Argumentem je v obou případech true. Poté následují specifikace velikosti, tvaru, barvy a gramáže písma. Pak se kurzor vrátí na začátek.

   oCursor = oField.createTextCursor()

   oCursor.gotoStart(true)

   oCursor.gotoEnd(true)

   oCursor.CharHeight = 10

   oCursor.CharFontName = "Arial, Helvetica, Tahoma"

   oCursor.CharColor = RGB(0,0,0)

   oCursor.CharWeight = 100.000000   'com::sun::star::awt::FontWeight

   oCursor.gotoStart(false)

Pokud je v poli text a byl zadán požadavek na vyhledávání, je nyní tento text prohledáván, aby byl nalezen hledaný řetězec. Vnější smyčka se nejprve ptá, zda jsou tyto podmínky splněny; vnitřní smyčka zjišťuje, zda se hledaný řetězec skutečně nachází v textu v poli MemoFormat. Tato nastavení lze ve skutečnosti vynechat, protože dotaz, na němž je formulář založen, zobrazuje pouze text, který tyto podmínky splňuje.

   If oField.Text <> "" And stSearchtext <> "" Then

      If inStr(oField.Text, stSearchtext) Then

         inSearch = 1

         inSearchOld = 0

         inLen = Len(stSearchtext)

V textu se hledá hledaný řetězec. To probíhá ve smyčce, která skončí, jakmile se nezobrazí žádná další shoda. InStr() vrací umístění prvního znaku hledaného řetězce v zadaném formátu zobrazení, nezávisle na velikosti písmen. Smyčka je řízena požadavkem, aby na konci každého cyklu byl začátek inSearch zvýšen o 1 (-1 v prvním řádku smyčky a +2 v posledním řádku). V každém cyklu se kurzor přesune na počáteční pozici bez označení pomocí oCursor.goRight(Position, false) a poté doprava s označením podle délky hledaného řetězce. Poté se použije požadované formátování (modré a poněkud tučnější) a kurzor se přesune zpět do výchozího bodu pro další běh.

         Do While inStr(inSearch, oField.Text, stSearchtext) > 0

            inSearch = inStr(inSearch, oField.Text, stSearchtext) - 1

            oCursor.goRight(inSearch-inSearchOld,false)

            oCursor.goRight(inLen,true)

            oCursor.CharColor = RGB(102,102,255)

            oCursor.CharWeight = 110.000000

            oCursor.goLeft(inLen,false)

            inSearchOld = inSearch

            inSearch = inSearch + 2

         Loop

      End If

   End If

End Sub

Procedura ContentWrite slouží k přenosu obsahu textového pole MemoFormat do databáze. To probíhá nezávisle na tom, zda dojde k nějaké změně.

Postup je vázán na Formulář > Události > Před změnou záznamu.

Sub ContentWrite(oEvent As Object)

   Dim oForm As Object

   Dim inMemo As Integer

   Dim loID As Long

   Dim oField As Object

   Dim stMemo As String

   oForm = oEvent.Source

   If InStr(oForm.ImplementationName, "ODatabaseForm") Then

Spouštěcí událost je implementována dvakrát. Správný přístup k záznamu umožňuje pouze název implementace, který končí na OdatabaseForm (implementace jsou vysvětleny na straně 1).

      If Not oForm.isBeforeFirst() And Not oForm.isAfterLast() Then

Při načítání formuláře nebo při opětovném načtení stojí kurzor před aktuálním záznamem. Pokud se o to pokusíme, zobrazí se zpráva „Invalid cursor status“.

         inMemo = oForm.findColumn("memo")

         loID = oForm.findColumn("ID")

         oField = oForm.getByName("MemoFormat")

         stMemo = oField.Text

         If stMemo <> "" Then

            oForm.updateString(inMemo,stMemo)

         End If

         If stMemo <> "" And oForm.getString(loID) <> "" Then

            oForm.UpdateRow()

         End If

      End If

   End If

End Sub

Pole tabulky „memo“ se nachází ve zdroji dat formuláře spolu s polem „ID“.  Pokud pole MemoFormat obsahuje text, přenese se do pole Memo zdroje dat pomocí oForm.updateString(). Pouze pokud je v poli ID záznam (jinými slovy byl nastaven primární klíč), následuje aktualizace. V opačném případě je nový záznam vložen běžnou prací s formulářem; formulář změnu rozpozná a uloží ji samostatně.

Kontrola pravopisu při zadávání dat

Toto makro lze použít pro víceřádková formátovaná textová pole. Stejně jako v předchozí kapitole je třeba nejprve zapsat obsah každého záznamu a poté lze nový záznam načíst do ovládacího prvku formuláře. Procedury TransferContent a WriteContent se liší pouze v tom, v jakém okamžiku lze funkci vyhledávání vyřadit ze závorky.

Bild7

Obrázek 7: Formulář pro kontrolu pravopisu

Kontrola pravopisu se ve výše uvedeném formuláři spustí vždy, když je v ovládacím prvku formuláře stisknuta mezera nebo návrat. Jinými slovy, spustí se na konci každého slova. Mohlo by to být také spojeno se ztrátou zaměření ovládacího prvku, aby se zajistilo, že bude zkontrolováno poslední slovo.

Postup je vázán na Formulář > Události > Klávesa uvolněna.

SUB MarkWrongWordsDirect(oEvent As Object)

   GlobalScope.BasicLibraries.LoadLibrary("Tools")

Funkce RTrimStr slouží k odstranění interpunkčního znaménka na konci řetězce. Jinak by se všechna slova, která končí čárkou, tečkou nebo jiným interpunkčním znaménkem, zobrazovala nahoře jako pravopisné chyby. Kromě toho se funkce LTrimChar používá k odstranění závorek na začátku slov.

   Dim aProp() As New com.sun.star.beans.PropertyValue

   Dim oLinuSvcMgr As Object

   Dim oSpellChk As Object

   Dim oField As Object

   Dim arText()

   Dim stWord As String

   Dim inlenWord As Integer

   Dim ink As Integer

   Dim i As Integer

   Dim oCursor As Object

   Dim stText As Object

   oLinguSvcMgr = createUnoService("com.sun.star.linguistic2.LinguServiceManager")

   If Not IsNull(oLinguSvcMgr) Then

      oSpellChk = oLinguSvcMgr.getSpellChecker()

   End If

Nejprve se deklarují všechny proměnné. Poté je zpřístupněn modul Basic pro kontrolu pravopisu SpellChecker. Právě tento modul bude skutečně kontrolovat správnost jednotlivých slov.

   oField = oEvent.Source.Model

   ink = 0

   If oEvent.KeyCode = 1280 Or oEvent.KeyCode = 1284 Then

Událostí, která makro spustí, je stisknutí klávesy. Tato událost obsahuje kód KeyCode pro každou jednotlivou klávesu. KeyCode pro klávesu Return je 1280, pro mezeru 1284. Stejně jako mnoho dalších informací se tyto položky získávají pomocí nástroje Xray. Po stisknutí mezerníku nebo klávesy Enter se zkontroluje pravopis. Jinými slovy se spouští na konci každého slova. Pouze test posledního slova neprobíhá automaticky.

Při každém spuštění makra se zkontrolují všechna slova v textu. Kontrola jednotlivých slov by byla také možná, ale vyžadovala by mnohem více práce.

Text je rozdělen na jednotlivá slova. Oddělovačem je znak mezery. Předtím je třeba slova rozdělená zalomeními řádků opět spojit, jinak by mohlo dojít k záměně těchto částí za celá slova.

      stText = Join(Split(oField.Text,CHR(10))," ")

      stText = Join(Split(stText,CHR(13))," ")

      arText = Split(RTrim(stText)," ")

      For i = LBound(arText) To Ubound(arText)

         stWord = arText(i)

         inlenWord = len(stWord)

         stWord = Trim( RtrimStr( RtrimStr( RtrimStr( RtrimStr( RtrimStr(
           
RtrimStr(stWord,","), "."),"?"),"!"),"."),")"))

         stWord = LTrimChar(stWord,"(")

Jednotlivá slova jsou přečtena. Jejich nezkrácená délka je potřebná pro následující krok úprav. Jen tak lze určit pozici slova v rámci celého textu (což je nezbytné pro konkrétní zvýraznění pravopisných chyb).

Funkce Trim se použije pro odstranění mezer, zatímco RTrimStr odstraní čárky a tečky na konci textu a LTrimChar jakákoliv interpunkční znaménka na začátku.

         If stWord <> "" Then

            oCursor = oField.createTextCursor()

            oCursor.gotoStart(false)

            oCursor.goRight(ink,false)

            oCursor.goRight(inLenWord,true)

            If Not oSpellChk.isValid(stWord, "en", aProp()) Then

               oCursor.CharUnderline = 9

               oCursor.CharUnderlineHasColor = True

               oCursor.CharUnderlineColor = RGB(255,51,51)

            Else

               oCursor.CharUnderline = 0

            End If

         End If

         ink = ink + inLenWord + 1

      Next

   End If

End Sub

Pokud slovo není nulové, vytvoří se textový kurzor. Tento kurzor se přesune bez zvýraznění na začátek textu v zadávaném poli. Pak skočí dopředu doprava, stále bez zvýraznění, na výraz uložený v proměnné ink. Tato proměnná začíná jako 0, ale po spuštění první smyčky je rovna délce slova (+1 za následující mezeru). Pak se kurzor posune doprava o délku aktuálního slova. Vlastnosti písma jsou upraveny tak, aby vytvořily zvýrazněnou oblast.

Spustí se kontrola pravopisu. Jako argumenty vyžaduje slovo a kód země; bez kódu země se vše považuje za správné. Argument pole je obvykle prázdný.

Pokud slovo není ve slovníku, je označeno červenou vlnovkou. Tento typ podtržení je zde reprezentován '9'. Pokud je slovo nalezeno, není podtrženo ('0'). Tento krok je nutný, protože jinak by se slovo rozpoznané jako chybné a následně opravené nadále zobrazovalo červenou vlnovkou. Nikdy by nebyl odstraněn, protože nebyl dán žádný konfliktní formát.

Pole se seznamem jako seznamy s možností zadání

Tabulku s jedním záznamem lze vytvořit přímo pomocí polí se seznamem a neviditelných číselných polí a odpovídající primární klíč zadat do jiné tabulky.

Tip

Použití polí se seznamem namísto seznamů nalezneme v databázi Example_Combobox_Listfield.odb, která je přiřazena k této knize.

Ovládací prvek Pole se seznamem zachází s formulářovými poli pro kombinované zadávání a výběr hodnot (pole se seznamem) jako se seznamem s možností zadání. Za tímto účelem se kromě polí se seznamem ve formuláři ukládají hodnoty klíčových polí, které se mají přenést do podkladové tabulky, do samostatných číselných polí. Pole lze deklarovat jako neviditelná. Klíče z těchto polí jsou načteny při načtení formuláře a pole se seznamem je nastaveno tak, aby zobrazovalo odpovídající obsah. Pokud se obsah pole se seznamem změní, uloží se a nový primární klíč se přenese do příslušného číselného pole, které se uloží do hlavní tabulky.

Pokud se místo tabulek používají upravitelné dotazy, lze text, který se má zobrazit v kombinačních polích, určit přímo z dotazu. Pro tento krok pak není nutné používat makro.

Předpokladem pro fungování makra je, že primárním klíčem tabulky, která je zdrojem dat pro kombinační pole, je automaticky se zvyšující celé číslo. Předpokládá se také, že název pole pro primární klíč je ID.

Zobrazení textu v polích se seznamem

Tento podprogram má zobrazit text v poli se seznamem podle hodnoty neviditelných polí cizího klíče z hlavního formuláře. Lze ji použít i pro seznamy, které odkazují na dvě různé tabulky. K tomu může dojít, pokud je například poštovní směrovací číslo v poštovní adrese uloženo odděleně od města. V takovém případě lze poštovní směrovací číslo načíst z tabulky, která obsahuje pouze cizí klíč pro město. V seznamu by se mělo zobrazit poštovní směrovací číslo a město společně.

Sub ShowText(oEvent As Object)

Toto makro by mělo být vázáno na následující událost formuláře: 'Po změně záznamu'.

Makro se volá přímo z formuláře. Spouštěcí událost je zdrojem všech proměnných, které makro potřebuje. Některé proměnné již byly deklarovány globálně v samostatném modulu a nejsou zde znovu deklarovány.

   Dim oForm As Object

   Dim oFieldList As Object

   Dim stFieldValue As String

   Dim inCom As Integer

   Dim stQuery As String

   oForm = oEvent.Source

Ve formuláři je skrytý ovládací prvek, ze kterého lze získat názvy všech různých polí se seznamem. Tato pole se seznamem jsou jedno po druhém zpracována makrem.

   aComboboxes() = Split(oForm.getByName("combofields").Tag,",")

   For inCom = LBound(aComboboxes) TO Ubound(aComboboxes)

      ...

   Next inCom   

Doplňující informace (Tag) připojené ke skrytému ovládacímu prvku obsahují tento seznam názvů polí se seznamem oddělených čárkami. Názvy jsou zapsány do pole a poté zpracovány v rámci smyčky. Smyčka končí výrazem NEXT.

Pole se seznamem, které nahradilo seznam, se nazývá oFieldList. Abychom získali cizí klíč, potřebujeme správný sloupec v tabulce, která je základem formuláře. To je dostupné pomocí názvu pole tabulky, který je uložen v doplňkových informacích pole se seznamem.

      oFieldList = oForm.getByName(Trim(aComboboxes(inCom)))

      stFieldID = oForm.getString(oForm.findColumn(oFieldList.Tag))

      oFieldList.Refresh()

Pole se seznamem se znovu načte pomocí Refresh()v případě, že se obsah pole změnil vložením nových dat.

Dotaz potřebný k zobrazení viditelného obsahu pole se seznamem je založen na poli, které je základem ovládacího prvku, a na hodnotě určené pro cizí klíč. Aby byl kód SQL použitelný, jsou odstraněny všechny případné operace třídění. Poté se zkontrolují případné definice relací (které budou začínat slovem WHERE). Ve výchozím nastavení funkce InStr() nerozlišuje mezi velkými a malými písmeny, takže zahrnuje všechny kombinace velkých a malých písmen.  Pokud existuje relace, znamená to, že dotaz obsahuje pole ze dvou různých tabulek. Potřebujeme najít tabulku, která poskytuje cizí klíč pro odkaz. Makro zde závisí na tom, že primární klíč v každé tabulce se nazývá ID.

Pokud není definována žádná relace, dotaz přistupuje pouze k jedné tabulce. Informace o tabulce lze vynechat a podmínku formulovat přímo pomocí hodnoty cizího klíče.

      If stFieldID <> "" Then

         stQuery = oFieldList.ListSource

         If InStr(stQuery,"order by") > 0 Then

            stSql = Left(stQuery, InStr(stQuery,"order by")-1)

         Else

            stSql = stQuery

         End If

         If InStr(stSql,"where") Then

            st = Right(stSql, Len(stSql)-InStr(stSql,"where")-4)

            If InStr(Left(st, InStr(st,"=")),".""ID""") Then

               a() = Split(Right(st, Len(st)-InStr(st,"=")-1),".")

            Else

               a() = Split(Left(st, InStr(st,"=")-1),".")

            End If

            stSql = stSql + "AND "+a(0)+".""ID"" = "+stFieldID

         Else

            stSql = stSql + "WHERE ""ID"" = "+stFieldID

         End If

Každý název pole a tabulky musí být do příkazu SQL zadán se dvěma sadami uvozovek. Uvozovky jsou normálně interpretovány Basicem jako oddělovače textových řetězců, takže se při předávání kódu do jazyka SQL již nezobrazují. Zdvojení uvozovek zajistí, že se předá jedna sada. ""ID"" značí, že v dotazu bude použito pole "ID" s jednou sadou uvozovek, kterou SQL vyžaduje.

Nyní se provede dotaz uložený v proměnné stSql a jeho výsledek se uloží do proměnné oResult.

         oDatasource = ThisComponent.Parent.CurrentController

         If Not (oDatasource.isConnected()) Then

            oDatasource.connect()

         End If

         oConnection = oDatasource.ActiveConnection()

         oSQL_Command = oConnection.createStatement()

         oResult = oSQL_Command.executeQuery(stSql)

Výsledek dotazu je načten ve smyčce. Stejně jako u dotazu v grafickém uživatelském rozhraní lze zobrazit několik polí a záznamů. Konstrukce tohoto dotazu však vyžaduje pouze jeden výsledek, který se nachází v prvním sloupci (1) množiny výsledků dotazu. Jedná se o záznam, který poskytuje zobrazený obsah pole se seznamem. Obsahem je text (getString()), proto příkaz oResult.getString(1).

         While oResult.next

            stFieldValue = oResult.getString(1)

         Wend

Nyní musí být pole se seznamem nastaveno na textovou hodnotu získanou dotazem.

         oFieldList.Text = stFieldValue

      Else

Pokud v poli cizího klíče oField není žádná hodnota, dotaz se nezdařil a pole se seznamem je nastaveno na prázdný řetězec.

         oFieldList.Text = ""

      End If

   Next inCom

End Sub

Tento postup spravuje kontakt mezi polem se seznamem a cizím klíčem dostupným v poli zdroje dat formuláře. To by mělo stačit k zobrazení správných hodnot v polích se seznamem. Uložení nových hodnot by vyžadovalo další postup.

Přenos hodnoty cizího klíče z pole se seznamem do číselného pole

Pokud je do pole se seznamem zadána nová hodnota (a to je koneckonců účel, pro který bylo toto makro vytvořeno), musí být odpovídající primární klíč zadán do základní tabulky formuláře jako cizí klíč.

Sub TextSelectionSaveValue(oEvent As Object)

Toto makro by mělo být vázáno na následující událost formuláře: 'Před akcí záznamu'.

Po deklarování proměnných (zde nejsou uvedeny) musíme nejprve přesně určit, která událost má makro spustit. Před akcí záznamu se postupně zavolají dvě implementace. Je důležité, aby makro samo načetlo objekt formuláře. To lze provést v obou implementacích, ale různými způsoby. Zde je odfiltrována implementace s názvem OdatabaseForm.

   If InStr(oEvent.Source.ImplementationName,"ODatabaseForm") Then

      ...

   End If

End Sub

Tato smyčka se vytvoří na stejném začátku jako procedura Display_text:

      oForm = oEvent.Source

      aComboboxes() = Split(oForm.getByName("combofields").Tag,",")

      For inCom = LBound(aComboboxes) To Ubound(aComboboxes)

         ...

      Next inCom   

Pole oFieldList zobrazuje text. Může se nacházet uvnitř ovládacího prvku tabulky a v takovém případě k němu není možné přistupovat přímo z formuláře. V takových případech by doplňkové informace pro skryté ovládací prvky polí se seznamem měly obsahovat cestu k poli pomocí pole se seznamem „tablecontrol“. Rozdělení této položky odhalí, jakým způsobem se má k poli se seznamem přistupovat.

      a() = Split(Trim(aComboboxen(inCom)),">")

         If Ubound(a) > 0 Then

            oFieldList = oForm.getByName(a(0)).getByName(a(1))

         Else   

            oFieldList = oForm.getByName(a(0))

         End If

Poté je dotaz načten z pole se seznamem a rozdělen na jednotlivé části. U jednoduchých polí se seznamem jsou nezbytnými informacemi název pole a název tabulky:

SELECT "Field" FROM "Table"

To by v některých případech mohlo být doplněno o pokyn k třídění. Kdykoli mají být v poli se seznamem dvě pole pohromadě, bude třeba více práce s jejich oddělením.

SELECT "Field1"||' '||"Field2" FROM "Table"

Tento dotaz spojí dvě pole a vloží mezi ně mezeru. Protože oddělovačem je mezera, makro ji vyhledá a rozdělí text na dvě části. To samozřejmě bude spolehlivě fungovat pouze tehdy, pokud Field již neobsahuje text, ve kterém jsou mezery povoleny. V opačném případě, pokud je křestní jméno „Anne Marie“ a příjmení „Müller“, bude makro považovat „Anne“ za křestní jméno a „Marie Müller“ za příjmení. V takových případech je třeba použít vhodnější oddělovač, který pak může být nalezen makrem. V případě jmen by to mohlo být „Příjmení, Jméno“.

Situace se ještě více zkomplikuje, pokud obě pole pocházejí z různých tabulek:

SELECT "Table1"."Field1"||' > '||"Table2"."Field2"

FROM "Table1", "Table2"

WHERE "Table1"."ID" = "Table2"."ForeignID"

ORDER BY "Table1"."Field1"||' > '||"Table2"."Field2" ASC

Zde je třeba pole od sebe oddělit, určit tabulku, do které každé pole patří, a určit odpovídající cizí klíče.

         stQuery = oFieldList.ListSource

         aFields() = Split(stQuery, """")

         stContent = ""

         For i=LBound(aFields)+1 To UBound(aFields)

Obsah dotazu je zbaven zbytečného balastu. Jednotlivé části jsou znovu sestaveny do pole s neobvyklou kombinací znaků jako oddělovačem.  FROM odděluje zobrazení viditelných polí od názvů tabulek. WHERE odděluje podmínku od názvů tabulek. Spojení nejsou podporována.

            If Trim(UCASE(aFields(i))) = "ORDER BY" Then

               Exit For

            ElseIf Trim(UCASE(aFields(i))) = "FROM" Then

               stContent = stcontent+" §§ "

            ElseIf Trim(UCASE(aFields(i))) = "WHERE" Then

               stContent = stcontent+" §§ "

            Else

               stContent = stContent+Trim(aFields(i))

            End If

         Next i

         aContent() = Split(stContent, " §§ ")

V některých případech pochází obsah viditelného zobrazení polí z různých polí:

         aFirst() = Split(aContent(0),"||")

         If UBound(aFirst) > 0 Then

            If UBound(aContent) > 1 Then

První část obsahuje nejméně dvě pole. Pole začínají názvem tabulky. Druhá část obsahuje dva názvy tabulek, které lze určit z první části. Třetí část obsahuje vztah s cizím klíčem, oddělený znakem =:

               aTest() = Split(aFirst(0),".")

               NameTable1 = aTest(0)

               NameTableField1 = aTest(1)

               Erase aTest

               stFieldSeperator = Join(Split(aFirst(1),"'"),"")

               aTest() = Split(aFirst(2),".")

               NameTable2 = aTest(0)

               NameTableField2 = aTest(1)

               Erase aTest

               aTest() = Split(aContent(2),"=")

               aTest1() = Split(aTest(0),".")

               If aTest1(1) <> "ID" Then

                  NameTab12ID = aTest1(1)

                  IF aTest1(0) = NameTable1 Then

                     Position = 2

                  Else

                     Position = 1

                  End If

               Else

                  Erase aTest1            

                  aTest1() = Split(aTest(1),".")

                  NameTab12ID = aTest1(1)   

                  If aTest1(0) = NameTable1 Then

                     Position = 2

                  Else

                     Position = 1

                  End If

               End If

            Else

První část obsahuje dva názvy polí bez názvů tabulek, případně s oddělovači. Druhá část obsahuje názvy tabulek. Třetí část neexistuje:

               If UBound(aFirst) > 1 Then

                  NameTableField1 = aFirst(0)

                  stFieldSeperator = Join(Split(aFirst(1),"'"),"")

                  NameTableField2 = aFirst(2)

               Else

                  NameTableField1 = aFirst(0)

                  NameTableField2 = aFirst(1)

               End If

               NameTable1 = aContent(1)

            End If

         Else

Z jedné tabulky je pouze jedno pole:

            NameTableField1 = aFirst(0)

            NameTable1 = aContent(1)

         End If

Maximální délka znaku, kterou může záznam mít, je dána funkcí ColumnSize. Pole se seznamem nelze použít k omezení velikosti, protože může být nutné, aby obsahovalo dvě pole současně.

         LengthField1 = ColumnSize(NameTable1,NameTableField1)

         If NameTableField2 <> "" Then

            If NameTable2 <> "" Then

               LengthField2 = ColumnSize(NameTable2,NameTableField2)

            Else

               LengthField2 = ColumnSize(NameTable1,NameTableField2)

            End If

         Else

            LengthField2 = 0

         End If

Obsah pole se seznamem se načte:

         stContent = oFieldList.getCurrentValue()

V případě potřeby se odstraní počáteční a koncové mezery a netisknutelné znaky.

   stContent = Trim(stContent)

   If stContent <> "" Then

      If NameTableField2 <> "" Then

Pokud existuje druhé pole tabulky, musí být obsah pole se seznamem rozdělen. Pro určení místa, kde má dojít k rozdělení, použijeme oddělovač polí, který je funkci zadán jako argument.

         a_stParts = Split(stContent, FieldSeparator, 2)

Poslední parametr znamená, že maximální počet částí je 2.

Podle toho, který údaj odpovídá poli 1 a který poli 2, se nyní obsah pole se seznamem přiřadí jednotlivým proměnným. "Position = 2" zde slouží jako označení, že druhá část obsahu znamená pole 2.

         If Position = 2 Then

            stContent = Trim(a_stParts(0))

            If UBound(a_stParts()) > 0 Then

               stContentField2 = Trim(a_stParts(1))

            Else

               stContentField2 = ""

            End If

            stContentField2 = Trim(a_stParts(1))

         Else

            stContentField2 = Trim(a_stParts(0))

            If UBound(a_stParts()) > 0 Then

               stContent = Trim(a_stParts(1))

            Else

               stContent = ""

            End If

            stContent = Trim(a_stParts(1))

         End If   

      End If

Může se stát, že při dvou oddělitelných obsazích se instalovaná velikost pole se seznamem (délka textu) nevejde do polí tabulky, která se mají uložit. U polí se seznamem, která představují jediné pole, se to obvykle řeší vhodnou konfigurací ovládacího prvku formuláře. Zde naopak potřebujeme nějaký způsob, jak takové chyby zachytit. Kontroluje se maximální přípustná délka příslušného pole.

   If (LengthField1 > 0 And Len(stContent) > LengthField1) Or (LengthField2 > 0 And  Len(stContentField2) > LengthField2) Then

Pokud je délka pole první nebo druhé části příliš velká, uloží se do jedné z proměnných výchozí řetězec. Znak Chr(13) se používá pro zalomení řádku.

      stmsgbox1 = "The field " + NameTableField1 + " must not exceed " + Field1Length + "characters in length." + Chr(13)

      stmsgbox2 = "The field " + NameTableField2 + " must not exceed " + Field2Length + "characters in length." + Chr(13)

Pokud jsou oba obsahy polí příliš dlouhé, zobrazí se oba texty.

      If (LengthField1 > 0 And Len(stContent) > LengthField1) And (LengthField2 > 0 And Len(stContentField2) > LengthField2) Then

         MsgBox("The entered text is too long." + Chr(13) + stmsgbox1 + stmsgbox2 + "Please shorten it.",64,"Invalid entry")

Zobrazení používá funkci MsgBox(). Jako první argument se očekává textový řetězec, dále volitelně číslo (které určuje typ zobrazeného okna zprávy) a nakonec volitelný textový řetězec jako název okna. V okně se proto zobrazí nadpis "Invalid entry" (Neplatné zadání, pozn. překl.) a číslo "64" poskytuje pole obsahující symbol Informace.

Následující kód se vztahuje na všechny další případy, kdy se může vyskytnout příliš dlouhý text.

      ElseIf (Field1Length > 0 And Len(stContent) > Field1Length) Then

         MsgBox("The entered text is too long." + Chr(13) + stmsgbox1 + "Please shorten it.",64,"Invalid entry")

      Else

         MsgBox("The entered text is too long." + Chr(13) + stmsgbox2 + "Please shorten it.",64,"Invalid entry")

      End If

   Else

Pokud není text příliš dlouhý, může funkce pokračovat. V opačném případě se zde ukončí.

Nyní jsou položky maskovány tak, aby případné uvozovky nevyvolaly chybu.

               stContent = String_to_SQL(stContent)

               If stContentField2 <> "" Then

                  stContentField2 = String_to_SQL(stContentField2)

               End If

Nejprve jsou předalokovány proměnné, které lze následně dotazem měnit. Proměnné inID1 a inID2 uchovávají obsah polí primárních klíčů obou tabulek. Pokud dotaz neposkytne žádné výsledky, přiřadí Basic této celočíselné proměnné hodnotu 0. Tato hodnota by však také mohla znamenat úspěšný dotaz vracející hodnotu primárního klíče 0; proto je proměnná přednastavena na –1. HSQLDB nemůže tuto hodnotu nastavit pro pole automatické hodnoty.

Dále se nastaví připojení k databázi, pokud ještě neexistuje.

      inID1 = -1

      inID2 = -1

      oDatasource = ThisComponent.Parent.CurrentController

      If Not (oDatasource.isConnected()) Then

         oDatasource.connect()

      End If

      oConnection = oDatasource.ActiveConnection()

      oSQL_Command = oConnection.createStatement()

      If NameTableField2 <> "" And Not IsEmpty(stContentField2) And NameTable2 <> "" Then

Pokud existuje druhé pole tabulky, musí být nejprve deklarována druhá závislost.

         stSql = "SELECT ""ID"" FROM """ + NameTable2 + """ WHERE """ + NameTableField2 + """='" + stContentField2 + "'"

         oResult = oSQL_Command.executeQuery(stSql)

            While oResult.next

               inID2 = oResult.getInt(1)

            Wend

         If inID2 = -1 Then

            stSql = "INSERT INTO """ + NameTable2 + """ (""" + NameTableField2 + """) VALUES ('" + stContentField2 + "') "

            oSQL_Command.executeUpdate(stSql)

            stSql = "CALL IDENTITY()"

Pokud se obsah v poli se seznamem nenachází v příslušné tabulce, vloží se tam. Výsledná hodnota primárního klíče se pak přečte. Pokud je přítomen, načte se existující primární klíč stejným způsobem. Funkce používá automaticky generovaná pole primárního klíče (IDENTITY).

            oResult = oSQL_Command.executeQuery(stSql)

               While oResult.next

                  inID2 = oResult.getInt(1)

               Wend

         End If

Primární klíč pro druhou hodnotu je dočasně uložen v proměnné inID2 a poté zapsán jako cizí klíč do tabulky odpovídající první hodnotě. Podle toho, zda byl záznam z první tabulky již k dispozici, se obsah čerstvě uloží (INSERT) nebo změní (UPDATE):

         If inID1 = -1 Then

            stSql = "INSERT INTO """ + NameTable1 + """ (""" + NameTableField1 + """,""" + NameTab12ID + """) VALUES ('" + stContent + "','" + inID2 + "') "

            oSQL_Command.executeUpdate(stSql)

A odpovídající ID se přímo načte:

            stSql = "CALL IDENTITY()"

            oResult = oSQL_Command.executeQuery(stSql)

               While oResult.next

                  inID1 = oResult.getInt(1)

               Wend

Primární klíč první tabulky je nakonec nutné znovu načíst, aby mohl být přenesen do základní tabulky formuláře.

         Else

            stSql = "UPDATE """ + NameTable1 + """ SET """ + NameTab12ID + """='" + inID2 + "' WHERE """ + NameTableField1 + """ = '" + stContent + "'"

            oSQL_Command.executeUpdate(stSql)

         End If

      End If

V případě, že obě pole, která jsou základem pole se seznamem, jsou ve stejné tabulce (například Surname a Firstname v tabulce Name), je třeba použít jiný dotaz:

      If NameTableField2 <> "" And NameTable2 = "" Then

         stSql = "SELECT ""ID"" FROM """ + NameTable1 + """ WHERE """ + NameTableField1 + """='" + stContent + "' AND """ + NameTableField2 + """='" + stContentField2 + "'"

         oResult = oSQL_Command.executeQuery(stSql)

            While oResult.next

               inID1 = oResult.getInt(1)

            Wend

         If inID1 = -1 Then

... a druhá tabulka neexistuje:

         stSql = "INSERT INTO """ + NameTable1 + """ (""" + NameTableField1 + """,""" + NameTableField2 + """) VALUES ('" + stContent + "','" + stContentField2 + "') "

         oSQL_Command.executeUpdate(stSql)

Poté se znovu načte primární klíč.

         stSql = "CALL IDENTITY()"

         oResult = oSQL_Command.executeQuery(stSql)

            While oResult.next

               inID1 = oResult.getInt(1)

            Wend

      End If

   End If

   IF NameTableField2 = "" Then

Nyní se budeme zabývat nejjednodušším případem: Druhé pole tabulky neexistuje a záznam v tabulce ještě není. Jinými slovy, do pole se seznamem byla zadána jedna nová hodnota.

      stSql = "SELECT ""ID"" FROM """ + NameTable1 + """ WHERE """ + NameTableField1 + """='" + stContent + "'"

      oResult = oSQL_Command.executeQuery(stSql)

         While oResult.next

            inID1 = oResult.getInt(1)

         Wend

      If inID1 = -1 Then

Pokud druhé pole neexistuje, vloží se obsah pole jako nový záznam.

         stSql = "INSERT INTO """ + NameTable1 + """ (""" + NameTableField1 + """) VALUES ('" + stContent + "') "

         oSQL_Command.executeUpdate(stSql)

... a výsledné ID se přímo odečte.

         stSql = "CALL IDENTITY()"

         oResult = oSQL_Command.executeQuery(stSql)

            While oResult.next

               inID1 = oResult.getInt(1)

            Wend

      End If

   End If

Je třeba určit hodnotu pole primárního klíče, aby ji bylo možné přenést do hlavní části formuláře.

Poté se hodnota primárního klíče, která vznikla na základě všech těchto smyček, přenese do neviditelného pole v hlavní tabulce a v základní databázi. Pole tabulky propojené s polem formuláře se získá pomocí 'BoundField'. 'updateInt' umístí do tohoto pole celé číslo (viz definice číselného typu).

         oForm.updateLong(oForm.findColumn(oFeldList.Tag),inID1)

      End If

   ELSE

Pokud nemá být zadán žádný primární klíč, protože v poli se seznamem nebyl žádný záznam nebo byl tento záznam odstraněn, musí být odstraněn i obsah neviditelného pole. updateNull() se použije k vyplnění pole výrazem specifickým pro databázi pro prázdné pole, NULL.

               oForm.updateNULL(oForm.findColumn(oFeldList.Tag),NULL)

         End If

      NEXT inCom

   End If

End Sub

Funkce pro měření délky položky pole se seznamem

Následující funkce udává počet znaků v příslušném sloupci tabulky, aby se příliš dlouhé záznamy pouze nezkracovaly. Pro poskytování návratových hodnot je zde zvoleno Function. Příkaz SUB nemá žádnou návratovou hodnotu, kterou by bylo možné předat a zpracovat jinde.

Function ColumnSize(Tablename As String, Fieldname As String) As Integer

   oDatasource = ThisComponent.Parent.CurrentController

   If Not (oDatasource.isConnected()) Then

      oDatasource.connect()

   End If

   oConnection = oDataSource.ActiveConnection()

   oSQL_Command = oConnection.createStatement()

   stSql = "SELECT ""COLUMN_SIZE"" FROM ""INFORMATION_SCHEMA"".""SYSTEM_COLUMNS"" WHERE ""TABLE_NAME"" = '" + Tablename + "' AND ""COLUMN_NAME"" = '" + Fieldname + "'"

   oResult = oSQL_Command.executeQuery(stSql)

      While oResult.next

         i = oResult.getInt(1)

      Wend

   ColumnSize = i

End Function

Generování akcí databáze

Sub GenerateRecordAction(oEvent As Object)

Toto makro by mělo být vázáno na událost Při zaměření pole seznamu. Je nutné, aby se ve všech případech, kdy se mění pole seznamu, změna uložila. Bez tohoto makra by v aktuální tabulce nedošlo k žádné změně, kterou by aplikace Base rozpoznala, protože pole se seznamem není vázáno na formulář.

Toto makro přímo mění vlastnosti formuláře:

   Dim oForm As Object

   oForm = oEvent.Source.Model.Parent

   oForm.IsModified = TRUE

End Sub

Toto makro není nutné pro formuláře, které používají dotazy pro obsah polí se seznamem. Změny v polích se seznamem se zaznamenávají přímo.

Navigace z jednoho formuláře do druhého

Formulář se má otevřít, když nastane určitá událost.

Ve vlastnostech ovládacího prvku formuláře zadáme na řádku "Další informace" (značka) název formuláře. Zde lze také zadat další informace a následně je oddělit pomocí funkce Split().

Sub From_form_to_form(oEvent As Object)

   Dim stTag As String

   stTag = oEvent.Source.Model.Tag

   aForm() = Split(stTag, ",")

Pole je deklarováno a naplněno názvy formulářů; jednak formuláře, který má být otevřen, a jednak aktuálního formuláře, který bude po otevření druhého zavřen.

   ThisDatabaseDocument.FormDocuments.getByName( Trim(aForm(0)) ).open

   ThisDatabaseDocument.FormDocuments.getByName( Trim(aForm(1)) ).close

End Sub

Pokud se má jiný formulář otevřít až po zavření aktuálního formuláře, například pokud existuje hlavní formulář a všechny ostatní formuláře se z něj ovládají pomocí tlačítek, mělo by být následující makro vázáno na formulář s Nástroje > Přizpůsobit > Události > Dokument zavřen:

Sub Mainform_open

   ThisDatabaseDocument.FormDocuments.getByName( "Mainform" ).open

End Sub

Pokud jsou dokumenty formuláře v rámci souboru ODB řazeny do adresářů, musí být makro pro změnu formuláře rozsáhlejší:

Sub From_form_to_form_with_folders(oEvent As Object)

   REM Nejprve se zadá formulář, který se má otevřít.

   REMPokud se formulář nachází ve složce, použijeme "/" pro definování relace

   REM, aby bylo možné podsložku najít.

   Dim stTag As String

   stTag = oEvent.Source.Model.Tag 'Tag is entered in the additional information

   aForms() = Split(stTag, ",")   'Here the form name for the new form comes first, then the one for the old form

   aForms1() = Split(aForms(0),"/")

   aForms2() = Split(aForms(1),"/")

   If UBound(aForms1()) = 0 Then

      ThisDatabaseDocument.FormDocuments.getByName( Trim(aForms1(0)) ).open

   Else

      ThisDatabaseDocument.FormDocuments.getByName( Trim(aForms1(0)) ).getByName( Trim(aForms1(1)) ).open

   End If

   If UBound(aForms2()) = 0 Then

      ThisDatabaseDocument.FormDocuments.getByName( Trim(aForms2(0)) ).close

   Else

      ThisDatabaseDocument.FormDocuments.getByName( Trim(aForms2(0)) ).getByName( Trim(aForms2(1)) ).close

   End If

End Sub

Dokumenty formuláře, které leží v adresáři, se zadávají do pole Další informace jako adresář/formulář. To musí být převedeno na:

...getByName("Directory").getByName("Form").

Hierarchické seznamy

Nastavení v jednom poli seznamu mají přímo ovlivňovat nastavení jiného pole. Pro jednoduché případy to již bylo popsáno výše v části o filtrování záznamů. Předpokládejme však, že první pole seznamu má ovlivnit obsah druhého pole seznamu, které pak ovlivní obsah třetího pole seznamu atd.

Image1

Obrázek 8: Příklad polí se sezname pro hierarchické uspořádání polí se seznamem

V tomto příkladu obsahuje první pole seznamu (Jahrgang = Years) všechny školní roky. Classes (Třídy, pozn. překl.) v jednotlivých ročnících jsou označeny písmeny. Names (Jména, pozn. překl.) jsou jmény členů třídy.

Za normálních okolností by se v seznamu Years zobrazilo všech 13 ročníků, v seznamu Classes všechna písmena tříd a v seznamu Names všichni žáci školy.

Pokud se jedná o hierarchické seznamy, je výběr tříd po výběru roku omezen. Zobrazena jsou pouze ta písmena tříd, která se v daném roce skutečně vyskytují. To se může lišit, protože pokud se zvyšuje počet žáků, může se zvýšit i počet tříd v ročníku. Poslední pole seznamu, Names, je velmi omezené. Namísto více než 1000 žáků jich bude jen 30.

Na začátku lze vybrat pouze rok. Po provedení této operace se zpřístupní (omezený) seznam tříd. Teprve na konci je uveden seznam jmen.

Pokud je pole seznamu Years změněno, musí se sekvence začít znovu. Pokud se změní pouze pole Classes, zůstane v platnosti číslo roku pro poslední pole seznamu.

Aby bylo možné takovou funkci vytvořit, musí být ve formuláři možné uložit prostřední proměnnou. To probíhá ve skrytém ovládacím prvku.

Makro je vázáno na změnu obsahu pole se seznamem: Vlastnosti pole se seznamem > Události > Změněno. Potřebné proměnné jsou uloženy v doplňkových informacích pole seznamu.

Zde je příklad doplňujících informací:
MainForm,Year,hidden control,Listbox_2

Formulář se nazývá MainForm. Aktuální pole seznamu se jmenuje Listbox1. Toto pole seznamu zobrazuje obsah pole tabulky Year a následující pole seznamu musí být filtrována podle této položky. Skrytý ovládací prvek je označen hidden_control a existence druhého pole seznamu (Listbox_2) je předána filtrovací proceduře.

Sub Hierarchical_control(oEvent As Object)

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm As Object

   Dim oFieldHidden As Object

   Dim oField As Object   

   Dim oField1 As Object   

   Dim stSql As String

   Dim acontent()

   Dim stTag As String

   oField = oEvent.Source.Model

   stTag = oField.Tag

   oForm = oField.Parent

 

   REM Značka přechází do pole Další informace

   REM Obsahuje:

   REM 0. Název pole, které se má v tabulce filtrovat

   REM 1. Název pole skrytého ovládacího prvku, do kterého bude uložena filtrovaná hodnota

   REM 2. Případné další pole se seznamem

   REM Značka se načte z prvku, který makro spouští. Proměnná je

   REM předáno proceduře a případně všem dalším polím se seznamem

   aFilter() = Split(stTag, ",")

   stFilter = ""

Po deklaraci proměnných se obsah značky předá do pole, aby bylo možné přistupovat k jednotlivým prvkům. Poté se deklaruje přístup k jednotlivým polím formuláře.

Určí se pole seznamu, které makro vyvolalo, a přečte se jeho hodnota. Pouze pokud tato hodnota není NULL, bude zkombinována s názvem filtrovaného pole, v našem příkladu Year, a vytvoří příkaz SQL. Jinak zůstane filtr prázdný. Pokud jsou pole seznamu určena k filtrování formuláře, není k dispozici žádný skrytý ovládací prvek. V tomto případě je hodnota filtru uložena přímo ve formuláři.

      If Trim(aFilter(1)) = "" Then

      If oField.getCurrentValue <> "" Then

         stFilter = """"+Trim(aFilter(0))+"""='"+oField.getCurrentValue()+"'"

Pokud již existuje filtr (například filtr, který se zabývá seznamem Listbox 2, ke kterému se nyní přistupuje), nový obsah se připojí k předchozímu obsahu uloženému ve skrytém ovládacím prvku.

         If oForm.Filter <> ""

K tomu musí dojít pouze v případě, že stejné pole ještě nebylo filtrováno. Například pokud filtrujeme podle pole Year, opakování filtru nenajde žádné další záznamy pro pole seznamu Name. Osoba může být nalezena pouze v jednom roce. Musíme proto vyloučit možnost, že název filtru již byl použit.

            And InStr(oForm.Filter, """"+Trim(aFilter(0))+"""='") = 0 Then

            stFilter = oForm.Filter + " AND " + stFilter

Pokud filtr existuje a pole, které bude použito pro filtrování, se již ve filtru nachází, je třeba předchozí filtrování na tomto poli smazat a vytvořit nový filtr.

         ElseIf oForm.Filter <> "" Then

            stFilter = Left(oForm.Filter,

            InStr(oForm.Filter, """"+Trim(aFilter(0))+"""='")-1) + stFilter

         End If

      End If

Poté se filtr zadá do formuláře. Tento filtr může být také prázdný, pokud bylo vybráno první pole seznamu a nemá žádný obsah.

      oForm.Filter = stFilter

      oForm.reload()

Stejný postup se spustí, pokud formulář není třeba filtrovat okamžitě. V tomto případě je hodnota filtru uložena v průměrném čase ve skrytém ovládacím prvku.

   Else

      oFieldHidden = oForm.getByName(Trim(aFilter(1)))

      If oField.getCurrentValue <>"" Then

         stFilter = """"+Trim(aFilter(0))+"""='"+oField.getCurrentValue()+"'"

         If oFieldHidden.HiddenValue <> ""

         And InStr(oFieldHidden.HiddenValue, """"+Trim(aFilter(0))+"""='") = 0 Then

            stFilter = oFieldHidden.HiddenValue + " AND " + stFilter

         ElseIf oFieldHidden.HiddenValue <> "" Then

            stFilter = Left(oFieldHidden.HiddenValue,

               InStr(oFieldHidden.HiddenValue, """"+Trim(aFilter(0))+"""='")-1) +                   stFilter

         End If

      End If

      oFieldHidden.HiddenValue = stFilter

   End If

Pokud má pole Další informace záznam s číslem 4 (číslování začíná od 0), musí být následující pole se seznamem nastaveno na odpovídající položku ze seznamu volajícího.

   If UBound(aFilter()) > 1 Then

      oField1 = oForm.getByName(Trim(aFilter(2)))

      aFilter1() = Split(oField1.Tag,",")

Potřebné údaje pro filtrování se načtou z pole Další informace (Tag) v příslušném seznamu. Není možné zapsat pouze čerstvý kód SQL do pole se seznamem a poté načíst hodnoty pole se seznamem. Místo toho je třeba hodnoty odpovídající dotazu zapsat přímo do pole seznamu.

Vytvoření kódu vychází z toho, že tabulka, na kterou se formulář odkazuje, je stejná jako tabulka, na kterou se odkazují seznamy. Takové pole se seznamem není určeno k přenosu cizích klíčů do tabulky.

      If oField.getCurrentValue <> "" Then

         stSql = "SELECT DISTINCT """+Trim(aFilter1(0))+""" FROM """+oForm.Command+             """ WHERE "+stFilter+" ORDER BY """+Trim(aFilter1(0))+""""

         oDatasource = ThisComponent.Parent.CurrentController

         If Not (oDatasource.isConnected()) Then

            oDatasource.connect()

         End If

         oConnection = oDatasource.ActiveConnection()

         oSQL_Statement = oConnection.createStatement()

         oQuery_result = oSQL_Statement.executeQuery(stSql)

Hodnoty se načtou do pole. Pole se přenáší přímo do pole seznamu. Příslušné indexy pole rostou v rámci smyčky.

         inIndex = 0

         While oQuery_result.next

            ReDim Preserve aContent(inIndex)   

            acontent(inIndex) = oQuery_result.getString(1)

            inIndex = inIndex+1   

         WEnd

      Else

         aContent(0) = ""

      End If

      oField1.StringItemList = aContent()

Obsah pole se seznamem byl vytvořen nově. Nyní je třeba ho znovu načíst. Poté se pomocí vlastnosti Další informace aktualizovaného pole seznamu vyprázdní každé následující závislé pole seznamu a spustí se smyčka pro všechny následující pole seznamu, dokud se nedosáhne takového, který nemá ve svých Dalších informacích čtvrtý výraz.

      oField1.refresh()

      While UBound(aFilter1()) > 1

         Dim aLeer()

         oField2 = oForm.getByName(Trim(aFilter1(2)))

         Dim aFilter1()

         aFilter1() = Split(oField2.Tag,",")

         oField2.StringItemList = aEmpty()

         oField2.refresh()

      Wend

   End If

End Sub

Viditelný obsah polí se seznamem je uložen v oField1.StringItemList. Pokud je třeba uložit nějakou další hodnotu pro přenos do podkladové tabulky jako cizí klíč, jak je obvyklé u polí se seznamem ve formulářích, musí být tato hodnota předána do dotazu samostatně a poté uložena pomocí oField1.ValueItemList.

Takové rozšíření vyžaduje další proměnné, jako je kromě tabulky, do které se mají ukládat hodnoty formuláře, také tabulka, ze které se vykresluje obsah pole seznamu.

Formulaci dotazu na filtr je třeba věnovat zvláštní pozornost.

   stFilter = """"+Trim(aFilter(1))+"""='"+oField.getCurrentValue()+"'"

To bude fungovat pouze v případě, že základní verze LibreOffice je 4.1 nebo novější, protože jako CurrentValue() se zadává hodnota, která má být uložena, a ne hodnota, která se zobrazuje. Chceme-li zajistit, aby fungoval v různých verzích, nastavíme Vlastnost: Nastavte vlastnost Seznam > Data > Vázané pole > '0'.

Zadávání časů pomocí milisekund

Ukládání časů s přesností na milisekundy vyžaduje v tabulce pole časové značky, které je pro tento účel zvlášť upraveno v jazyce SQL (viz „Vytvoření tabulky“ v kapitole 3). Takové pole může být ve formuláři reprezentováno formátovaným polem ve formátu MM:SS,00. Při prvním pokusu o zápis do něj však záznam selže. To lze napravit pomocí následujícího makra, které by mělo být navázáno na vlastnost formuláře „Před záznamem“:

SUB Timestamp

   Dim unoStmp As New com.sun.star.util.DateTime

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm As Object

   Dim oFeld As Object

   Dim stZeit As String

   Dim ar()

   Dim arMandS()

   Dim loNano As Long

   Dim inSecond As Integer

   Dim inMinute As Integer

   oDoc = thisComponent

   oDrawpage = oDoc.Drawpage

   oForm = oDrawpage.Forms.getByName("MainForm")

   oField = oForm.getByName("Time")

   stTime = oField.Text

Proměnné se deklarují jako první. Zbytek kódu se provede pouze tehdy, když je v poli Time něco uvedeno. V opačném případě vnitřní mechanismus formuláře nastaví pole na NULL.

   If stTime <> "" Then

      ar() = Split(stTime,".")

      loNano = CLng(ar(1)&"0000000")

      arMandS() = Split(ar(0),":")

      inSecond = CInt(arMandS(1))

      inMinute = Cint(arMandS(0))

Položka v poli Time je rozdělena na jednotlivé prvky.

Nejprve se oddělí desetinná část a doplní se nulovými znaky vpravo na celkem devět číslic. Takto vysoké číslo lze uložit pouze do dlouhé proměnné.

Zbytek času se pak rozdělí na minuty a sekundy pomocí dvojtečky jako oddělovače a převede se na celá čísla.

      With unoStmp

         .NanoSeconds = loNano

         .Seconds = inSecond

         .Minutes = inMinute

         .Hours = 0

         .Day = 30

         .Month = 12

         .Year = 1899

      End With

Hodnoty časových razítek jsou přiřazeny standardnímu datu LibreOffice 30.12.1899. Vedle něj lze samozřejmě uložit i aktuální datum.

Poznámka

Získání a uložení aktuálního data:

Dim now As Date
  now
= Now()
  With
unoStmp
     .NanoSeconds = loNano
     .Seconds = inSecond
     .Minutes = inMinute
     .Hours = Hour(now)
     
.Day = Day(now)
     
.Month = Month(now)
     
.Year = Year(now)
  End With
     
oField.BoundField.updateTimestamp(unoStmp)
  End If
End Sub

Nyní se vytvořené časové razítko přenese do pole pomocí updateTimestamp a uloží se do formuláře.

V dřívějších výukových materiálech se NanoSekundy nazývaly HundrethSeconds. To neodpovídá rozhraní API LibreOffice a způsobí to chybové hlášení.

Jedna událost – několik implementací

Při použití formulářů se může stát, že makro spojené s jednou událostí bude spuštěno dvakrát. K tomu dochází proto, že s uložením například upraveného záznamu je spojeno více procesů současně. Různé příčiny takové události lze určit následujícím způsobem:

Sub Determine_eventcause(oEvent As Object)

   Dim oForm As Object

   oForm = oEvent.Source

   MsgBox oForm.ImplementationName

End Sub

Při ukládání upraveného záznamu se používají dvě implementace s názvy org.openoffice.comp.svx.FormControllercom.sun.star.comp.forms.ODatabaseForm. Pomocí těchto názvů můžeme zajistit, aby makro prošlo svým kódem pouze jednou. Duplicitní spuštění obvykle způsobí jen (malou) přestávku ve vykonávání programu, ale může vést k tomu, že se kurzor vrátí o dva záznamy místo jednoho. Každá implementace umožňuje pouze určité příkazy, takže znalost názvu implementace může být důležitá.

Uložení s potvrzením

U složitých změn záznamů má smysl se před provedením zeptat uživatele, zda má být změna skutečně provedena. Pokud je odpověď v dialogovém okně Ne, uložení se přeruší, změna se zahodí a kurzor zůstane na aktuálním záznamu.

Sub Save_confirmation(oEvent As Object)

   Dim oFormFeature As Object

   Dim oFormOperations As Object

   Dim inAnswer As Integer

   oFormFeature = com.sun.star.form.runtime.FormFeature

   Select Case oEvent.Source.ImplementationName

      Case "org.openoffice.comp.svx.FormController"

         inAnswer = MsgBox("Should the record be changed?" ,4, "Change_record")
        Select Case
inAnswer

            Case 6   ' Yes, no further action

            Case 7   ' No, interrupt save

               oFormOperations = oEvent.Source.FormOperations

               oFormOperations.execute(oFormFeature.UndoRecordChanges)

            Case Else

         End Select

      Case "com.sun.star.comp.forms.ODatabaseForm"

   End Select

End Sub

Existují dva spouštěcí momenty s různými názvy implementace. Tyto dvě implementace jsou rozlišeny v příkazu SELECT CASE. Kód se provede pouze pro implementaci FormController. Je to proto, že pouze FormController má proměnnou FormOperations.

Kromě Ano  a Ne může uživatel také klepnout na tlačítko zavřít. Tím však získáme stejnou hodnotu jako u Ne, tedy 7.

Pokud se ve formuláři pohybujeme pomocí klávesy tabelátoru, zobrazí se uživateli pouze dialogové okno s výzvou k potvrzení. Uživatelům, kteří používají lištu navigace, se však také zobrazí zpráva, že záznam nebude změněn.

Primární klíč z běžného čísla a roku

Při přípravě faktur jsou ovlivněny roční zůstatky. To často vede k touze oddělit tabulky faktur v databázi podle jednotlivých let a každý rok začít novou tabulku.

Následující řešení pomocí makra používá jinou metodu. Do tabulky se automaticky zapíše hodnota pole ID, ale také se zohlední pole Year, které v tabulce existuje jako sekundární primární klíč. V tabulce se tedy mohou vyskytovat následující primární klíče:

Tabulka 28: Příklad primárních klíčů kombinujících hodnoty rok a ID

year

ID

2014

1

2014

2

2014

3

2015

1

2015

2

Tímto způsobem lze snáze získat přehled o dokumentech za daný rok.

Sub Current_Date_and_ID

   Dim oDatasource As Object

   Dim oConnection As Object

   Dim oSQL_Command As Object

   Dim stSql As String

   Dim oResult As Object

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm As Object

   Dim oField1 As Object

   Dim oField2 As Object

   Dim oField3 As Object

   Dim inIDnew As Integer

   Dim inYear As Integer

   Dim unoDate

   oDoc = thisComponent

   oDrawpage = oDoc.drawpage

   oForm = oDrawpage.forms.getByName("MainForm")

   oField1 = oForm.getByName("fmt_year")

   oField2 = oForm.getByName("fmtID")   

   oField3 = oForm.getByName("dat_date")

   If IsEmpty(oField2.getCurrentValue()) Then

      If IsEmpty(oField3.getCurrentValue()) Then

         unoDate = createUnoStruct("com.sun.star.util.Date")

         unoDate.Year = Year(Date)

         unoDate.Month = Month(Date)

         unoDate.Day = Day(Date)

         inYear = Year(Date)

      Else

         inYear = oField3.CurrentValue.Year

      End If

      oDatasource = ThisComponent.Parent.CurrentController

      If Not (oDatasource.isConnected()) Then

         oDatasource.connect()

      End If

      oConnection = oDatasource.ActiveConnection()

      oSQL_Command = oConnection.createStatement()

      stSql = "SELECT MAX( ""ID"" )+1 FROM ""orders"" WHERE ""year"" = '"

         + inYear + "'"

      oResult = oSQL_Command.executeQuery(stSql)

      While oResult.next

         inIDnew = oresult.getInt(1)

      Wend

      If inIDnew = 0 Then

         inIDnew = 1

      End If

      oField1.BoundField.updateInt(inYear)

      oField2.BoundField.updateInt(inIDnew)

      If IsEmpty(oField3.getCurrentValue()) Then

         oField3.BoundField.updateDate(unoDate)

      End If

   End If

End Sub

Všechny proměnné jsou deklarovány. Jsou zpřístupněny ovládací prvky formuláře v hlavním formuláři. Zbytek kódu se spustí pouze v případě, že položka pole fmtID je stále prázdná. Pokud nebylo zadáno žádné datum, vytvoří se struktura data, aby se aktuální datum a rok přenesly do příslušných polí. Poté se vytvoří připojení k databázi, pokud ještě neexistuje. Nejvyšší hodnota pole ID pro aktuální rok se zvýší o 1. Pokud je sada výsledků prázdná, znamená to, že v poli ID nejsou žádné položky. V tomto okamžiku by mohla být v ovládacím prvku fmtID zadána hodnota 0, ale číslování příkazů by mělo začínat hodnotou 1, takže proměnná inIDnew získá hodnotu 1.

Do formuláře se přenese vrácená hodnota year, ID a aktuální datum (pokud nebylo zadáno žádné datum).

Ve formuláři jsou pole primárních klíčů ID a Year chráněna proti zápisu. Proto jim lze zadat hodnoty pouze pomocí tohoto makra.

Úlohy databáze rozšířené pomocí maker

Vytvoření připojení k databázi

oDataSource = ThisComponent.Parent.DataSource

If Not oDataSource.IsPasswordRequired Then

   oConnection = oDataSource.GetConnection("","")

Zde by bylo možné zadat uživatelské jméno a heslo, pokud by to bylo nutné. V takovém případě by závorky obsahovaly ("Username", "Password"). Namísto zadání uživatelského jména a hesla v otevřeném textu se vyvolá dialogové okno pro ochranu heslem:

Else

   oAuthentication = createUnoService("com.sun.star.sdb.InteractionHandler")

   oConnection = oDataSource.ConnectWithCompletion(oAuthentication)

End If

Pokud však k databázi přistupuje formulář v rámci stejného souboru aplikace Base, stačí:

oDataSource = ThisComponent.Parent.CurrentController

If Not (oDataSource.isConnected()) Then

   oDataSource.connect()

End If

oConnection = oDataSource.ActiveConnection()

Zde je databáze známá, takže uživatelské jméno a heslo nejsou nutné, protože jsou již vypnuty v základní konfiguraci HSQLDB pro interní verzi.

V případě formulářů mimo aplikaci Base se připojení provádí prostřednictvím prvního formuláře:

oDataSource = Thiscomponent.Drawpage.Forms(0)

oConnection = oDataSource.activeConnection

Kopírování dat z jedné databáze do druhé

Interní databáze je databáze pro jednoho uživatele. Záznamy jsou uloženy v souboru *.odb. Výměna dat mezi různými databázovými soubory nebyla povolena, nicméně je možná pomocí exportu a importu.

Soubory *.odb jsou však často nastaveny tak, aby umožňovaly automatickou výměnu dat mezi databázemi. Zde může být užitečný následující postup.

Po deklaraci proměnných se z tlačítka na formuláři načte cesta k aktuální databázi. Název databáze je oddělen od zbytku cesty. V této složce se nachází také cílový soubor pro záznamy. Název tohoto souboru je připojen k cestě, aby bylo možné navázat spojení s cílovou databází.

Připojení ke zdrojové databázi je určeno vzhledem k formuláři, který obsahuje tlačítko: ThisComponent.Parent.CurrentController. Připojení k externí databázi se nastavuje pomocí DatabaseContext a cesty.

Sub DataCopy

   Dim oDatabaseContext As Object

   Dim oDatasource As Object

   Dim oDatasourceZiel As Object

   Dim oConnection As Object

   Dim oConnection Ziel As Object

   Dim oDB As Object

   Dim oSQL_Command As Object

   Dim oSQL_CommandTarget As Object

   Dim oResult As Object

   Dim oResultTarget As Object

   Dim stSql As String

   Dim stSqlTarget As String

   Dim inID As Integer

   Dim inIDTarget As Integer

   Dim stName As String

   Dim stTown As String

   oDB = ThisComponent.Parent

   stDir = Left(oDB.Location,Len(oDB.Location)-Len(oDB.Title))

   stDir = ConvertToUrl(stDir & "TargetDB.odb")

   oDatasource = ThisComponent.Parent.CurrentController

   If Not (oDatasource.isConnected()) Then

      oDatasource.connect()

   End If

   oConnection = oDatasource.ActiveConnection()

   oDatabaseContext = createUnoService("com.sun.star.sdb.DatabaseContext")

   oDatasourceTarget = oDatabaseContext.getByName(stDir)

   oConnectionTarget = oDatasourceTarget.GetConnection("","")

   oSQL_Command = oConnection.createStatement()

   stSql = "SELECT * FROM ""table"""

   oResult = oSQL_Command.executeQuery(stSql)

   While oResult.next

      inID = oResult.getInt(1)

      stName = oResult.getString(2)

      stTown = oResult.getString(3)

      oSQL_CommandTarget = oConnectionTarget.createStatement()

      stSqlTarget = "SELECT ""ID"" FROM ""table"" WHERE ""ID"" = '"+inID+"'"

      oResultTarget = oSQL_CommandZiel.executeQuery(stSqlTarget)

      inIDZiel = - 1

      While oResultTarget.next

         inIDTarget = oResultTarget.getInt(1)

      Wend

      If inIDTarget = - 1 Then

         stSqlTarget = "INSERT INTO ""table"" (""ID"",""name"",""town"") VALUES
           ('"
+inID+"','"+stName+"','"+stTown+"')"

         oSQL_CommandTarget.executeUpdate(stSqlZiel)

      End If

   Wend

End Sub

Kompletní tabulky zdrojové databáze jsou načteny a vloženy řádek po řádku do tabulek cílové databáze pomocí nastaveného připojení. Před vložením se zkontroluje, zda byla nastavena hodnota primárního klíče. Pokud ano, záznam se nekopíruje.

Lze také zařídit, aby se místo nového záznamu zkopíroval stávající záznam. Ve všech případech se tím zajistí, aby cílová databáze obsahovala záznamy se správným primárním klíčem zdrojové databáze.

Přístup k dotazům

Vytvářet dotazy v grafickém uživatelském rozhraní je jednodušší než přenášet jejich text do maker, navíc s komplikací, že pro všechny názvy tabulek a polí jsou nutné duplicitní dvojité uvozovky.

Sub aQueryContent

   Dim oDatabaseFile As Object

   Dim oQuery As Object

   Dim stQuery As String

   oDatabaseFile = ThisComponent.Parent.CurrentController.DataSource

   oQuery = oDatabaseFile.getQueryDefinitions()

   stQuery = oQuery.getByName("Query").Command

   MsgBox stQuery

End Sub

Zde je obsah souboru *.odb zpřístupněn z formuláře. K dotazu se dostanete pomocí getQueryDefinitions(). Kód SQL pro dotaz je v jeho poli Command.  Tento příkaz pak lze dále použít v makru.

Při použití kódu SQL dotazu je třeba dbát na to, aby kód neodkazoval na jiný dotaz. To nevyhnutelně vede ke zprávě, že (zdánlivá) tabulka z databáze je neznámá. Z tohoto důvodu je jednodušší vytvářet pohledy z dotazů a poté k nim přistupovat v makru.

Zabezpečení databáze

Někdy se může stát, zejména při vytváření databáze, že se soubor ODB neočekávaně zkrátí. Časté ukládání po úpravách je proto užitečné, zejména při používání modulu Sestavy.

Pokud je databáze používána, může být poškozena selháním operačního systému, pokud k němu dojde právě při ukončování souboru aplikace Base. V tomto okamžiku se obsah databáze zapisuje do souboru.

Kromě toho existují obvyklé příčiny, proč se soubory náhle odmítají otevřít, například selhání pevného disku. Není proto na škodu mít záložní kopii, která je co nejaktuálnější. Stav dat se nemění, dokud je soubor ODB otevřený. Z tohoto důvodu mohou být bezpečnostní podprogramy přímo spojeny s otevřením souboru. Soubor jednoduše zkopírujeme pomocí cesty Zálohy uvedené v Nástroje > Možnosti > LibreOffice > Cesty. Makro začne přepisovat nejstarší verzi po určitém počtu kopií (inMax).

Sub Databasebackup(inMax As Integer)

   Dim oPath As Object

   Dim oDoc As Object

   Dim sTitle As String

   Dim sUrl_End As String

   Dim sUrl_Start As String

   Dim i As Integer

   Dim k As Integer

   oDoc = ThisComponent

   sTitle = oDoc.Title

   sUrl_Start = oDoc.URL

   Do While sUrl_Start = ""

      oDoc = oDoc.Parent

      sTitle = oDoc.Title

      sUrl_Start = oDoc.URL

   Loop

Pokud je makro spuštěno při spuštění souboru ODB, budou hodnoty sTitle a sUrl_Start správné. Pokud však makro provádí formulář, musí nejprve zjistit, zda je k dispozici adresa URL. Pokud je adresa URL prázdná, vyhledá se hodnota na vyšší úrovni (oDoc.Parent).

   oPath = createUnoService("com.sun.star.util.PathSettings")

   For i = 1 To inMax + 1

      If Not FileExists(oPath.Backup & "/" & i & "_" & sTitle) Then

         If i > inMax Then

            For k = 1 To inMax - 1 To 1 Step -1

            If FileDateTime(oPath.Backup & "/" & k & "_" & sTitle) <=  FileDateTime(oPath.Backup & "/" & k+1 & "_" & sTitle) Then

               If k = 1 Then

                     i = k

                     Exit For

               End If

            Else

               i = k + 1

               Exit For

            End If

            Next

         End If

         Exit For

      End If

   Next

   sUrl_End = oPath.Backup & "/" & i &"_" & sTitle

   FileCopy(sUrl_Start,sUrl_End)

End Sub

Zálohování můžeme provést i za běhu databáze Base, pokud je možné data z mezipaměti zapsat zpět do souboru před provedením podprogramu Databasebackup. To by mohlo být užitečné třeba po uplynutí určitého času nebo po stisknutí tlačítka na obrazovce. Toto vyčištění mezipaměti se provádí pomocí následujícího podprogramu:

Sub Write_data_out_of_cache

   Dim oData As Object

   Dim oDataSource As Object

   oData = ThisDatabaseDocument.CurrentController

   If Not ( oData.isConnected() ) Then oData.connect()

   oDataSource = oData.DataSource

   oDataSource.flush

End Sub

Má-li být vše spuštěno z jediného tlačítka na formuláři, musí být obě procedury volány další procedurou:

Sub BackupNow

   Write_data_out_of_cache

   DatabaseBackup(10)

End Sub

Zejména v případě bezpečnostního makra by bylo vhodné, aby bylo makro přístupné prostřednictvím nástrojové lišty databáze. To se provádí v hlavním okně souboru Base pomocí Nástroje > Přizpůsobit > Nástrojové lišty.

Image3

Obrázek 9: Použití karty Nástrojové lišty v dialogovém okně Přizpůsobit pro zpřístupnění makra na standardní nástrojové liště

V dialogovém okně Přizpůsobit v části Rozsah platnosti musí být příkaz uložen v souboru Base, což je v tomto případě Media_with_Macros.odb.

V části Cíl vybereme Standardní nástrojová lišta, který funguje ve všech částech aplikace Base.

Dialogové okno nyní zobrazuje příslušné funkce v pravém seznamu. Vybereme proceduru BackupNow.

Příkaz je nyní k dispozici pro použití na nástrojové liště nejvyšší úrovně. Chceme-li příkazu přiřadit ikonu, zvolíme Upravit > Změnit ikonu a otevřeme dialogové okno zobrazené na obrázku 10.

Bild4

Obrázek 10: Dialogové okno Změnit ikonu

Vybereme vhodnou ikonu. Můžeme si také vytvořit a přidat vlastní ikonu.

Bild5

Obrázek 11: Nová ikona viditelná na Standardní nástrojové liště

Místo názvu procedury se nyní zobrazí ikona. Název se stane nápovědou.

Proceduru provedeme klepnutím na ikonu na nástrojové liště.

Komprimace databáze

Jedná se o jednoduchý příkaz SQL (SHUTDOWN COMPACT), který by se měl čas od času provést, zejména po odstranění velkého množství dat. Databáze ukládá nová data, ale stále si vyhrazuje místo pro smazaná data. V případech, kdy byla data podstatně změněna, je proto nutné databázi zkomprimovat.

Poznámka

Od verze LibreOffice 3.6 se tento příkaz automaticky provede pro interní HSQLDB při uzavření databáze. Proto již toto makro není pro interní databázi nutné.

Po zkomprimování již nejsou tabulky přístupné. Soubor je třeba znovu otevřít. Proto toto makro uzavře formulář, ze kterého je voláno. Samotný dokument však nelze zavřít, aniž by došlo k obnovení při jeho opětovném otevření. Proto je tato funkce zakomentována.

Sub Database_compaction

   Dim stMessage As String

   oDataSource = ThisComponent.Parent.CurrentController   ' Accessible from the form

   If Not (oDataSource.isConnected()) Then

      oDataSource.connect()

   End If

   oConnection = oDataSource.ActiveConnection()

   oSQL_Statement = oConnection.createStatement()

   stSql = "SHUTDOWN COMPACT"   ' The database is being compacted and shut down

   oSQL_Statement.executeQuery(stSql)

   stMessage = "The database is being compacted." + Chr(13) + "The form will now close."

   stMessage = stMessage + Chr(13) + "Following this, the database file should be closed."

   stMessage = stMessage + Chr(13) + "The database can only be accessed after reopening the database file."

   MsgBox stMessage   

   ThisDatabaseDocument.FormDocuments.getByName( "Maintenance" ).close

   REM Uzavření databázového souboru způsobí při jeho opětovném otevření operaci obnovení.

'   ThisDatabaseDocument.close(True)   

End Sub

Snížení indexu tabulky pro pole automatických hodnot

Pokud je z tabulky odstraněno velké množství dat, uživatelé se často obávají, že posloupnost automaticky generovaných primárních klíčů jednoduše pokračuje směrem nahoru, místo aby začala znovu od nejvyšší aktuální hodnoty klíče. Následující podprogram načte aktuálně nejvyšší hodnotu pole ID v tabulce a nastaví další počáteční hodnotu klíče o 1 vyšší, než je tato maximální hodnota.

Pokud se pole primárního klíče nejmenuje ID, je třeba makro odpovídajícím způsobem upravit.

Sub Table_index_down(stTable As String)

   REM Tento podprogram nastaví pole primárního klíče s automatickým přírůstkem s přednastaveným názvem "ID" na nejnižší možnou hodnotu.

   Dim inCount As Integer

   Dim inSequence_Value As Integer

   oDataSource = ThisComponent.Parent.CurrentController   ' Accessible through the  form

   If Not (oDataSource.isConnected()) Then

      oDataSource.connect()

   End If

   oConnection = oDataSource.ActiveConnection()

   oSQL_Statement = oConnection.createStatement()

   stSql = "SELECT MAX(""ID"") FROM """+stTable+""""   ' The highest value in "ID" is determined

   oQuery_result = oSQL_Statement.executeQuery(stSql)   ' Dotaz je spuštěn a návratová hodnota je uložena v proměnné oQuery_result

   If Not IsNull(oQuery_result) Then

      While oQuery_result.next

      inCount = oQuery_result.getInt(1)   ' First data field is read

      Wend    ' další záznam, v tomto případně žádný, protože existuje pouze jeden záznam

      If inCount = "" Then   'Pokud nejvyšší hodnota není hodnota, což znamená, že tabulka je prázdná, je nejvyšší hodnota nastavena na -1.

         inCount = -1

      End If

      inSequence_Value = inCount+1   ' Nejvyšší hodnota se zvýší o 1

      REM Pro databázi je připraven nový příkaz. ID začne znovu od inCount+1.

      REM Tento příkaz nemá žádnou návratovou hodnotu, protože se nečte žádný záznam

      oSQL_statement = oConnection.createStatement()

      oSQL_statement.executeQuery("ALTER TABLE """ + stTable + """ ALTER COLUMN ""ID"" RESTART WITH " + inSequence_Value + "")

   End If

End Sub

Tisk z Base

Standardním způsobem, jak získat dokument k tisku ze systému Base, je použití sestavy. Tabulky a dotazy lze také zkopírovat do aplikace Calc a připravit je tam k tisku. Samozřejmě je možný i přímý tisk formuláře z obrazovky.

Tisk sestavy z interního formuláře

Generování sestav se obvykle provádí z uživatelského rozhraní aplikace Base. Klepnutím na název sestavy se spustí její příprava. Bylo by samozřejmě jednodušší, kdyby bylo možné sestavu spustit přímo z formuláře.

Sub Reportlaunch

   ThisDatabaseDocument.ReportDocuments.getByName("Report").open

End Sub

Všechny sestavy jsou přístupné podle názvu ze svého kontejneru ReportDocuments. Otevírají se pomocí open. Pokud je sestava vázána na dotaz, který je filtrován prostřednictvím formuláře, umožňuje tato metoda vypsat aktuální záznam.

Spuštění, formátování, přímý tisk a uzavření sestavy

Ještě lepší by bylo, kdyby bylo možné sestavu odeslat přímo do tiskárny. Následující kombinace postupů přidává několik drobností. Nejprve vybere aktivní záznam ve formuláři, přeformátuje sestavu tak, aby se textová pole automaticky nastavila na správnou výšku, a poté sestavu spustí. Nakonec se sestava vytiskne a volitelně uloží ve formátu pdf. To vše se děje téměř zcela na pozadí, protože sestava se po otevření formuláře přepne do neviditelného režimu a po vytištění se opět zavře. Návrhy na různé postupy předložili Andrew Pitonyak, Thomas Krumbein a Lionel Elie Mamane.

Sub ReportStart(oEvent As Object)

   Dim oForm As Object

   Dim stSql As String

   Dim oDatasource As Object

   Dim oConnection As Object

   Dim oSQL_command As Object

   Dim oReport As Object

   Dim oReportView As Object

   oForm = oEvent.Source.model.parent

   stSql = "UPDATE ""Filter"" SET ""Integer"" = '" +
     
oForm.getInt(oForm.findColumn("ID")) + "' WHERE ""ID"" = TRUE"

   oDatasource = ThisComponent.Parent.CurrentController

   If Not (oDatasource.isConnected()) Then

      oDatasource.connect()

   End If

   oConnection = oDatasource.ActiveConnection()

   oSQL_command = oConnection.createStatement()

   oSQL_command.executeUpdate(stSql)

   oReport = ThisDatabaseDocument.ReportDocuments.getByName("Reportname").open

   oReportView = oReport.CurrentController.Frame.ContainerWindow

   oReportView.Visible = False

   ReportLineHeightAuto(oReport)

End Sub

Procedura ReportStart je propojena s tlačítkem ve formuláři. Pomocí tohoto tlačítka lze načíst primární klíč aktuálního záznamu. Z události, která makro spustí, se dostaneme do formuláře (oForm). Název pole primárního klíče je zde uveden jako "ID". Pomocí oForm.getInt(oForm.findColumn("ID")) se klíč načte z pole jako celé číslo. Tato hodnota je uložena v tabulce filtrů. Tabulka filtru řídí dotaz, aby bylo zajištěno, že pro sestavu bude použit pouze aktuální záznam.

Sestavu lze otevřít bez odkazu na formulář. Pak je přístupný jako objekt (oReport). Okno sestavy je neviditelné. Všimneme si, že při vyvolání nemůže být neviditelný, takže se zobrazí jen na chvíli a pak se na pozadí vyplní příslušným obsahem.

Dále se spustí procedura ReportLineHeightAuto. Této proceduře je jako argument předán odkaz na otevřenou sestavou.

Výšku řádku záznamu lze nastavit automaticky při tisku. Pokud je v určitém poli pravděpodobně příliš mnoho textu, text se zkrátí a zbytek se označí červeným trojúhelníkem. Pokud tato funkce nefunguje, následující postup zajistí, že ve všech tabulkách s názvem Detail bude zapnuta automatická kontrola výšky.

Sub ReportLineHeightAuto(oReport As Object)

   Dim oTables As Object

   Dim oTable As Object

   Dim inT As Integer

   Dim inI As Integer

   Dim oRows As Object

   Dim oRow As Object

   oTables = oReport.getTextTables()

   For inT = 0 TO oTables.count() - 1

      oTable = oTables.getByIndex(inT)

      If Left$(oTable.name, 6) = "Detail" Then

         oRows = oTable.Rows

         For inI = 0 To oRows.count - 1

            oRow = oRows.getByIndex(inI)

            oRow.IsAutoHeight = True

         Next inI

      End If

   Next inT

   PrintCloseReport(oReport)

End Sub

Při vytváření sestavy je třeba dbát na to, aby všechna pole na stejném řádku sekce Detail měla stejnou výšku. V opačném případě může automatická kontrola výšky náhle nastavit dvojnásobnou výšku řádku.

Jakmile je u všech tabulek s názvem Detail nastavena automatická kontrola výšky, je sestava odeslána na tiskárnu pomocí procedury PrintCloseReport.

Pole Props obsahuje hodnoty, které jsou v dokumentu přiřazeny k tiskárně. Pro příkaz tisk je důležitý název výchozí tiskárny. Sestava by měla zůstat otevřená až do skutečného dokončení tisku. To je zajištěno zadáním názvu tiskárny a příkazu „Počkej, až budu hotový“ (Wait) jako argumentů.

Sub PrintCloseReport(oReport As Object)

   Dim Props

   Dim stPrinter As String

   Props = oReport.getPrinter()

   stPrinter = Props(0).value

   Dim arg(1) As New com.sun.star.beans.PropertyValue

   arg(0).name = "Name"   

   arg(0).value = "<" & stPrinter & ">"

   arg(1).name = "Wait"   

   arg(1).value = True

   oReport.print(arg())   

   oReport.close(true)

End Sub

Teprve po úplném odeslání tisku do tiskárny se dokument uzavře.

Nastavení tiskárny nalezneme v části Nastavení tiskárny a tisku na wiki.

Pokud chceme místo tisku (nebo jako doplněk k tisku) uložit dokument ve formátu pdf jako bezpečnostní kopii, můžeme použít metodu storeToURL():

Sub ReportPDFstore(oReport As Object)

   Dim stUrl As String

   Dim arg(0) As New com.sun.star.beans.PropertyValue

   arg(0).name = "FilterName"   

   arg(0).value = "writer_pdf_Export"

   stUrl = "file:///...."

   oReport.storeToURL(stUrl, arg())

End Sub

Adresa URL musí být samozřejmě úplná adresa URL. Ještě lépe by tato adresa měla být spojena s trvalým záznamem vytištěného dokumentu, například s číslem faktury. V opačném případě by se mohlo stát, že bezpečnostní soubor bude při dalším tisku jednoduše přepsán.

Tisk sestav z externího formuláře

Při použití externích formulářů dochází k problémům. Sestavy se nacházejí v souboru *.odb a nejsou dostupné pomocí prohlížeče datových zdrojů.

Sub Reportstart(oEvent As Object)

   Dim oFeld As Object

   Dim oForm As Object

   Dim oDocument As Object

   Dim oDocView As Object

   Dim Arg()

   oField = oEvent.Source.Model

   oForm = oField.Parent

   sURL = oForm.DataSourceName

   oDocument = StarDesktop.loadComponentFromURL(sURL, "_blank", 0, Arg() )

   oDocView = oDocument.CurrentController.Frame.ContainerWindow

   oDocView.Visible = False

   oDocument.getCurrentController().connect

   Wait(100)

   oDocument.ReportDocuments.getByName("Report").open

   oDocument.close(True)

End Sub

Sestava se spouští z tlačítka na externím formuláři. Tlačítko sdělí formuláři cestu k souboru *.odb: oForm.DataSourceName. Poté se soubor otevře pomocí loadComponentFromUrl. Soubor by měl zůstat na pozadí, takže se zpřístupní zobrazení dokumentu a rozhraní se nastaví na Visible = False. Ideální by bylo použít přímo seznam argumentů Arg(), ale testy ukázaly, že to nedává správný výsledek.

Sestavu nelze z otevřeného dokumentu okamžitě vyvolat, protože připojení ještě není připraveno. Sestava se zobrazí s šedým pozadím a poté LibreOffice spadne. Krátké čekání o 100 milisekundách tento problém řeší. Pro stanovení minimální čekací doby jsou nezbytné praktické zkoušky. Nyní je sestava spuštěna. Protože zpráva bude v samostatném textovém souboru, otevřený soubor *.odb lze opět zavřít. Metoda oDocument.close(True) předá tento pokyn souboru *.odb. Soubor se uzavře pouze tehdy, když již není aktivní, tj. do sestavy již nemají být předávány žádné další záznamy.

Podobný přístup lze spustit z formulářů v souboru *.odb, ale v tomto případě by dokument neměl být uzavřen.

Pomocí maker v kombinaci s funkcí hromadné korespondence nebo textových polí můžeme získat kvalitní výtisky podstatně rychleji než pomocí nástroje Návrhář sestav.

Provedení hromadné korespondence z Base

Někdy je sestava pro vytvoření kvalitních dopisů adresátům prostě nedostatečná. Textová pole v sestavě mají v praxi velmi omezený význam. Místo toho lze v aplikaci Writer vytvořit dopis typu hromadné korespondence. Není však nutné nejprve otevřít Writer, provést v něm veškeré zadávání a přizpůsobení a teprve poté tisknout. To vše můžeme provést přímo z aplikace Base pomocí makra.

Sub MailmergePrint

   Dim oMailMerge As Object

   Dim aProps()

   oMailMerge = CreateUnoService("com.sun.star.text.MailMerge")

Název uvedený pro zdroj dat je název, pod kterým je databáze zaregistrována v LibreOffice. Tento název nemusí být totožný s názvem souboru. Registrovaný název v tomto příkladu je Addresses.

   oMailMerge.DataSourceName = "Addresses"

Cesta k souboru hromadné korespondence musí být naformátována podle zvyklostí našeho operačního systému, v tomto příkladu je to absolutní cesta v systému Linux.

   oMailMerge.DocumentURL = ConvertToUrl("home/user/Dokuments/mailmerge.odt")

Je stanoven typ příkazu. 0 znamená tabulku, 1 dotaz a 2 přímý příkaz SQL.

   oMailMerge.CommandType = 1

Zde byl zvolen dotaz s názvem MailmergeQuery.

   oMailMerge.Command = "MailmergeQuery"

Filtr slouží k určení záznamů, které mají být použity pro tisk hromadné korespondence. Tento filtr může být například zadán pomocí ovládacího prvku formuláře a předán z aplikace Base do makra. Použití primárního klíče záznamu by mohlo způsobit vytištění jediného dokumentu.

V tomto příkladu je vybráno pole Gender v dotazu MailmergeQuery a poté jsou vyhledány záznamy, které mají v tomto poli hodnotu 'm'.

   oMailMerge.Filter = """Gender""='m'"

Dostupné typy výstupu jsou Tiskárna (1), Soubor (2) a E-mail (3). Zde je pro testovací účely vybrán výstupní soubor. Tento soubor je uložen v zadané cestě. Pro každý záznam hromadné korespondence bude proveden jeden tisk. Pro odlišení tohoto tisku je pole příjmení začleněno do názvu souboru.

   oMailMerge.OutputType = 2

   oMailMerge.OutputUrl = ConvertToUrl("home/user/Documents")

   oMailMerge.FileNameFromColumn = True

   oMailMerge.Filenameprefix = "Surname"

   oMailMerge.execute(aProps())

End Sub

Pokud jsou filtru poskytnuta data prostřednictvím formuláře, umožňuje to provádět hromadnou korespondenci bez otevření Writeru.

Tisk prostřednictvím textových polí

Pomocí Vložit > Pole > Další pole > Funkce > Zástupný znak lze ve Writeru vytvořit vzor dokumentu, který má být v budoucnu vytištěn. Zástupné znaky by měly mít stejné názvy jako pole v databázové tabulce nebo dotazu, na kterém je založen formulář, z něhož je makro voláno.

V jednoduchém případě je typem zástupného symbolu Text.

V makru musí být uvedena cesta k modelu.  Vytvoří se nový dokument Unknown1.odt. Makro vyplní zástupné symboly obsahem aktuálního záznamu z dotazu. Otevřený dokument pak můžeme podle potřeby upravovat.

Příklad databáze Example_database_mailmerge_direct.odb ukazuje, jak lze vytvořit kompletní fakturu pomocí textových polí a přístupu k připravené tabulce v rámci vzorového dokumentu. Na rozdíl od faktur vytvořených pomocí nástroje Návrhář sestav nemá tento typ vytváření faktur výškové omezení pro pole z tabulky. Zobrazí se veškerý text.

Zde je část kódu, kterou dodal především DPunch:
http://de.openoffice.info/viewtopic.php?f=8&t=45868#p194799

Sub Filling_Textfields

   oForm = thisComponent.Drawpage.Forms.MainForm

   If oForm.RowCount = 0 Then

      MsgBox "No available record for printing"

      Exit Sub

   End If

Hlavní formulář je aktivován. Tlačítko, kterým se makro spustí, by mohlo sloužit také k vyhledání formuláře. Makro pak zjistí, že formulář skutečně obsahuje tisknutelná data.

   oColumns = oForm.Columns

   oDB = ThisComponent.Parent

Přímý přístup na adresu URL z formuláře není možný. Musí být provedeno pomocí odkazu na databázi vyšší úrovně.

   stDir = Left(oDB.Location,Len(oDB.Location)-Len(oDB.Title))

Název databáze je oddělen od adresy URL.

   stDir = stDir & "Beispiel_Textfelder.ott"

Model je nalezen a otevřen.

   Dim args(0) As New com.sun.star.beans.PropertyValue

   args(0).Name = "AsTemplate"

   args(0).Value = True

   oNewDoc = StarDesktop.loadComponentFromURL(stDir,"_blank",0,args)

Textová pole jsou zapsána.

   oTextfields = oNewDoc.Textfields.createEnumeration

   Do While oTextfields.hasMoreElements

      oTextfield = oTextfields.nextElement  

      If oTextfield.supportsService("com.sun.star.text.TextField.JumpEdit") Then

         stColumnname = oTextfield.PlaceHolder   

Zástupný symbol představuje textové pole.

         If oColumns.hasByName(stColumnname) Then

Pokud je název textového pole stejný jako název sloupce v podkladové datové sadě, přenese se obsah databáze do pole v textovém dokumentu.

            inIndex = oForm.findColumn(stColumnname)

            oTextfield.Anchor.String = oForm.getString(inIndex)

         End If

      End If

   Loop

End Sub

Volání aplikací pro otevírání souborů

Tato procedura umožňuje jediným klepnutím do textového pole vyvolat program, který je v operačním systému spojen s příponou podle názvu souboru. Tímto způsobem lze sledovat internetové odkazy nebo spustit e-mailový program pro konkrétní adresu uloženou v databázi.

Pro tuto část viz také příklad databáze Example_Mail_File_activate.odb.

Sub Website_Mail_activate

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm As Object

   Dim oField As Object

   Dim oShell As Object

   Dim stField As String

   oDoc = thisComponent

   oDrawpage = oDoc.Drawpage

   oForm = oDrawpage.Forms.getByName("form")

   oField = oForm.getByName("url_mail")

Obsah pojmenovaného pole je načten. Může to být webová adresa začínající 'http://', e-mailová adresa začínající '@' nebo cesta k dokumentu (například externě uložený obrázek nebo soubor PDF).

   stFeld = oField.Text

   If stField = "" Then

      Exit Sub

   End If

Pokud je pole prázdné, makro se okamžitě ukončí. Při zadávání dat se často stává, že se k polím přistupuje pomocí myši, ale klepnutí na pole za účelem prvního zápisu do něj by nemělo vést ke spuštění kódu makra.

Nyní se v poli hledá znak '@'. To by znamenalo e-mailovou adresu. Na tuto adresu by měl být spuštěn e-mailový program pro odesílání pošty.

   If InStr(stField,"@") Then

      stField = "mailto:"+stField

Pokud není '@', je výraz převeden na adresu URL. Pokud začíná 'http://', nejedná se o soubor v místním souborovém systému, ale o internetový zdroj, který je třeba vyhledat pomocí webového prohlížeče. Jinak bude cesta začínat výrazem 'file:///'.

   Else

      stFeld = convertToUrl(stField)

   End If

Nyní se vyhledá program, který operační systém těmto souborům přiřadil. Pro klíčové slovo 'mailto:' je to poštovní program, pro 'http://' prohlížeč a jinak musí systém rozhodnout pomocí přípony jména souboru.

   oShell = createUnoService("com.sun.star.system.SystemShellExecute")

   oShell.execute(stField,,0)

End Sub

Volání poštovního programu s předdefinovaným obsahem

Předchozí příklad lze rozšířit na spuštění poštovního programu s předem definovaným předmětem a obsahem.

Pro tuto část viz také příklad databáze Example_Mail_File_activate.odb.

Poštovní program se spouští pomocí 'mailto:recipient?subject= &body= &cc= &bcc= '. Poslední dvě položky se ve formuláři nevyskytují. Přílohy nejsou v definici 'mailto' uvedeny, ale někdy funguje 'attachment='.

Sub Mai*l_activate

   Dim oDoc As Object

   Dim oDrawpage As Object

   Dim oForm As Object

   Dim oField1 As Object

   Dim oField2 As Object

   Dim oField3 As Object

   Dim oField4 As Object

   Dim oShell As Object

   Dim stField1 As String

   Dim stField2 As String

   Dim stField3 As String

   Dim stField4 As String

   oDoc = thisComponent

   oDrawpage = oDoc.Drawpage

   oForm = oDrawpage.Forms.getByName("form")

   oField1 = oForm.getByName("mail_to")

   oField2 = oForm.getByName("mail_subject")

   oField3 = oForm.getByName("mail_body")

   stField1 = oField1.Text

   If stField1 = "" Then

      MsgBox "Missing email address." & Chr(13) &
        
"Email program would not be activated" , 48, "Send Email"

      Exit Sub

   End If

Převod na adresu URL je nutný, aby se zabránilo rušení volání speciálními znaky a zalomením řádků. Tím se však k cestě připojí předpona 'file:///'. Těchto 8 znaků na začátku se nepřenáší.

   stField2 = Mid(ConvertToUrl(oFeld2.Text),9)

   stField3 = Mid(ConvertToUrl(oFeld3.Text),9)

Na rozdíl od prostého spuštění programu jsou zde podrobnosti o volání e-mailu uvedeny jako součást volání execute.

   oShell = createUnoService("com.sun.star.system.SystemShellExecute")

   oShell.execute("mailto:" + stField1 + "?subject=" + stField2 + "&body=" + stField3,,0)

End Sub

Poznámka

Odeslání e-mailu pomocí poštovního programu lze provést také pomocí následujícího kódu, ale vlastní obsah e-mailu tímto způsobem vložit nelze.

Dim attachs(0)
oMailer = createUnoService("com.sun.star.system.SimpleSystemMail")
oMailProgramm = oMailer.querySimpleMailClient()
oNewmessage = oMailProgramm.createSimpleMailMessage()
oNeemessage.setRecipient(stField1)
oNewmessage.setSubject(stField2)
attachs(0) = "file:///..."
oNeueNachricht.setAttachement(attachs())
oMailprogramm.sendSimpleMailMessage(oNeuenachricht, 0 )

Možné parametry nalezneme v podrobnostech rozhraní com::sun::star::system::XSimpleMailMessageReferenci API.

Změna ukazatele myši při přechodu přes odkaz

Ukazatel myši prochází odkazem a mění se na ukazující ruku, což je na internetu běžné a aplikace Base tuto funkcionalitu používá také. Text odkazu může také změnit své vlastnosti a může být modrý a podtržený. Podobnost s internetovým odkazem je dokonalá. Každý uživatel očekává, že klepnutím otevře externí program.

Pro tuto část viz příklad databáze Example_Mail_File_activate.odb.

Tato krátká procedura by měla být vázána na událost textového pole 'Mouse inside'.

Sub Mouse_pointer(Event As Object)

   REM Viz také Standardlibraries: Nástroje → ModuleControls → SwitchMousePointer

   Dim oPointer As Object

   oPointer = createUnoService("com.sun.star.awt.Pointer")

   oPointer.setType(27)   'Typy viz com.sun.star.awt.SystemPointer

   Event.Source.Peer.SetPointer(oPointer)

End Sub

Zobrazení formulářů bez panelu nástrojů

Nové uživatele Base často rozčiluje, že nástrojová lišta existuje, ale není možné ji ve formuláři použít. Tyto nástrojové lišty lze odstranit různými způsoby. Nejlepší způsoby ve všech verzích LibreOffice jsou dva a jsou popsané dále.

Velikost oken a nástrojových lišt se obvykle řídí makrem, které se spouští z dokumentu formuláře pomocí Nástroje > Přizpůsobit > Události > Otevřít dokument. Týká se to celého dokumentu, nikoli jednotlivých hlavních formulářů nebo podformulářů.

Formuláře bez panelu nástrojů v okně

Velikost okna lze měnit. Pomocí příslušného tlačítka ji lze také zavřít. Tyto úkoly provádí správce oken systému. Polohu a velikost okna na obrazovce lze zadat makrem při spuštění programu.

Sub Hide_toolbar

   Dim oFrame As Object

   Dim oWin As Object

   Dim oLayoutMng As Object

   Dim aElements()

   oFrame = StarDesktop.getCurrentFrame()

Název formuláře se zobrazí v záhlaví okna.

   oFrame.setTitle "My Form"

   oWin = oFrame.getContainerWindow()

Okno je maximalizováno. To není totéž jako celoobrazovkový režim, protože hlavní lišta je stále viditelná a okno má titulkový pruh, který lze použít ke změně jeho velikosti nebo k jeho zavření.

   oWin.IsMaximized = true

Je možné vytvořit okno s určitou velikostí a polohou. To se provádí pomocí 'oWin.setPosSize(0,0,600,400,15)'. Zde se okno zobrazí v levém horním rohu obrazovky o šířce 600 pixelů a výšce 400. Poslední číslo znamená, že jsou uvedeny všechny pixely. Nazývá se 'Flag' (Značka, pozn. překl.). 'Flag' se vypočítá ze součtu následujících hodnot: x=1, y=2, šířka=4, výška=8. Protože jsou dány hodnoty x, y, šířka a výška, má 'Flag' velikost 1+2+4+8=15.

   oLayoutMng = oFrame.LayoutManager

   aElements = oLayoutMng.getElements()

   For i = LBound(aElements) To UBound(aElements)

      If aElements(i).ResourceURL =

         "private:resource/toolbar/formsnavigationbar" Then

      Else

         oLayoutMng.hideElement(aElements(i).ResourceURL)

      End If      

   Next

End Sub

V případě lišty navigace formuláře není třeba dělat nic. Formulář musí zůstat použitelný i v případech, kdy není zabudován ovládací prvek lišty navigace (který by stejně způsobil skrytí lišty navigace). Skryté by měly být pouze jiné nástrojové lišty než lišta navigace. Z tohoto důvodu není v tomto případě podána žádná akce.

Pokud nástrojové lišty neobnovíme přímo po opuštění formuláře, budou stále skryté. Lze je samozřejmě obnovit pomocí Zobrazení > Nástrojové lišty. Bylo by však poněkud nepříjemné, kdyby chyběla standardní nástrojová lišta (Zobrazit > Nástrojové lišty > Standardní) nebo stavový řádek (Zobrazit > Stavový řádek).

Tato procedura obnoví ('showElement') nástrojové lišty ze skrytého stavu ('hideElement'). V komentářích jsou uvedeny lišty, jejichž absence by byla nejpravděpodobněji zaznamenána.

Sub Show_toolbar

   Dim oFrame As Object

   Dim oLayoutMng As Object

   Dim aElements()

   oFrame = StarDesktop.getCurrentFrame()

   oLayoutMng = oFrame.LayoutManager

   aElements = oLayoutMng.getElements()

   For i = LBound(aElements) To UBound(aElements)

      oLayoutMng.showElement(aElements(i).ResourceURL)

   Next

   ' důležité prvky, které mohou chybět:

   '   "private:resource/toolbar/standardbar"

   '   "private:resource/statusbar/statusbar"

End Sub

Makra jsou vázána na: Nástroje > Přizpůsobit > Události > Otevřít dokument > Hide_toolbarZavřít dokument > Show_toolbar.

Nástrojové lišty se často nevracejí. V nejhorších případech může být užitečné nenačítat ty prvky, které správce rozvržení již zná, ale nejprve vytvořit konkrétní nástrojové lišty a pak je zobrazit:

Sub Hide_toolbar

   Dim oFrame As Object

   Dim oLayoutMng As Object

   Dim i As Integer

   Dim aElements(5) As String

   oFrame = StarDesktop.getCurrentFrame()

   oLayoutMng = oFrame.LayoutManager

   aElements(0) = "private:resource/menubar/menubar"

   aElements(1) = "private:resource/statusbar/statusbar"

   aElements(2) = "private:resource/toolbar/formsnavigationbar"

   aElements(3) = "private:resource/toolbar/standardbar"

   aElements(4) = "private:resource/toolbar/formdesign"

   aElements(5) = "private:resource/toolbar/formcontrols"

   For Each i In aElemente()

      IF Not(oLayoutMng.requestElement(i)) Then

         oLayoutMng.createElement(i)

      End If

   oLayoutMng.showElement(i)

   Next i

End Sub

Nástrojové lišty, které mají být vytvořeny, jsou explicitně pojmenovány. Pokud správce rozvržení nemá k dispozici odpovídající nástrojovou lištu, je vytvořen pomocí createElement a poté zobrazen pomocí showElement.

Formuláře v režimu celé obrazovky

V celoobrazovkovém režimu je formulářem pokryta celá obrazovka. Není zde žádná hlavní lišta ani jiné prvky, které by mohly zobrazovat, zda jsou spuštěny jiné programy.

Function Fullscreen(boSwitch As Boolean)

   Dim oDispatcher As Object

   Dim Props(0) As New com.sun.star.beans.PropertyValue

   oDispatcher = createUnoService("com.sun.star.frame.DispatchHelper")

   Props(0).Name = "FullScreen"

   Props(0).Value = boSwitch

   oDispatcher.executeDispatch(ThisComponent.CurrentController.Frame,
     
".uno:FullScreen", "", 0, Props())

End Function

Tato funkce se spouští následujícím postupem. Při tomto postupu se současně provede i předchozí postup pro odstranění nástrojových lišt – jinak se nástrojová lišta zobrazí a lze pomocí ní vypnout celoobrazovkový režim. Jedná se rovněž o nástrojovou lištu, i když má pouze jeden symbol.

Sub Fullscreen_on

   Fullscreen(true)

   Hide_toolbar

End Sub

Režim celé obrazovky ukončíme stisknutím klávesy 'ESC'. Pokud má být místo toho pro tento příkaz použito konkrétní tlačítko, lze použít následující řádek:

Sub Fullscreen_off

   Fullscreen(false)

   Show_toolbar

End Sub

Spouštění formulářů přímo z otevření databáze

Pokud jsou nástrojové lišty pryč nebo má být formulář zobrazen v celoobrazovkovém režimu, musí se při otevření databázového souboru spustit přímo formulář. Jednoduchý příkaz k otevření formuláře nebude fungovat, protože v okamžiku otevření souboru připojení k databázi ještě neexistuje.

Následující makro se spouští z Nástroje > Přizpůsobit > Události > Otevřít dokument. Použijme možnost Uložit do > Databasefile.odb.

Sub Form_Directstart

   Dim oDatasource As Object

   oDatasource = ThisDatabaseDocument.CurrentController

   If Not (oDatasource.isConnected()) Then

      oDatasource.connect()

   End If

   ThisDatabaseDocument.FormDocuments.getByName("Formname").open

End Sub

Nejprve je třeba navázat spojení s databází. Kontrolér je součástí ThisDatabaseDocument, stejně jako formulář. Pak lze formulář spustit a načíst jeho data z databáze.

Přístup k databázi MySQL pomocí maker

Všechna dosud zobrazená makra byla součástí interní databáze HSQLDB. Při práci s externími databázemi je nutné provést několik změn a rozšíření.

Kód MySQL v makrech

Při přístupu k interní databázi musí být tabulky a pole uzavřeny v duplicitních dvojitých uvozovkách oproti SQL:

SELECT "Field" FROM "Table"

Protože tyto příkazy SQL musí být připraveny uvnitř maker, musí být dvojité uvozovky maskovány:

stSQL = "SELECT ""Field"" FROM ""Table"""

Dotazy MySQL používají jinou formu maskování:

SELECT `Field` FROM `Database`.`Table`

Uvnitř kódu makra se tato forma maskování zobrazuje jako:

stSql = "SELECT `Field` FROM `Database`.`Table`"

Dočasné tabulky jako samostatné mezisklady

V předchozí kapitole byla často používána jednořádková tabulka pro vyhledávání nebo filtrování tabulek. To nebude fungovat v systému s více uživateli, protože ostatní uživatelé by pak byli závislí na hodnotě filtru někoho jiného. Dočasné tabulky v systému MySQL jsou přístupné pouze uživateli aktivního připojení, takže k těmto tabulkám lze přistupovat za účelem vyhledávání a filtrování.

Takové tabulky samozřejmě nelze vytvořit předem. Musí být vytvořeny při otevření souboru aplikace Base. Proto by se na otevření souboru *.odb mělo vázat následující makro.

Sub CreateTempTable

   oDatasource = thisDatabaseDocument.CurrentController

   If Not (oDatasource.isConnected()) Then oDatasource.connect()

   oConnection = oDatasource.ActiveConnection()

   oSQL_Statement = oConnection.createStatement()

   stSql = "CREATE TEMPORARY TABLE IF NOT EXISTS `Searchtmp` (`ID` INT PRIMARY KEY,

      `Name` VARCHAR(50))"

   oSQL_Statement.executeUpdate(stSql)

End Sub

Při prvním otevření souboru *.odb neexistuje žádné připojení k externí databázi MySQL. Připojení musí být vytvořeno. Poté lze vytvořit dočasnou tabulku s potřebnými poli.

Dialogová okna

V aplikaci Base můžeme pro zadávání dat, jejich úpravu nebo údržbu databáze používat spíše dialogová okna než formuláře. Dialogová okna lze přímo přizpůsobit aktuálnímu prostředí aplikace, ale přirozeně nejsou předem definována tak pohodlně jako formuláře. Zde je krátký úvod zakončený poměrně složitým příkladem pro použití při údržbě databáze.

Spouštění a ukončování dialogů

Nejprve je třeba vytvořit dialogové okno v příslušném počítači. To se provádí pomocí Nástroje > Makra > Uspořádat dialogová okna > Název souboru databáze > Standard > Nový. Dialogové okno se zobrazí se souvislou šedou plochou a záhlavím s ikonou zavření. Toto prázdné dialogové okno lze nyní vyvolat a poté opět zavřít.

Po klepnutí na dialogové okno je možné v části Obecné vlastnosti nastavit velikost a polohu. Lze zadat také obsah nadpisu Dialogového okno Start.

Bild8

Obrázek 12: Dialog Spustit dialogy

Nástrojová lišta na spodním okraji okna obsahuje různé ovládací prvky formuláře. Z toho byla pro naše dialogové okno vybrána dvě tlačítka, která umožňují spouštět další dialogová okna. Úprava obsahu a vazba maker na události se provádí stejným způsobem jako u tlačítek ve formulářích.

Umístění deklarací proměnných pro dialogová okna vyžaduje zvláštní péči. Dialogové okno je deklarováno jako globální proměnná, aby k němu mohly přistupovat různé procedury. V tomto případě se dialogové okno nazývá oDialog0, protože budou existovat další dialogová okna s vyššími pořadovými čísly.

Dim oDialog0 As Object

Nejprve se načte knihovna pro dialogové okno. Pokud nebyl při vytváření dialogového okna zvolen jiný název, nachází se v adresáři Standard. Samotné dialogové okno je v této knihovně dostupné pod názvem Dialog0. Execute() spustí dialogové okno.

Sub Dialog0Start

   DialogLibraries.LoadLibrary("Standard")

   oDialog0 = createUnoDialog(DialogLibraries.Standard.Dialog0)

   oDialog0.Execute()

End Sub

Dialogové okno lze v zásadě zavřít pomocí tlačítka Zavřít na rámečku okna. Pokud však k tomu chceme použít jiné specifické tlačítko, je třeba v rámci procedury použít příkaz EndExecute().

Sub Dialog0Ende

   oDialog0.EndExecute()

End Sub

V tomto rámci lze spustit a opět zavřít libovolný počet dialogů.

Jednoduchý dialog pro zadávání nových záznamů

Bild9

Obrázek 13: Jednoduchý dialog pro zadávání nových záznamů

Toto dialogové okno je prvním krokem pro následující dialogové okno pro úpravu záznamů. Nejprve je objasněn základní přístup ke správě tabulek. Zde se jedná o uložení záznamů s novými primárními klíči nebo o úplně nové zadání záznamů. Nakolik může takové malé dialogové okno stačit pro zadávání do konkrétní databáze, závisí na požadavcích uživatele.

Dim oDialog1 As Object

přímo vytvoří globální proměnnou pro dialogové okno na nejvyšší úrovni modulu před všemi procedurami.

Dialogové okno se otevírá a zavírá stejným způsobem jako předchozí dialogové okno. Pouze se změní název z Dialog0 na Dialog1. Procedura pro zavření dialogového okna je vázána na tlačítko Exit.

Tlačítko New pomocí procedury DatafieldsClear vymaže v dialogovém okně všechny ovládací prvky z dřívějších záznamů.

Sub DatafieldsClear

   oDialog1.getControl("NumericField1").Text = ""

   oDialog1.getControl("TextField1").Text = ""

   oDialog1.getControl("TextField2").Text = ""

End Sub

Každý ovládací prvek vložený do dialogového okna je přístupný podle názvu. Uživatelské rozhraní zajistí, aby se názvy neduplikovaly, což se u ovládacích prvků ve formuláři neděje.

Metoda getControl se používá s názvem ovládacího prvku. Také číselná pole mají vlastnost Text, kterou zde lze použít. To je jediný způsob, jak lze číselné pole vyprázdnit. Prázdný text existuje, ale prázdné číslo neexistuje. Místo toho musí být v poli primárního klíče zapsána 0.

Tlačítko Uložit spustí proceduru Data1Save:

Sub Data1Save

   Dim oDatasource As Object

   Dim oConnection As Object

   Dim oSQL_Command As Object

   Dim loID As Long

   Dim stForename As String

   Dim stSurname As String

   loID = oDialog1.getControl("NumericField1").Value

   stForename = oDialog1.getControl("TextField1").Text

   stSurname = oDialog1.getControl("TextField2").Text

   If loID > 0 And stSurname <> "" Then

      oDatasource = thisDatabaseDocument.CurrentController

      If Not (oDatasource.isConnected()) Then

         oDatasource.connect()

      End If

      oConnection = oDatasource.ActiveConnection()

      oSQL_Command = oConnection.createStatement()

      stSql = "SELECT ""ID"" FROM ""name"" WHERE ""ID"" = '"+loID+"'"

      oResult = oSQL_Command.executeQuery(stSql)

      While oResult.next

         MsgBox ("The value for field 'ID' already exist",16,
           
"Duplicate Value")

         Exit Sub

      Wend

      stSql = "INSERT INTO ""name"" (""ID"", ""forename"", ""surname"")
        VALUES ('"
+loID+"','"+stForename+"','"+stSurname+"')"

      oSQL_Command.executeUpdate(stSql)

      DatafieldsClear

   End If

End Sub

Stejně jako v postupu DatafieldsClear se přistupuje ke vstupním polím. Tentokrát je přístup pouze pro čtení. Záznam bude předán pouze v případě, že v poli ID je hodnota větší než 0 a pole Surname obsahuje také text. Nulovou hodnotu ID lze vyloučit, protože číselná proměnná  pro celá čísla je vždy inicializována na 0. Prázdné pole je proto uloženo s nulovou hodnotou.

Pokud je obsah obou polí zadán, vytvoří se spojení s databází. Protože ovládací prvky nejsou ve formuláři, musí být spojení s databází vytvořeno pomocí thisDatabaseDocument.CurrentController.

Nejprve se provede dotaz na databázi, zda již neexistuje záznam s daným primárním klíčem. Pokud tento dotaz vyprodukuje výsledek, zobrazí se okno se zprávou obsahující symbol Stop (kód: 16) a zprávu „Duplicitní záznam“. Pak se procedura ukončí příkazem Exit SUB.

Pokud dotaz nenajde žádný záznam se stejným primárním klíčem, vloží se nový záznam do databáze pomocí příkazu insert. Poté se zavolá procedura DatafieldsClear, která vytvoří nový prázdný formulář.

Dialog pro úpravu záznamů v tabulce

Bild10

Obrázek 14: Dialog pro vytváření, úpravy a mazání záznamů

Toto dialogové okno zjevně nabízí více možností než předchozí. Zde lze zobrazit všechny záznamy a procházet jimi, vytvářet nové záznamy nebo je mazat. Kód je samozřejmě mnohem složitější.

Tlačítko Exit je vázáno na proceduru, upravenou pro Dialog2, který byl popsán v předchozím dialogovém okně pro zadávání nových záznamů. Zde jsou popsána zbývající tlačítka a jejich funkce.

Zadávání dat v dialogovém okně je omezeno tím, že pole ID musí mít minimální hodnotu 1. Toto omezení souvisí s manipulací s proměnnými v jazyce Basic: číselné proměnné jsou z definice inicializovány na hodnotu 0. Pokud jsou tedy načteny číselné hodnoty z prázdných polí a z polí obsahujících 0, Basic mezi nimi nezjistí žádný rozdíl. To znamená, že pokud by v poli ID byla použita nula, musela by být nejprve načtena jako text a později případně převedena na číslo.

Dialogové okno se načte za stejných podmínek jako dříve. Postup načítání je však závislý na nulové hodnotě proměnné předané procedurou DataLoad.

Sub DataLoad(loID As Long)

   Dim oDatasource As Object

   Dim oConnection As Object

   Dim oSQL_Command As Object

   Dim stForename As String

   Dim stSurname As String

   Dim loRow As Long

   Dim loRowMax As Long

   Dim inStart As Integer

   oDatasource = thisDatabaseDocument.CurrentController

   If Not (oDatasource.isConnected()) Then

      oDatasource.connect()

   End If

   oConnection = oDatasource.ActiveConnection()

   oSQL_Command = oConnection.createStatement()

   If loID < 1 Then

      stSql = "SELECT MIN(""ID"") FROM ""name"""

      oResult = oSQL_Command.executeQuery(stSql)

      While oResult.next

         loID = oResult.getInt(1)

      Wend

      inStart = 1

   End If

Proměnné jsou deklarovány. Připojení k databázi pro dialogové okno se vytvoří, jak je popsáno výše. Na začátku má loID hodnotu 0. Tento případ poskytuje nejnižší hodnotu primárního klíče povolenou v jazyce SQL. Příslušný záznam se později zobrazí v dialogovém okně. Zároveň je proměnná inStart nastavena na hodnotu 1, aby bylo možné dialogové okno spustit později. Pokud tabulka neobsahuje žádné záznamy, loID zůstane 0. V takovém případě nebude třeba vyhledávat počet a obsah odpovídajících záznamů.

Pouze v případě, že loID je větší než 0, bude dotaz testovat, které záznamy jsou v databázi k dispozici. Druhý dotaz pak spočítá všechny záznamy, které se mají zobrazit. Třetí dotaz udává pozici aktuálního záznamu spočítáním všech záznamů s aktuálním primárním klíčem nebo menším.

   If loID > 0 Then

      stSql = "SELECT * FROM ""name"" WHERE ""ID"" = '"+loID+"'"

      oResult = oSQL_Command.executeQuery(stSql)

      While oResult.next

         loID = oResult.getInt(1)

         stForename = oResult.getString(2)

         stSurname = oResult.getString(3)

      Wend

      stSql = "SELECT COUNT(""ID"") FROM ""name"""

      oResult = oSQL_Command.executeQuery(stSql)

      While oResult.next

         loRowMax = oResult.getInt(1)

      Wend

      stSql = "SELECT COUNT(""ID"") FROM ""name"" WHERE ""ID"" <= '"+loID+"'"

      oResult = oSQL_Command.executeQuery(stSql)

      While oResult.next

         loRow = oResult.getInt(1)

      Wend

      oDialog2.getControl("NumericField1").Value = loID

      oDialog2.getControl("TextField1").Text = stForename

      oDialog2.getControl("TextField2").Text = stSurname

   End If

   oDialog2.getControl("NumericField2").Value = loRow

   oDialog2.getControl("NumericField3").Value = loRowMax

   If loRow = 1 Then

      ' previous Row (předchozí řádek, pozn. překl.)

      oDialog2.getControl("CommandButton4").Model.enabled = False

   Else

      oDialog2.getControl("CommandButton4").Model.enabled = True

   End If

   If loRow <= loRowMax Then

      ' next Row | new Row | delete (další řádek | nový řádek | odstranit, pozn. překl.)

      oDialog2.getControl("CommandButton5").Model.enabled = True

      oDialog2.getControl("CommandButton2").Model.enabled = True

      oDialog2.getControl("CommandButton6").Model.enabled = True

   Else

      oDialog2.getControl("CommandButton5").Model.enabled = False

      oDialog2.getControl("CommandButton2").Model.enabled = False

      oDialog2.getControl("CommandButton6").Model.enabled = False

   End If

   IF inStart = 1 Then

      oDialog2.Execute()

   End If

End Sub

Získané hodnoty se přenesou do polí dialogového okna. Vždy se zapisují údaje o aktuálním čísle záznamu a celkovém počtu vyhledaných záznamů, které nahrazují výchozí číselnou hodnotu 0.

Navigační tlačítka (CommandButton5 a CommandButton4) jsou použitelná pouze tehdy, když je možné se dostat k příslušnému záznamu. V opačném případě jsou dočasně deaktivovány pomocí enabled = False. Totéž platí pro tlačítka New (Nový, pozn. překl.) a Delete (Odstranit, pozn. překl.). Neměly by být dostupné, pokud je číslo zobrazeného řádku vyšší než stanovený maximální počet řádků. Toto je výchozí nastavení tohoto dialogového okna při zadávání záznamů.

Dialogové okno by se mělo pokud možno spouštět pouze tehdy, když má být vytvořeno přímo ze startovacího souboru pomocí DataLoad(0). Proto má speciální proměnná inStart na začátku procedury hodnotu 1.

Tlačítko < slouží k přechodu na předchozí záznam. Proto je toto tlačítko aktivní pouze v případě, že zobrazený záznam není první v seznamu. Navigace vyžaduje, aby byl primární klíč aktuálního záznamu načten z pole NumericField1.

Zde existují dva možné případy:

  1. Přecházíme na nový záznam, takže příslušné pole nemá žádnou hodnotu. V tomto případě má loID výchozí hodnotu, která je podle definice celočíselné proměnné 0.

  2. Jinak bude loID obsahovat hodnotu větší než 0. Pak lze dotazem určit hodnotu ID přímo pod aktuální hodnotou.

Sub PreviousRow

   Dim loID As Long

   Dim loIDnew As Long

   loID = oDialog2.getControl("NumericField1").Value

   oDatasource = thisDatabaseDocument.CurrentController

   If Not (oDatasource.isConnected()) Then

      oDatasource.connect()

   End If

   oConnection = oDatasource.ActiveConnection()

   oSQL_Command = oConnection.createStatement()

   If loID < 1 Then

      stSql = "SELECT MAX(""ID"") FROM ""name"""

   Else

      stSql = "SELECT MAX(""ID"") FROM ""name"" WHERE ""ID"" < '"+loID+"'"

   End If

   oResult = oSQL_Command.executeQuery(stSql)

   While oResult.next

      loIDnew = oResult.getInt(1)

   Wend

   If loIDnew > 0 Then

      DataLoad(loIDnew)

   End If

End Sub

Pokud je pole ID prázdné, mělo by se zobrazení změnit na nejvyšší hodnotu čísla primárního klíče. Pokud se naopak pole ID vztahuje k záznamu, měla by být vrácena předchozí hodnota ID.

Výsledek tohoto dotazu se použije k opětovnému spuštění procedury DataLoad s odpovídající hodnotou klíče.

Tlačítko > slouží k přechodu na další záznam. Tato možnost by měla existovat pouze v případě, že dialogové okno nebylo pro zadání nového záznamu vyprázdněno. To se přirozeně projeví při spuštění dialogového okna a také při prázdné tabulce.

Hodnota v poli NumericField1 je povinná. Na základě této hodnoty může SQL určit, který primární klíč je v tabulce další nejvyšší. Pokud je množina výsledků dotazu prázdná, protože neexistuje žádný odpovídající záznam, hodnota loIDnew = 0. V opačném případě se obsah dalšího záznamu načte pomocí DataLoad.

Sub NextRow

   Dim loID As Long

   Dim loIDnew As Long

   loID = oDialog2.getControl("NumericField1").Value

   oDatasource = thisDatabaseDocument.CurrentController

   If Not (oDatasource.isConnected()) Then

      oDatasource.connect()

   End If

   oConnection = oDatasource.ActiveConnection()

   oSQL_Command = oConnection.createStatement()

   stSql = "SELECT MIN(""ID"") FROM ""name"" WHERE ""ID"" > '"+loID+"'"

   oResult = oSQL_Command.executeQuery(stSql)

   While oResult.next

      loIDnew = oResult.getInt(1)

   Wend

   If loIDnew > 0 Then

      DataLoad(loIDnew)

   Else

      Datafields2Clear

   End If

End Sub

Pokud při přechodu na další záznam není k dispozici žádný další záznam, navigační klávesa spustí následující proceduru Datafields2Clear, která slouží k přípravě na zadání nového záznamu.

Procedura Datafields2Clear nevyprázdní pouze samotná datová pole. Pozice aktuálního záznamu je nastavena na hodnotu o jedna vyšší, než je maximální číslo záznamu, čímž je zřejmé, že záznam, na kterém se právě pracuje, ještě není zařazen do databáze.

Jakmile je spuštěna funkce Datafields2Clear, je aktivována možnost skoku na předchozí záznam, Skoky na následující záznam a použití procedur New a Delete jsou deaktivovány.

Sub Datafields2Clear

   loRowMax = oDialog2.getControl("NumericField3").Value

   oDialog2.getControl("NumericField1").Text = ""

   oDialog2.getControl("TextField1").Text = ""

   oDialog2.getControl("TextField2").Text = ""

   oDialog2.getControl("NumericField2").Value = loRowMax + 1

   oDialog2.getControl("CommandButton4").Model.enabled = True   ' Previous record

   oDialog2.getControl("CommandButton5").Model.enabled = False   ' Next record   oDialog2.getControl("CommandButton2").Model.enabled = False   ' New record

   oDialog2.getControl("CommandButton6").Model.enabled = False   ' Delete

End Sub

Uložení záznamů by mělo být možné pouze tehdy, pokud pole ID a Surname obsahují záznamy. Pokud je tato podmínka splněna, postup testuje, zda se jedná o nový záznam. Využívá se přitom ukazatel záznamu, který je pro nové záznamy nastaven tak, aby byl o jedničku vyšší než maximální počet záznamů.

U nových záznamů se provádí kontrola toho, že operace uložení proběhne úspěšně. Pokud bylo číslo použité pro primární klíč použito již dříve, zobrazí se varování. Pokud je na zobrazenou otázku odpovězeno Ano, stávající záznam s tímto číslem se přepíše. V opačném případě bude ukládání přerušeno. Pokud v databázi nejsou žádné existující záznamy (loRowMax = 0), je tento test zbytečný a nový záznam lze uložit přímo. Pro nový záznam se počet záznamů zvýší o 1 a zadané hodnoty se vymažou pro další záznam.

Existující záznamy se jednoduše přepíšou příkazem update.

Sub Data2Save(oEvent As Object)

   Dim oDatasource As Object

   Dim oConnection As Object

   Dim oSQL_Command As Object

   Dim oDlg As Object

   Dim loID As Long

   Dim stForename As String

   Dim stSurname As String

   Dim inMsg As Integer

   Dim loRow As Long

   Dim loRowMax As Long

   Dim stSql As String

   oDlg = oEvent.Source.getContext()

   loID = oDlg.getControl("NumericField1").Value

   stForename = oDlg.getControl("TextField1").Text

   stSurname = oDlg.getControl("TextField2").Text

   If loID > 0 And stSurname <> "" Then

      oDatasource = thisDatabaseDocument.CurrentController

      If Not (oDatasource.isConnected()) Then

         oDatasource.connect()

      End If

      oConnection = oDatasource.ActiveConnection()

      oSQL_Command = oConnection.createStatement()

      loRow = oDlg.getControl("NumericField2").Value

      loRowMax = oDlg.getControl("NumericField3").Value

      If loRowMax < loRow Then

         If loRowMax > 0 Then

            stSql = "SELECT ""ID"" FROM ""name"" WHERE ""ID"" = '"+loID+"'"

            oResult = oSQL_Command.executeQuery(stSql)

            While oResult.next

               inMsg = MsgBox ("The value for field 'ID' already exist." &
                 CHR
(13) & "Should the row be updated?",20,
                 
"Duplicate Value")

               If inMsg = 6 Then

                  stSql = "UPDATE ""name"" SET ""forename""='"+stForename+"',
                    ""surname""='"
+stSurname+"' WHERE ""ID"" = '"+loID+"'"

                  oSQL_Command.executeUpdate(stSql)

                  DataLoad(loID)   ' Pomocí aktualizace byl přepsán řádek.. Rowcount                      musí být vynulován

               End If

               Exit Sub

            Wend

         End If

         stSql = "INSERT INTO ""name"" (""ID"", ""forename"", ""surname"") VALUES
           ('"
+loID+"','"+stForename+"','"+stSurname+"')"

         oSQL_Command.executeUpdate(stSql)

         oDlg.getControl("NumericField3").Value = loRowMax + 1   
           
' Po vložení se přidá jeden řádek

         Datafields2Clear
           
' After insert would be moved to next insert

      Else

         stSql = "UPDATE ""name"" SET ""forename""='"+stForename+"',
           ""surname""='"
+stSurname+"' WHERE ""ID"" = '"+loID+"'"

         oSQL_Command.executeUpdate(stSql)

      End If

   End If

End Sub

Postup mazání je opatřen doplňující otázkou, která má zabránit náhodnému smazání. Vzhledem k tomu, že toto tlačítko je deaktivováno, když jsou vstupní pole prázdná, nemělo by se prázdné pole NumericField1 nikdy objevit. Proto lze kontrolní podmínku IF loID > 0 vynechat.

Při mazání se počet záznamů sníží o 1. To je třeba opravit pomocí loRowMax – 1. Poté se zobrazí záznam následující po aktuálním.

Sub DataDelete(oEvent As Object)

   Dim oDatasource As Object

   Dim oConnection As Object

   Dim oSQL_Command As Object

   Dim oDlg As Object

   Dim loID As Long

   oDlg = oEvent.Source.getContext()

   loID = oDlg.getControl("NumericField1").Value

   If loID > 0 Then

      inMsg = MsgBox ("Should current data be deleted?",20,
        
"Delete current row")

      If inMsg = 6 Then

         oDatasource = thisDatabaseDocument.CurrentController

         If Not (oDatasource.isConnected()) Then

            oDatasource.connect()

         End If

         oConnection = oDatasource.ActiveConnection()

         oSQL_Command = oConnection.createStatement()   

         stSql = "DELETE FROM ""name"" WHERE ""ID"" = '"+loID+"'"

         oSQL_Command.executeUpdate(stSql)

         loRowMax = oDlg.getControl("NumericField3").Value

         oDlg.getControl("NumericField3").Value = loRowMax - 1   

         NextRow

      End If

   ELSE

      MsgBox ("No row deleted." & CHR(13) &
        
"No data selected.",64,"Delete impossible")

   End If

End Sub

Tento malý dialog ukázal, že použití makrokódu může poskytnout základ pro zpracování záznamů. Přístup prostřednictvím formulářů je mnohem jednodušší, ale dialogové okno se může velmi flexibilně přizpůsobit požadavkům programu. Není však vhodný pro rychlé vytvoření databázového rozhraní.

Použití dialogového okna k vyčištění chybných záznamů v tabulkách

Chyby v polích se často objeví až později. Často je nutné upravit shodné záznamy v několika záznamech současně. V běžném zobrazení tabulky je to nepohodlné, zejména když je třeba upravit několik záznamů, protože každý záznam vyžaduje samostatný zápis.

Formuláře mohou k tomuto účelu používat makra, ale abychom to mohli provést pro několik tabulek, potřebovali bychom stejně konstruované formuláře. Dialogová okna to zvládnou. Dialogové okno může být na začátku opatřeno potřebnými údaji pro příslušné tabulky a může být vyvoláno několika různými formuláři.

graphics3

Obrázek 15: Návrh dialogového okna pro úpravu tabulky pomocí nástrojové lišty Nástroje (vlevo)

Dialogová okna se ukládají spolu s moduly pro makra. Jejich tvorba je podobná tvorbě formuláře. K dispozici jsou velmi podobná kontrolní pole. Chybí pouze ovládací prvky tabulky pro formuláře jako zvláštní možnost zadání.

graphics4

Obrázek 16: Použití dialogového okna Úprava tabulky

Vzhled ovládacích prvků dialogového okna je určen nastavením grafického uživatelského rozhraní.

Výše uvedené dialogové okno slouží v ukázkové databázi k úpravě tabulek, které nejsou přímo použity jako základ formuláře. Tak například typ média je přístupný pouze prostřednictvím pole se seznamem (ve verzi s makry se z něj stane kombinované pole). Ve verzi s makry lze obsah pole rozšířit o nový obsah, ale změna stávajícího obsahu není možná. Ve verzi bez maker se změny provádějí pomocí samostatného ovládacího prvku tabulky.

Zatímco změny v tomto případě lze snadno provést bez maker, je poměrně obtížné změnit typ média u mnoha médií najednou. Předpokládejme, že jsou k dispozici následující typy: "Book, bound", "Book, hard-cover", "Paperback" a "Ringfile". Nyní, po dlouhé době používání databáze, se ukazuje, že aktivnější současníci předpokládali podobné další typy médií pro tištěná díla. Jejich rozlišování se stalo obtížným úkolem. Chceme je proto omezit, nejlépe na jediný termín. Bez maker by bylo nutné záznamy v tabulce médií vyhledat (pomocí filtru) a jednotlivě změnit. Pokud známe jazyk SQL, můžeme to udělat mnohem lépe pomocí příkazu SQL. Všechny záznamy v tabulce Media můžeme změnit jediným záznamem. Druhý příkaz SQL pak odstraní nyní přebytečné typy médií, které již nemají žádnou vazbu na tabulku Média. Přesně tato metoda se použije pomocí dialogového okna Nahradit za – pouze příkaz SQL se nejprve přizpůsobí tabulce Media Type pomocí makra, které může upravovat i jiné tabulky.

Často se do tabulky dostanou položky, které lze zpětně ve formuláři změnit, a proto již nejsou potřeba. Není na škodu takové osiřelé položky jednoduše odstranit, ale pomocí grafického uživatelského rozhraní se poměrně špatně hledají. I v tomto případě je užitečný vhodný příkaz SQL spojený s příkazem k odstranění. Tento příkaz pro dotčené tabulky je obsažen v dialogovém okně v části Odstranit všechny nadbytečné položky.

Pokud má být dialogové okno použito k provedení více změn, je to označeno zaškrtávacím políčkem Upravit více záznamů. Pak se dialogové okno po klepnutí na tlačítko OK jednoduše neukončí.

Celý kód makra pro toto dialogové okno si můžeme prohlédnout v databázi příkladů. Níže jsou vysvětleny pouze úryvky.

Sub Table_purge(oEvent As Object)

Makro by mělo být spuštěno zadáním do sekce Další informace pro příslušná tlačítka:

0: Formulář, 1: Podformulář, 2: Podformulář, 3: Rozevírací seznam nebo ovládací prvek tabulky, 4: Pole cizího klíče ve formuláři, prázdné pro ovládací prvek tabulky, 5: Název pomocné tabulky, 6: Pole Field1 pomocné tabulky, 7: Pole Field2 pomocné tabulky nebo 8: Název pomocné tabulky pro pole Field2 tabulky.

Položky v této oblasti jsou uvedeny na začátku makra jako komentáře. Přenesou se čísla, která jsou s nimi svázána, a příslušný záznam se načte z pole. Makro může upravovat seznamy, které mají dvě položky oddělené znakem ">". Tyto dva záznamy mohou také pocházet z různých tabulek a mohou být spojeny pomocí dotazu, jako například v tabulce Postcode, která má pro město pouze pole cizího klíče Town_ID, což vyžaduje tabulku Town pro zobrazení názvů měst.

   Dim aForeignTable(0, 0 to 1)

   Dim aForeignTable2(0, 0 to 1)

Mezi proměnnými definovanými na začátku jsou dvě pole. Zatímco normální pole lze vytvořit příkazem Split() během provádění podprogramu, dvourozměrná pole je třeba definovat předem. Dvourozměrná pole jsou nutná pro uložení několika záznamů z jednoho dotazu, pokud se dotaz sám vztahuje k více než jednomu poli. Obě výše deklarovaná pole musí být schopna interpretovat dotazy, které se vztahují ke dvěma polím tabulky. Proto jsou definována pro dva různé obsahy s použitím hodnot 0 až 1 pro druhý rozměr.

   stTag = oEvent.Source.Model.Tag

   aTable() = Split(stTag, ", ")

   For i = LBound(aTable()) To UBound(aTable())

      aTable(i) = trim(aTable(i))

   Next

Poskytnuté proměnné se čtou. Pořadí je takové, jaké je uvedeno v komentáři výše. Existuje maximálně devět záznamů a je třeba deklarovat, zda existuje osmý záznam pro tabulku field2 a devátý záznam pro druhou tabulku.

Pokud mají být hodnoty z tabulky odstraněny, je třeba nejprve zkontrolovat, zda neexistují jako cizí klíče v jiné tabulce. V jednoduchých strukturách tabulek bude mít daná tabulka pouze jedno spojení cizího klíče s jinou tabulkou. V uvedené příkladové databázi však existuje tabulka Town, která se používá jak pro místo vydání médií, tak pro město a pro adresy. Primární klíč tabulky Town je tedy zadán dvakrát do různých tabulek. Tyto tabulky a názvy cizích klíčů lze samozřejmě zadat také pomocí pole Další informace. Bylo by však lepší, kdyby mohly být poskytovány univerzálně pro všechny případy. To lze provést pomocí následujícího dotazu.

   stSql = "SELECT ""FKTABLE_NAME"", ""FKCOLUMN_NAME"" FROM ""INFORMATION_SCHEMA"".""SYSTEM_CROSSREFERENCE"" WHERE ""PKTABLE_NAME"" = '" + aTable(5) + "'"

V databázi obsahuje oblast INFORMATION_SCHEMA všechny informace o tabulkách databáze, včetně informací o cizích klíčích. K tabulkám, které obsahují tyto informace, lze přistupovat pomocí "INFORMATION_SCHEMA". "SYSTEM_CROSSREFERENCE". KTABLE_NAME" uvádí tabulku, která poskytuje primární klíč pro připojení. FKTABLE_NAME udává tabulku, která používá tento primární klíč jako cizí klíč. Konečně FKCOLUMN_NAME udává název pole cizího klíče.

Tabulka, která poskytuje svůj primární klíč pro použití jako cizí klíč, je v dříve vytvořeném poli na pozici 6. A počet začíná 0, hodnota se načte z pole pomocí aTable(5).

   inCount = 0   

   stForeignIDTab1Tab2 = "ID"

   stForeignIDTab2Tab1 = "ID"

   stAuxiltable = aTable(5)

Před zahájením čtení polí je třeba nastavit některé výchozí hodnoty. Jedná se o index pole, do kterého se budou zapisovat hodnoty z pomocné tabulky, výchozí primární klíč, pokud nepotřebujeme cizí klíč pro druhou tabulku, a výchozí pomocnou tabulku, propojenou s hlavní tabulkou, pro PSČ a obec, tabulku Postcode.

Pokud jsou dvě pole propojena pro zobrazení v seznamu, mohou, jak je popsáno výše, pocházet ze dvou různých tabulek. Pro zobrazení PSČ a města je dotaz následující:

SELECT "Postcode"."Postcode" || ' > ' || "Town"."Town" FROM "Postcode", "Town" WHERE "Postcode"."Town_ID" = "Town"."ID"

Tabulka pro první pole (PSČ) je propojena s druhou tabulkou cizím klíčem. Makru se předávají pouze informace z těchto dvou tabulek a polí Postcode a Town. Všechny primární klíče se v ukázkové databázi ve výchozím nastavení nazývají ID. Cizí klíč Town v položce Postcode je proto třeba určit pomocí makra.

Stejným způsobem musí makro přistupovat ke každé tabulce, s níž je obsah pole seznamu spojen cizím klíčem.

   oQuery_result = oSQL_Statement.executeQuery(stSql)

   If Not IsNull(oQuery_result) Then

      While oQuery_result.next

         ReDim Preserve aForeignTable(inCount,0 to 1)

Pole musí být pokaždé nově dimenzováno. Aby se zachoval stávající obsah, zálohuje se pomocí (Preserve).

         aForeignTables(inCount,0) = oQuery_result.getString(1)

Načtení prvního pole s názvem tabulky, která obsahuje cizí klíč. Výsledkem pro tabulku Postcode je tabulka Address.

         aForeignTables(inCount,1) = oQuery_result.getString(2)

Načtení druhého pole s názvem pole cizího klíče. Výsledkem pro tabulku Postcode je pole Postcode_ID v tabulce Address.

Pokud volání podprogramu obsahuje název druhé tabulky, spustí se následující smyčka. Pouze pokud se název druhé tabulky vyskytuje jako tabulka cizího klíče pro první tabulku, je změněn výchozí záznam. V našem případě k tomu nedojde, protože tabulka Town nemá cizí klíč z tabulky Postcode. Výchozí položkou pomocné tabulky proto zůstává Postcode; kombinace PSČ a obce je nakonec základem tabulky Address, která obsahuje cizí klíč z tabulky Postcode.

         If UBound(aTable()) = 8 Then

            If aTable(8) = aForeignTable(inCount,0) Then

               stForeignIDTab2Tab1 = aForeignTable(inCount,1)

               stAuxiltable = aTable(8)

            End If

         End If

         inCount = inCount + 1

Protože může být nutné načíst další hodnoty, index se zvýší, aby se změnila velikost polí. Pak smyčka skončí.

      Wend

   End If

Pokud při volání podprogramu existuje druhý název tabulky, spustí se stejný dotaz pro tuto tabulku:

   If UBound(aTable()) = 8 Then

Probíhá identicky s tím rozdílem, že smyčka testuje, zda se snad první název tabulky nevyskytuje jako název tabulky cizího klíče. To je tento případ: tabulka Postcode obsahuje cizí klíč Town_ID z tabulky Town. Tento cizí klíč je nyní přiřazen proměnné stForeignIDTab1Tab2, aby bylo možné definovat vztah mezi tabulkami.

            If aTable(5) = aForeignTable2(inCount,0) Then

               stForeignIDTab1Tab2 = aForeignTable2(inCount,1)

            End If

Po několika dalších nastaveních, která zajistí návrat na správný formulář po spuštění dialogu (určení čísla řádku formuláře, abychom se po novém načtení mohli vrátit zpět na toto číslo řádku), se spustí smyčka, která znovu vytvoří dialog po dokončení první akce, ale dialog musí zůstat otevřený pro další akce. Nastavení opakování se provádí pomocí příslušného zaškrtávacího políčka.

   Do

Před spuštěním dialogu se nejprve určí obsah polí se seznamem. Je třeba dbát na to, aby seznamová pole představovala dvě pole tabulky a možná se dokonce vztahovala ke dvěma různým tabulkám.

      If UBound(aTable()) = 6 Then

Pole se seznamem se vztahuje pouze k jedné tabulce a jednomu poli, protože pole argumentů končí u pole Tablefield1 pomocné tabulky.

         stSql = "SELECT """ + aTable(6) + """ FROM """ + aTable(5) + """ ORDER BY """ + aTable(6) + """"

      ElseIf UBound(aTable()) = 7 Then

Pole se seznamem se vztahuje ke dvěma polím tabulky, ale pouze k jedné tabulce, protože pole argumentů končí na poli Tablefield2 pomocné tabulky.

         stSql = "SELECT """ + aTable(6) + """||' > '||""" + aTable(7) + """ FROM """ + aTable(5) + """ ORDER BY """ + aTable(6) + """"

      Else

Pole se seznamem je založeno na dvou polích ze dvou tabulek. Tento dotaz odpovídá příkladu s poštovním směrovacím číslem a městem.

         stSql = "SELECT """ + aTable(5) + """.""" + aTable(6) + """||' > '||""" + aTable(8) + """.""" + aTable(7) + """ FROM """ + aTable(5) + """, """ + aTable(8) + """ WHERE """ + aTable(8) + """.""" + stForeignIDTab2Tab1 + """ = """ + aTable(5) + """.""" + stForeignIDTab1Tab2 + """ ORDER BY """ + aTable(6) + """"

      End If

Zde máme první vyhodnocení pro určení cizích klíčů. Proměnné stForeignIDTab2Tab1 a stForeignIDTab1Tab2 začínají hodnotou ID. Pro stForeignIDTab1Tab2 je výsledkem vyhodnocení předchozího dotazu jiná hodnota, a to hodnota Town_ID. Tímto způsobem předchozí konstrukce dotazu poskytuje přesně ten obsah, který byl již formulován pro PSČ a obec – pouze rozšířený o řazení.

Nyní je třeba navázat kontakt s poli se seznamem, abychom jim dodali obsah vrácený dotazy. Tato pole se seznamem ještě neexistují, protože dialogové okno ještě nebylo vytvořeno. Toto dialogové okno se nejprve vytvoří v paměti pomocí následujících řádků a teprve poté se vykreslí na obrazovku.

      DialogLibraries.LoadLibrary("Standard")

      oDlg = CreateUnoDialog(DialogLibraries.Standard.Dialog_Table_purge)

Dále následuje nastavení polí dialogového okna. Zde je například pole se seznamem, do kterého se vloží výsledky výše uvedeného dotazu:

      oCtlList1 = oDlg.GetControl("ListBox1")

      oCtlList1.addItems(aContent(),0)

Přístup k polím dialogového okna se provádí pomocí GetControl s příslušným názvem. V dialogových oknech není možné, aby dvě pole měla stejný název, protože by to způsobilo problémy při vyhodnocování dialogu.

Do rozevíracího seznamu je vložen obsah dotazu, který je uložen v poli aContent(). Pole se seznamem obsahuje pouze obsah, který se má zobrazit jako pole, takže je vyplněna pouze pozice 0.

Po vyplnění všech polí s požadovaným obsahem se spustí dialogové okno.

      Select Case oDlg.Execute()

      Case 1    'Case 1 znamená, že bylo klepnuto na tlačítko "OK"

      Case 0 'Pokud bylo použito tlačítko "Zrušit"

         inRepetition = 0

      End Select

   Loop While inRepetition = 1

Dialogové okno se spouští opakovaně, dokud je hodnota "inRepetition" 1. Nastavuje se pomocí příslušného zaškrtávacího políčka.

Zde je ve zkratce uveden obsah po klepnutí na tlačítko "OK":

   Case 1

      stInhalt1 = oCtlList1.getSelectedItem() 'Přečteme hodnotu prvního pole se seznamem Listbox1 ...

      REM ... a určíme odpovídající hodnotu ID.

Hodnota ID prvního pole se seznamem je uložena v proměnné "inLB1".

      stText = oCtlText.Text   ' Read the field value.

Pokud textové pole není prázdné, je zadání v textovém poli zpracováno. Pole se seznamem pro náhradní hodnotu ani zaškrtávací pole pro odstranění všech osiřelých záznamů se nezohledňují. To je zřejmé z toho, že zadávání textu nastavuje tato ostatní pole jako neaktivní.

      If stText <> "" Then

Pokud textové pole není prázdné, zapíše se nová hodnota na místo staré hodnoty pomocí dříve načteného pole ID v tabulce. Je zde možnost dvou záznamů, stejně jako v případě pole se seznamem. Oddělovač je >. Pro dva záznamy v různých tabulkách je třeba spustit dva příkazy UPDATE, které se zde vytvoří současně a předají se oddělené středníkem.

      ElseIf oCtlList2.getSelectedItem() <> "" Then

Pokud je textové pole prázdné a pole listbox 2 obsahuje hodnotu, musí být hodnota z pole listbox 1 nahrazena hodnotou v poli listbox 2. To znamená, že všechny záznamy v tabulkách, pro které jsou záznamy v polích seznamu cizími klíči, musí být zkontrolovány a v případě potřeby zapsány se změněným cizím klíčem.

      stInhalt2 = oCtlList2.getSelectedItem()   

      REM Čtení hodnoty z pole se seznamem.

      REM Určení ID pro hodnotu pole se seznamu.

Hodnota ID druhého pole se seznamem je uložena v proměnné inLB2. I zde se situace vyvíjí odlišně v závislosti na tom, zda je v poli se seznamem obsaženo jedno nebo dvě pole, a také na tom, zda je základem obsahu pole se seznamem jedna nebo dvě tabulky.

Proces nahrazení závisí na tom, která tabulka je definována jako tabulka poskytující cizí klíč pro hlavní tabulku. Ve výše uvedeném příkladu se jedná o tabulku Postcode, protože Postcode_ID je cizí klíč, který je předáván přes Listbox 1 a Listbox 2.

   If stAuxilTable = aTable(5) Then

      For i = LBound(aForeignTables()) To UBound(aForeignTables())

Nahrazení staré hodnoty ID novou hodnotou ID se stává problematickým v relacích n:m, protože v takových případech může být stejná hodnota přiřazena dvakrát. To možná chceme, ale je třeba tomu zabránit, pokud je cizí klíč součástí primárního klíče. V tabulce rel_Media_Author tedy nemůže mít médium dvakrát stejného autora, protože primární klíč je vytvořen z Media_ID a Author_ID. V dotazu jsou prohledána všechna klíčová pole, která mají společně vlastnost UNIQUE nebo byla definována jako cizí klíče s vlastností UNIQUE pomocí indexu.

Pokud má tedy cizí klíč vlastnost UNIQUE a je v něm již zastoupen požadovaný budoucí klíč inLB2, nelze jej nahradit.

stSql = "SELECT ""COLUMN_NAME"" FROM ""INFORMATION_SCHEMA"".""SYSTEM_INDEXINFO"" WHERE ""TABLE_NAME"" = '" + aForeignTables(i,0) + "' AND ""NON_UNIQUE"" = False AND ""INDEX_NAME"" = (SELECT ""INDEX_NAME"" FROM ""INFORMATION_SCHEMA"".""SYSTEM_INDEXINFO"" WHERE ""TABLE_NAME"" = '" + aForeignTables(i,0) + "' AND ""COLUMN_NAME"" = '" + aForeignTables(i,1) + "')"

' "NON_UNIQUE" = False ' uvádí názvy sloupců, které jsou UNIQUE. Nejsou však potřeba všechny názvy sloupců, ale pouze ty, které tvoří index s polem cizího klíče. To se řeší pomocí podvýběru se stejnými názvy tabulek (které obsahují cizí klíč) a názvy polí cizího klíče.

Pokud je nyní cizí klíč přítomen v sadě, lze hodnotu klíče nahradit pouze tehdy, pokud jsou ostatní pole použita k definování příslušného indexu jako UNIQUE. Při výměně je třeba dbát na to, aby nebyla narušena jedinečnost kombinace indexů.

   If aForeignTables(i,1) = stFieldname Then

      inUnique = 1

   Else

      ReDim Preserve aColumns(inCount)   

      aColumns(inCount) = oQuery_result.getString(1)   

      inCount = inCount + 1

   End If

Všechny názvy sloupců, kromě známých názvů sloupců pro pole cizího klíče jako Index s vlastností UNIQUE, jsou uloženy v poli. Protože název sloupce pole cizího klíče patří také do skupiny, lze jej použít k určení, zda se má při úpravě dat kontrolovat jedinečnost.

If inUnique = 1 Then

   stSql = "UPDATE """ + aForeignTables(i,0) + """ AS ""a"" SET """ + aForeignTables(i,1) + """='" + inLB2 + "' WHERE """ + aForeignTables(i,1) + """='" + inLB1 + "' AND ( SELECT COUNT(*) FROM """ + aForeignTables(i,0) + """ WHERE """ + aForeignTables(i,1) + """='" + inLB2 + "' )"

   If inCount > 0 Then

      stFieldgroup = Join(aColumns(), """||  ||""")

Pokud kromě pole cizího klíče existuje několik polí, která dohromady tvoří UNIQUE index, jsou zde spojena pro seskupení SQL. Jinak se jako stFieldgroup zobrazí pouze aColumns(0).

      stFieldname = ""

      For ink = LBound(aColumns()) To UBound(aColumns())

         stFieldname = stFieldname + " AND """ + aColumns(ink) + """ = ""a"".""" + aColumns(ink) + """ "

Části jazyka SQL jsou kombinovány pro korelovaný poddotaz.

      Next ink

   stSql = Left(stSql, Len(stSql) 1)

Předchozí dotaz končí závorkou. Nyní má být do poddotazu přidán další obsah, proto je třeba tento uzávěr opět odstranit. Poté je dotaz rozšířen o další podmínky.

   stSql = stSql + stFeldbezeichnung + "GROUP BY (""" + stFeldgruppe + """) ) < 1"   

End If

Pokud cizí klíč není spojen s primárním klíčem nebo s indexem UNIQUE, nezáleží na tom, zda je obsah duplicitní.

Else

   stSql = "UPDATE """ + aForeignTables(i,0) + """ SET """ + aForeignTables(i,1) + """='" + inLB2 + "' WHERE """ + aForeignTables(i,1) + """='" + inLB1 + "'"

End If

oSQL_Statement.executeQuery(stSql)

NEXT

Aktualizace se provádí tak dlouho, dokud se vyskytují různá spojení s jinými tabulkami, tj. dokud je aktuální tabulka zdrojem cizího klíče v jiné tabulce. V případě tabulky Town je to dvakrát: v tabulce Media a v tabulce Postcode.

Poté lze starou hodnotu z pole listbox 1 vymazat, protože již nemá žádnou vazbu na ostatní tabulky.

stSql = "DELETE FROM """ + aTable(5) + """ WHERE ""ID""='" + inLB1 + "'"

oSQL_Statement.executeQuery(stSql)

V některých případech je nyní třeba stejnou metodu provést pro druhou tabulku, která poskytla data pro pole se seznamem. V našem příkladu je první tabulkou tabulka Postcode a druhou tabulka Town.

Pokud je textové pole prázdné a pole listbox 2 také nic neobsahuje, zkontrolujeme, zda je v příslušném zaškrtávacím políčku uvedeno, že mají být odstraněny všechny přebytečné položky. To znamená záznamy, které nejsou vázány na jiné tabulky cizím klíčem.

ElseIf oCtlCheck1.State = 1 Then

   stCondition = ""

   If stAuxilTable = aTable(5) Then

      For i = LBound(aForeignTables()) To UBound(aForeignTables())

         stCondition = stCondition + """ID"" NOT IN (SELECT """ + aForeignTables(i,1) + """ FROM """ + aForeignTables(i,0) + """) AND "

      Next

   Else

      For i = LBound(aForeignTables2()) To UBound(aForeignTables2())

         stCondition = stCondition + """ID"" NOT IN (SELECT """ + aForeignTables2(i,1) + """ FROM """ + aForeignTables2(i,0) + """) AND "

      Next

   End If

Poslední AND musí být odstraněn, protože jinak by instrukce delete končila AND.

   stCondition = Left(stCondition, Len(stCondition) - 4)   '

   stSql = "DELETE FROM """ + stAuxilTable + """ WHERE " + stCondition + ""

   oSQL_Statement.executeQuery(stSql)

Vzhledem k tomu, že tabulka již byla jednou vyčištěna, lze index tabulky zkontrolovat a případně opravit směrem dolů. Viz podprogram popsaný v jedné z předchozích částí.

Table_index_down(stAuxilTable)

Poté lze v případě potřeby aktualizovat pole seznamu ve formuláři, z něhož bylo dialogové okno Table_purge vyvoláno. V některých případech je třeba celý formulář znovu načíst. Za tímto účelem se na začátku podprogramu určí aktuální záznam, aby bylo možné po obnovení formuláře aktuální záznam obnovit.

   oDlg.endExecute()   'End dialog ...

   oDlg.Dispose()   '... and remove from storage

End Sub

Dialogová okna jsou ukončena příkazem endExecute() a zcela odstraněna z paměti příkazem Dispose().

Psaní maker pomocí Access2Base

Verze LibreOffice od verze 4.2 mají integrovanou Access2Base. Tato knihovna zavádí vrstvu Basicu se svým specifickým API mezi kód uživatele a obvyklé rozhraní UNO. Poskytované API samo o sobě nepřináší nové funkce, ale v mnoha případech je čitelnější, stručnější a snadněji použitelné než UNO.

Rozhraní API vypadá velmi podobně jako rozhraní navržené společností Microsoft pro software Access. Databáze Base a Access mají mnoho společného, ale rozhodně ne jejich nativní programovací styly. Access2Base tuto mezeru vyplňuje.

Anglickou dokumentaci s příklady nalezneme na adrese www.access2base.com.

Stručně ilustrujeme, jak Access2Base skrývá složitost UNO:

Access2Base zpracovává dvě hlavní kategorie objektů, které se zaměřují buď na:

API Access2Base se tradičně vyvolává z jazyka Basic. Od verze LibreOffice 6.4 umožňuje brána přístup k rozhraní API také ze skriptů Python, a to bez omezení oproti Basicu. V jedné aplikaci lze bezproblémově integrovat skripty Basicu a Pythonu, a to i sdílením stejných instancí objektů.

V následujících odstavcích budou všechny příklady uvedeny jak v jazyce Basic, tak v jazyce Python. Jsou zcela rovnocenné.

Chceme-li ke knihovně přistupovat z aplikace Base, připojíme následující postup k události OpenDocument souboru Base:

(BASIC)

Sub DBOpen(Optional oEvent As Object)

   If GlobalScope.BasicLibraries.hasByName("Access2Base") then

      GlobalScope.BasicLibraries.loadLibrary("Access2Base")

   End If

   Call Application.OpenConnection(ThisDatabaseDocument)

End Sub

(PYTHON)

from access2base import *

def DBOpen(event = None):

        Application.OpenConnection()

g_exportedScripts = (DBOpen, )

Chceme-li získat přístup k databázi z jiné aplikace než Base, můžeme také spustit:

(BASIC)

Function DBOpen() As Object

   If GlobalScope.BasicLibraries.hasByName("Access2Base") then

      GlobalScope.BasicLibraries.loadLibrary("Access2Base")

   End If

   Set myDb = Application.OpenDatabase(" … database file name … ")

End Function

(PYTHON)

from access2base import *

def DBOpen():

   return Application.OpenDatabase( database file name )

Cílem této knihy není kopírovat dokumentaci výše uvedených webových stránek. V tomto dokumentu se omezíme na shrnutí hlavních konceptů rozhraní API.

Objektový model

Na obrázku 17, počínaje kořenovým objektem Aplikace, je schéma popisující navigaci po nejpoužívanějších objektech.

Image2

Obrázek 17: Objektový model Access2Base

Příklad pro usnadnění čtení schématu:

Několik příkladů

Tisk seznamu názvů tabulek a polí

(BASIC)

Sub ScanTables()

Dim oDatabase As Object, oTable As Object, oField As Object

Dim i As Integer, j As Integer

   Set oDatabase = Application.CurrentDb()

   With odatabase

      For i = 0 To .TableDefs.Count - 1

         Set oTable = .TableDefs(i)   '   Získání jednotlivých definic tabulek

         DebugPrint oTable.Name

         For j = 0 To oTable.Fields.Count - 1

            Set oField = oTable.Fields(j)   '   Získáme jednotlivá pole

            DebugPrint "", oField.Name, oField.TypeName

         Next j

      Next i

   End With

End Sub

(PYTHON)

def ScanTables():

   oDatabase = Application.OpenDatabase("/home/somedir/TT NorthWind.odb")

   for oTable in oDatabase.TableDefs():

      DebugPrint(oTable.Name)

      for oField in oTable.Fields():

         DebugPrint(“”, oField.Name, oField.TypeName)

Uložení dat vytvořených dotazem do pole jazyka Basic nebo do n-tice jazyka Python.

(BASIC)

Sub LoadQuery()

Dim oRecords As Object, vData As Variant

   Set oRecords = Application.CurrentDb().OpenRecordset("myQuery")

   vData = oRecords.GetRows(1000)

   orecords.mClose()

End Sub

(PYTHON)

def LoadQuery():

   oRecords= Application.CurrentDb().Openrecordset(“myQuery”)

   vData = orecords.GetRow(1000)

   oRecords.Close()

Nastavení výchozích hodnot v položkách formuláře

Chceme-li určit, že po každém záznamu bude nějaký ovládací prvek předvyplněn poslední nastavenou hodnotou, přiřadíme další rutinu události Po změně záznamu formuláře:

(BASIC)

Sub SetDefaultNewRec(poEvent As Object)

Dim oForm As Object, oControl As Object

   Set oForm = Application.Events(poEvent).Source   '   Get the current form

   Set oControl = oForm.Controls("txtCountry")

   oControl.DefaultValue = oControl.Value

End Sub

(PYTHON)

def SetDefaultNewRec(poEvent):

   oForm = Application.Events(poEvent).Source

   oControl = oForm.Controls(“txtCountry”)

   oControl.DefaultValue = oControl.Value

Databázové funkce

Pro zkrácení přístupu k hodnotám databáze na jeden řádek je k dispozici kolekce funkcí: DLookup, DMax, DMin, Dsum. Všechny přijímají stejné argumenty: název pole nebo výraz založený na názvech polí, název tabulky nebo dotazu a klauzuli SQL-where bez klíčového slova WHERE. Například:

(BASIC)

Function Lookup(psField As String, psSearchField As String, psSearchValue As String) As Variant

   Lookup = Application.DLookup(psField, "myTable", _

               psSearchField & "='" & psSearchValue & "'")

End Function

(PYTHON)

def Lookup(psField, psSearchField, psSearchValue):

   return Application.Dlookup(psField, “myTable”

      , psSearchField + =’” + psSearchValue + “’”)

Speciální příkazy

Třída DoCmd (2. kořenová třída) nabízí sadu praktických funkcí, které umožňují provádět v jednom příkazu jazyka Basic složité, i když časté a praktické akce. Jmenujme alespoň některé:

Tabulka 29: Třída Access2Base DoCmd, speciální členské funkce

Funkce

Popis

CopyObject

Kopírování tabulky nebo dotazu v rámci stejné databáze nebo mezi dvěma databázemi.

OpenSQL

Provedení zadaného SQL příkazu SELECT a zobrazení výsledku v datovém listu.

OutputTo

Uložení dat z tabulky nebo dotazu do souboru HTML.

Uložení skutečného obsahu formuláře do souboru PDF.

SelectObject

Aktivace daného okna (formuláře, sestavy, ...)

SendObject

Pošleme e-mailem s daným formulářem v příloze.

Objekt „Basic“ v jazyce Python

Z jazyka Python byl do brány Access2Base zaveden další objekt, nazvaný jednoduše „Basic“, který umožňuje spouštět řadu známých vestavěných funkcí jazyka Basic. Jsou vyvolány přesně se stejnými argumenty a chovají se v obou prostředích striktně stejně. Patří mezi ně:

Knihovna ScriptForge

Programátoři maker často potřebují provádět úlohy, jako je vytváření a otevírání souborů, přístup k ovládacím prvkům formulářů, čtení dat z databází vložených do dokumentů Base atd. Cílem knihovny ScriptForge je usnadnit provádění takových příkazů, aniž by bylo nutné se učit potřebná rozhraní API (Application Programming Interfaces) a příkazy LibreOffice, což může být pro příležitostné programátory obtížné.

Knihovna ScriptForge je uspořádána do sady služeb, z nichž každá poskytuje metody a vlastnosti související s určitým tématem. Například služba Dialog umožňuje přístup k dialogovým oknům dostupným ve skriptovacích modulech a služba Databáze umožňuje provádět příkazy SQL v dokumentech Base. Mnoho metod je společných pro různé služby.

Od verze 7.2 je anglická verze dokumentace knihoven ScriptForge plně integrována do nápovědy LibreOffice (https://help.libreoffice.org/7.3/en-US/text/sbasic/shared/03/lib_ScriptForge.html?DbPAR=BASIC).

Tabulka 30: Služby poskytované knihovnou ScriptForge

Kategorie

Služby

 

LibreOffice Basic

Array
Dictionary
Exception

FileSystem
String
TextStream

Obsah dokumentu

Base
Calc
Chart

Database
Document

Uživatelské rozhraní

Dialog
DialogControl
Form

FormControl
PopupMenu
UI

Nástroje

Basic
L10N
Platform

Services
Session
Timer

Ukázka ScriptForge s Base pomocí Pythonu

Tato část je stručnou ukázkou některých hlavních služeb, které jsou pro Base zajímavé, s využitím jazyka Python. Tato ukázka přesně navazuje na příklady nápovědy, na které se lze odkázat v případě základního jazyka. Pro práci s dokumenty Base je zapotřebí řada služeb, včetně: UI (User Interface), FileSystem, Base, Database, Form a FormControl. Na podporu demonstrace budou využity další služby.

Vytváření skriptů Python pomocí ScriptForge

Potřebujeme funkční prostředí Pythonu, takže použijeme nápovědu a projdeme si Vytváření skriptů Pythonu pomocí ScriptForge.

V první části je vysvětlena řada rozdílů mezi spuštěním ScriptForge v jazycích Basic a Python. Python nemá v LibreOffice vestavěné IDE, takže je třeba jej nainstalovat a určit umístění Moje makra. Klíčovou částí je Spouštění skriptů uvnitř procesu LibreOffice:

  1. Použití rozšíření APSO

  2. Vytváření souborů skriptů Python

Pokud makro increment_cell proběhlo, úspěšně jsme vytvořili funkční prostředí Pythonu se ScriptForge. Pokud tomu tak není, vrátíme se zpět k jednotlivým krokům, dokud nebude fungovat.

Inicializace ukázky

Stejný postup lze použít i pro vývoj demonstračního modulu. Pro spuštění skriptů z LibreOffice musíme otevřít dokument, stačí nový tabulkový procesor bez názvu, protože nemusí být uložen. Dále vytvoříme soubor SF_Base_Demo.py Module přímo v sekci Moje makra.

Je efektivnější umístit společné deklarace na začátek modulu. Všechna makra používají knihovnu ScriptForge, takže modul začneme importem metody CreateScriptService, aby bylo možné přistupovat ke službám knihovny. Mnoho maker zobrazuje okno se zprávou a používá okna aplikací LibreOffice, takže je také definujeme:

# Ukázkový modul ScriptForge aplikace Base

from scriptforge import CreateScriptService

bas = CreateScriptService("Basic")

ui = CreateScriptService("UI")

Demonstrace potřebuje funkční složku, ale různé operační systémy používají různé souborové systémy. Naštěstí se SourceForge o tuto složitost stará pomocí služby FileSystem. Stačí iniciovat službu a přidat proměnnou pro pracovní cestu, další podrobnosti jsou uvedeny v dokumentaci.

fs = CreateScriptService("FileSystem")

fs.FileNaming = "SYS"

workpath = fs.BuildPath(fs.HomeFolder, "demo")

První makro slouží k rychlému otestování, zda SourceForge funguje správně, takže přidáme níže uvedené makro sf_HomeFolder a spustíme jej, aby se v systému zobrazila domovská složka. Toto je poslední připomínka k aktualizaci g_exportedScripts.

def sf_HomeFolder(args=None):

    bas.MsgBox("Home Folder: " + str(fs.HomeFolder))

 

g_exportedScripts = (sf_HomeFolder, )

Vytvoření, otevření a používání databáze

Jediným způsobem, jak vytvořit základní soubor, je metoda služby uživatelského rozhraní CreateBaseDocument. Možnosti nalezneme v dokumentaci SourceForge, ale pokud cesta neexistuje, bude vytvořena spolu se souborem a stávající soubor bude bez varování přepsán. Spustíme následující příklad:

def CreateBaseDocumentUI(workpath = workpath):

    doc = ui.CreateBaseDocument(fs.BuildPath(workpath, "myDB.odb"))

    bas.MsgBox("Created now closing: " + fs.BuildPath(workpath, "myDB.odb"))

    doc.CloseDocument()

Jakmile soubor Base existuje, lze k otevření souboru Base použít metodu služby uživatelského rozhraní OpenBaseDocument. Spustíme toto makro:

def OpenBaseDocumentUI(workpath = workpath):

    ui.OpenBaseDocument(fs.BuildPath(workpath, "myDB.odb"), \

        macroexecution = ui.MACROEXECALWAYS)

Otevřený základní dokument lze aktivovat pomocí metody GetDocument služby uživatelského rozhraní. Soubor myDB.odb by měl být stále otevřený, takže jej minimalizujeme, ale nezavíráme. Pokud je zavřený, stačí jej znovu otevřít a minimalizovat. Pro aktivaci spustíme následující makro:

def GetDocumentUI(workpath = workpath):

    myDoc = ui.GetDocument(fs.BuildPath(workpath, "myDB.odb"))

    bas.MsgBox("Bye!")

    myDoc.CloseDocument(True)

Služba Database také umožňuje přímý přístup k databázi bez dokumentu Base. To umožňuje přístup k tabulkám a dotazům a k datům, která obsahují. V dokumentu Base není přístup k formulářům a sestavám, ke kterým je nutné přistupovat prostřednictvím služeb Base. Spustíme níže uvedené makro, které vrátí data přímo z databáze:

def database(workpath = workpath):

    myDatabase = CreateScriptService("Database", fs.BuildPath(workpath, "myDB.odb"))

    # Spouštění dotazů, příkazů SQL,

    bas.MsgBox("Closing: " + fs.BuildPath(workpath, "myDB.odb"))

    myDatabase.CloseDatabase()

Soubor myDB.odb ve složce demo ve vaší domovské složce již nebudeme potřebovat, takže jej můžeme odstranit.

Ukázková databáze zaměstnanců

Nyní přejděme k demonstraci. Chceme-li používat formuláře v ukázce bez jejich nastavení, stačí stáhnout soubor employees.odb do složky s ukázkou v naší domovské složce. Je čas otevřít databázi, a proto vytvoříme další makro a spustíme jej, čímž povolíme makra:

def open_database(workpath = workpath):

    doc = ui.OpenBaseDocument(fs.BuildPath(workpath, "Employees.odb"))

    # Uživatel a heslo jsou v případě potřeby uvedeny níže.

    myDatabase = doc.GetDatabase()

Soubor employees.odb z příručky Začínáme s aplikací Base nejspíše známe jako databázi Automobile.odb s dodatečným formulářem sf_demo pro spouštění maker. Od této chvíle jej můžeme používat místo APSO.

Zadáme a spustíme následující makro pro načtení dat do databáze. Tato ukázka není určena k výuce jazyka SQL, pouze demonstruje, že jej lze spustit pomocí databázové služby s metodou RunSql. S daty můžeme experimentovat. Při každém spuštění tohoto makra příkaz Drop Table odstraní tabulku, pokud existuje, před vytvořením tabulky a načtením dat.

# SQL Demo – Vytvoření tabulky s testovacími daty

def create_table_with_values(args=None):

    # Příprava SQL – Odstranit; Vytvořit; Přidat data

    mysql = """

Drop Table "EmployeeData" IF EXISTS;

 

CREATE TABLE "EmployeeData" (

  "ID" INT  IDENTITY,

  "FirstName" VARCHAR(50),

  "LastName" VARCHAR(50),

  "Position" VARCHAR(50),

  "Salary" DECIMAL(10, 2),

  "City" VARCHAR (50)

);

 

-- If no Field_name specified, all fields must be completed in right order

INSERT INTO "EmployeeData"

VALUES

  (NULL, 'Paul', 'Mejia', 'Family Therapist', 50000, 'Hogworth');

 

INSERT INTO "EmployeeData"

VALUES

  (NULL, 'Jordin', 'Wise', 'Sales', 83954, 'Springstead');

 

INSERT INTO "EmployeeData"

VALUES

  (NULL, 'Kaitlin', 'Palmer', 'Manager', 83250, 'Chicago');

 

INSERT INTO "EmployeeData"

VALUES

  (NULL, 'Paul', 'Smith', 'Mechanic', 74600, 'Goldview');

 

INSERT INTO "EmployeeData"

VALUES

  (NULL, 'Conrad', 'Palmer', 'Manager', 115000, 'North Dodgestead');

 

INSERT INTO "EmployeeData"

VALUES

  (NULL, 'Kate', 'Wordsmith', 'Sales', 75000, 'Chicago');

 """

    # get base document

    # doc = CreateScriptService("Document", bas.ThisDatabaseDocument)

    doc = ui.GetDocument(bas.ThisDatabaseDocument)

    # Vytvoří databázovou službu s aktuální databází

    myDatabase = doc.GetDatabase()

    # Spustí SQL

    myDatabase.RunSql(mysql, directsql=True)

    # nefunguje

    doc.RunCommand("DBRefreshTables")

    myDatabase.CloseDatabase()

    # Zobrazení zprávy

    bas.MsgBox("Table Created")

Statistiky jsou běžně potřebné z databází, takže použijme několik metod Database pro tabulku Employees. Nejprve získáme přístup k právě načteným datům pomocí View > Obnovit tabulky.

# Výpočet statistik zaměstnanců

def table_stats(args=None):

    # Vytvoří databázovou službu s aktuální databází

    doc = ui.GetDocument(bas.ThisDatabaseDocument)

    Employees = doc.GetDatabase()

    # Pro alternativní zobrazení řetězce

    item_a = "Employee Count"

    # Metoda Dcount

    item_b = str(Employees.DCount("[ID]", "EmployeeData"))

    bas.MsgBox("Employee Count" + "\n" + item_b.center(len(item_a), "_"))

    # Metoda DSum – Selektivní

    bas.MsgBox("Total Managers Salary - " + str(Employees.Dsum( \

        "[Salary]", "EmployeeData", "[Position] = 'Manager'")))

Podrobnosti o jednom záznamu lze vrátit pomocí služby Dlookup. Všimneme si, že pokud je v dotazu vráceno více záznamů, vrátí pouze první záznam.

def get_first_match(args=None):

    # Vytvoří databázovou službu s aktuální databází

    myDB = CreateScriptService("Database", bas.ThisDatabaseDocument.URL, "", False)

    # Metoda Dlookup

    bas.MsgBox(

        "Select First Occurance FirstName with" + "\n"

        + "Descending Seq of FirstName" + "\n"

        + "and LastName like Smith" + "\n\n"

        + (myDB.DLookup("[FirstName]", "EmployeeData",     \

           Criteria = "[LastName] LIKE LOWER ('%Smith%')", \

           OrderClause = "[FirstName] DESC")))

Služba Database nakonec umožňuje načíst celou tabulku do pole pomocí metody GetRows.

# Získat záznamy o zaměstnancích

def get_employee_records(args=None):

    # Vytvoří databázovou službu s aktuální databází

    doc = ui.GetDocument(bas.ThisDatabaseDocument)

    Employees = doc.GetDatabase()

    # queryResults je vrácené pole

    # záhlaví jsou názvy sloupců

    queryResults = Employees.GetRows("EmployeeData", header=True)

    bas.MsgBox(

        "Retrieve table data; Show:" + "\n"

        + "FirstName of first record" + "\n\n"

        + (queryResults[1][1])

    )

    # queryResults je vrácené pole

    queryResults = Employees.GetRows(

        "SELECT * FROM EmployeeData ORDER BY [FirstName]", maxrows=5

    )

    bas.MsgBox(

        "Retrieve table data; Show:" + "\n"

        + "FirstName of first record" + "\n"

        + "Select statement ordered by FirstName" + "\n\n"

        + (queryResults[0][1])

    )

Většina služeb SourceForge Base se týká formulářů. Formuláře se používají v případech, kdy je přímý zápis do tabulky nepohodlný, k rychlému zachycení chyb při zadávání dat nebo v případech, kdy příliš mnoho tabulek znemožňuje přímou správu dat. Další makro demonstruje použití formuláře s podformulářem:

# get_form a subform – zobrazení dat z každého z nich

def get_form(args=None):

    # Vytvoří službu DB pomocí aktuálního dokumentu

    doc = CreateScriptService("Document", bas.ThisDatabaseDocument)

    # Níže uvedené prohlášení je nutné pouze v případě, že formulář ještě nebyl otevřen

    form_doc = doc.OpenFormDocument("Fuel")

    # Formuláře – V dokumentu `Fuel` přistupujeme k `Mainform`

    form = doc.Forms("Fuel", "MainForm")

    # získání kontroly nad `MainForm`

    form_control = form.Controls("fmtOdometer")

    # Zobrazení hodnoty pole

    bas.MsgBox("Odometer - " + str(form_control.Value))

    # Získání dílčího formuláře pomocí hlavního formuláře

    subform = form.Subforms("SubForm")

    # Získání ovládání mřížky tabulky

    subform_control = subform.Controls("SubForm_Grid")

    # Získání sloupce v ovládacím prvku mřížky

    grid_control = subform_control.Controls("FuelCost")

    # Zobrazení hodnoty pole ve vybraném záznamu

    bas.MsgBox("First FuelCost in Grid - " + str(grid_control.Value))

    # Zavření formuláře

    form.CloseFormDocument()

 

def get_form2(args=None):

    bas = CreateScriptService("Basic")

    # Vytvoří službu DB pomocí aktuálního dokumentu

    doc = CreateScriptService("Document", bas.ThisDatabaseDocument)

    # Níže uvedené prohlášení je nutné pouze v případě, že formulář ještě nebyl otevřen

    form_doc = doc.OpenFormDocument("Fuel")

    # Formuláře – V dokumentu `Fuel` přistupujeme k `Mainform`

    form = doc.Forms(thisFormDocument, "MainForm")

    # získání kontroly nad `MainForm`

    form_control = form.Controls("fmtOdometer")

Službu FormEvent lze použít k přístupu k podrobnostem ovládacího prvku FormControl, který událost vyvolal. Další makro je přiřazeno tlačítku formuláře a spuštěno pomocí akce execute. Všimneme si, že dokument formuláře není ve ScriptForge přímo k dispozici, ale je k dispozici jeho název, který se používá k vyhledání dokumentu formuláře.

# zavření formulář

def close_form(evt): # přiřazení vykonání akce tlačítku formuláře události makra

    control = CreateScriptService("FormEvent", evt) # tlačítko

    form = control.Parent

    fdoc_name = form.BaseForm if CreateScriptService("Platform" \

    ).OfficeVersion.split()[1] >= "7.3.2" else "sf_demo"

    doc = CreateScriptService("Document", bas.ThisDatabaseDocument)

    fdoc = doc.Forms(fdoc_name, form.Name)

    # Zavření formuláře

    fdoc.CloseFormDocument()

Dokument lze přímo zavřít:

# zavření aktuálního dokumentu

def close_doc(args=None):

    # Vytvoří službu DB pomocí aktuálního dokumentu

    doc = CreateScriptService("Document", bas.ThisDatabaseDocument)

    # Zavření aktuálního dokumentu

    doc.CloseDocument(True)

Další zdroje

Podrobnosti o službách a další příklady najdeme v dokumentaci ScriptForge nebo v nápovědě LibreOffice. Můžeme si stáhnout úplnější verzi této ukázky.

Obsah