-2

Allegro Common Lisp フォームは、Delphi フォームに非常によく似ています。ただし、少なくとも Delphi フォームでは、Form1、Button1、Button2 などのグローバル変数にアクセスできます。

Allegro common lisp では、ボタンのプロパティとフォームのプロパティにアクセスする方法を理解できる唯一の方法は、find-sibling を使用して LET でローカル変数を設定するか、独自のグローバル変数を設定することです。便宜上アクセスできる一般的なlispのbutton1、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';

find sibling を使用せずに allegro common lisp で button1 タイトル (delphi のキャプションと同じ) を設定するにはどうすればよいですか? 関数内のオブジェクトと対話している場合は、ウィジェット関数パラメーター (delphi Sender を TButton として使用) を使用できます。ただし、他のコンポーネントには find-sibling を使用する必要があります。allegro common lisp は、単にbutton1button2form1のようなグローバル変数を与えるのではなく、find-sibling コードを書くことを強制しているようです。

編集: Delphi では form1 はグローバルですが、button1 と button2 はグローバル フォーム クラスの一部にすぎません。それら自体はグローバルではありませんが、できるのでグローバルのように動作します。

form1.button1
form1.button2

他のユニットから

(または、現在の unit1 の self.button1 ですが、デルファイでは、キーボード入力の利便性のために、常に 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
    ;; ...
    ))

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メッセージ処理では、操作はキューから取り出されず、処理されません。

于 2015-02-10T01:57:53.917 に答える