这是浪费时间,但在大多数情况下并不多。我会先回答,然后再提供一些经验。
对于大多数情况,这可能就足够了:
(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 生成的 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
消息处理中,操作不会出队,也不会被处理。