ADN Open CIS
Сообщество программистов Autodesk в СНГ

06/02/2015

Отладка dcl

Не секрет, что диалоги в лиспе - это практически всегда dcl. Если разработать dcl не так уж и трудно, то заставить нормально работать сколько-нибудь сложный диалог удается далеко не всегда. Каким образом можно облегчить себе жизнь - именно этому и посвящена статья.

Допустим, нам необходимо, чтобы функция выводила окошко с определенным нами сообщением и двумя кнопками - OK и Cancel. Для усложнения задачи добавим текстовое поле и флажковый переключатель, который будет менять режим доступности текстового поля.

Итак, прежде всего - создаем временный dcl-файл и прописываем сам диалог. Это мне требуется только для того, чтобы проверить - как окно будет выглядеть в результате. Допустим, создали, проверили - все замечательно.

Код - Auto/Visual LISP: [Выделить]
  1. dlg:dialog{label="Диалог";
  2.         :text{key="msg";}
  3.         :toggle{key="chk_edit";label="Редактировать текст";}
  4.         :edit_box{key="txt_edit";width=50;}
  5.         ok_cancel;
  6.         }

После этого пишется функция, которая будет создавать временный dcl-файл в каталоге %temp% (к примеру) и возвращать полное имя такого файла.

Код - Auto/Visual LISP: [Выделить]
  1. (defun fun_create-dcl-file (/ file handle)
  2.   ;; Возвращает имя файла dcl
  3.   (setq file   (strcat (vl-string-right-trim "\\" (getenv "tmp")) "\\dlg.dcl")
  4.         handle (open file "w")
  5.         ) ;_ end of setq
  6.   (foreach item '("dlg:dialog{label=\"Диалог\";"                   " :text{key=\"msg\";}"
  7.                   " :toggle{key=\"chk_edit\";label=\"Редактировать текст\";}"
  8.                   " :edit_box{key=\"txt_edit\";width=50;} "        " ok_cancel;"
  9.                   " }"
  10.                   )
  11.     (write-line item handle)
  12.     ) ;_ end of foreach
  13.   (close handle)
  14.   file
  15.   ) ;_ end of defun

Теперь приступаем к разработке основной функции.

После стандартных строк

Код - Auto/Visual LISP: [Выделить]
  1. (setq dcl_file (fun_create-dcl-file)
  2.   dcl_id (load_dialog dcl_file)
  3.   ) ;_ end of setq

по идее должно идти нечто типа

Но обратимся к справке (мой вольный перевод):

Код - Auto/Visual LISP: [Выделить]
  1. (new_dialog dlgname dcl_id [action [screen-pt]])

Аргументы

dlgname Строка, определяющая создаваемый диалог (в одном dcl-файле может быть описано несколько диалогов; главное, чтобы у них имена были уникальными)
dcl_id Идентификатор, созданный через функцию (load_dialog...)
action Необязательный параметр. Строка, содержащая выражение на языке AutoLISP, являющееся реакцией по умолчанию на изменение любого элемента диалога. Строка становится обязательной, если определяется следующий параметр. Допускается применение пустой строки ("")
screen-pt Координаты двумерной точки, определяющие положение левого верхнего угла окна диалога на экране.

Куски справки, не являющиеся критичными, я пропустил.

То есть получается, что в

можно указать какую-то дополнительную "callback"-функцию, которая и будет срабатывать по умолчанию! Отлично, нарисуем?

Что в эту функцию надо передавать? Понятно, что надо как минимум передавать ключ элемента диалога и его значение (после изменения). Еще крайне желательно бы передавать переменную, в которой будем хранить все установленные значения всех контролов.

Код - Auto/Visual LISP: [Выделить]
  1. (defun fun_dlg-callback (key value reflist)
  2.                         ;|
  3.         * Функция реакции на элементы диалога
  4.         * Параметры вызова:
  5.         key ключ элемента диалога
  6.         value установленное значение
  7.         reflist ссылка на переменную, в которой храним все данные. Передавать только по ссылке
  8.         |;
  9.  ) ;_ end of defun

Если ключ у нас chk_edit, то действия одни, если txt_edit - другие, ну и т.п. Понятно, что используем конструкцию cond:

Код - Auto/Visual LISP: [Выделить]
  1. (defun fun_dlg-callback (key value reflist)
  2.                         ;|
  3.         * Функция реакции на элементы диалога
  4.         * Параметры вызова:
  5.         key ключ элемента диалога
  6.         value установленное значение
  7.         reflist ссылка на переменную, в которой храним все данные. Передавать только по ссылке
  8.         |;
  9.   (cond
  10.     ((and (= key "chk_edit") (= value "0"))
  11.      ;; Отжат chk_edit
  12.      (mode_tile "txt_edit" 1) ; txt_edit -> недоступен
  13.      )
  14.     ((and (= key "chk_edit") (= value "1"))
  15.      ;; ch_edit нажат
  16.      (mode_tile "txt_edit" 0) ; txt_edit -> доступен
  17.      )
  18.     ((= key "txt_edit")
  19.      ;; Изменен текст
  20.      )
  21.     ) ;_ end of cond
  22.   ) ;_ end of defun
  23.  

Теперь надо каким-то манером менять reflist. Понятно, что reflist - список из точечных пар ("ключ элемента диалога" . "значение элемента диалога"). Если пара с таким ключом в списке есть, то надо менять ее значение. Если нет - то добавить. Чтобы не множить код, создадим отдельную функцию:

Код - Auto/Visual LISP: [Выделить]
  1. (defun fun_add-or-subst (lst key value)
  2.                         ;|
  3.         * Добавление или замена точечной пары
  4.         * Параметры вызова:
  5.         lst обрабатываемый список
  6.         key ключ
  7.         value значение
  8.         |;
  9.   (if (assoc key lst)
  10.     (subst (cons key value)
  11.            (assoc key lst)
  12.            lst
  13.            ) ;_ end of subst
  14.     (cons (list (cons key value)) lst)
  15.     ) ;_ end of if
  16.   ) ;_ end of defun

Теперь меняем fun_dlg-callback (в целях демонстрации добавлены alert'ы):

Код - Auto/Visual LISP: [Выделить]
  1. (defun fun_dlg-callback (key value reflist)
  2.                         ;|
  3.         * Функция реакции на элементы диалога
  4.         * Параметры вызова:
  5.         key ключ элемента диалога
  6.         value установленное значение
  7.         reflist ссылка на переменную, в которой храним все данные. Передавать только по ссылке
  8.         |;
  9.   (cond
  10.     ((and (= key "chk_edit") (= value "0"))
  11.      ;; Отжат chk_edit
  12.      (mode_tile "txt_edit" 1) ; txt_edit -> недоступен
  13.      (set reflist (fun_add-or-subst (eval reflist) key t))
  14.      (alert "chk_edit -> 0")
  15.      )
  16.     ((and (= key "chk_edit") (= value "1"))
  17.      ;; ch_edit нажат
  18.      (mode_tile "txt_edit" 0) ; txt_edit -> доступен
  19.      (set reflist (fun_add-or-subst (eval reflist) key nil))
  20.      (alert "chk_edit -> 1")
  21.      )
  22.     ((= key "txt_edit")
  23.      ;; Изменен текст
  24.      (set reflist (fun_add-or-subst (eval reflist) key (vl-string-trim " " value)))
  25.      (set_tile "msg" (strcat (cdr (assoc "msg" (eval reflist))) " " (cdr (assoc key (eval reflist)))))
  26.      (alert "txt_edit")
  27.      )
  28.     ) ;_ end of cond
  29.   ) ;_ end of defun
  30.  

Теперь собственно делаем вызов диалога, устанавливаем для него значения по умолчанию и для каждого элемента вызываем соответствующий обработчик:

Код - Auto/Visual LISP: [Выделить]
  1. (defun fun_dlg-execute (/ dcl_file dcl_id dcl_lst dcl_res)
  2.                        ;|
  3.         * Собственно вызов диалога
  4.         |;
  5.   (setq dcl_file (fun_create-dcl-file)
  6.         dcl_id   (load_dialog dcl_file)
  7.         dcl_lst  '(("chk_edit" . "1") ("txt_edit" . "Введите текст") ("msg" . "Сообщение:"))
  8.         ) ;_ end of setq
  9.   (new_dialog "dlg" dcl_id "(fun_dlg-callback $key $value 'dcl_lst)")
  10.   (action_tile "accept" "(done_dialog 1)")
  11.   (action_tile "cancel" "(done_dialog 0)")
  12.   (foreach item dcl_lst
  13.     (set_tile (car item) (cdr item))
  14.     (fun_dlg-callback (car item) (cdr item) 'dcl_lst)
  15.     ) ;_ end of foreach
  16.   (setq dcl_res (start_dialog))
  17.   (unload_dialog dcl_id)
  18.   (if (= dcl_res 1)
  19.     (alert "Была нажата OK")
  20.     (alert "Была нажата Отмена (Cancel)")
  21.     ) ;_ end of if
  22.   ) ;_ end of defun

Что дает такой подход?

  1. Код достаточно легко расширять: достаточно в callback прописать обработку нового ключа
  2. Отладка кода перестает быть мучительной и слабовыполнимой задачей: в callback ставится точка останова и все!
  3. Если взять на вооружение также и программное создание диалога на лету, то отдавать можно вообще один lsp или fas - конечный пользователь никакой разницы не заметит



Автор: Алексей Кулик.

Обсуждение: http://adn-cis.org/forum/index.php?topic=1841

Опубликовано 06.02.2015
Отредактировано 03.03.2015 в 17:06:57