-2

Allegro Common Lisp 表单与 Delphi 表单非常相似。但是delphi表单至少可以让你访问Form1、Button1、Button2等全局变量。

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

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

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

如何在不使用查找兄弟的情况下在 allegro common lisp 中设置按钮 1 标题(与 delphi 中的标题相同)。但必须对其他组件使用 find-sibling。似乎 allegro common lisp 迫使人们编写查找兄弟代码,而不是只给你一个全局变量,如button1button2form1

编辑:在 delphi 中 form1 是全局的,但 button1 和 button2 只是全局表单类的一部分,它们本身不是全局的,但它们的行为就像全局一样,因为你可以这样做

form1.button1
form1.button2

来自其他单位

(或当前unit1中的self.button1,但delphi并不要求你一直说SELF,为了键盘打字方便)。

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

4

1 回答 1

2

这是浪费时间,但在大多数情况下并不多。我会先回答,然后再提供一些经验。

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

(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-accessorsslot-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 生成的 maker 函数。

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

这里有很多选择。

例如,您可能不想自动定义一个方法,因为您可能想自己定义一个具有业务初始化逻辑的方法,因此您可以在对话框生成器函数中初始化槽。但是,您将与 IDE 生成的代码作斗争(您始终可以将其用于原型设计,然后调整您的代码),这就是我将某些代码表示为可选代码的原因。initialize-instance :after

或者您可以扩展宏以将初始化代码作为参数(包含在生成的 中initialize-instance),或者在内部使用单独的宏或代替initialize-instance :after,或两者兼而有之,前者将使用后者。


我可以告诉你,当有很多 UI 更新时,这种轻微但反复浪费的时间变得相关。对许多人来说,我的意思是在几十秒或几分钟内每秒至少打几十个电话。大多数对话窗口不应该像这样,因为它们只会从用户那里查询数据或像带有操作按钮的工具窗口一样工作。

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

使用访问器或插槽代替find将大大提高性能,正如您使用 Allegro 的分析器自己看到的那样,但这只是最热门的地方。

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

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

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

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


PS:Allegro 已经对跨线程 UI 操作排队通过一些支持post-funcall-in-cg-process,包括带参数的累积操作和带:delete-types参数的幂等操作:unless-types

问题是这个队列只在event-loop通常用作顶级事件循环的情况下进行处理(相对于模式或菜单事件循环,或可能在其他函数中发生的消息处理)。在非event-loop消息处理中,操作不会出队,也不会被处理。

于 2015-02-10T01:57:53.917 回答