Возвращаясь к dcl – написание, обработка, отладка
Пока свежо в памяти, решил рассказать, как я разрабатываю функции, использующие dcl-окна.
Допустим, нам необходимо, чтобы функция выводила окошко с определенным нами сообщением и двумя кнопками - OK и Cancel. Для усложнения задачи добавим текстовое поле и флажковый переключатель, который будет менять режим доступности текстового поля.
Итак, прежде всего - создаем временный dcl-файл и прописываем сам диалог. Это мне требуется только для того, чтобы проверить - как окно будет выглядеть в результате. Допустим, создали, проверили - все замечательно.
1 2 3 4 5 6 | dlg:dialog{label="Диалог"; :text{key="msg";} :toggle{key="chk_edit";label="Редактировать текст";} :edit_box{key="txt_edit";width=50;} ok_cancel; } |
После этого пишется функция, которая будет создавать временный dcl-файл в каталоге %temp% (к примеру) и возвращать полное имя такого файла.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | (defun fun_create-dcl-file (/ file handle) ;; Возвращает имя файла dcl (setq file (strcat (vl-string-right-trim "\\" (getenv "tmp")) "\\dlg.dcl") handle (open file "w") ) ;_ end of setq (foreach item '("dlg:dialog{label=\"Диалог\";" " :text{key=\"msg\";}" " :toggle{key=\"chk_edit\";label=\"Редактировать текст\";}" " :edit_box{key=\"txt_edit\";width=50;} " " ok_cancel;" " }" ) (write-line item handle) ) ;_ end of foreach (close handle) file ) ;_ end of defun |
Теперь приступаем к разработке основной функции.
После стандартных строк
1 2 3 | (setq dcl_file (fun_create-dcl-file) dcl_id (load_dialog dcl_file) ) ;_ end of setq |
по идее должно идти нечто типа
1 | (new_dialog "dlg" dcl_id) |
Но обратимся к справке (мой вольный перевод):
1 (new_dialog dlgname dcl_id [action [screen-pt]])Аргументы
dlgname Строка, определяющая создаваемый диалог (в одном dcl-файле может быть описано несколько диалогов; главное, чтобы у них имена были уникальными) dcl_id Идентификатор, созданный через функцию (load_dialog...) action Необязательный параметр. Строка, содержащая выражение на языке AutoLISP, являющееся реакцией по умолчанию на изменение любого элемента диалога. Строка становится обязательной, если определяется следующий параметр. Допускается применение пустой строки ("") screen-pt Координаты двумерной точки, определяющие положение левого верхнего угла окна диалога на экране.
Куски справки, не являющиеся критичными, я пропустил.
То есть получается, что в
1 | (new_dialog "dlg" dcl_id) |
можно указать какую-то дополнительную "callback"-функцию, которая и будет срабатывать по умолчанию! Отлично, нарисуем?
Что в эту функцию надо передавать? Понятно, что надо как минимум передавать ключ элемента диалога и его значение (после изменения). Еще крайне желательно бы передавать переменную, в которой будем хранить все установленные значения всех контролов.
1 2 3 4 5 6 7 8 9 | (defun fun_dlg-callback (key value reflist) ;| * Функция реакции на элементы диалога * Параметры вызова: key ключ элемента диалога value установленное значение reflist ссылка на переменную, в которой храним все данные. Передавать только по ссылке |; ) ;_ end of defun |
Если ключ у нас chk_edit, то действия одни, если txt_edit - другие, ну и т.п. Понятно, что используем конструкцию cond:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | (defun fun_dlg-callback (key value reflist) ;| * Функция реакции на элементы диалога * Параметры вызова: key ключ элемента диалога value установленное значение reflist ссылка на переменную, в которой храним все данные. Передавать только по ссылке |; (cond ((and (= key "chk_edit") (= value "0")) ;; Отжат chk_edit (mode_tile "txt_edit" 1) ; txt_edit -> недоступен ) ((and (= key "chk_edit") (= value "1")) ;; ch_edit нажат (mode_tile "txt_edit" 0) ; txt_edit -> доступен ) ((= key "txt_edit") ;; Изменен текст ) ) ;_ end of cond ) ;_ end of defun |
Теперь надо каким-то манером менять reflist. Понятно, что reflist - список из точечных пар ("ключ элемента диалога" . "значение элемента диалога"). Если пара с таким ключом в списке есть, то надо менять ее значение. Если нет - то добавить. Чтобы не множить код, создадим отдельную функцию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | (defun fun_add-or-subst (lst key value) ;| * Добавление или замена точечной пары * Параметры вызова: lst обрабатываемый список key ключ value значение |; (if (assoc key lst) (subst (cons key value) (assoc key lst) lst ) ;_ end of subst (cons (list (cons key value)) lst) ) ;_ end of if ) ;_ end of defun |
Теперь меняем fun_dlg-callback (в целях демонстрации добавлены alert'ы):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | (defun fun_dlg-callback (key value reflist) ;| * Функция реакции на элементы диалога * Параметры вызова: key ключ элемента диалога value установленное значение reflist ссылка на переменную, в которой храним все данные. Передавать только по ссылке |; (cond ((and (= key "chk_edit") (= value "0")) ;; Отжат chk_edit (mode_tile "txt_edit" 1) ; txt_edit -> недоступен (set reflist (fun_add-or-subst (eval reflist) key t)) (alert "chk_edit -> 0") ) ((and (= key "chk_edit") (= value "1")) ;; ch_edit нажат (mode_tile "txt_edit" 0) ; txt_edit -> доступен (set reflist (fun_add-or-subst (eval reflist) key nil)) (alert "chk_edit -> 1") ) ((= key "txt_edit") ;; Изменен текст (set reflist (fun_add-or-subst (eval reflist) key (vl-string-trim " " value))) (set_tile "msg" (strcat (cdr (assoc "msg" (eval reflist))) " " (cdr (assoc key (eval reflist))))) (alert "txt_edit") ) ) ;_ end of cond ) ;_ end of defun |
Теперь собственно делаем вызов диалога, устанавливаем для него значения по умолчанию и для каждого элемента вызываем соответствующий обработчик:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | (defun fun_dlg-execute (/ dcl_file dcl_id dcl_lst dcl_res) ;| * Собственно вызов диалога |; (setq dcl_file (fun_create-dcl-file) dcl_id (load_dialog dcl_file) dcl_lst '(("chk_edit" . "1") ("txt_edit" . "Введите текст") ("msg" . "Сообщение:")) ) ;_ end of setq (new_dialog "dlg" dcl_id "(fun_dlg-callback $key $value 'dcl_lst)") (action_tile "accept" "(done_dialog 1)") (action_tile "cancel" "(done_dialog 0)") (foreach item dcl_lst (set_tile (car item) (cdr item)) (fun_dlg-callback (car item) (cdr item) 'dcl_lst) ) ;_ end of foreach (setq dcl_res (start_dialog)) (unload_dialog dcl_id) (if (= dcl_res 1) (alert "Была нажата OK") (alert "Была нажата Отмена (Cancel)") ) ;_ end of if ) ;_ end of defun |
Что дает такой подход?
- Код достаточно легко расширять: достаточно в callback прописать обработку нового ключа
- Отладка кода перестает быть мучительной и слабовыполнимой задачей: в callback ставится точка останова и все!
P.S. Коды отдельно не выкладываю - статья носит характер шпаргалки
Нашел маленькую ошибочку в fun_add-or-subst
при добавлении нового элемента (cons (list (cons key value)) lst) будет работать неадекватно
тут надо (append (list (cons key value)) lst)
И по теме в целом:
Хотел написать простенькое окошко с радиокнопками и ни в какую не получалось вытащить указанный вариант, но не об этом хочу спросить. Идею то общую понял.
Как то не хватает понимания как dcl взаимодействует с lisp, а именно с возвращает значения. У Полещука все туманно, а тут стало еще запущенней. В итоге пошел по другому пути, но разобраться бы не помешало для общего развития.
Да, сорри, ступил
А так-то ситуация проста до ужаса: в отдельную ссылку передаем по значению ключ контрола, его значение - тоже по значению; и общий список всех установленных контролов - но уже по ссылке. И вот этот последний параметр и меняем.
Можно заменить немного работу: передавать только ключ и значение, а менять "глобальный" список. Может быть, такой код будет проще понять:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
;|
* Добавление или замена точечной пары
* Параметры вызова:
lst обрабатываемый список
key ключ
value значение
|;
(if (assoc key lst)
(subst (cons key value)
(assoc key lst)
lst
) ;_ end of subst
(cons (cons key value) lst)
) ;_ end of if
) ;_ end of defun
(defun fun_dlg-callback (key value)
;|
* Функция реакции на элементы диалога
* Параметры вызова:
key ключ элемента диалога
value установленное значение
reflist ссылка на переменную, в которой храним все данные. Передавать только по ссылке
|;
(cond
((and (= key "chk_edit") (= value "0"))
;; Отжат chk_edit
(mode_tile "txt_edit" 1) ; txt_edit -> недоступен
(setq dcl_lst (fun_add-or-subst dcl_lst key t))
(alert "chk_edit -> 0")
)
((and (= key "chk_edit") (= value "1"))
;; ch_edit нажат
(mode_tile "txt_edit" 0) ; txt_edit -> доступен
(setq dcl_lst (fun_add-or-subst dcl_lst key nil))
(alert "chk_edit -> 1")
)
((= key "txt_edit")
;; Изменен текст
(setq dcl_lst (fun_add-or-subst dcl_lst key (vl-string-trim " " value)))
(set_tile "msg" (strcat (cdr (assoc "msg" dcl_lst)) " " (cdr (assoc key dcl_lst))))
(alert "txt_edit")
)
) ;_ end of cond
) ;_ end of defun
(defun fun_dlg-execute (/ dcl_file dcl_id dcl_lst dcl_res)
;|
* Собственно вызов диалога
|;
(setq dcl_file (fun_create-dcl-file)
dcl_id (load_dialog dcl_file)
dcl_lst '(("chk_edit" . "1") ("txt_edit" . "Введите текст") ("msg" . "Сообщение:"))
) ;_ end of setq
(new_dialog "dlg" dcl_id "(fun_dlg-callback $key $value)")
(action_tile "accept" "(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(foreach item dcl_lst
(set_tile (car item) (cdr item))
(fun_dlg-callback (car item) (cdr item))
) ;_ end of foreach
(setq dcl_res (start_dialog))
(unload_dialog dcl_id)
(if (= dcl_res 1)
(alert "Была нажата OK")
(alert "Была нажата Отмена (Cancel)")
) ;_ end of if
) ;_ end of defun
(defun fun_create-dcl-file (/ file handle)
;; Возвращает имя файла dcl
(setq file (strcat (vl-string-right-trim "\" (getenv "tmp")) "\\dlg.dcl")
handle (open file "w")
) ;_ end of setq
(foreach item '("dlg:dialog{label="Диалог";" " :text{key="msg";}"
" :toggle{key="chk_edit";label="Редактировать текст";}"
" :edit_box{key="txt_edit";width=50;} " " ok_cancel;"
" }"
)
(write-line item handle)
) ;_ end of foreach
(close handle)
file
) ;_ end of defun
Просто я очень не люблю использовать внешние переменные, если это не является жизненно необходимым...
Насколько я понимаю сейчас механизм:
-при изменении любого параметра в диалоговом окне каждый раз вызываются все функции между (new_dialog... и (start_dialog), в том числе и fun_dlg-callback
-если нажаты ок/отмена - окно закрывается
-при изменении текста меняется список (set reflist (fun_add-or-subst (eval reflist) key (vl-string-trim " " value)))
или во втором варианте напрямую (setq dcl_lst (fun_add-or-subst dcl_lst key nil))
Но вот привязать это к своей ситуации не получается. у меня 4 радиокнопки '(("culvert1" . 1) ("culvert2" . 0) ("culvert3" . 0) ("culvert4" . 0)) ;;первая включена по умолчанию
Сколько не переключаю варианты возвращает то же самое. Подозреваю, что надо обращаться не к :radio_button, а к :radio_column, но у него вроде как нет значений key и value. (по крайней мере в примерах Полещука не показаны)
Где я не прав?
Нет, неправильно ).
При вызове (new_dialog...) по умолчанию прописывается реакция на все контролы. Потом принудительно прописываются "уникальные" реакции (action_tile
При обработке диалога вызывается соответствующая фунция. При клике на чекбоксе - fun_dlg-callback, в которую передается как раз ключ нажатого контрола и его текущее значение (сейчас говорю про второй код). И уже fun_dlg-callback обрабатывает все что надо и как надо. Утрируя и упрощая, можно получить следующее:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
;|
* Добавление или замена точечной пары
* Параметры вызова:
lst обрабатываемый список
key ключ
value значение
|;
(if (assoc key lst)
(subst (cons key value)
(assoc key lst)
lst
) ;_ end of subst
(cons (cons key value) lst)
) ;_ end of if
) ;_ end of defun
(defun fun_dlg-callback (key value reflist)
;|
* Функция реакции на элементы диалога
* Параметры вызова:
key ключ элемента диалога
value установленное значение
reflist ссылка на переменную, в которой храним все данные. Передавать только по ссылке
|;
(cond
((= key "op1")
(set reflist (fun_add-or-subst reflist "key" 1))
)
((= key "op2")
(set reflist (fun_add-or-subst reflist "key" 2))
)
((= key "op3")
(set reflist (fun_add-or-subst reflist "key" 3))
)
((= key "op4")
(set reflist (fun_add-or-subst reflist "key" 4))
)
) ;_ end of cond
) ;_ end of defun
(defun fun_dlg-execute (/ dcl_file dcl_id dcl_lst dcl_res)
;|
* Собственно вызов диалога
|;
(setq dcl_file (fun_create-dcl-file)
dcl_id (load_dialog dcl_file)
dcl_lst '(("opt1" . "1"))
) ;_ end of setq
(new_dialog "dlg" dcl_id "(fun_dlg-callback $key $value 'dcl_lst)")
(action_tile "accept" "(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(foreach item dcl_lst
(set_tile (car item) (cdr item))
(fun_dlg-callback (car item) (cdr item) 'dcl_lst)
) ;_ end of foreach
(setq dcl_res (start_dialog))
(unload_dialog dcl_id)
(if (= dcl_res 1)
(alert "Была нажата OK")
(alert "Была нажата Отмена (Cancel)")
) ;_ end of if
) ;_ end of defun
(defun fun_create-dcl-file (/ file handle)
;; Возвращает имя файла dcl
(setq file (strcat (vl-string-right-trim "\" (getenv "tmp")) "\\dlg.dcl")
handle (open file "w")
) ;_ end of setq
(foreach item '("dlg:dialog{label="Диалог";"
" :text{key="msg";}"
" :radio_button{key="opt1";label="Radio 1";}"
" :radio_button{key="opt2";label="Radio 2";}"
" :radio_button{key="opt3";label="Radio 3";}"
" :radio_button{key="opt4";label="Radio 4";}"
" ok_cancel;"
" }"
)
(write-line item handle)
) ;_ end of foreach
(close handle)
file
) ;_ end of defun
А... как то я упустил из виду строчку (new_dialog "dlg" dcl_id "(fun_dlg-callback $key $value 'dcl_lst)") и у себя пустую строку загнал.
Зато сходу другой косяк. на каждую трубу прописано:
((and (= key "culvert1") (= value "1"))
;; выбран первый
(setq dlg_lst (sad-add-or-subst dlg_lst key value))
)
((and (= key "culvert1") (= value "0"))
;; выбран первый
(setq dlg_lst (sad-add-or-subst dlg_lst key value))[cc lang="lisp"]
при нажатии на радиокнопку значение меняется на 1 у нажатого и не обнуляется у того что был раньше. в итоге, понажимав "в раздумьях" все варианты, получаю ((culvert1 . 1) (culvert2 . 1) (culvert3 . 1) (culvert4 . 1)) - то есть выбраны все варианты XD
Получается надо не условие для кнопки со значением 0, а при назначении одной принудительно сбрасывать остальные при выборе
Судя по всему тут уже надо не списком оперировать, точнее для всех радиокнопок предусмотреть один элемент списка с кучей возможных вариантов.
В общем все заработало хоть уже конкретно этого окна не надо, потому как уже дописал автоматическое определение типа трубы по содержимому чертежа.
Спасибо за пояснения.
Ну писал я на бегу ))) принцип понятен?
Одного не понял.
2
3
4
(set_tile (car item) (cdr item))
(fun_dlg-callback (car item) (cdr item) 'dcl_lst)
) ;_ end of foreach
Зачем тут нужен (fun_dlg-callback (car item) (cdr item) 'dcl_lst), если он прописан в (new_dialog...
У себя закомментировал - результат не поменялся...
А если в dcl_lst поменять '(("opt1" . "1")) на '(("opt3" . "1")) ?
Это просто установка начальных значений диалога )))
кажись доперло
То есть тут для каждого set_tile сразу же и fun_dlg-callback следом. Мне он особо и нужен был в моем случае - начальные параметры заданы прямо в dcl и set_tile не использовался.
ИМХО самое неудачное решение. Стартовые значения могут быть прочитаны из реестра, из стороннего файла, могут задаваться вообще на основе каких-то параметров - ну не править же каждый раз текст диалога?
Что еще раз доказывает, что не дорос я еще до серьезного программирования. До идеи запоминать значения сам как то не допер.
Ну, "мы все учились понемногу" Я тоже не сразу до такого дошел ))
Во, пока вспомнил: я здесь где-то расписывал варианты хранения данных между функциями, файлами и сессиями AutoCAD. Может быть, будет полезным.
Алексей, спасибо за наводку. Как надо будет - поищу. Пока что такой необходимости не было. Я между делом читал "САПР на базе..." там тоже был раздел посвящен, представление имею что искать надо.
При всем уважении, но статья - бред от начала до конца.
Во-первых, создание диалога налету - параноидальный мусор. Достаточно скомпилировать код в vlx-модуль и диалог никуда не потеряется.
Во-вторых, call-back функция заменяет, чуть более, чем полностью, action_tile, которая становится абсолютно не нужна...
В-третьих,
(foreach item dcl_lst
(set_tile (car item) (cdr item))
(fun_dlg-callback (car item) (cdr item) 'dcl_lst)
) ;_ end of foreach
Этот бред я вообще не понял... call-back функция вызывается всего один раз перед стартом диалога!
В-четвертых, call-back- функция не нуждается ни в каких параметрах. Есть встроенные переменные $key и т.д. Причем при открытии диалога $key всегда равен nil. Достаточно в call-back функцию добавить проверку (null $key), чтоб заполнять диалог прямо в call-back функции.
В-пятых, в call-back функции должны быть описаны только управляющие элементы: всегда кнопки, в отдельных случаях списки, радиокнопки и чекбоксы. А не все подряд...
В итоге весь код диалога сосредоточен в call-back функции, а для вызова ее достаточно 5 строчек, которые можно оформить в виде библиотечной функции в 1 строчку.
Все это описано в справке, поэтому я не вижу причин для подобного велосипедостроения.
Прежде всего - предоставляется один lsp. Кроме того, при совместной компиляции нескольких сотен lsp, некоторые из которых используют диалоги, делать vlx нерентабельно. Сейчас dcl у меня создаются именно "на лету", в некоторых случаях они формируются на основе внешних данных. Очень удобно.
Кто сказал? Она вызывается ровно так же, как и любая другая функция.
Опять же, кто сказал? И если это так, то почему мой подход работает уже не первый год?
И снова - кто это сказал? С чего бы вдруг мне не описать в функции, которую я разрабатываю то, что лично мне нужно? Странный подход - "нельзя потому что нельзя". Можно!
Ок, бога ради. Тот принцип, который я описал и который использую, на данный момент оправдывает все вложения.
О, в качестве иллюстрации: http://forum.dwg.ru/showpost.php?p=1435329&postcount=66 (код не смотрел, не до того). Если бы построение диалога шло "на лету", вопросов бы вообще не возникло никаких.
P.S. Я в принципе не доверяю vlx / fas кодам, предоставляемым без исходников. А если есть исходники - тут же перекомпилирую.
kpblc, то,что твой код работает, не делает его оптимальным. Смотри ниже то, о чем я написал, и сравнивай. Если убрать функцию создания файла, то станется кода с гулькин хвост.
(defun fun_dlg-execute
(/ dcl_file dcl_id dcl_lst dcl_res dcl_proc)
(defun dcl_proc ()
(cond
((null $key)
(set_tile "opt" "opt1")
(set_tile "msg" "Выбери опцию")
)
((= $key "cancel")
(done_dialog 0)
)
((= $key "accept")
(setq dcl_lst (list (get_tile "opt")))
(done_dialog 1)
)
) ;_ end of cond
) ;_ end of defun
(setq dcl_file (fun_create-dcl-file)
dcl_id (load_dialog dcl_file)
) ;_ end of setq
(new_dialog "dlg" dcl_id "(dcl_proc)")
(dcl_proc)
(setq dcl_res (start_dialog))
(unload_dialog dcl_id)
(if (= dcl_res 1)
(alert "Была нажата OK")
(alert "Была нажата Отмена (Cancel)")
) ;_ end of if
dcl_lst
) ;_ end of defun
(defun fun_create-dcl-file (/ file handle)
;; Возвращает имя файла dcl
(setq file (strcat (vl-string-right-trim "\\" (getenv "tmp"))
"\\dlg.dcl"
)
handle (open file "w")
) ;_ end of setq
(foreach item '("dlg:dialog{label=\"Диалог\";"
" :text{key=\"msg\";}"
" :boxed_radio_column{key =\"opt\";"
" :radio_button{key=\"opt1\";label=\"Radio 1\";}"
" :radio_button{key=\"opt2\";label=\"Radio 2\";}"
" :radio_button{key=\"opt3\";label=\"Radio 3\";}"
" :radio_button{key=\"opt4\";label=\"Radio 4\";}}"
" ok_cancel;"
"}"
)
(write-line item handle)
) ;_ end of foreach
(close handle)
file
) ;_ end of defun
Ок. Фактически мы с тобой спорим ни о чем. У тебя в callback-функции используется "почти глобальные" переменные $key, $value и dcl_lst. Если от них попытатся избавиться, то получится примерно мое творчество. Да, action для accept и cancel я прописываю в функции вызова диалога - извини, привычка. А в остальном - что в лоб, что по лбу. ИМХО абсолютно одинаковые коды.