Отладка dcl
Не секрет, что диалоги в лиспе - это практически всегда dcl. Если разработать dcl не так уж и трудно, то заставить нормально работать сколько-нибудь сложный диалог удается далеко не всегда. Каким образом можно облегчить себе жизнь - именно этому и посвящена статья.
Допустим, нам необходимо, чтобы функция выводила окошко с определенным нами сообщением и двумя кнопками - OK и Cancel. Для усложнения задачи добавим текстовое поле и флажковый переключатель, который будет менять режим доступности текстового поля.
Итак, прежде всего - создаем временный dcl-файл и прописываем сам диалог. Это мне требуется только для того, чтобы проверить - как окно будет выглядеть в результате. Допустим, создали, проверили - все замечательно.
- dlg:dialog{label="Диалог";
- :text{key="msg";}
- :toggle{key="chk_edit";label="Редактировать текст";}
- :edit_box{key="txt_edit";width=50;}
- ok_cancel;
- }
После этого пишется функция, которая будет создавать временный dcl-файл в каталоге %temp% (к примеру) и возвращать полное имя такого файла.
- (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
Теперь приступаем к разработке основной функции.
После стандартных строк
- (setq dcl_file (fun_create-dcl-file)
- dcl_id (load_dialog dcl_file)
- ) ;_ end of setq
по идее должно идти нечто типа
- (new_dialog "dlg" dcl_id)
Но обратимся к справке (мой вольный перевод):
- (new_dialog dlgname dcl_id [action [screen-pt]])
Аргументы
dlgname | Строка, определяющая создаваемый диалог (в одном dcl-файле может быть описано несколько диалогов; главное, чтобы у них имена были уникальными) |
dcl_id | Идентификатор, созданный через функцию (load_dialog...) |
action | Необязательный параметр. Строка, содержащая выражение на языке AutoLISP, являющееся реакцией по умолчанию на изменение любого элемента диалога. Строка становится обязательной, если определяется следующий параметр. Допускается применение пустой строки ("") |
screen-pt | Координаты двумерной точки, определяющие положение левого верхнего угла окна диалога на экране. |
Куски справки, не являющиеся критичными, я пропустил.
То есть получается, что в
- (new_dialog "dlg" dcl_id)
Что в эту функцию надо передавать? Понятно, что надо как минимум передавать ключ элемента диалога и его значение (после изменения). Еще крайне желательно бы передавать переменную, в которой будем хранить все установленные значения всех контролов.
- (defun fun_dlg-callback (key value reflist)
- ;|
- * Функция реакции на элементы диалога
- * Параметры вызова:
- key ключ элемента диалога
- value установленное значение
- reflist ссылка на переменную, в которой храним все данные. Передавать только по ссылке
- |;
- ) ;_ end of defun
Если ключ у нас chk_edit, то действия одни, если txt_edit - другие, ну и т.п. Понятно, что используем конструкцию cond:
- (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 - список из точечных пар ("ключ элемента диалога" . "значение элемента диалога"). Если пара с таким ключом в списке есть, то надо менять ее значение. Если нет - то добавить. Чтобы не множить код, создадим отдельную функцию:
- (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'ы):
- (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
Теперь собственно делаем вызов диалога, устанавливаем для него значения по умолчанию и для каждого элемента вызываем соответствующий обработчик:
- (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 ставится точка останова и все!
- Если взять на вооружение также и программное создание диалога на лету, то отдавать можно вообще один lsp или fas - конечный пользователь никакой разницы не заметит
Автор: Алексей Кулик.
Обсуждение: http://adn-cis.org/forum/index.php?topic=1841
Опубликовано 06.02.2015Отредактировано 03.03.2015 в 17:06:57