2. Ověření příkazu

V prvním praktickém příkladu napíšeme kód, který spustí příkaz (key-in) MicroStationu, nikoli přímo, ale až po ověření, zda ho opravdu spustit chceme.

Řekněme, že hodláme smazat nevyužité vrstvy ve výkresu. Tento úkol neposkytuje žádný nástroj, vrstvy můžeme mazat pouze jednotlivě ve Správci vrstev. K dispozici ale máme příkaz delete unused levels. Když ho spustíme, vrstvy se okamžitě a bez výstrahy smažou. Příkaz lze sice vrátit zpět, hypoteticky bychom ho ale mohli spustit omylem a zjistit to až při dalším otevření výkresu, kdy už je pozdě na krok zpět. Bylo by tedy bezpečnější nejdříve ověřit, zda ho opravdu chceme provést.

Výchozí projekt

Jde o malou pomocnou funkci, pro kterou nemusíme zakládat nový VBA projekt. Ve výchozím stavu by měl MicroStation obsahovat projekt Default. Ten můžeme využít jako naši osobní knihovnu pro právě takové příruční funkce. Je zaváděn automaticky, takže to nemusíme dělat ručně ve VBA správce projektů.

Pokud v naší konfiguraci MicroStationu projekt Default neexistuje, prostě si ho vytvoříme, stejně jako jsme v předchozí části vytvořili projekt HelloWorld. Název Default není povinný, můžeme zvolit jiný. Jak dosáhneme toho, aby byl automaticky zaveden? Upravíme naši konfiguraci: Otevřeme dialog Konfigurační proměnné (menu Prostředí > Konfigurace…) v panelu Kategorie vybereme Visual Basic for Application, v panelu Nastavení prostředí VBA vybereme Název standardního projektu (proměnná MS_VBAAUTOLOADPROJECTS), a stiskneme tlačítko Editovat. V režimu Přidat za (automaticky zaváděných projektů může být více) vložíme do pole Hodnota název našeho projektu. Pokud jsme ho umístili do některé z výchozích složek, stačí zadat pouze jméno (včetně přípony .mvba), pokud ne, zadáme i celou cestu k němu. Které složky jsou výchozí, zjistíme v proměnné Adresář pro hledání VBA projektů (MS_VBASEARCHDIRECTORIES). Podle potřeby můžeme i sem přidávat další složky.

Potvrdíme změny v konfiguračním souboru a při dalším spuštění MicroStationu si v dialogu VBA správce projektů ověříme, že je náš projekt zaveden.

Otevřeme ho ve VBA editoru. Obsahuje výchozí modul Module1. Modul může obsahovat více různých procedur pro různé účely, pro přehlednost je ale lepší používat pro každý účel samostatný modul pojmenovaný tak, aby název tento účel vystihoval. Pro náš účel použijeme DelUnusedLevels. V okně Project -Default vybereme Module1. Okno Properties – Module1 (pokud ho nevidíme, otevřeme ho v menu View > Properties Window, F4) obsahuje položku (Name) s hodnotou Module1, kterou přepíšeme na DelUnusedLevels a potvrdíme klávesou Enter.

Otevřeme tento modul. Máme vše připraveno pro psaní kódu.

Začneme delkarací procedury. Nazveme ji podobně jako modul: napíšeme Sub DeleteUnusedLevels a stiskneme Enter. VBE doplní zbývající povinný kód procedury.

MessageBox 2

V první části jsme se už seznámili s funkcí MsgBox. Je to velmi užitečná funkce, kterou využijeme i v tomto příkladu pro zobrazení a zpracování ověřovacího dotazu. Podívejme se na ni podrobněji.

Po zadání názvu funkce a mezery (velikost písmen nemusíme řešit, editor ji automaticky upraví) se zobrazí kontextová nápověda. Obsahuje název funkce, seznam parametrů v závorce a datový typ návratové hodnoty:

Obr. 1 Funkce MsgBox

Pokud bychom chtěli, podrobnější informace zjistíme v nápovědě (umístíme kurzor do názvu funkce a stiskneme F1).

Některé parametry jsou zapsány v hranatých závorkách. Znamená to, že jsou nepovinné, nemusíme je zadávat.

První parametr má název Prompt (výzva) a je povinný. Už víme, že je to textový řetězec, číslo nebo výraz vracející text nebo číslo. Napíšeme tedy ověřovací dotaz:

MsgBox "Opravdu chcete provést příkaz Smazat nevyužité vrstvy?"

Při psaní kódu se vyplatí myslet do budoucnosti a psát kód, který se co nejméně opakuje. Můžeme předpokládat, že podobné ověření možná použijeme i pro jiný příkaz. První část dotazu přitom bude stejná. Mohli bychom si ji někam uložit?

Proměnné, konstanty a datové typy

Hlavním účelem našeho programování v MicroStationu je nějaký způsob práce s daty, hlavně s těmi, která jsou uložena přímo ve výkrese jako jeho prvky. V kódu ale potřebujeme pracovat i s vlastními daty sloužícími pro účely programu. Tyto data ukládáme do proměnných (variable).

Proměnná je pojmenované místa v paměti počítače. Jaký je druh dat, uložený v konkrétní proměnné a velikost vyhrazené paměti určuje její datový typ (data type). VBA používá několik datových typů, například celé číslo (Integer), reálné číslo (Double), textový řetězec (String). Budeme se s nimi průběžně seznamovat.

Jak napovídá termín proměnná, její hodnota, tedy přesný údaj uložený na daném pojmenovaném místě paměti, se může při běhu programu měnit. Neplatí to vždy, některá data zůstávají stejná po celou dobu běhu programu. Jsou konstantní a pro jejich uložení můžeme použít místa v paměti zvaná konstanty (constant). VBA samotný používá velké množství vestavěných konstant.

A my si vytvoříme vlastní konstantu pro uložení neměnné části ověřovacího dotazu.

Deklaraci konstanty tvoří klíčové slovo Const, její název, klíčové slovo as pro určení datového typu, datový typ konstanty a přiřazení její konkrétní hodnoty pomocí operátoru =:

const COMMAND_QUERY as String = "Opravdu chcete provést příkaz "

Pro pojmenování konstant, stejně jako proměnných, platí stejná pravidla jako pro pojmenování procedur. Není to nutné, ale často se používají velká písmena a podtržítka pro oddělení slov.

Důležité je, kde deklaraci konstanty umístíme. Pokud bude uvnitř procedury, bude použitelná jen v této proceduře – bude mít lokální platnost. Pokud ji chceme použít ve více procedurách, umístíme ji na začátek modulu, nad první proceduru. Tak je konstanta platná nejen v celém modulu, ale i ve všech případných dalších modulech, v celém projektu. Je tzv. veřejná (public). Pokud je to náš záměr, pro přehlednost ho raději deklarujeme – před slovo Const napíšeme klíčové slovo Public. Pokud konstantu použijeme pouze v aktivním modulu, omezíme její platnost klíčovým slovem Private.

Zatím tuto konstantu umístíme do naší procedury jako lokální a použijeme ji v parametru Prompt v kombinaci s textem pro konkrétní příkaz. Oba řetězce – jeden uložený v konstantě, druhý přímo zadaný, sloučíme operátorem +:

MsgBox COMMAND_QUERY + "Smazat nevyužité vrstvy?"

Výsledek po spuštění procedury:

Obr. 2 Složený Prompt v Message Boxu

Vidíme dotaz, nemáme ale možnost reagovat jinak než stiskem tlačítka OK. Je to dáno druhým parametrem funkce MsgBox s názvem Buttons. Je nepovinný a má přiřazenou hodnotu vbOkOnly. Znamená to, že pokud nezadáme jinou, použije se toto hodnota jako výchozí. A co je tato hodnota? Když se podíváme do nápovědy, zjistíme, že parametr Buttons je číselný výraz a je zde seznam, jaké hodnoty může mít. Pro samotné tlačítko OK je to hodnota 0. Nemusíme si to ale pamatovat, VBA ji předdefinoval jako konstantu, stejně jako pro všechny ostatní hodnoty, které může parametr Buttons nabývat.

Parametry funkce se oddělují čárkou a mezerou. Po napsání těchto znaků se automaticky rozevře seznam všech dostupných konstant. Vybereme vbYesNOCancel.

Parametr Buttons poněkud nelogicky určuje kromě tlačítek i ikonu, která může být v dialogu zobrazena. Náš dotaz bude fungovat jako výstraha před spuštěním příkazu, použijeme tedy konstantu vbExclamation kterou přidáme k předchozí konstantě operátorem +.

Třetím, nepovinným parametrem je Title – textový řetězec, který se zobrazí v záhlaví dialogu namísto výchozího MicroStation. Je to jasný kandidát na veřejnou konstantu, asi ho budeme používat opakovaně na různých místech. Takže deklaraci umístíme nad proceduru, na začátek modulu, např. takto:

Public Const TITLE as String = "Jesyka"

Konstantu použijeme ve funkci MsgBox. Celý kód momentálně vypadá takto:

Public Const TITLE as String = "Jesyka"

Sub DeleteUnusedLevels()
Const COMMAND_QUERY As String = "Opravdu chcete provést příkaz "

    MsgBox COMMAND_QUERY + "Smazat nevyužité vrstvy?", _
        vbYesNoCancel + vbExclamation, TITLE

End Sub

Po spuštění procedury vidíme:

Obr. 4 Přizpůsobený Message Box.

Bez ohledu na to, které tlačítko zmačkneme, se dialog zavře a nic dalšího se nestane. Musíme doplnit další kód.

Většina funkcí slouží k provedení nějaké operace s vloženými daty a vrací výsledek této operace – má návratovou hodnotu (return value).

Funkce MsgBox má návratovou hodnotu vbMsgBoxResult. V nápovědě vidíme, že je to číslo, které se liší podle toho, které tlačítko bylo stisknuto. Opět jsou pro tyto hodnoty předdefinovány VBA konstanty.

Vyzkoušíme, jak to funguje: Výsledek (návratovou hodnotu) funkce MsgBox uložíme do proměnné result a tu předáme funkci Debug.Print, která její hodnotu zobrazí v ladícím okně.

Všimněte si, že na rozdíl od předchozích příkladů jsou parametry funkce MsgBox uzavřené do závorky. Pokud očekáváme výsledek nějaké funkce, musí tomu tak být:

result = MsgBox(COMMAND_QUERY + "Smazat nevyužité vrstvy?", _
        vbYesNoCancel + vbExclamation, TITLE)

Debug.Print result

Příklad můžeme zjednodušit – jako parametr metody Debug.Print můžeme použít přímo funkci MsgBox a protože s jejím výsledkem dál pracovat nebudeme, nemusíme ho ukládat. Proměnnou result nepotřebujeme:

Debug.Print MsgBox(COMMAND_QUERY + "Smazat nevyužité vrstvy?", _
        vbYesNoCancel + vbExclamation, TITLE)

Po spuštění a stisknutí některého z tlačítek vidíme výsledek v ladícím okně. Jsou to hodnoty 6 pro Ano, 7 pro Ne a 2 pro Zrušit, což odpovídá konstantám vbYes, vbNo a vbCancel.

If Then

Algoritmus, který potřebujeme, je velmi jednoduchý: Příkaz MicroStationu se provede pouze tehdy, když je odpověď vbYes. Takové řízení průběhu kódu dosáhneme pomocí konstrukce If Then. Má následující syntaxi:

lf podmínka Then 
   'příkazy při splnění podmínky
Else 
   'příkazy při nesplnění podmínky
End If

Část Else je nepovinná. Pokud ji nepotřebujeme a příkaz při splnění je jednoduchý, můžeme použít jednodušší zápis:

lf podmínka Then 'příkaz při splnění podmínky

Podmínkou je v našem případě výrok, že výsledek funkce MsgBox se rovná hodnotě vbYes:

If MsgBox(COMMAND_QUERY + "Smazat nevyužité vrstvy?", _
            vbYesNoCancel + vbExclamation, TITLE) = vbYes Then
        Debug.Print "Příkaz bude proveden."
Else
        Debug.Print "Příkaz nebude proveden."
End If

Jestliže je výrok pravdivý, příkaz bude proveden, jinak nikoli.

Zbývá nám napsat kód pro spuštění příkazu.

Knihovna MicroStationDGN

V první části jsme se už seznámili s knihovnami, které nám ve VBA poskytují přístup k aplikacím, pro které je náš kód určen. V našem případě je touto aplikací MicroStation. Prostřednictvím knihovny MicroStationDGN máme přístup téměř k veškeré jeho funkcionalitě, vlastnostem a nastavením. VBA sice nenabízí tolik možností jako nativní MDL (MicroStation Development Library, implementovaná v jazyce C a pokrývající komplexní sadu funkcí MicroStationu), ale i tak je velmi mocný.

Pro přístup k objektům a funkcím knihovny MicroStationDGN slouží její hlavní objekt s názvem Application. Operátorem tečka přistupujeme k objektům na nižších úrovních.

Objekt pro posílání a přijímání příkazů se nazývá CadInputQueue. Obsahuje metodu SendCommand, jejímž parametrem je textový řetězec obsahující daný příkaz.

Kód pak vypadá takto:

Public Const TITLE as String = "Jesyka"

Sub DeleteUnusedLevels()
Const COMMAND_QUERY As String = "Opravdu chcete provést příkaz "

    If MsgBox(COMMAND_QUERY + "Smazat nevyužité vrstvy?", _
            vbYesNoCancel + vbExclamation, TITLE) = vbYes Then
        Application.CadInputQueue.SendCommand "delete unused levels"
    End If

End Sub

A funguje přesně tak, jak jsme zamýšleli:

MicroStation jsme si obohatili o jeden nový nástroj. Pojďme ale ještě trochu dál: Když takový nástroj tvoříme, vyplatí se ho vždy navrhnout tak, aby se dal využít co nejlépe. V tomto našem příkladu by bylo dobré, kdyby byl universálně použitelný pro další příkazy.

Volání procedur

Představme si, že bychom podobným způsobem chtěli ověřovat spuštění dalších příkazů, například delete unused linestyles a delete unused textstyles.

Mohli bychom samozřejmě zkopírovat naši funkci, kopie přejmenovat a změnit v nich text příkazu. To je ale zbytečné opakování kódu, kterému bychom se měli vždy vyhnout.

Procedura může, stejně jako funkce, přijímat parametry. Vytvoříme tedy universální proceduru pro ověřování příkazu a příkaz ji předáme jako parametr. Deklarace takové procedury je následující:

Sub ConfirmCommand(Command As String)

End Sub

Procedura je pojmenovaná ConfirmCommand, má parametr se jménem Command, který je datového typu String (textový řetězec).

Kód této procedury bude stejný jako kód v proceduře DeleteUnesedLevels, pouze jako parametr funkce SendCommand místo textu příkazu použijeme jméno parametru Command. Použijeme ho i pro vytvoření textu výzvy, pro přehlednost ho převedeme na velká písmena pomocí vestavěné VBA funkce UCase(uppercase):

Private Sub ConfirmCommand(Command As String)
Const COMMAND_QUERY As String = "Opravdu chcete provést příkaz "

    If MsgBox(COMMAND_QUERY + UCase(Command) + "?", _
            vbYesNoCancel + vbExclamation, TITLE) = vbYes Then
        Application.CadInputQueue.SendCommand Command
    End If

End Sub

V proceduře DeleteUnesedLevels původní kód smažeme a prostřednictvím klíčového slova Call zavoláme novou universální proceduru s příslušným parametrem:

Call ConfirmCommand("delete unused levels")

Klíčové slovo Call není povinné, můžeme ho vynechat. V tom případě se parametry nedávají do závorky:

ConfirmCommand "delete unused levels"

Teprve takto opravenou funkci zkopírujeme, kopie přejmenujeme a upravíme v nich text příkazu:

Public Const TITLE as String = "Jesyka"

Sub DeleteUnusedLevels()
    
    ConfirmCommand "delete unused levels"

End Sub

Sub DeleteUnusedLineStyles()
    
    ConfirmCommand "delete unused linestyles"

End Sub

Sub DeleteUnusedTextStyles()
    
    ConfirmCommand "delete unused textstyles"

End Sub

Private Sub ConfirmCommand(Command As String)
Const COMMAND_QUERY As String = "Opravdu chcete provést příkaz "

    If MsgBox(COMMAND_QUERY + UCase(Command) + "?", _
            vbYesNoCancel + vbExclamation, TITLE) = vbYes Then
        Application.CadInputQueue.SendCommand Command
    End If

End Sub

Modul teď obsahuje několik procedur. Pro pořádek ho přejmenujeme, aby jeho název lépe odpovídal jeho hlavnímu účelu: ConfirmCommand.

Všimněte si, že v deklaraci funkce ConfirmCommand je klíčové slovo Private, se kterým jsme se již setkali u popisu platnosti konstant. U procedur to funguje podobně, Private jsou viditelné v daném modulu, Public, což je výchozí hodnota, kterou nemusíme zadávat, jsou viditelné v celém projektu.

Platnost procedury má význam i z pohledu uživatele našeho programu. V dialogu Makra jsou viditelné pouze veřejné (Public) procedury. Zkuste si to – označte např. proceduru DeleteUnusedTextStyles jako Private, a podívejte se do dialogu Makra. Není tam.

Stejně tak tam není procedura ConfirmCommand. Je to interní funkce, pro uživatele nemá význam., proto je označena jako Private. V tomto případě je to spíše pro nás, tvůrce kódu, kteří v něm chceme mít pořádek. Uživatel by ji totiž neviděl ani v případě, že by byla veřejná. Má totiž deklarovaný parametr, a procedury s parametry se v dialogu Makra nezobrazují.

A co kdybychom chtěli uživateli nabídnout právě tuto možnost – použít naši ověřovací proceduru pro libovolný příkaz, který uživatel sám zadá?

Input Box

Pro zadání nějakých dat uživatelem existuje vestavěná VBA funkce InputBox. Je podobná funkci MsgBox a obsahuje textové pole pro zápis předávané hodnoty.

Tato funkce zobrazí výzvu pro zadání dané informace a čeká na její zadání nebo stisknutí tlačítka. Po stisku klávesy Enter nebo tlačítka OK vrací obsah textového pole jako datový typ String. Po stisku tlačítka Cancel vrací prázdný řetězec (″″).

Funkce má, stejně jako funkce MsgBox, povinný parametr Prompt, což je text výzvy k zadání uživatelského vstupu. Dál má několik nepovinných parametrů: Title, titulek dialogu, je rovněž stejný jako u funkce MsgBox. Parametr Default, se zobrazí v textovém poli jako výchozí hodnota, kterou může uživatel buď potvrdit, nebo přepsáním upravit. My tento parametr nebudeme zadávat.

Algoritmus je opět jednoduchý: zobrazíme výzvu, když uživatel zadá příkaz, předáme ho proceduře ConfirmCommand, když ne, nestane se nic. Použijeme tedy opět konstrukci If Then, podmínkou bude, že výsledek funkce InputBox není prázdný řetězec:

Sub RunCommand()

    result = InputBox("Zadejte příkaz:", TITLE)
    If result <> "" Then ConfirmCommand (result)
    
End Sub

Máme veřejnou proceduru RunCommand, ve které voláme funkci InputBox s výzvou pro zadání příkazu, výsledek uložíme do proměnné result a pokud to není prázdný text, což zjistíme pomocí operátoru nerovná se <>, předáme ho proceduře ConfirmCommand.

Deklarace proměnných

Tento kód funguje, ale není optimální. Všimněte si, že proměnnou result jsme nijak nedeklarovali, neurčili jsme její datový typ. VBA to umožňuje, dynamicky zabezpečí správu typů. Není to ale zadarmo, cenou je pomalejší běh programu, větší množství potřebné paměti a někdy i těžko zjistitelné chyby.

Je mnohem bezpečnější se na VBA nespoléhat a všechny typy deklarovat. Můžeme přitom VBA sdělit, aby deklarace striktně vyžadoval a kontroloval, a to zadáním příkazu Option Explicit na první řádek modulu.

Pozn. 1: Automatické vložení příkazu Option Explicit
Automatické vložení tohoto příkazu na začátek každého nově vytvořeného modulu zajistíme volbou Require Variable Declaration v dialogu Options (menu Tools > Options…, na kartě Editor.
Určitě to proveďte, vyvarujete se tak mnoha problémům!

Co se stane, když tento příkaz zadáme a spustíme proceduru?

Obr. 5 Chyba při kompilaci

Zobrazí se upozornění na chybu při kompilaci (kompilace je překlad kódu do binární spustitelné podoby) a její popis – proměnná není definována. Současně je tato proměnná vybrána.

Po uzavření upozornění zůstáváme v ladícím (debug) režimu. Je to signalizováno žlutým zvýrazněním deklarace procedury. Průběh procedury je pozastaven a máme možnost opravit chybu a pak pokračovat v běhu (tlačítko Run se změnilo na Continue). Oprava během ladění není možná vždy, záleží na typu chyby. Kdykoli ale můžeme ladící režim ukončit tlačítkem Reset (menu Run > Reset):

Obr. 6 Ladící režim

Deklarace proměnné je stejná jako deklarace konstanty, jen místo klíčového slova Const použijeme slovo Dim. Je to zkratka slova dimension.

Dim result As String

Stejně jako u konstant funguje i rozsah platnosti proměnné: uvnitř procedury je lokální – pouze pro ni, mimo proceduru (na začátku modulu) může být buď Private pouze pro modul, nebo Public (nebo bez označení) pro celý projekt.

V našem příkladu nám stačí lokální platnost, deklaraci umístíme do procedury. V ní může být kdekoli, avšak vždy před jejím prvním použitím. Bývá zvykem psát všechny deklarace proměnných na začátku procedury, hned pod jejím názvem.

Pravidla pro pojmenování proměnných jsou stejná jako u procedur a konstant. Deklarace má ještě jednu výhodu: při každém použití proměnné VBA automaticky upraví velikost písma v jejím názvu tak, jak jsme zadali v deklaraci. Můžeme psát malým písmem a nezdržovat se velkými. Navíc se vyhneme překlepům.

Vyhodnocování textových řetězců

Druhou optimalizaci, kterou provedeme, je změna způsobu vyhodnocení podmínky, zda uživatel zadal text příkazu. Mnohem rychlejší než porovnávání řetězce operátorem rovná se/nerovná se je zjištění jeho délky pomocí VBA funkce Len. Tato funkce vrací počet znaků v řetězci a nulu, pokud je řetězec prázdný. Takže naše podmínka bude pravdivá, když délka řetězce nebude nulová:

Sub RunCommand()
Dim result As String

    result = InputBox("Zadejte příkaz:", TITLE)
    If Len(result) <> 0 Then ConfirmCommand (result)
    
End Sub

V našem jednoduchém příkladu nemá tato optimalizace velký význam, změna rychlosti běhu programu je nepostřehnutelná. U složitějších procedur a funkcí, kdy může docházet k tisícům takových vyhodnocování, může tato změna činit desítky sekund i minuty.


Tady s tímto příkladem skončíme. Vytvořili jsme první doplněk rozšiřující funkčnost MicroStationu. Není přímo nepostradatelný, ale ukázali jsme si na něm mnoho důležitých informací o VBA.

Kompletní kód příkladu:

Option Explicit
Public Const TITLE as String = "Jesyka"

Sub RunCommand()
Dim result As String

    result = InputBox("Zadejte příkaz:", TITLE)
    If Len(result) <> 0 Then ConfirmCommand (result)
    
End Sub

Sub DeleteUnusedLevels()
    
    ConfirmCommand "delete unused levels"

End Sub

Sub DeleteUnusedLineStyles()
    
    ConfirmCommand "delete unused linestyles"

End Sub

Sub DeleteUnusedTextStyles()
    
    ConfirmCommand "delete unused textstyles"

End Sub

Private Sub ConfirmCommand(Command As String)
Const COMMAND_QUERY As String = "Opravdu chcete provést příkaz "

    If MsgBox(COMMAND_QUERY + UCase(Command) + "?", _
            vbYesNoCancel + vbExclamation, TITLE) = vbYes Then
        Application.CadInputQueue.SendCommand Command
    End If

End Sub

Vytvořte si web nebo blog na WordPress.com