時間の無駄ですが、ほとんどの場合、それほど多くはありません。最初に答えてから、これについて後から考えた経験を提供します。
ほとんどの場合、これで十分です。
(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
;; ...
))
1 つの代替方法は、ウィンドウにスロット (特定のクラス) を定義することです。
これは、コントロールにグローバル変数ではなくオブジェクト フィールドを使用するため、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
;; ...
)))
ここには多くのオプションがあります。
たとえば、メソッドを自動的に定義したくない場合があります。これは、ビジネス初期化ロジックを使用して独自にメソッドを定義したい場合があるため、代わりにダイアログ メーカー関数でスロットを初期化できます。しかし、その後、IDE によって生成されたコードと戦うことになります (いつでもプロトタイプ作成に使用して、コードを適応させることができます)。これが、いくつかのコードをオプションとして示した理由です。initialize-instance
:after
または、マクロを拡張して、初期化コードを引数として取る (生成された に含めるinitialize-instance
) か、別のマクロを の内部または代わりにinitialize-instance :after
、あるいはその両方で使用することもできます。前者は後者を使用します。
多くの UI の更新がある場合、この小さな、しかし繰り返される時間の無駄が重要になると言えます。そして、多くの場合、数十秒または数分の間に少なくとも毎秒数十回の呼び出しを意味します。ほとんどのダイアログ ウィンドウは、ユーザーからデータを照会するか、アクション ボタンを備えたツール ウィンドウのように動作するため、このように動作するべきではありません。
しかし、進行状況ダイアログなどのようなケースに陥ったと仮定しましょう。
Allegro のプロファイラーを使用して自分で確認できるように、代わりにアクセサーまたはスロットを使用find
すると、パフォーマンスがかなり向上しますが、それは最上位のホット スポットにすぎません。
このような状況では、UI の更新が本当に必要かどうかを知ることが必要になる場合があります。そのため、ダイアログやその項目に本当に触れる必要があるかどうかを知るために、簡単な簿記を行ってください。これは実際には非常に簡単で、ダイアログ項目へのアクセスを最適化するよりも節約できます。適切な候補のデータ型は、カウンターとタイムスタンプです。
さらに別の手法は、決定された間隔で更新を遅らせることです。おそらく、以前の更新要求をまとめて UI を更新するタイマーを使用します (たとえば、更新をキューに入れ、まだ開始されていない場合はタイマーを開始し、タイマーを 1 回限りにして、開始されないようにします。不要なときに実行しないようにするには、実際に更新する前にタイマー関数でキューに入れられた更新を減らします)。時間単位ごとに多くの更新が予想される場合は、これが最大の最適化になる可能性があります。ただし、これは最も具体的で骨の折れる作業でもあり、単純さから外れるとエラーが発生しやすくなります。
このキューを実装すると、非 UI バックグラウンド ワーカー スレッドで発生する可能性があるビジネス モデル プロパティの変更/状態変更/進行イベントでの UI 更新の登録など、スレッド間通信が得られる可能性があります。
PS: これで、これらのアプローチの 1 つだけを実装する必要があると言っているのではありません。
PS: Allegro は、引数を使用post-funcall-in-cg-process
した累積操作:delete-types
や引数を使用した冪等操作など、クロススレッド UI 操作のキューイングを既にサポートしてい:unless-types
ます。
問題は、このキューがevent-loop
通常トップレベルのイベント ループとして使用される場合にのみ処理されることです (モーダルまたはメニュー イベント ループ、または他の関数で発生する可能性のあるメッセージ処理とは異なります)。非event-loop
メッセージ処理では、操作はキューから取り出されず、処理されません。