В приложении Allegro Common Lisp Form для Windows есть глобальные переменные формы и кнопки?

Формы Allegro Common Lisp очень похожи на формы Delphi. Но формы Delphi, по крайней мере, позволяют вам обращаться к глобальным переменным, таким как Form1, Button1, Button2 и т. Д.

В Allegro common lisp единственный способ выяснить, как получить доступ к свойствам кнопок и свойствам формы, - это использовать find-sibling и настроить локальную переменную с помощью LET или настроить собственную глобальную переменную. Уже существует глобальная переменная для доступа к виджетам, таким как button1, form1 и т. Д. В общем lisp, к которым вы можете получить доступ для удобства...

Например, если я хочу получить доступ к button1 в форме form1 в Allegro CL, нажав другую кнопку 2, я бы сказал:

(let ((but1 (find-sibling :button1 widget)))  
  (setf (title but1) "hello world" )) 

Использование find-sibling утомительно и, по-видимому, является пустой тратой времени по сравнению с тем, если бы была просто глобальная переменная для доступа, как в delphi:

form1.color := clBlue;
button1.caption := 'Hello world';
button2.caption := 'I am button2';

Как установить заголовок кнопки buttonf (аналогично заголовку в delphi) в общем lisp allegro без использования find sibling.... Я могу использовать параметр функции виджета (например, delphi Sender в качестве TButton), если я говорю с объектом внутри функции, но придется использовать find-sibling для других компонентов. Похоже, что allegro common lisp заставляет людей писать код для поиска брата, а не просто дает вам глобальную переменную, такую ​​как button1, button 2 и form1.

Изменить: в delphi form1 является глобальным, но button1 и button 2 являются только частью глобального класса формы, они сами не являются глобальными, но они действуют как глобальные, так как вы можете сделать

form1.button1
form1.button2

из других подразделений

(или self.button1 из текущего unit1, но delphi не требует, чтобы вы постоянно говорили SELF, для удобства набора с клавиатуры).

РЕДАКТИРОВАТЬ: Нет, продукт "allegro common lisp" просто не может справиться с базовыми задачами программирования, потому что это lisp, а не практический язык.

1 ответ

Решение

Это пустая трата времени, но не так много для большинства случаев. Сначала я отвечу, а затем предоставлю некоторый опыт после этого.

Этого может быть достаточно для большинства случаев:

(defmacro with-components ((&rest names) dialog &body body)
  (assert (every #'symbolp names))
  (let ((dialog-sym (gensym (symbol-name '#:dialog))))
    `(let ((,dialog-sym ,dialog))
       (let (,@(mapcar #'(lambda (name)
                           `(,name
                             (find-component
                              ,(intern (symbol-name name) :keyword)
                              ,dialog-sym)))
                       names))
         ,@body))))

(defun do-something (my-dialog)
  (with-components (my-progress-bar) my-dialog
    ;; ...
    ))

Одна из альтернатив - определить слоты, то есть определенный класс, для вашего окна.

Это так же близко к Delphi или VB, как вы получите, потому что они используют объектные поля, а не глобальные переменные, для элементов управления. Это просто вопрос синтаксиса и области действия: тогда как в некоторых языках вы можете ссылаться на поле экземпляра внутри метода, в Common Lisp вы используете либо функции доступа / with-accessors или же slot-value / with-slots,

(defclass my-dialog (dialog)
  ((my-progress-bar :reader my-dialog-my-progress-bar)))

(defmethod initialize-instance :after ((dialog my-dialog) &rest initargs)
  (declare (ignore initargs))
  (with-slots (my-progress-bar) dialog
    (setf my-progress-bar (find-component :my-progress-bar dialog))))

(defun my-dialog ()
  (find-or-make-application-window :my-dialog 'make-my-dialog))

(defun make-my-dialog (&key owner #| ...|#)
  (make-window :my-dialog
    :owner (or owner (screen *system*))
    :class 'my-dialog
    :dialog-items (make-my-dialog-widgets)
    ;; ...
    ))

(defun make-my-dialog-widgets ()
  (list
   (make-instance 'progress-indicator
     :name :my-progress-bar
     :range '(0 100)
     :value 0
     ;; ...
     )
   ;; ...
   ))

Это может быть дополнительно упрощено с помощью макроса, где вы определяете имя элементов диалога и их initargs, и он должен генерировать класс со слотом для каждого элемента диалога и initialize-instance:after метод, рассчитывающий на функции создателя, сгенерированные IDE.

(defmacro defdialog (name (&rest supers) (&rest slots) &rest options)
  (let ((static-dialog-item-descs (find :static-dialog-items options
                                        :key #'first))
        (dialog-sym (gensym (symbol-name '#:dialog)))
        (initargs-sym (gensym (symbol-name '#:initargs)))
        (owner-sym (gensym (symbol-name '#:owner))))
    `(progn

       (defclass ,name (,@supers dialog)
         (,@slots
          ;; TODO: intern reader accessors
          ,@(mapcar #'(lambda (static-dialog-item-desc)
                        `(,(first static-dialog-item-desc)
                          :reader ,(intern (format nil "~a-~a"
                                                   name
                                                   (first static-dialog-item-desc)))))
                    (rest static-dialog-item-descs)))
         ,@(remove static-dialog-item-descs options))

       (defmethod initialize-instance :after ((,dialog-sym ,name) &rest ,initargs-sym)
         (declare (ignore ,initargs-sym))
         (with-slots (,@(mapcar #'first (rest static-dialog-item-descs))) ,dialog-sym
           ,@(mapcar #'(lambda (static-dialog-item-desc)
                         `(setf ,(first static-dialog-item-desc)
                                (find-component
                                 ,(intern (symbol-name (first static-dialog-item-desc))
                                          :keyword)
                                 ,dialog-sym)))
                     (rest static-dialog-item-descs))))

       ;; Optional
       (defun ,name ()
         (find-or-make-application-window ,(intern (symbol-name name) :keyword)
                                          'make-my-dialog))

       (defun ,(intern (format nil "~a-~a" '#:make name))
           (&key ((:owner ,owner-sym)) #| ... |#)
         (make-window ,(intern (symbol-name name) :keyword)
           :owner (or ,owner-sym (screen *system*))
           :class ',name
           :dialog-items (,(intern (format nil "~a-~a-~a" '#:make name '#:widgets)))
           ;; ...
           ))

       (defun ,(intern (format nil "~a-~a-~a" '#:make name '#:widgets)) ()
         (list
          ,@(mapcar #'(lambda (static-dialog-item-desc)
                        `(make-instance ,(second static-dialog-item-desc)
                           :name ,(intern (symbol-name (first static-dialog-item-desc))
                                          :keyword)
                           ,@(rest (rest static-dialog-item-desc))))
                    (rest static-dialog-item-descs)))))))

(defdialog my-dialog ()
  ()
  (:static-dialog-items
   (my-progress-bar #| Optional |# 'progress-indicator
     :range '(0 100)
     :value 0               
     ;; ...
     )))

Здесь есть много вариантов.

Например, вы можете не захотеть автоматически определять initialize-instance:after метод, потому что вы можете захотеть определить его самостоятельно с помощью логики бизнес-инициализации, поэтому вместо этого вы можете инициализировать слоты в функции создателя диалогов. Но тогда вы будете бороться с кодом, сгенерированным IDE (вы всегда можете использовать его для создания прототипов, а затем адаптировать свой код), поэтому я назвал некоторый код необязательным.

Или вы можете расширить макрос, чтобы взять инициализирующий код в качестве аргумента (чтобы включить в сгенерированный initialize-instance), или отдельный макрос, который будет использоваться внутри или вместо initialize-instance :after или оба, где первый будет использовать второй.


Я могу сказать вам, что, когда есть много обновлений пользовательского интерфейса, эта незначительная, но многократная трата времени становится актуальной. Под многими я подразумеваю, по крайней мере, несколько десятков вызовов в секунду в течение нескольких десятков секунд или минут. Большинство диалоговых окон не должны вести себя так, поскольку они просто запрашивают данные у пользователя или действуют как окна инструментов с кнопками действий.

Тем не менее, давайте предположим, что вы попали в такой случай, например, диалог прогресса.

Использование аксессуаров или слотов вместо find Это немного улучшит производительность, как вы сами можете убедиться, используя профилировщик Allegro, но это всего лишь самая горячая точка.

Может возникнуть необходимость узнать, действительно ли вам нужно обновление пользовательского интерфейса в таких обстоятельствах, поэтому держите небольшую легкую бухгалтерию, чтобы знать, действительно ли вам нужно коснуться диалогового окна или его элементов. На самом деле это очень просто, и вы можете сэкономить больше, чем оптимизируя доступ к элементам диалога. Хорошими типами данных-кандидатами являются счетчики и временные метки.

Еще один метод заключается в задержке обновлений на определенные интервалы, возможно, с помощью таймера, который обновляет пользовательский интерфейс, объединяющий предыдущие запросы на обновления (например, поставьте в очередь обновление, запустите таймер, если он еще не запущен, сделайте таймер одноразовым, чтобы он выиграл " • Запускайте, когда в этом нет необходимости, сделайте так, чтобы функция таймера уменьшала количество обновлений в очереди перед тем, как обновлять). Если вы ожидаете много обновлений за единицу времени, это может быть наибольшей оптимизацией. Тем не менее, это также самый конкретный и трудоемкий и довольно подвержен ошибкам, если вещи выпадают из простоты.

Выигрыш в том, что если вы реализуете эту очередь, вы можете заработать межпотоковое взаимодействие, например, регистрируя обновления пользовательского интерфейса в событиях изменения свойств бизнес-модели / изменения состояния / прогресса, которые могут происходить в фоновых рабочих потоках, не связанных с пользовательским интерфейсом.

PS: При этом я не говорю, что вы должны реализовывать только один из этих подходов, я объясняю, какое улучшение по сравнению с усилиями вы получаете, если вы не можете тратить много времени на это.


PS: Allegro уже имеет некоторую поддержку для организации многопотокового пользовательского интерфейса. post-funcall-in-cg-process в том числе совокупные операции с :delete-types аргумент и идемпонентные операции с :unless-types аргумент.

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

Другие вопросы по тегам