在 Windows 上的 Allegro Common Lisp Form 应用程序中是否有全局表单和按钮变量?

In Allegro Common Lisp Form application on Windows is there global form and button variables?

Allegro Common Lisp 形式与 Delphi 形式非常相似。但是delphi表单至少允许你访问全局变量,比如Form1、Button1、Button2等

在 Allegro common lisp 中,我唯一能弄清楚如何访问按钮属性和表单属性的方法是使用 find-sibling 并使用 LET 设置局部变量,或者设置我自己的全局变量。是否已经有一个全局变量来访问 button1、form1 等常用 lisp 中的小部件,您可以方便地访问...

例如,如果我想通过单击另一个按钮 2 来访问 Allegro CL 中 form1 上的按钮 1,我会去:

(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';

如何在不使用 find sibling 的情况下在 allegro common lisp 中设置 button1 标题(与 delphi 中的标题相同)....我可以使用小部件函数参数(如 delphi Sender as TButton) 如果我在函数内部与 object 对话,但必须对其他组件使用 find-sibling。似乎 allegro common lisp 迫使人们编写 find-sibling 代码,而不是仅仅给你一个 global 变量,比如 button1button2form1.

编辑:在 delphi 中,form1 是全局的,但 button1 和 button2 只是全局表单的一部分 class,它们本身不是全局的,但它们的行为就像全局一样,因为您可以 [= =14=]

form1.button1
form1.button2

来自其他单位

(或当前 unit1 的 self.button1,但 delphi 不要求您一直说 SELF,以方便键盘输入)。

编辑:不,产品 "allegro common lisp" 根本无法处理基本的编程任务,因为它是 lisp,不是一种实用的语言。

这是浪费时间,但对于大多数情况来说并不算多。我会先回答,然后再提供一些事后的经验。

对于大多数情况这可能就足够了:

(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
    ;; ...
    ))

一种替代方法是为您的 window.

定义插槽,即特定的 class

这与您将获得的 Delphi 或 VB 非常接近,因为它们使用对象字段而不是全局变量来进行控制。这只是语法和范围的问题:在某些语言中,您可以在方法内引用实例字段,而在 Common Lisp 中,您可以使用访问器函数/with-accessors or 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
     ;; ...
     )
   ;; ...
   ))

这可以通过一个宏进一步简化,您可以在宏中定义对话框项的名称及其初始化参数,它应该生成 class 每个对话框项都有一个插槽和 initialize-instance :after method,依靠 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,或两者兼而有之,前者将使用后者。


我可以告诉你,当有很多 UI 更新时,这种次要但重复的时间浪费就变得有意义了。很多,我的意思是在几十秒或几分钟内每秒至少有几十个电话。大多数对话框 windows 不应该像这样,因为它们只会向用户查询数据或像带有操作按钮的工具 windows 一样工作。

但是,假设您遇到了这种情况,例如进度对话框。

使用访问器或槽而不是 find 会大大提高性能,您可以使用 Allegro 的分析器亲眼看到,但这只是最热门的地方。

在这种情况下可能有必要知道您是否真的需要 UI 更新,因此请保留一些轻量级的簿记以了解您是否真的需要触摸对话框或其项目。这实际上非常简单,这样做可能比优化对话框项访问节省更多。好的候选数据类型是计数器和时间戳。

另一种技术是按确定的时间间隔延迟更新,也许使用更新 UI 批处理先前更新请求的计时器(例如,将更新排队,如果尚未启动则启动计时器,使计时器是一次性的,因此在不需要时不会 运行,使计时器功能在实际更新之前减少排队的更新)。如果您期望每个时间单位有很多更新,这可能是最大的优化。然而,它也是最具体和最费力的一个,如果事情不简单的话,它很容易出错。

好处是,如果您实现该队列,您可能会获得线程间通信,例如在业务模型 属性 change/state change/progress 事件上注册 UI 更新,这可能发生在非 UI 后台工作线程中。

PS:有了这个,我并不是说你应该只实施这些方法中的一种,我是在解释你得到的改进,以防你不能花太多时间解决这个问题.


PS:Allegro 已经支持通过 post-funcall-in-cg-process 进行的跨线程 UI 操作排队,包括使用 :delete-types 参数的累积操作和使用:unless-types 参数。

要注意的是这个队列只在 event-loop which is typically used as the top-level event loop (versus a modal or menu event loop, or message processing that may happen in other functions) 中处理。在非 event-loop 消息处理中,操作不会出队也不会被处理。