Jedes Collection-Objekt, auf das wir im Zuge unserer
ActiveX-Programmierung stossen, stellt uns die auf den
ersten Blick sehr praktisch erscheinende Methode
Item
zur Verfügung - diese hat aber einen gravierenden Nachteil,
der bereits in einem der vorigen Kapitel angesprochen wurde:
Wenn wir auf ein nicht vorhandenes Element zugreifen wollen,
wird ein Fehler verursacht. Noch einmal das Layer-Beispiel -
wir greifen zunächst auf Layer "0" zu, da wir sicher sein
können, dass dieser existiert:
(defun layer-test1( layer-name / )
(vl-load-com)
(vlax-invoke-method
(vlax-get-property
(vlax-get-property
(vlax-get-acad-object)
"ActiveDocument"
)
"Layers"
)
"Item"
layer-name
)
)
(layer-test1 "0")
=> #<VLA-OBJECT IAcadLayer 0fc04474>
(layer-test1 "NichtVorhanden")
=> ; Fehler: Automatisierungsfehler
Schlüssel nicht gefunden
Unser Zugrif auf den Layer "NichtVorhanden" würde so also mit
einem Programm-Abbruch enden. Zwei verschiedene Strategien haben
wir, um das zu verhindern:
-
Zuerst das Collection-Objekt durchsuchen, um festzustellen,
ob ein bestimmtes Element darin vorhanden ist, und erst dann
die Item-Methode anwenden
-
Auf gut Glück zugreifen und den Fehler gegebenenfalls mit
geeigneten Mitteln abfangen
VisualLisp stellt uns zwei Funktionen zum Bearbeiten von
Collection-Objekten zur Verfügung:
(vlax-for ...) und
(vlax-map-collection ...). Die Annahme liegt nahe, dass
es hier um
(foreach) und
(mapcar) für Collections
geht - das werden wir gleich untersuchen:
(defun test-vlax-for( / )
(vlax-for item
(vla-get-layers
(vla-get-activedocument
(vlax-get-acad-object)
)
)
(print(vla-get-Name item))
)
)
(test-vlax-for)
=> "0"
"AndererLayer"
"NochEinLayer""NochEinLayer"
!item
=> nil
Dass die Layernamen von
(print) ausgegeben werden - nun,
etwas anderes war nicht zu erwarten. Und auch der anschliessende
kleine Test mit
!item zeigt, dass hier völlig analog zu
(foreach)
gearbeitet wird. Das Symbol
item ist also lokal zu
(vlax-for) und muss nicht als lokale Variable deklariert
werden. Wir bemerken auch noch, dass der letzte Layer doppelt
erscheint - das ist völlig OK, denn genauso arbeitet auch
(foreach): Das eine Mal wurde der Layer von
(print)
ausgegeben, und daran schliesst sich die Funktionsrückgabe
(Ergebnis der letzten Evaluation) an. Und nun ein Test, ob auch
(vlax-map-collection) analog zu
(mapcar) arbeitet:
(defun test-vlax-map-collection( / )
(vlax-map-collection
(vla-get-layers
(vla-get-activedocument
(vlax-get-acad-object)
)
)
(function
(lambda(item / )
(vla-get-Name item)
)
)
)
)
(test-vlax-map-collection)
=> #<VLA-OBJECT IAcadLayers 0fc04694>
Das Ergebnis zeigt sofort, dass hier die Programmierer von
AutoDesk das Klassenziel mal wieder völlig verfehlt haben:
anstatt der erwarteten Liste mit den Layernamen wird das
Collection-Objekt zurückgegeben. Fazit: Dem schönen heterogenen
Paar
(mapcar) (Effekt) /
(foreach)(Seiteneffekt)
wird hier das gleichartige Paar
(vlax-map-collection)
(Seiteneffekt) /
(vlax-for) (auch Seiteneffekt) zur Seite
gestellt.
Für die, die den im Einsteiger-Tutorial dargestellten
Unterschied zwischen
(foreach) und
(mapcar) nicht
so genau nachgelesen haben, den Hinweis, dies noch einmal schnell
nachzuholen. Und wer sich über Effekt und Seiteneffekt nicht
so ganz im Klaren ist: Das kann man in dem entsprechenden
Kapitel 'Seitensprünge' im Fortgeschrittenen-Tutorial
nachschlagen.
Noch einmal mit anderen Worten:
(vlax-map-collection) ist
zu nichts nütze - das, was diese Funktion macht, erledigt
(vlax-for) genauso. Nehmen wir die Funktion
(vlax-dump-object) wie im Hilfe-Beispiel:
; die eine Variante
(vlax-map-collection
(vla-get-layers
(vla-get-activedocument
(vlax-get-acad-object)
)
)
'vlax-dump-object
)
; und die andere Variante
(vlax-for item
(vla-get-layers
(vla-get-activedocument
(vlax-get-acad-object)
)
)
(vlax-dump-object item)
)
Geringfügige Unterschiede in der Formulierung, aber sonst
völlig gleiche Funktionsweise. Ein 'mehrdimensionales'
Arbeiten wie bei
(mapcar) ist sowieso nicht gegeben.
Ach ja -
(vlax-dump-object) ist eine ganz simple
Funktion, die alles über ein Objekt auf dem Bildschirm
ausgibt - etwa vergleichbar mit dem Liste-Befehl von AutoCAD.
Auf
(vlax-map-collection) können wir also wegen des
Implementationsfehlers getrost verzichten.
Da uns also leider kein
(mapcar) für die Collections
zur Verfügung steht, müssen wir die Rückgabeliste von Hand
bilden - was natürlich auch keine besonder schwere Aufgabe
ist:
(defun collection-items(collection / r-liste)
(vlax-for item collection
(setq r-liste(cons item r-liste))
)
r-liste
)
haben wir nun diese Funktion, können wir darauf aufbauend
eine
(member)-Funktion definieren, mit der wir
gefahrlos testen können, ob ein Element Bestandteil der
Collection ist. Da es sich im Gegensatz zu
(member)
um eine echte Prädikatfunktion handeln soll, kennzeichnen
wir dies durch den Namenszusatz '-p'.
(defun collection-member-p(name collection / r-liste)
(vlax-for item collection
(setq r-liste
(cons(vla-get-Name item)r-liste)
)
)
(not(not(member name r-liste)))
)
Hier gibt es eine Stelle, die vielleicht Irritation hervorrufen
könnte - die doppelte Negation der Rückgabe. Eine kurze
Erklärung dazu:
(member) gibt ja bekanntlich den Listenrest
ab dem gefundenen Element zurück. Dies soll aber verhindert
werden - es soll, wie es sich für eine echte Prädikatfunktion
gehört, entweder
T oder
nil zurückgegeben werden.
Der Grund dafür ist, dass diese Funktion im nächsten Kapitel
noch einmal defiert wird, und zwar mit Hilfe des Abfangen eines
Fehlers. Dort kann natürlich gar kein Listenrest entstehen, es
geht also nur darum, die beiden Versionen kompatibel zueinander
zu halten.
Unser Beispiel mit Layer "NichtVorhanden" könnte jetzt also
so formuliert werden, wie es anschliessend gezeigt wird. Den
Code habe ich jetzt aber etwas überarbeitet, einerseits um
ihn einfach zu verkürzen, andererseits um mal wieder zu zeigen,
dass auch hier viele Wege nach Rom führen.
(defun layer-test2( layer-name / layers)
(vl-load-com)
(if
(collection-member layer-name
(setq layers
(vla-get-Layers
(vla-get-ActiveDocument
(vlax-get-acad-object)
)
)
)
)
(vla-item layers layer-name)
)
)
(layer-test2 "0")
=> #<VLA-OBJECT IAcadLayer 0fc04474>
(layer-test2 "NichtVorhanden")
=> nil
Das schlichte
nil am Ende war das, worauf es ankommt - es
wird das Auftreten eines Fehlers vermieden und
nil zurückgegeben,
wie man es eigentlich (Lisp-gemäss) erwarten würde.
Da das zweite Modell, wie man hier vorgehen könnte - den Fehler einfach
abfangen - bringt einiges Neues ins Spiel. Deshalb werden wir uns im
nächsten Kapitel ausschliesslich mit diesem Thema befassen.