Okienka dialogowe

W programach, nawet najprostszych nakładkach potrzebne są okienka dialogowe. W LISP okienka dialogowe można tworzyć za pośrednictwem mechanizmów DCL. DCL pozwala na tworzenie jedynie bardzo prostych okienek. Możliwe jest użycie pól tekstowych, przycisków, list, opcji, suwaków i tekstów. Wszystkie są szczegółowo opisane w helpie, więc nie będę się tu skupiał nad dokładnym opisaniem każdego elementu okienka (czasem można się spotkać z określeniem, że są to "wycinki", ja może zostanę przy określeniu "elementy" albo "kontrolki"). Chciałbym opisać zasadę działania okienek, jak zacząć pracę z nimi, kilka interesujących możliwości, które nie są oczywiste przy pierwszym spotkaniu z DCL. W poszukiwaniu szczegółów zapraszam do lektury helpa lub dyskusji na forum.
Opinie na temat DCL są ogólnie podzielone. Wielu twórców nakładek ma złe zdanie na temat DCL, ja też się ku temu zdaniu przychylam. DCL ma wiele ograniczeń, które powodują, że jeśli mam do stworzenia nakładkę z okienkami, wybieram VBA.

Kilka właściwości, które powodują, że DCL nie jest zbyt poręczy w użyciu:

  • Trudności w ustawieniu kontrolek w okienku
  • Brak możliwości tworzenia okienek niemodalnych
  • Brak możliwości dodawania innych kontrolek
  • Brak obiektowości elementów
  • Brak możliwości zmian dynamicznych (w czasie działania programu])

Istnieją środowiska pozwalające na rozszerzenie możliwości DCL, jest to OpenDCL i ObjectDCL. Dają one o wiele większe możliwości, jak np.: Tworzenie okienek niemodalnych, Okienka podobne do okna właściwości, Dodatkowe konktolki jak np. zakładki, kalendarz, kontrolki obiektowe, podgląd bloków i inne. Ten kurs ma być jednak poświęcony podstawom LISP, a ja nie znam tych rozszerzeń na tyle, żeby móc doradzać którekolwiek z nich, więc nie będę się więcej wgłębiał w dodatkowe DCLe.

DCL wymaga dwóch plików. Konieczny jest plik tekstowy z rozszerzeniem DCL, zawierający definicję okienka oraz plik LSP zawierający całą obsługę okienka.
Najprostszy, przykład wygląda następująco:

//W pliku test.DCL:
Najprostszy : dialog {
: button {
label = "Anuluj";
key = "Anuluj";
is_cancel = true;
}
}

; W pliku LSP
(defun c:testDCL()
(setq dcl_id(load_dialog "test.DCL"))
(new_dialog "Najprostszy" dcl_id)
(start_dialog)
(unload_dialog dcl_id)
)

Przykład nic nie robi, wciśnięcie przycisku spowoduje jedynie zamknięcie okna. Obsługą w LISP, zajmiemy się później. Możemy jednak na tym przykładzie zobaczyć podstawowe zasady tworzenia okienek DCL:

  1. Nazwa elementu musi być poprzedzona znakiem :
  2. Wszystkie definicje elementów okienka, muszą być ograniczone znakami {}
  3. Istnieją atrybuty dla każdego elementu okienka. Dla różnych elementów mogą być różne atrybuty
  4. Każdy atrybut musi być zakończony znakiem ;
  5. Nie wszystkie atrybuty muszą być zadeklarowane. Dla tych atrybutów, które nie są określone, przyjmowane są wartości domyślne.
  6. W jednym pliku może być zdefiniowane wiele okienek.

Okienka DCL mają układ tabelaryczny. Domyślnie, kolejne definicje kontrolek są wyświetlane w kolejnych wierszach. jeśli chcemy, żeby wyświetlały się w innym układzie, konieczne jest grupowanie ich w rzędy i kolumny, które mogą być zagnieżdżone.

Kilka przykładowych definicji

Dialog

Nazwa :dialog {
label = "Przykład";
}

Definicja każdego okienka musi zaczynać się właśnie od dialog. Nazwa poprzedzająca słowo kluczowe :dialog, identyfikuje jednoznacznie okienko. Chyba jedynym interesującym atrybutem, dla całego okienka jest label definiujący tekst wyświetlany w lewym górnym narożniku okienka, na niebieskim tle. Dla całego okienka istotna jest też jego szerokość i wysokość. Jest ona automatycznie obliczana tak, żeby okienko obejmowało wszystkie elementy tworzące okienko.

Pole tekstowe

:edit_box {
key = "DTxt";
label = "X";
value = "Wartość";
mnemonic = "D";
action = "(DCL_CheckNum)";
edit_limit = 10;
edit_width = 10;
}

Pole tekstowe to element, w którym wpisać możemy dowolny tekst.
Najbardziej interesujące atrybuty tego elementu to:
key - identyfikator elementu. W czasie pracy z okienkiem aby odczytać wartość z pola. musimy znać jego key
action - nazwa funkcji, jaka ma się wykonać, po zakończeniu edycji wartości, czyli po zaprzestaniu pisania w polu. Funkcja taka może np. sprawdzać poprawność wpisanych danych
value - to domyślna treść, jaka zostanie wyświetlona przy pierwszym uruchomieniu okna.
Dodatkowe własności to:
label - etykieta, jest to tekst, jaki wyświetlał się będzie z boku pola tekstowego, może służyć opisaniu znaczenia pola
mnemonic - jako wartość wpisujemy tutaj jedną literę. Kiedy okno będzie aktywne, po wciśnięciu tej litery na klawiaturze, "kursor" zostanie przeniesiony do tego elementu.
alignment - wyrównanie, określa jak dany element ma być wyrównany w pionie i poziomie.
width - szerokość jest automatycznie obliczana na podstawie szerokości innych elementów, chyba, że dodatkowo jest ustawiony atrybut fixed_width. Spisujemy tutaj liczbę znaków, jakie jednocześnie mają być wyświetlane
edit_width - określa ilość znaków, jakie mają być jednocześnie wyświetlane, określa szerokość elementu. edit_limit - określa maksymalną ilość znaków, jakie mogą być wpisane w polu. (domyślne jest to 132), maksymalnie można wpisać 256.
is_enabled - określa czy element jest domyślne dostępny, Jeśli zostanie ustawiony na nil. element będzie domyślne zablokowany, taki szary, jakby wyłączony.
upper_only lower_only - definiuje, czy wpisywane mogą być jedynie małe/wielkie litery
password_char - maska hasła. Znak podany w tym atrybucie, będzie się wyświetlał zamiast liter, które użytkownik będzie wpisywał

Jeśli chcemy, aby możliwe było wpisanie większej ilości tekstu, wyświetlanego w więcej, niż jednej linii, konieczne jest użycie innej kontrolki: multi_edit_box

Przycisk

:button {
label="Wskaż";
key="DP";
action = "(wykonaj)"; }

Najważniejsze atrybuty przycisku to: label - etykieta, czyli tekst wyświetlany na przycisku
key - Identyfikator przycisku, bardzo przydatny, jeśli chcemy w czasie działania programu przypisać funkcję do przycisku, wynika z tego, że w czasie działania programu, temu samemu przyciskowi można przypisać różne działanie, w zależności od zaistniałych warunków. Przypisanie takie, może być wykonane jedynie przed uruchomieniem okienka, ale przy odrobinie wyobraźni i wiedzy, o której później, da się zrobić dobre narzędzie.
action - nazwa funkcji, jaka ma się wykonać po naciśnięciu przycisku. Możliwe jest nie przypisywanie funkcji do przycisku w pliku DCL, da się to zrobić w czasie działania programu.

Pole wyboru

:toggle {
label = "Dodaj kreskowanie podstawy";
value = "1";
key = "Kresk";
}

Dla tych, którzy znają inne języki programowania, to jest to samo co checkbox, dla pozostałych, to jest pole, które można zaznaczać.
Większość atrybutów, to wspomniane wcześniej, szerokość, wyrównanie, funkcja wywoływana po zmianie wartości,blokowanie, wartość itp.
Jedyne, co jest tutaj istotne, to właśnie wartość. Wartość określamy w postaci "0" i "1". Czyli "0" świadczy o tym, że znacznik nie jest zaznaczony, "1" oznacza włączenie opcji. Ważne jest, że NIE są to wartości liczbowe a są typu tekstowego.

Przyciski opcji

:boxed_radio_column {
label = "Opcje" ;
: radio_button {
key = "rb1" ;
label = "Opcja&1" ;
value = "1" ;
}
:radio_button {
key = "rb2" ;
label = "Opcja&2" ;
}
:radio_button {
key = "rb3" ;
label = "Opcja&3" ;
}
}

Wszystkie atrybuty opisane są szczegółowo w dokumentacji, więc nie będę poświęcał im więcej czasu, to są wszystko atrybuty, z którymi spotkaliśmy się już wcześniej.
Najważniejsze jest tutaj, że cała grupa przycisków wzajemnie oddziałuje na siebie.
W linii value = "1". Określiliśmy która z przycisków opcji ma być domyślne na początku ustawiona. W całej grupie może być tylko jedna taka, domyślnie ustawiona opcja.
Wartości podobnie jak w toggle są typu tekstowego i możliwe są wartości "1" lub "0"

Listy

:popup_list {
key = "punkty";
edit_width = 15;
alignment = "left";
label = "Punkt wstawienia";
}

Jest to odpowiednik combobox znanego z innych jezyków programowania, czyli lista, gdzie wyświetlana jest jedna linia, a obok niej strzałka skierowana w dół, po kliknięciu której rozwija się cała lista dostępnych wartości

:list_box {
action = "(subdir)";
label = "Wybierz kolor";
list = "Czerwony\nZielony\nNiebieski";
value = "0";
}

czyli zwykła lista z wierszami i kolumnami. wszystkie "komórki", są jednego typu, czyli teksty. Dla tych, którzy szukają czegoś jak datagrid z C++ mam przykrą wiadomość: DCL nie daje takiej możliwości.

Obsługa list jest dosyć skomplikowana, wymaga pracy na globalnej liście wartości, może okaże się to jasne na przykładzie. Na razie warto jedynie zaznaczyć, że wartość odczytana z listy, to indeks wybranego element w liście. Nie mam możliwości odczytania wartości wybranej, więc trzeba mieć dostępną listę wszystkich wartości i pobierać z niej wartość o danym indeksie.
Listę wartości dostępnych w kontrolkach można zdefiniować już w pliku DCL, ustawiając atrybut list na wartość np.: "Czerwony\nZielony\nNiebieski"; Jednak z mojego doświadczenie wynika, że częściej zdarza się zawartość list wypełniać w czasie działania programu. Moim zdaniem daje to też większe możliwości.

Obrazki

:image {
action = "(dimvars)";
height = 24;
key = "DB";
width = 36;
}

Istnieje możliwość wyświetlania obrazków w oknach DCL. Mogą to obrazki w formacie SLD czyli slajdów, lub można w czasie ładowania programu "rysować" w przestrzeni obrazka przy użyciu wektorów. Może więcej o tym później. Najprostsza definicja obrazka musi zawierać atrybuty key, szerokość i wysokość. Zawartość będziemy rysować w czasie działania programu.
Bardzo zbliżony do tego elementu jest inny: image_button jak łatwo się domyślić jest to połączenie przycisku i obrazka.

Przykład

Poznaliśmy już podstawowe pojęcia z zakresu okienek dialogowych. Istnieje kilka innych, o których nie wspominałem. są to teksty, "ozdobniki" w formie pustych odstępów, i coś o czym na pewno zapomniałem.
Ale może przejdźmy do konkretów. Na potrzeby naszego kursu, przygotowałem taki mały przykład, na podstawie parametrów, długości, szerokości i wysokości, narysowany zostanie prostopadłościan. Nasze okienko będzie wyglądało jak widać na poniższym obrazku:

dcl

Układ naszego przykładu ma następującą postać:

dcllayout

 

Gdzie całe okienko zostało podzielone na dwa wiersze, zaznaczone czarną ramką. Pierwszy wiersz zawiera w zasadzie wszystkie wymiary, podgląd itp. a drugi wiersz, to przyciski Rysuj i Anuluj. Oba te wiersze podzielone są na kolumny. Główna część podzielona jest na dwie kolumny (zaznaczone czerwoną ramką), dolny wiersz, to trzy kolumny (zaznaczone kolorem ciemnym niebieskim ). Pierwsza jest pusta, druga zawiera przycisk Rysuj, trzecia Anuluj. Prawa kolumna głównego wiersza to tylko podgląd. Lewa została podzielona na wiersze (zaznaczona kolorem jasno niebieskim). Wiersz z wymiarami zawiera wiesze (zielone) dla poszczególnych wymiarów. Wiersze te podzielone są na kolumny (pomarańczowe). Widzimy na tym przykładzie, że sam rozkład kontrolek jest dosyć skomplikowany. Rozkład na wiersze i kolumny w kodzie sprowadza się do użycia elementów row, column, boxed_row, boxed_column, boxed_radio_row, boxed_radio_column.

Elementy te mogą być wzajemnie zagnieżdżone.

Row i column definiują wiersz i kolumnę.
Przedrostek boxed, powoduje, że cały wiersz/kolumna będzie ograniczona ramką w naszym przykładzie ramki są wokół kolumny podglądu i danych.
Przedrostek radio, powoduje, że tworzona jest grupa przycisków wzajemnie wykluczających. W tym przykładzie tego nie, ale pojawi się w następnym.

Pliki naszych przykładów będą miały następującą postać:

Test : dialog {
label = "Przykład";

:row{

:column{

:boxed_column{
label = "dane";
height = 5;
fixed_height = true;
:row{
:edit_box {
edit_limit = 10;
edit_width = 10;
key = "DTxt";
label = "X";
mnemonic = "D";
action = "(DCL_CheckNum)";
}
: button {
label="Wskaż";
key="DP";
}
}
:row{
:edit_box {
edit_limit = 10;
edit_width = 10;
key = "STxt";
label = "Y";
mnemonic = "S";
action = "(DCL_CheckNum)";
}
: button {
label="Wskaż";
key="SP";
}
}
:row{
:edit_box {
edit_limit = 10;
edit_width = 10;
key = "WTxt";
label = "Z";
mnemonic = "W";
action = "(DCL_CheckNum)";
}
: button {
label="Wskaż";
key="WP";
}
;}
;}
:row{
height = 7;
}
:popup_list {
key = "punkty";
edit_width = 15;
alignment = "left";
label = "Punkt wstawienia";
}
: toggle {
label = "Dodaj kreskowanie podstawy";
value = "1";
key = "Kresk";
}
}
:column{
label = "podgląd";
: image {
key = "sld";
height = 20;
width = 50;
color = 0;
is_enabled = false;
is_tab_stop = false;
}
}
}
:column{
width = 50;
fixed_width = true;
alignment = right;
spacer;
:row{
: button {
label = "Rysuj";
mnemonic = "O";
key = "OK";
width = 20;
fixed_width = true;
}
: button {
label = "Anuluj";
mnemonic = "C";
key = "Anuluj";
is_cancel = true;
width = 20;
fixed_width = true;
}
}
}
}

Natomiast plik obsługujący nasz przykład ma postać:

(vl-load-com) (setq *ZCApp* (vlax-get-zwcad-object))
(setq *ActiveDocument* (vla-get-activedocument *ZCApp*))
(setq *MSpace* (vla-get-modelspace *ActiveDocument*))

(defun L2v(lista typ / NObj SelObjArray iCount iList SelObjArrayVar)
(setq NObj (length lista)
SelObjArray (vlax-make-safearray typ (cons 0 (1- NObj) ))
iCount 0)
(repeat NObj
(vlax-safearray-put-element SelObjArray iCount (nth iCount lista))
(setq iCount (1+ iCount))
)
(setq SelObjArrayVar (vlax-make-variant SelObjArray))
)

(defun FullGetpoint (startPoint promp DefVal / ans outVal )
(if startPoint
(setq ans(vl-catch-all-apply 'getpoint (list startPoint promp )))
(setq ans(vl-catch-all-apply 'getpoint (list promp )))
)
(if (not(vl-catch-all-error-p ans))
(progn
(if ans
(setq outVal ans)
(setq outVal DefVal)
)
)
(setq outVal nil)
)
outVal
)

(defun FullGetdist (startPoint promp DefVal / ans outVal )
(if startPoint
(setq ans(vl-catch-all-apply 'getdist (list startPoint promp )))
(setq ans(vl-catch-all-apply 'getdist (list promp )))
)
(if (not(vl-catch-all-error-p ans))
(progn
(if ans
(setq outVal ans)
(setq outVal DefVal)
)
)
(setq outVal nil)
)
outVal
)

(defun DCL_WriteToImage (slide Key / War width height)
(setq width (dimx_tile Key))
(setq height (dimy_tile Key))
(start_image Key)
(fill_image 0 0 width height 0)
(slide_image 0 0 width height slide)
(end_image)
)

(defun DCL_WriteToList (Wartosci Nazwa / War )
(start_list Nazwa )
(foreach War Wartosci
(add_list War )
)
(end_list)
)

(defun C:testDCL ( / LastCMDECHO dcl_idp what_next sldName DWidth SWidth WWidth PtNr Hatch )
(if (null dcl_idp)(progn
(setq dcl_idp (load_dialog "Test.DCL")) ; Należy pamiętać, że plik DCL musi być w widocznym katalogu, lub podać musimy pełną ścieżkę
))

(setq what_next 1)
(while (> what_next 0)
(if(not(new_dialog "Najprostszy" dcl_idp))(exit))
(DCL_WriteToList (list "1" "2" "3" "4" "5" "6" "7" "8") "punkty" )
(setq sldName "BOX.SLD") ; Należy pamiętać, że plik sld musi być w widocznym katalogu, lub podać musimy pełną ścieżkę
(DCL_WriteToImage sldName "sld")

(if (numberp DWidth) (set_tile "DTxt" (rtos DWidth)))
(if (numberp SWidth) (set_tile "STxt" (rtos SWidth)))
(if (numberp WWidth) (set_tile "WTxt" (rtos WWidth)))
(action_tile "Anuluj" "(done_dialog -1)")
(action_tile "DP" "(done_dialog 11)")
(action_tile "SP" "(done_dialog 12)")
(action_tile "WP" "(done_dialog 13)")

(action_tile "OK"
"
(setq DWidth (atof(get_tile \"DTxt\")))
(setq SWidth (atof(get_tile \"STxt\")))
(setq WWidth (atof(get_tile \"WTxt\")))
(setq PtNr (get_tile \"punkty\"))
(setq Hatch (if (= (get_tile \"Kresk\") \"0\" ) nil t))
(done_dialog 1)
")

(setq what_next (start_dialog))
(cond
((= what_next 11) (setq DWidth (FullGetdist nil "Podaj szerokość" nil)))
((= what_next 12) (setq SWidth (FullGetdist nil "Podaj długość" nil)))
((= what_next 13) (setq WWidth (FullGetdist nil "Podaj wysokość" nil)))
((= what_next 1) (RysujBox DWidth SWidth WWidth PtNr Hatch))
)

)
(command "regen")

)

(defun RysujBox (X Y Z Pt IsHatch / P0 Px Pt Ptc Podstawa1 Podstawa2 bok1 bok2 bok3 bok4
element Box Hatch )
(setq P0 (list 0 0 0))
(setq Px(FullGetpoint nil "Wskaż punkt wstawinia" nil))

(setq Pt (+(atoi Pt)1))
(cond
((= Pt 1)(setq Ptc (list 0 (- Y) 0)))
((= Pt 2)(setq Ptc (list (- X) (- Y) 0)))
((= Pt 3)(setq Ptc (list (- X) 0 0)))
((= Pt 4)(setq Ptc (list 0 0)))
((= Pt 5)(setq Ptc (list 0 (- Y) (- Z))))
((= Pt 6)(setq Ptc (list (- X) (- Y) (- Z))))
((= Pt 7)(setq Ptc (list (- X) 0 (- Z))))
((= Pt 8)(setq Ptc (list 0 0 (- Z))))
)
(setq Podstawa1(drawRectByDiagonal P0(list X Y 0))
Podstawa2(drawRectByDiagonal (list 0 0 Z)(list X Y Z)))
(setq bok1 (vla-addline *MSpace* (vlax-3d-point P0)(vlax-3d-point(list 0 0 Z)))
bok2 (vla-addline *MSpace* (vlax-3d-point (list X 0 0))(vlax-3d-point(list X 0 Z)))
bok3 (vla-addline *MSpace* (vlax-3d-point (list X Y 0))(vlax-3d-point(list X Y Z)))
bok4 (vla-addline *MSpace* (vlax-3d-point (list 0 Y 0))(vlax-3d-point(list 0 Y Z))))
(setq Box(list Podstawa1 Podstawa2 bok1 bok2 bok3 bok4))

(foreach element Box
(vlax-invoke-method element 'MOVE (vlax-3d-point P0)(vlax-3d-point Px))
(vlax-invoke-method element 'MOVE (vlax-3d-point P0)(vlax-3d-point Ptc))
)
(if IsHatch (setq Hatch (HatchinObj Podstawa1 )))
nil
)

(defun HatchinObj (Obj / NewHatch )
(setq NewHatch (vla-addHatch *MSpace* zcHatchPatternTypePreDefined "SOLID" t ))
(vla-AppendOuterLoop NewHatch Obj )
(vla-put-HatchStyle NewHatch zcOuter)
(vla-update NewHatch)
NewHatch
)

(defun DCL_CheckNum ( / )
(if (not(> (atof $value) 0)) (set_tile $key "")(set_tile $key (rtos(atof $value)) ) )
)
(defun drawRectByDiagonal (P1 P2 / Polypoints Poly)
(setq Polypoints (l2v(append P1
(list (car P2 )(cadr P1)(caddr P1))
P2
(list (car P1)(cadr P2)(caddr P1)))
vlax-vbDouble))
(setq Poly(vlax-invoke-method *MSpace* 'add3DPoly Polypoints ))
(vlax-put-property Poly 'Closed t)
Poly
)

(C:testDCL)

 

Mam nadzieję, że po przedstawieniu podstawowych informacji o DCL struktura okienka jest jasna, mamy w wierszach i kolumnach typowe kontrolki, i wszystko powinno być jasne. Skupmy się wiec na obsłudze okienka. W powyższym listingu zdefiniowanych mamy kilka podstawowych funkcji, jak zamiana listy na variant (l2v), obsługa integracji z użytkownikiem (FullGetpoint) (FullGetDist). Nie dotyczą one bezpośrednio DCL i były już wcześniej opisywane, więc nie poświęcajmy im więcej czasu. Interesujące jest dla nas teraz jak uruchomić okienko.

Najważniejsze funkcje, służące obsłudze DCL to:

  • (load_dialog) - Wczytuje plik definicji okien do przestrzenie ZWCADa. Nie są wyświetlane żadne okna, w pliku może być kilka definicji okien. Funkcja zwraca identyfikator okna, należy go zapamiętać w zmiennej, będzie on później przydatny.
  • (new_dialog) - Tworzy nowe okno, Jest ono utworzone, można dokonać inicjalizacji, wartości w listach, rysować w obrazkach, przypisywać działania do przycisków. Dotychczas okno nie jest jeszcze wyświetlone na ekranie.
  • (start_dialog) - teraz okno zostaje wyświetlone, a funkcja wykonuje się tak długo, jako okno jest aktywne. Dopiero, kiedy zostanie zamknięte, funkcja zwróci wartość.
  • (done_dialog) - funkcja zamykająca okno, Przestaje ono być wyświetlane.
  • (unload_dialog) - funkcja zwalnia uchwyt pliku definicji okna, jest to konieczne na koniec pracy z oknem
  • (term_dialog) - zamyka wszystkie okna. Działa analogicznie jak done dialog, ale dotyczy wszystkich aktywnych okien
  • (set_tile) - ustawia wartość kontrolki, np. tekst wpisany w edit_box, lub aktwny wybór w liście
  • (get_tile) - pobiera wartość z kontrolki
  • (action_tile) - ustawia działanie jaki ma być wykonane po zmianie wartości kontrolki, lub funkcję uaktywnianą przez wciśnięcie przycisku.

A w praktyce wygląda to w sposób następujący:

  1. (defun C:testDCL ( / LastCMDECHO dcl_idp what_next sldName DWidth SWidth WWidth PtNr Hatch )
  2. (if (null dcl_idp)(progn
  3. (setq dcl_idp (load_dialog "Test.DCL"))
  4. ))
  5. (setq what_next 1)
  6. (while (> what_next 0)
  7. (if(not(new_dialog "Test" dcl_idp))(exit))
  8. (DCL_WriteToList (list "1" "2" "3" "4" "5" "6" "7" "8") "punkty" )
  9. (setq sldName "BOX.SLD")
  10. (DCL_WriteToImage sldName "sld")
  11. (if (numberp DWidth) (set_tile "DTxt" (rtos DWidth)))
  12. (if (numberp SWidth) (set_tile "STxt" (rtos SWidth)))
  13. (if (numberp WWidth) (set_tile "WTxt" (rtos WWidth)))
  14. (action_tile "Anuluj" "(done_dialog -1)")
  15. (action_tile "DP" "(done_dialog 11)")
  16. (action_tile "SP" "(done_dialog 12)")
  17. (action_tile "WP" "(done_dialog 13)")
  18. (action_tile "OK"
  19. "
  20. (setq DWidth (atof(get_tile \"DTxt\")))
  21. (setq SWidth (atof(get_tile \"STxt\")))
  22. (setq WWidth (atof(get_tile \"WTxt\")))
  23. (setq PtNr (get_tile \"punkty\"))
  24. (setq Hatch (if (= (get_tile \"Kresk\") \"0\" ) nil t))
  25. (done_dialog 1)
  26. ")
  27. (setq what_next (start_dialog))
  28. (cond
  29. ((= what_next 11) (setq DWidth (FullGetdist nil "Podaj szerokość" nil)))
  30. ((= what_next 12) (setq SWidth (FullGetdist nil "Podaj długość" nil)))
  31. ((= what_next 13) (setq WWidth (FullGetdist nil "Podaj wysokość" nil)))
  32. ((= what_next 1) (RysujBox DWidth SWidth WWidth PtNr Hatch))
  33. )
  34. )
  35. (command "regen")
  36. )

3. Ładownie pliku definicji okien. Jako parametr wywołania funkcji wpisujemy ścieżkę i nazwę pliku DCL. Ścieżkę można pominąć, jeśli plik jest w katalogu widocznym przez ZWCADa, czyli w katalogu ZWCADa, w katalogu wyszczególnionym w opcjach.
W linii 2 mamy warunek, mający sprawdzić, czy plik nie jest wcześniej wczytany. Jest to dobre rozwiązanie, jeśli mamy kilka okien w jednym pliku. W takim przypadku wczytujemy plik tylko raz.

8. new_dialog tworzy okno, Jeśli w pliku dcl nie ma zdefiniowanego okienka o zadanej nazwie lub nie dało się otworzyć pliku definicji, warunek na początku linii obsłuży ten wyjątek, program nie wyświetli błędu, a jedynie przejdzie do polecenia (exit) czyli zakończy działanie funkcji.

9. Po utworzeniu okienka, inicjalizujemy wartości w liście i podglądzie. W tym celu utworzyłem oddzielne funkcje, ich szczegółowym opisem zajmę się później.
Jeśli nasze okienko będzie się wyświetlało nie tylko raz, ale po ponownym otwarciu chcemy, żeby zapamiętywane zostały poprzednio wpisane wartości musimy te wartości wpisać, i teraz jest najlepszy czas, żeby to zrobić. Wartości przechowujemy w zmiennych globalnych, więc po ponownym uruchomieniu okienka po prostu wpisujemy odpowiednie wartości we właściwe pola.

13-15. Możemy przetłumaczyć jako: Jeśli zmienna np. Dwidth, jest typu liczbowego, to jego wartość w postaci tekstowej wpisujemy do kontrolki DTxt. Funkcja (set_tile) przyjmuje dwa parametry wywołania: Uchwyt kontrolki, czyli to co w definicji DCL jest atrybutem key, oraz wartość, która ma być wpisana, oba parametry muszą być typu tekstowego.

16-30. Funkcje obsługujące przyciski.
(action_tlie), przypisuje działanie do przycisku. Podobnie jak poprzednio, oba parametry muszą być typu tekstowego. Pierwszy parametr, to identyfikator przycisku, czyli key, drugi, to tekst - nazwa funkcji, która ma być wywołana w momencie kliknięcia przycisku. (done_dialog) - wyłącza okno. To NIE jest tak, że po prostu przestaje ono być wyświetlane, ale gdzieś tam tkwi w pamięci. Po prostu jest wyłączane. Jest to o tyle problematyczne, jeśli chcemy, żeby po naciśnięciu przycisku użytkownik wskazał punkt, lub dwa. W takim przypadku musimy właśnie zapisać wszystkie wartości w zmiennych, wyłączyć całe okienko , a kiedy użytkownik zrobi co ma zrobić, musimy przywrócić wszystkie wartości i ponownie wyświetlić okienko. Funkcja (done_dialog) przyjmuje jeden opcjonalny parametr, jest to wartość liczbowa. I to właśnie ona zostanie zwrócona przez funkcję (start_dialog) Dzięki takiemu możemy w dalszej części programu rozpoznać "powód" zamknięcia okienka. Nasz program będzie się inaczej zachowywał, kiedy zostanie zamknięty, by użytkownik mógł podać jakąś wartość, punkt itp., a inaczej, jeśli ma po prostu zakończyć działanie po wykonaniu tego, do czego był przeznaczony.

Na przykładzie obsługi przycisku "OK", możemy zauważyć, że jednemu przyciskowi może być przypisana nie tylko jedna funkcja, ale cała ich sekwencja. A na dodatek może ona być warunkowa. Ważne, że funkcja, lub cała ich sekwencja musi być typu tekstowego, stąd właśnie bierze się w liniach 23-27 spora liczba znaków \. Sens takiego zachowania, jest taki, że teksty muszą być ograniczone na początku i końcu znakiem", natomiast ID kontrolek również muszą być ograniczone tymi samymi znakami, zdarzyło się więc że teksty są zagnieżdżone. Gdyby po prostu zostawić znaki " w każdym miejscu, gdzie być powinny, drugi cudzysłów zostałoby to zinterpretowany jako koniec tekstu, i wywołałoby to błąd. Znak \ powoduje, że " znak stojący za nim nie jest interpretowany jako znak końca tekstu, a po prostu znak bez żadnego znaczenia. Mam nadzieję, że wystarczająco to wyjaśniłem.

32 start_dialog Funkcja wyświetlająca okno. Po wyświetleniu okna, do niego zostanie przekazane sterowanie, kolejne linie nie zostaną wykonane, tak długo, jak okno będzie aktywne. Funkcja zwraca taką wartość, jaka jest parametrem funkcji done_dialog zamykającej okno. Może się też zdarzyć tak, że okno nie jest zamknięte przez funkcję done_dialog, a term_dialog. Wówczas start_dialog zwróci -1.

Wątpliwości może jeszcze budzić zasadność i sens zapętlenia wszystkiego w while. Chodzi o to, że bez tego, okienko wyświetliłoby się tylko raz. Po jego zamknięciu nie byłoby możliwości jego ponownego wyświetlenia, jedynie przez ponowne uruchomienie funkcji a wówczas stracilibyśmy wpisane już wartości. Zamknięcie okienka bywa jednak konieczne np.: do podania długości, lub wskazania punktu wstawienia całego elementu. Dzięki konstrukcji while okno będzie się wyświetlało za każdym razem, tak długo, aż start_dialog zwróci wartość mniejszą niż 0. A wartość jaka będzie zwrócona, zależy od parametru funkcji done_dialog. Czyli do każdego przycisku przypisujemy akcję (done_dialog ) z innym parametrem, ale np. większym niż 0. Tylko jednemu przyciskowi przypisujemy done_dialog 0 lub -1 i ten element, będzie na stałe zamykał okno, pozostałe będą zamykały okno tylko tymczasowo.
Poniżej funkcji start_dialog przez cond możemy obsłużyć wszystkie przypadki czyli dla każdego przypadku zamknięcia okna wpiszemy poszczególne procedury.

Mam nadzieję, że idea i sposób postępowania są jasne. Ewentualne pytania, wątpliwości proszę kierować na forum, postaram się odpowiedzieć.

Obsługa list
  1. (defun DCL_WriteToList (Wartosci Nazwa / War )
  2. (start_list Nazwa )
  3. (foreach War Wartosci
  4. (add_list War )
  5. )
  6. (end_list)
  7. )

Istotne są tu trzy funkcje:

  • (start_list) - rozpoczyna operacje na liście, Parametrem wywołania funkcji jest atrybut key listy, którą chcemy manipulować.
    Istnieje możliwość określenia trybu, w jakim lista ma być uaktywniona. Tryb określamy przez podanie odpowiedniej liczby jako drugi parametr wywołania funkcji. Tryby te to:
    • Nadpisanie całej listy np. (start_list "ID") lub (start_list "ID" 3) jest to tryb domyślny
    • Zmiana jednego elementu w liście (start_list "ID" 1 4) zamienia piąty element w liście
    • Dodanie elementu do listy (start_list "ID" 2)
  • (add_list) ustawia wartość w aktywnej liście. To, która lista jest aktywna, i czy wartość ma być zamieniona, czy dopisana, określane jest w poprzedniej funkcji - (start_list)
  • (end_list) - kończy działanie na aktywnej liście.
  • (get_tile), (set_tile) te dwie funkcje pozwalają w czasie działania programu odczytać i ustawić, który element z listy jest aktywny - zaznaczony. (get_tile) zwraca indeks zaznaczonego elementu - ale ważne jest, że w formie tekstu np. "3" oznacza czwarty element listy. Analogicznie aby ustawić, że aktywny jest trzeci element z listy musimy użyć (set_tile "ID" "2")

Jak widzimy powyżej, istnieje możliwość dopisywania do listy, nadpisywania listy, nie ma natomiast możliwości usuwania elementu z listy. Moim zdaniem należy usunąć element z listy, którą chcemy wyświetlać, a następnie jeszcze raz wyświetlić całą listę.

Kolejna sprawa, to listy wielokolumnowe. Jeśli chcemy np. w liście wypisać współrzędne punków. Kolejne współrzędne powinny być wyrównane w pionie czyli potrzebne są nam kolumny. Da się coś takiego zrobić, ale jest to jeszcze bardziej nieporęczne, niż całe pozostałe mechanizmy list.
Jedyne co możemy wpisywać do kolumn, to teksty a na dodatek nie możemy wpisywać w określonych komórkach, a jedynie całe wiersze. Aby móc podzielić wiersz na kolumny, musimy podzielić nasz tekst znakiem \t w ogólnej filozofii LISP \t oznacza tabulator i to jest jedyny sposób na wyrównanie kolumn tabeli. Nie jest możliwe zaznaczanie jednej wybranej komórki w liście, jedyne co możemy zrobić to zaznaczyć cały wiersz, więc te kolumny, to takie niby są, ale są bardzo niefunkcjonalne.



Przy okazji list, warto zauważyć jeszcze, że wszystkie te zasady działania dotyczą list_box i popup_list

Obsługa obrazów
  1. (defun DCL_WriteToImage (slide Key / War width height)
  2. (setq width (dimx_tile Key))
  3. (setq height (dimy_tile Key))
  4. (start_image Key)
  5. (fill_image 0 0 width height 0)
  6. (slide_image 0 0 width height slide)
  7. (end_image)
  8. )

Funkcje obsługujące obrazki:

  • (start_image) - ustawia aktywny element. Określa przestrzeń w któej będziemy rysować
  • (fill_image) - ustawia kolor wypełnienia. Dzięki temu tło może nie być czarne, tylko mieć dowolny inny kolor.
  • (slide_image) - powoduje wyświetlenie slajdu w kontrolce.
  • (vector_image) - rysuje wektor w przestrzeni obrazka.
  • (end_image) - kończy działanie na aktywnym elemencie okna
  • (dimx_tile) - zwraca szerokość elementu okna
  • (dimy_tile) - zwraca wysokość elementu okna

Najczęściej jest to wykorzystywane do wyświetlania slajdów. Aby obrazek był dobrze dopasowany do dostępnej przestrzeni kontrolki, potrzebne są nam szerokość i wysokość kontrolki. Te informacje możemy odczytać właśnie przez funkcje dimx_tile i dimy_tile, Następnie użyć ich należy w funkcjach slide_image do określenia ja jakiej przestrzeni slajd ma być wyświetlony. Nigdy tego nie robiłem, ale myślę, że może być też możliwe tworzenie kolorowych okien, czyli jedno image_box a w nim kilka slajdów, a kto wiem może przez fill_image da się zrobić tak, że tło nie jest jednolite, a różnokolorowe.

Pozostałe funkcje

Poprawność danych

Często istnieje konieczność ograniczenia wyobraźni użytkowników naszej nakładki. DCL daje podstawowe możliwości w tym zakresie. Możemy zdefiniować funkcję, która pobiera wartość z kontrolki, która wywołała tą funkcję, sprawdzić, czy dane są poprawne, czy są odpowiedniego typu,i mieszczą się w zadanym zakresie i ewentualnie je poprawić.

  1. (defun DCL_CheckNum ( / )
  2. (if (not(> (atof $value) 0)) (set_tile $key "")(set_tile $key (rtos(atof $value)) ) )
  3. )

Używamy tu dwu istotnych zmiennych
$value - wartość z aktywnej kontrolki
$key - identyfikator aktywnej kontrolki
W przedstawionym powyżej przypadku, pobierana jest wartość kontrolki, sprawdzane jest, czy jest ona większa od 0, jeśli nie, czyli jest mniejsza, lub wartość nie jest liczbą, to do kontrolki zostaje wpisana pusta wartość. Dzięki takiej funkcji, można jedną funkcją obsłużyć kontrolę stanu wielu elementów okna.

Tryby

Istnieje możliwość ustawienia trybu w jakim wyświetlona będzie kontrolka. Dostępne są następujące tryby:

0. Odblokowanie - jeśli element był zablokowany, aktywując ten tryb, ustawimy, że element staje się dostępny
1. Zablokowanie - Powoduje, że element będzie zablokowany, taki wyszarzony i nic się nie da tam zmienić. Przydatne jest to, kiedy w zależności od wybrania jednej opcji, inne stają się nie istotne, nie mające wpływu. Żeby nie zaciemniać, lepiej jest je zablokować
Obrazowo chodzi o to, co zaznaczone na czerwono:

dcl disable

 

2. Aktywuje kontrolkę, czyli powoduje jakby przeniesienie kursora do określonej kontrolki.
3. Zaznacza zawartość kontrolki
4. Zmienia podświetlenie obrazka.

Przykład:

(mode_tile "ID" 1)

Gdzie 1, to przykładowy numer trybu, jest to tryb blokujący kontrolkę jak na załączonym powyżej obrazku.

gif arrow10 Pobierz pliki z pełnym kodem źródłowym