3. Odebrat úsečky dle délky

V této části vytvoříme první reálný doplněk pro MicroStation, který je praktický a užitečný. Slouží k odebrání čar z výběrové množiny, a to podle délky, kterou zadá uživatel. Je to užitečné například při použití takového druhu čar, který se u úseček s malou délkou zobrazí neúplně a výsledek je pak velmi nepřehledný.

Používání doplňku je popsané v tomto článku. My ho tady vytvoříme znovu, krok za krokem.

Založíme nový VBA projekt, pojmenovaný např. ReLiByLe (remove lines by length). Výchozí Module1 přejmenujeme na Main – bude hlavním modulem doplňku.

Pokud na prvním řádku modulu není příkaz Option Explicit, velmi doporučuji ho doplnit, případně nastavit jeho automatické vkládání, viz předchozí část.

Budeme pracovat s výběrovou množinou prvků, tj. s prvky, které uživatel vybere pomocí nástroje Vybrat prvek. Jak se k ní dostaneme ve VBA?

Aktivní model

V MicroStationu pracujeme s výkresy – soubory dgn. Každý výkres může obsahovat více modelů, minimálně pak jeden, který je výchozí (Default). V jednom okamžiku je vždy aktivní jenom jeden model a všechny prvky, které vytváříme nebo upravujeme, se ukládají do tohoto aktivního modelu.

Ve VBA je aktivní model reprezentován objektem ActiveModelReference, členem objektu Application, hlavního objektu knihovny MicroStationDGN.

Takže zpět k výběrové množině: Ve VBA k ní získáme přístup pomocí metody GetSelectedElements objektu ActiveModelReference.

Element je základní objekt reprezentující každý grafický prvek (úsečka, útvar, kružnice, text apod.).

Metoda GetSelectedElements vrací objekt ElementEnumerator.

S objekty jsme se seznámili v první části jako s „balíčky“ s různými vlastnostmi a metodami. A jak nyní vidíme, mohou být použité jako datový typ.

Deklarace objektové proměnné je stejná jako deklarace proměnných ostatních typů. Uvádí se v ní konkrétní typ objektu, se kterým chceme pracovat:

Dim elEm as ElementEnumerator
Pozn. 1: Konvence pojmenovávání proměnných
Někteří programátoři pojmenovávají proměnné s předponou, která udává její datový typ, takže je na první pohled zřejmý. Například řetězcová proměnná Name se doplní na strName, objektová proměnná ele na objEle nebo jen oEle (tak je to v příkladech v nápovědě MicroStationu VBA). Já tento způsob nepoužívám, zdá se mi, že čtení kódu spíš komplikuje.

Přiřazení objektu k proměnné se provádí stejně jako u ostatních typů pomocí operátoru =, na rozdíl od nich je ale nutné použít klíčové slovo Set:

Set elEm = Application.ActiveModelReference.GetSelectedElements

Objekt Application je možné vynechat, je totiž automaticky předpokládán.

Element Enumerator

Objekt ElementEnumerator slouží pro přístup k prvkům nějaké množiny, v tomto případu množiny vybraných prvků. Jak napovídá název, umožňuje jejich výčet – postupné procházení, enumeraci.

Zpočátku, po přiřazení, se pozice enumerátoru nachází před prvním prvkem. Pomocí metody MoveNext se pokusí přesunout na první prvek. Pokud prvek existuje, vrátí tato metoda hodnotu True a přiřadí prvek do vlastnosti Current, Pokud prvek neexistuje, vrátí False a Current neukazuje na žádný prvek.

Pozn. 2: Datový typ Boolean
Návratová hodnota metody MoveNext je datového typu Boolean. Je to tzv. logický typ a může mít pouze dvě hodnoty True (Pravda) a False (Nepravda, výchozí hodnota).

Řekněme, že výběrová množina obsahuje dva prvky. ElementEnumerator pak funguje takto:

Sub GetTypes()
Dim elEm As ElementEnumerator
    Set elEm = ActiveModelReference.GetSelectedElements
    
    If elEm.MoveNext Then Debug.Print elEm.Current.Type
    If elEm.MoveNext Then Debug.Print elEm.Current.Type
    If elEm.MoveNext Then Debug.Print elEm.Current.Type
    
End Sub
'Výsledek:
'3
'3
'

Získali jsme ElementEnumerator a přiřadili ho do proměnné elEm.

Použijeme If Then: když MoveNext najde prvek (vrací True), tak k němu máme přístup přes Current a vytiskneme jeho typ (pokud je to úsečka, uvidíme v ladícím okně číslo 3, viz dále).

Vybrali jsme dva prvky, takže přejdeme na další. Vytiskne se další číslo 3.

Zkusíme přejít ještě dál. Nic se nestane, protože třetí prvek neexistuje, MoveNext vrací False a podmínka If není splněna.

Tento příklad je pouze ukázkový, v praxi takto enumerator nikdy nepoužíváme. Nemůžeme totiž vědět, kolik prvků se v procházené množině nachází.

Potřebujeme opakovat příkaz (nebo blok příkazů), aniž bychom předem věděli, kolikrát.

Konstrukce Do While

Tato konstrukce slouží přesně k tomuto účelu. Umožňuje provádění příkazů v cyklu (smyčce) tak dlouho, dokud je splněna zadaná podmínka:

Do While podmínka
   'příkazy
Loop

Klíčová slovaDo While a Loop tvoří začátek a konec cyklu. Když běh programu dojde k tomuto cyklu, vyhodnotí podmínku. Pokud není podmínka pravdivá, přeskočí cyklus a pokračuje za ním. Pokud je podmínka pravdivá, provede blok příkazů uvnitř cyklu a vrátí se na jeho začátek. To opakuje tak dlouho, dokud je podmínka pravdivá.

V našem případě bude podmínkou volání metody elEm.MoveNext. Pokud tato metoda vrátí hodnotu True (tzn. existuje další prvek), bude podmínka splněna a provede se příkaz uvnitř cyklu:

Do While elEm.MoveNext
Debug.Print elEm.Current.Type
Loop

Můžeme vybrat libovolný počet prvků. Tento kód vždy spolehlivě vypíše jejich typ.

Objekt ElementEnumerator nemá žádnou vlastnost ani metodu, která by nám předem řekla, kolik prvků je v množině. Pokud bychom tuto informaci potřebovali, musíme ji sami spočítat:

Sub GetCountAndTypes()
Dim elEm As ElementEnumerator
Dim count As Long
Set elEm = ActiveModelReference.GetSelectedElements
Do While elEm.MoveNext
count = count + 1
Loop
Debug.Print "Počet vybraných prvků: " & count
elEm.Reset
Do While elEm.MoveNext
Debug.Print elEm.Current.Type
Loop
End Sub

Deklarujeme proměnnou count datového typu Long, což je celé číslo. Jeho výchozí hodnota je nula.

Pozn. 3: Datový typ Long

Datový typ Long slouží k ukládání celých čísel v rozsahu -2 147 483 648 až 2 147 483 647. Vyžaduje 4 bajty paměti počítače.

Pokud jsme si jisti, že nebudeme pracovat s velkými čísly, můžeme použít i datový typ Integer. Má rozsah -32 768 až 32 767 a velikost 2 bajty.

Naopak pro čísla překračující rozsah typu Long slouží typ Decimal. V případě takové potřeby najdete podrobnosti v nápovědě VBA.

Použijeme enumerator a v každém kroku cyklu zvýšíme hodnotu proměnné Count.

Vypíšeme hodnotu count do ladícího okna. Všimněte si, že pro její sloučení s informativním textem jsme použily operátor &. Je to proto, že spojujeme řetězec typu String s proměnnou jiného typu. V tom případě nemůžeme použít operátor +.

Pozn. 4: Konverzní funkce

VBA obsahuje sadu funkcí pro převod mezi datovými typy. V tomto případě bychom mohli použít funkci Cstr, která převede číslo na text obsahující toto číslo. Operátor + pak opět funguje:

Debug.Print "Počet vybraných prvků: " + CStr(count)

Enumerator se nyní nachází za posledním prvkem množiny. Použijeme jeho metodu Reset, která ho vrátí před první prvek.

V dalším cyklu vypíšeme typy vybraných prvků.

Teď už víme, jak získat vybrané prvky a jak je procházet. V dalším kroku si ukážeme, jak zjistit, zda je vybraný prvek úsečka a pokud ano, jaká je její délka.

Objekt Element

Jak už víme, každý prvek ve výkresu (správněji v aktivním modelu) je ve VBA reprezentován objektem Element. Tento objekt obsahuje vlastnosti a metody, které jsou společné pro všechny typy prvků.

Vedle základního objektu Element existují objekty prvků různých typů: úsečka a lomená úsečka (SmartLine) jsou objektem LineElement, kružnice a elipsa jsou objektem EllipseElement apod. Tyto objekty mají vedle vlastností a metod objektu Element své vlastní specifické vlastnosti a metody. LineElement má vlastnost Length (délka), elipsa vlastnost PrimaryRadius a SecondaryRadius (primární a sekundární rádius) apod.

Zkusme vypsat délku vybraných úseček:

Sub GetLength1()
Dim elEm As ElementEnumerator
Dim liEl As LineElement
Set elEm = ActiveModelReference.GetSelectedElements
Do While elEm.MoveNext
Set liEl = elEm.Current
Debug.Print liEl.Length
Loop
End Sub

Vlastnost enumeratoru Current je typu Element, proto i v případě, kdy ukazuje na objekt LineElement, nemáme přístup k jeho vlastnosti Length. Musíme ho explicitně přiřadit do proměnné stejného typu. Deklarujeme tedy proměnnou liEl typu LineElement, v enumeračním cyklu do ní přiřazujeme aktuální objekt, na který odkazuje vlastnost enumeratoru Current a vypisujeme jeho vlastnost Length.

Funguje to, ale jen tehdy, když ve výkresu vybereme úsečky a lomové úsečky. Když je vybraná např. kružnice, dojde k chybě Type mismatch (neshoda typů):

Obr. 1 Chyba Type mismatch

Stiskem tlačítka Debug se přepneme do ladícího režimu a uvidíme řádek, kde došlo k chybě:

obr. 2 Řádek s chybou

Přiřadit do objektové proměnné nějakého typu objekt jiného typu není možné.

Element Type

Víme, že každý objekt Element má vlastnost Type, svůj typ. Každý typ je určen předdefinovanou konstantou, např. pro úsečku je to konstanta msdElementTypeLine (hodnota 3), pro lomenou úsečku je to msdElementTypeLineString (hodnota 4) a pro kružnici msdElementTypeEllipse (hodnota 15).

Můžeme použít osvědčenou konstrkukci If Then: když enumerator ukazuje na objekt typu msdElementTypeLine, tak je to úsečka a vypíšeme její délku:

Sub GetLength2()
Dim elEm As ElementEnumerator
Dim liEl As LineElement
Set elEm = ActiveModelReference.GetSelectedElements
Do While elEm.MoveNext
If elEm.Current.Type = msdElementTypeLine Then
Set liEl = elEm.Current
Debug.Print liEl.Length
End If
Loop
End Sub

Tento postup funguje bez chyb, objekt Element ale nabízí i jiný, trochu jednodušší. Obsahuje vlastnost IsLineElement s návratovou hodnotou typu Boolean, kterou můžeme použít v podmínce místo porovnávání. Takovou Is[typ prvku] vlastnost má pro všechny typy prvků.

Podobné zjednodušení existuje i pro přetypování objektu Element na LineElement. Pokud už s objektem dále v kódu nepracujeme a nepotřebujeme ho mít uložený v proměnné, můžeme použít vlastnost AsLineElement, která vrací LineElement a bezprostředně nabízí všechny jeho členy, včetně vlastnosti Length.

Takový kód je rychlejší na zápis, ale i při běhu programu:

Sub GetLength3()
Dim elEm As ElementEnumerator
Set elEm = ActiveModelReference.GetSelectedElements
Do While elEm.MoveNext
If elEm.Current.IsLineElement Then
Debug.Print elEm.Current.AsLineElement.Length
End If
Loop
End Sub

Uživatelský vstup a ošetření chyb

Můžeme přistoupit k dalšímu kroku: dotázat se uživatele na délku rozhodnou pro odebrání úsečky. Použijeme vestavěnou funkci InputBox:

Dim length As Double
length = InputBox("Zadejte délku:", TITLE)

Tuto funkci známe z minulé části. Víme, že vrací textový řetězec. Tady ji ale používáme pro přiřazení do proměnné typu Double. Pokud uživatel zadá celé nebo reálné číslo a stiskne tlačítko OK, je vše v pořádku. VBA provede automatický převod textu na číslo. Pokud stiskne tlačítko Cancel, dojde k chybě type mismatch. Funkce vrací prázdný řetězec a ten nelze automaticky převést na číslo.

Tuto situaci vyřešíme správným ošetřením chyby pomocí příkazu On Error GoTo. Příkaz GoTo převede běh programu na řádek, který je označený názvem (label). Může to být libovolný text, který začíná písmenem a končí dvojtečkou. Velikost písma je libovolná. Musí být na začátku řádku, o to se postará editor.

   GoTo label
   'kód zde bude přeskočen
label:
   'tady bude pokračovat běh programu
Pozn. 5: Špagetový kód
Pro ošetření chyby je použití příkazu GoTo v pořádku. Jinak bychom se mu měli vyhýbat a nepoužívat ho. Program by měl mít jasný začátek a konec a postupovat řádek po řádku. GoTo vede k nevyzpytatelnému přeskakování, které se těžko sleduje, k tzv. špagetovému kódu.

Příkaz On Error GoTo se pokusí provést příkaz na dalším řádku. Pokud dojde k chybě, přeskočí na označený řádek:

Sub RemoveLinyByLength()
Dim length As Double
On Error GoTo errorHandler
length = InputBox("Zadejte délku:", TITLE)
'TODO: implementace hlavního bloku příkazů
errorHandler:
End Sub

Pokud uživatel zadá špatný vstup nebo stiskne tlačítko Cancel, běh programu přeskočí za řádek označený jako errorHandler. Tam je konec procedury, takže běh programu se ukončí.

Zbývá implementovat hlavní blok příkazů:

Sub RemoveLinesByLength()
Dim elEm As ElementEnumerator
Dim length As Double
On Error GoTo errorHandler
length = InputBox("Zadejte délku:", TITLE)
Set elEm = ActiveModelReference.GetSelectedElements
Do While elEm.MoveNext
If elEm.Current.IsLineElement Then
If elEm.Current.AsLineElement.length <= length Then
ActiveModelReference.UnselectElement elEm.Current
End If
Else
ActiveModelReference.UnselectElement elEm.Current
End If
Loop
errorHandler:
End Sub

Pomocí enumeratoru procházíme vybrané prvky. Pokud je typu ElementLine, použijeme další blok If Then a pokud je jeho délka menší nebo rovna (<=), odebereme ji z výběru metodou UnselectElement objektu ActiveModelReference. Totéž učiníme, pokud vybraný prvek není LineElement.

Doplníme poslední kousek kódu, ve kterém ověříme, zda je vybrán alespoň jeden prvek:

Sub RemoveLinesByLength()
Dim elEm As ElementEnumerator
Dim length As Double
    
    If Not ActiveModelReference.AnyElementsSelected Then
        MsgBox "Není vybrán žádný prvek.", vbOKOnly + vbExclamation, TITLE
        Exit Sub
    End If
    On Error GoTo errorHandler
    length = InputBox("Zadejte délku:", TITLE)
   
    Set elEm = ActiveModelReference.GetSelectedElements
    Do While elEm.MoveNext
        If elEm.Current.IsLineElement Then
            If elEm.Current.AsLineElement.length <= length Then
                ActiveModelReference.UnselectElement elEm.Current
            End If
        Else
            ActiveModelReference.UnselectElement elEm.Current
        End If
    Loop
    
errorHandler:
End Sub

Použijeme metody AnyElementsSelected objektu ActiveModelReference, který vrací True, je-li vybrán alespoň jeden prvek. Nás zajímá opačná možnost False, použijeme proto logický operátor Not. Pokud podmínka platí, upozorníme uživatele a ukončíme proceduru příkazem Exit Sub.

V dalším pokračování přidáme možnost volby, zda se budou odebírat úsečky kratší nebo delší než zadaná délka. InputBox nahradíme vlastním dialogovým oknem.

Kompletní kód příkladů v této části:

Další část: 4. Odebrat úsečky dle délky – vlastní dialog