Lisp-Stat 翻譯 —— 第七章 窗口、菜單和對話框

第七章 窗口、菜單和對話框

本書剩餘幾章將描述Lisp-Stat的繪圖系統,該系統的主要目的是支持使用、自定義及開發動態統計繪圖。做爲該努力的一部分,系統將提供獲取用戶接口工具的方法,好比慘淡和對話框。該系統被設計成一個高級的工具集,即實如今微機或者工做站的窗體系統之上,好比Macintosh Toolbox或者X11系統。 前端

    本章將介紹一些基礎的視窗系統概念,重點突出Lisp-Stat繪圖系統的實際。此外,本章還描述了基本的Lisp-Stat窗體、菜單和對話框。接下來的兩章將描述Lisp-Stat 的繪圖窗體。 程序員

7.1 視窗系統接口

不少流行的微機和工做站都使用一個繪圖接口來使他們和他們的程序易於使用。這些接口將屏幕分割爲一些獨立的窗口,這個獨立的窗口能夠疊置,也能夠像在桌面上移動一張紙同樣移動它;菜單用來發布一些用來運行的命令;對話框用來向用戶詢問信息。 編程

    繪圖接口有不少共性,做爲由Xeror公司開發,由Macintosh普及的系統的後代,咱們就不以爲大驚小怪了。可是,與傳統的表示菜單、由哪一個窗體接收鍵盤敲入的字符等等操做相比,仍有些不一樣。這些傳統叫作用戶接口說明。例如,在Macintosh系統的菜單上一般放置一個菜單框。在SunView系統裏,在由三個按鍵的鼠標上,菜單在窗體的不一樣部分按下右鍵的時候會彈出。還有一個例子,在Macintosh系統上,爲了選擇一個文本集,你能夠在文本起始處單擊鼠標,再拖拽鼠標覆蓋文本集。經過在要擴充的選集後邊點擊鼠標,同時按下shift鍵,你也能夠擴充一個選集。在SunView裏,慣例是點擊鼠標左鍵以開始選擇,而後經過拖拽或者點擊鼠標中間的按鈕來擴展選集。 windows

    視窗系統是設計用來支持使用圖形用戶接口編寫程序的編程工具集。視窗系統負責繪製窗體和菜單,並監測用戶動做,好比移動或改變窗口大小,而後通知程序全部者該窗體是夠須要任何形式的更新。例如一些窗體系統,像Macintosh Toolbox和SunView系統,被設計來實現用戶接口直到的特定集合。其它的系統,好比X11系統,被設計能夠支持各類各樣不一樣的用戶接口,X11系統也想要用到更多種類的不一樣硬件上。尤爲是,它支持帶一個、兩個、三個按鈕的鼠標。 數組

    帶圖形接口的程序的主要特徵是須要響應用戶產生的事件:當窗體移動或改變大小時,它們可能須要重畫,當須要菜單時,有須要解釋的按鍵敲擊或者鼠標動做時,它們就要顯示。這就使得編寫一個這樣的程序比編寫一個僅僅運行一個任務到結束爲止要有至關大的難度,或者甚至比解釋一個每次每行敲入的文本的程序還要難。然而,選擇一個好的編程策略會使得這種編程變得更容易些。尤爲是,桌面虛擬化讓使用面向對象方法更加天然,在該編程策略裏物理窗體和菜單在軟件裏是以對象的方式表示的。指向這些對象的用戶動做可視爲消息。 app

    在面向對象的視窗系統裏,程序員只須要構造一個合適的對象集合,並給每一個須要響應的對象的消息編寫方法。而後視窗系統將運行「事件循環」,監測用戶動做並轉換任何須要程序處理成消息的事件。例如,若是視窗系統檢測到窗體的一部分顯露出來了,那麼它將向對象發送一個顯示窗體的消息要求該對象重畫其自身。一個面向對象視窗系統還能夠提供一個標準繪圖對象的庫,而後程序員就可以安排一個新對象來從庫裏的最適合的對象對對象集合那裏來繼承。由於庫裏的對象爲大多數消息提供了合理的默認方法,這就極大地減小了程序員編寫新方法的數量。另外,它有助於減小與用戶接口規範的背離,所以會使得程序更加易用。 框架

    Lisp-Stat繪圖系統是一個高級的、面向對象的工具集,設計它是爲了實如今更多通常的窗體系統之上的繪圖系統。爲了容許系統保持合理的間接性,一些這種方案是須要的,可是同時還要合理地擬合與用戶接口規範之間的不一樣。一些概念是有一點抽象地對待的,容許以一種最適合本地用戶接口的方式,用特定的實現來處理它們。例如,每一個Lisp-Stat繪圖窗口都有一個適合它的獨立的菜單。在Macintosh操做系統的XLISP-STAT裏,系統將確保不管何時窗體是最前面的窗體時,菜單都會被安裝到菜單欄上。在SunView裏,當右鍵在窗體裏點擊時,系統將彈出菜單。XLISP-STAT的X11版本在繪圖窗體裏提供了一個按鈕,當該按鈕按下的時候將彈出菜單。類似地,當窗體被告知有一個鼠標點擊事件發生的時候,任何有效的修改都會被通告。在Macintosh操做系統上,該系統有一個按鈕鼠標,經過在按住鼠標鍵的同時按下shift鍵或者option鍵,修改信號將被髮送。在帶三個鼠標按鍵的系統上,修改信號可能只識別按下的那個鼠標按鈕。 dom

圖7.1 一個用來檢測一個迴歸模型的菜單 ide

7.2 窗體

Lisp-Stat支持至少兩種窗體:繪圖窗體和對話框窗體。有的實現也可能支持其餘的窗體類型,像文本窗體。全部的窗體都共享必定的基本特性。這些特性被收集在一塊兒放到基本窗體原型window-proto裏。這個原型不能直接使用;它的做用僅僅是收集這些共性特徵。對話框窗體和繪圖窗體原型都繼承自window-proto。window-proto的:isnew方法容許一些特徵使用關鍵字參數來設置。除非另有說明,對更多特化的窗體類型的:isnew方法也能接受這些關鍵字。 函數

    每一個窗體都有一個標題,在不少用戶接口裏,這個標題顯示在窗體頂部的標題欄裏。例如,若是p是一個由histogram函數建立的直方圖窗體,那麼它的標題的初始化將由下式給出:

> (def hardness
       (list 45 55 61 66 71 71 81 86 53 60 64 68 79 81 56 68 75
             83 88 59 71 80 82 89 51 59 65 74 81 86))
HARDNESS

> (def tensile-strength
       (list 162 233 232 231 231 237 224 219 203 189 210 210 196
             180 200 173 188 161 119 161 151 165 151 128 161 146
             148 144 134 127))
TENSILE-STRENGTH

> (def abrasion-loss
       (list 372 206 175 154 136 112  55  45 221 166 164 113  82
              32 228 196 128  97  64 249 219 186 155 114 341 340
             283 267 215 148))
ABRASION-LOSS
> (setq p (histogram hardness))
#<Object: 13feb04, prototype = HISTOGRAM-PROTO>
> (send p :title)
"Histogram"
若是圖形裏包含一個變量名爲hardness的直方圖,那麼咱們給出窗體更適合的一個標題:
> (send p :title "Hardness")
"Hardness"
該窗體的:isnew方法容許使用:title關鍵字指定窗體的初始化標題。

    窗體的大小和位置用像素來量度。:size消息能夠用來肯定窗體的當前大小,返回的結果是窗體的長度和寬度的列表。若是p是咱們的直方圖窗體,那麼下邊的表達式的意思是窗體當前值是長度200像素,寬度100像素。

> (send p :size)
(250 125)
經過向該窗體發送帶兩個參數的:size消息,這兩個參數是新的寬度和高度值,窗體就會改變自身的大小:
> (send p :size 250 150)
(250 150)
窗體的位置由窗體左上角相對於屏幕左上角的座標進行描述的,窗體當前的位置可使用:location消息來設置和獲取。
> (send p :location)
(58 31)
上式的結果意味着p窗體的左上角離屏幕左上角的右側是100像素,離屏幕左上角下側是50像素。
> (send p :location 200 100)
(200 100)
經過上式,窗體能夠被移動到一個新的位置。

    窗體原型的:isnew方法容許使用:size和:location關鍵字參數指定窗體的初始化大小和位置。使用這些關鍵字參數的實參是帶兩個整型數列表的列表:針對大小尺寸的長度於寬度和針對位置的左座標和上座標。

    :size和:location消息肯定一個窗體的內容的尺寸大小和位置。不少窗體系統在窗體內容周圍防止一個框架。你也可使用:frame-size和:frame-location消息來肯定框架的左上角座標的位置和整個框架的大小。

    對於大部分目的而言,將一個Lisp-Stat窗體對象和窗體在屏幕上的圖像等同起來是方便的.可是事實上它們是有區別的。對象表示一種與本地視窗系統裏的圖像進行通訊的一種方式。當一個新的窗體對象建立的時候,它的圖像馬上可見,除非向它的:isnew方法傳遞了值爲nil的:show關鍵字。經過向對象發送:hide-window消息,該圖像能夠暫時從屏幕移除。:show-window消息可使一個隱藏的窗體再次可見,並把它移動到全部可見窗體的最前端。那麼,爲了隱藏一個由對象p表示的窗體的圖像,咱們可使用如下表達式:

> (send p :hide-window)
下邊的表達式將使圖像再次可見。
> (send p :show-window)
:show-window消息能夠用來將部分或所有被其它的窗體掩蓋的那個窗體帶到最前端。

    移動窗體的圖像的第二個方法是向窗體對象發送:remove消息,使用如下消息:

> (send p :remove)
該消息將永久性地移除窗體的圖像。

    區分窗體圖像的臨時性隱藏和永久性移除的一個緣由是本地窗體系統可能須要分配內存來表示圖像。隱藏圖像操做使一個窗體臨時性地不可見而不須要釋放它的內存;相反地,一旦一個窗口被移除,它的內存就被釋放了。理想狀況下,這種內存管理會自動處理,可是這不可能針對全部的本地窗體系統。結果,對再也不須要的窗體發送:remove消息是重要的。

    大多數用戶接口都提供一個標準方法,用來在窗體不須要的時候接觸窗體。在Macintosh操做系統上,窗體一般在其左上角有一個小的矩形,即關閉按鈕。在SunView系統裏,依附於窗體框架的一個菜單包含一個關閉選項。在關閉按鈕上點擊,或者採起標準動做來接觸窗體的話,將向窗體對象發送一個不帶參數的:close消息。若是你想讓窗體p在關閉的時候隱藏,而不是移除的話,你能夠像這樣定義一個新的:close方法:

> (defmeth p :close () (send self :hide-window))
:CLOSE

窗體的:isnew方法容許使用:go-away關鍵字來指定一個窗體是否能夠包含一個關閉設備,使用值爲nil的這個關鍵字將產生一個不帶關閉設備的窗體。

    有時建立一個從屬於其它毒性的對話框或者圖形是頗有用的。當主圖形從屏幕移除的時候,叢書圖形也應該被移除。爲了支持這個語法,你能夠經過向主窗體發送一個以從屬窗體爲參數的:add-subordinate消息來增長從屬窗體。從屬窗體可使用:delete-subordinate消息來刪除。window-proto原型的:remove方法確保全部安裝的從屬窗體在主窗體移除的時候也被髮送:remove消息。

練習 7.1

略。

7.3 菜單

繪圖菜單是用來讓程序採起一些可能的動做的策略。圖7.1展現了一個用來檢測迴歸模型的簡單的菜單。菜單一般是放置於菜單欄裏或者窗體的特定區域,用來響應鼠標點擊操做,鼠標釋放以前移植都會顯示。當鼠標光標從菜單上移過期,菜單項包括光標都是高亮的。若是在高亮的菜單項上釋放鼠標,那麼響應的動做將被執行。若是鼠標釋放的時候沒有菜單項是高亮的,那麼將不執行任何動做。特定時間不合適的菜單項多是不可用的。用來切換一些特徵開或者關的菜單項,當該特徵打開的時候它們前邊可能用一個複選框標記。圖7.1裏的Intercept菜單項被複選,表示當前模型包含一個截距。

    Lisp-Stat菜單由繼承自menu-proto原型的對象構造而成。每一個菜單都包含一個菜單項列表,它們是從menu-item-proto原型繼承來的對象。在支持分級菜單的系統上,菜單裏的菜單項多是其它菜單。

    對於以一些方式進行選擇這樣的需求,菜單是可用的。最簡單的方法就是在菜單欄裏安裝菜單。當菜單安裝到菜單欄裏的時候,它的標題出如今菜單欄裏。點擊標題將彈出菜單。菜單欄是Macintosh用戶接口的標準組件。針對其它用戶接口的Lisp-Stat實現提供了一個類似的策略,好比一個包含安裝好的菜單的標題的窗體。使菜單可用的其它方法還包括在繪圖窗體裏安裝它們,或者在繪圖窗裏彈出菜單來響應鼠標點擊。這些方法將在下一章裏作進一步討論。

    當系統要求召喚一個菜單的時候,將發生如下動做:

  • 它將不帶參數的:update消息發送給菜單裏的每一個菜單項。這容許菜單項檢查它們是否應該是使能的,是否應該包含一個複選標記。
  • 它指示這個菜單,若是光標在它內部將高亮菜單項。
  • 當鼠標按鍵釋放的時候,若是一個菜單項是高亮的,不帶參數的:do-action消息將會發送給相應的菜單項對象。

    讓咱們來看一下如何構造圖7.1裏的那個菜單。菜單自己能夠經過下式建立:

> (setf model-menu (send menu-proto :new "Model"))
#<Object: 144e774, prototype = MENU-PROTO, title = "Model">
menu-proto的:isnew方法須要一個參數,菜單的標題。這個標題儲存在title槽裏,它能夠經過使用:title消息來讀取和改變。菜單還有一個enabled槽,該槽的值表示當它被安裝到菜單欄的時候是否可以用來選擇,該槽的值能夠經過:enabled讀取方法來設置和獲取。對於這個由:enabled關鍵字指定的槽,:isnew方法接受一個值,其默認值爲t。

    :items消息返回安裝在一個菜單裏的菜單項的列表。對於咱們的菜單,

> (send model-menu :items)
NIL
初始狀況下,菜單中沒有菜單項。爲了完善該菜單,咱們須要構造三個菜單項,並將它們放置到菜單裏。

    菜單項包含以下槽,title、mark、enabled和action。這些槽可使用相應的讀取方法來讀取和修改。title槽出如今菜單裏的字符串。mark槽是t或者nil,是哪一個值依靠該菜單項前是否有個複選標記。enabled槽表示該槽是否可用。定義:do-action消息的menu-item-proto方法,若是該槽的值不是nil的話,是用來簡化不帶參數的action槽的調用內容的。那麼你能夠用兩種方式控制菜單項的行爲:經過在動做槽裏放置一個函數,或者經過定義一個新的:do-action方法。

    假設咱們爲2.5.1節裏的數據設置了一個迴歸模型對象,並做爲一個全局變量的值:

> (setf *current-model*
        (regression-model (list hardness tensile-strength)
                          abrasion-loss))

Least Squares Estimates:

Constant                   885.161      (61.7516)
Variable 0                -6.57083      (0.583188)
Variable 1                -1.37431      (0.194309)

R Squared:                0.840231    
Sigma hat:                 36.4893    
Number of cases:                30
Degrees of freedom:             27

#<Object: 141a688, prototype = REGRESSION-MODEL-PROTO>
而後咱們能夠構造它的概述信息,而且爲咱們的菜單繪製菜單項:
> (setf display-item
        (send menu-item-proto :new "Print Summary"
              :action #'(lambda ()
                          (send *current-model* :display))))
#<Object: 13e21b4, prototype = MENU-ITEM-PROTO, title = "Print Summary">
> (setf plot-item
        (send menu-item-proto :new "Plot Residuals"
              :action #'(lambda ()
                          (send *current-model* :plot-residuals))))
#<Object: 13e0ee4, prototype = MENU-ITEM-PROTO, title = "Plot Residuals">
截距項這樣構造:
> (setf intercept-item
        (send menu-item-proto :new "Intercept"
              :action
              #'(lambda ()
                  (send *current-model* :intercept
                        (not (send *current-model* :intercept))))))
#<Object: 13dffe4, prototype = MENU-ITEM-PROTO, title = "Intercept">
intercept-item的動做函數負責切換截距的打開與關閉。咱們能夠爲:update消息定義一個方法,以確保不管何時模型裏包含一個截距該菜單項都包含一個複選框標記:
> (defmeth intercept-item :update ()
    (send self :mark
          (if (send *current-model* :intercept) t nil)))
:UPDATE
這裏的if表達式用來確保傳遞給:mark消息的番薯是t或者nil。

    經過向菜單發送一個或多個菜單項做爲:append-items消息做爲參數,這些菜單項被置於菜單中。新的菜單項按次序追加到已存菜單項集合的尾部。爲了在咱們的菜單裏安裝3個菜單項,咱們使用以下方法:

> (send model-menu :append-items
        intercept-item display-item plot-item)
NIL
經過向菜單發送帶一個或多個參數的:delete-items消息,菜單項能夠從菜單欄裏移除。若是一個菜單項被安裝到一個菜單裏,:menu消息返回包含該菜單項的菜單。針對截距項的例子:
> (send intercept-item :menu)
#<Object: 141bcf8, prototype = MENU-PROTO, title = "Model">
若是這個菜單項沒有安裝到菜單裏,:menu消息返回nil。

    最後,咱們能夠經過向菜單發送:install消息,將菜單安裝到菜單欄中:

> (send model-menu :install)
NIL
在該表達式求值以後,菜單的表達應該已經出如今菜單欄裏了。在菜單標題上點擊鼠標按鍵,那麼將產生如圖7.1所示的菜單。你能夠經過向菜單對象發送:remove消息,從菜單欄裏移除該菜單。

7.1a 本節中安裝自定義菜單的圖示

    若是你使用相似菜單做爲一個大程序裏的一部分,你不可能老是容許截距能夠改變,你能夠經過使用以下表達式使截距項不可用:

> (send intercept-item :enabled nil)
NIL
    菜單包含的槽有title、enabled和items。菜單項包含的槽有title、enabled、mark和action。菜單和菜單項都包含一些沒有在上邊列出的其它的槽。這些槽用於內部使用,不該該被修改。這裏提到的一些槽也能夠用來做爲與本地窗體系統的接口,若是他們被直接修改了就不能合適地處理以上接口。所以它們僅能使用獲取方法來改變。

    在構造和調試菜單時,兩個附加工具是頗有用的,它們是dash-item-proto原型和sysbeep函數。虛線項原型繼承自菜單項原型,表示一個包含虛線的不能使用的項。這樣的項對於將菜單裏的菜單項分組是頗有用的。你可使用下式構建一個新的虛線項:

> (send dash-item-proto :new)
#<Object: 1352580, prototype = DASH-ITEM-PROTO, title = "-">
sysbeep函數只是產生一個音調,可用來做爲報警或者在調試過程當中做爲虛擬路徑的一部分。不給該函數傳遞參數,它將產生一個標準長度的音調;經過爲該參數傳遞一個整形參數,能夠改變音調的長度。

練習 7.2

略。

7.4 對話框

對話框是一類特殊的窗體,用來從用戶處獲取信息,或者給用戶一個向程序發送指令的機會。一個對話框包含一些不一樣種類的對話項:

  • 按鈕
  • 複選框
  • 單選按鈕羣
  • 滾動條
  • 靜態的和可編輯的文本域
  • 一維的或二維的可滾動的字符串列表

有兩類對話框窗體:模態對話框和非模態對話框。模態對話框更常見。一個模態對話框採起這樣一種方式:向用戶詢問一個問題,在程序繼續向前運行以前該問題必須獲得回答。當出現一個模態對話框的時候,全部的輸入都指向那個對話框直到它清除爲止,一般是經過點擊按鈕。另外一方面,非模態對話框就像任何一個基於窗體的程序裏的窗體同樣,不一樣的是它包含按鈕、複選框等等。非模態對話框與菜單的使用很像,當用戶點擊一個按鈕的時候,它也會像菜單操做同樣引發一個事件驅動的程序採起必定的動做。在Macintosh操做系統上,打開一個文件的標準對話框是模態對話框;Macintosh操做系統的控制面板是一個非模態對話框。

    Lisp-Stat的繪圖菜單裏的不少菜單項會打開對話框來得到額外的信息。這些對話框中的多數是模態的,能夠經過用來構建標準對話框的簡單函數來構造。

7.4.1 一些標準模態對話框

最簡單的模態對話框會通知用戶有些重要的事情發生了。message-dialog函數帶一個字符串參數,並提供一個包含字符串和一個OK按鈕的模態對話框,而後該對話框將等待用戶點擊按鈕。例如,下邊的表達式表示圖7.2中的對話框:

> (message-dialog "There is no variable named X")
圖7.2 一個消息對話框

    代替聲明一個不存在的變量,最好提供給用戶繼續或者異常終止操做的一個選擇。ok-or-cancel-dialog函數表示一個帶兩個按鈕的模態對話框,分別是OK按鈕盒Cancel按鈕。若是點擊OK按鈕,將返回t;不然,返回nil。那麼你可使用以下表達式:

> (let ((s (format nil "There is no variable named X. ~%~
Do you want to try another variable?")))
    (ok-or-cancel-dialog s))
T
> (let ((s (format nil "There is no variable named X. ~%~
Do you want to try another variable?")))
    (ok-or-cancel-dialog s))
NIL
代替前邊的表達式。

    ok-or-cancel-dialog帶一個額外的可選參數。若是這個參數是true(默認值),那麼OK按鈕是默認按鈕。也就是說,點擊回車鍵與點擊OK按鈕式同樣的。若是這個參數爲nil,Cancel按鈕是默認按鈕。

    可使用choose-item-dialog函數表達一個更普遍的選擇,該函數帶一個加速字符串和字符串列表,表示一個帶加速的對話框,針對列表的每一個元素的單選按鈕,OK按鈕盒Cancel按鈕。若是按下了Cancel按鈕,返回nil;若是按下了OK按鈕,將返回從0開始的被選元素的下標。例如,爲了容許對一個迴歸模型的獨立變量的選擇,咱們可使用如下表達式來表示一個帶有三個選擇項的對話框:

> (choose-item-dialog "Dependent variable:" '("Y0" "Y1" "Y2"))
1
若是標籤爲"Y1"的選項被選中,該表達式將返回1。關鍵字參數:initial在對話框顯示的時候,用來指定選擇以高亮的那個下標,其默認值爲0。

    choose-subset-dialog函數也是類似的,可是它經過使用一系列的複選框對話框,容許一個或多個選項供選擇。若是點擊了OK按鈕,它返回包含被選中下標的列表的列表;若是點擊了Cancel按鈕,它返回nil。返回值的形式容許你區分取消和沒有選項選中之間的不一樣。當對話框顯示的時候,:initial關鍵字用來指定用來複選的選項下標的列表,默認狀況,沒有複選選項被選中。爲了在一個迴歸模型裏選擇獨立變量,全部選擇的變量爲默認值,咱們可使用以下表達式:

> (choose-subset-dialog "Independent variables" '("X0" "X1" "X2") :initial '(0 1 2)) 
((0 1 2))
若是第一項和第三項被選中的話,結果將返回((0 2))。

    若是你沒法減小來自用戶的在一些選項中選擇的信息,你能夠請求輸入一個字符串或一個表達式,爲了請求一個字符串,你可使用get-string-dialog函數。下邊的表達式將打開圖7.3所示的模態對話框:

> (get-string-dialog "Name of the dependent variable:"
:initial "Y")
"Y"
若是你點擊了OK按鈕,可編輯文本域的當前內容的字符串將被返回;不然,返回nil。

    get-value-dialog與get-string-dialog類似,可是它將可編輯文本域裏的文本當成一個將被求值的Lisp表達式。若是按下了OK按鈕,那麼文本域裏的表達式將被讀取和求值,值得列表將返回;若是按下Cancel按鈕,返回nil。所以若是用戶輸入表達式(+ 1 2)並按下OK按鈕,那麼結果將是列表(3);若是用戶輸入表達式nil,那麼將返回(NIL)。這容許你將一個帶值nil的結果表達式和一個取消操做區別開來。由:initial關鍵字提供的可編輯文本域的初始化表達式,將被轉變爲一個字符串,該字符串是使用~s指令的format和print風格習慣的。

練習 7.3
略。

聯繫 7.4
略。

7.4.2 非模態滑動對話框

最有用的非模態對話框是構造用來控制一個動畫的滑塊。在2.7節裏,咱們使用sequence-slider-dialog構造了一個滑塊來控制一個動畫的能源轉換圖。這個函數須要一個參數——一個Lisp序列。對話框滾動值即序列的值,滾動條每滾動一次,動做函數就調用一次。動做函數須要一個調用參數,序列的當前值。經過將:action關鍵字參數傳遞給sequence-slider-dialog來指定動做函數。舉個簡單的例子,如下表達式打開了一個序列滑塊(譯者注:若是這是窗體未出現繪圖子窗體的話,能夠經過調用菜單欄的Windows->Tile來平鋪全部窗口):

> (sequence-slider-dialog
   (iseq 100 500)
   :action #'(lambda (x) (format t "Current element: ~s~%" x)))
#<Object: 146aaf0, prototype = SEQUENCE-SLIDER-DIALOG-PROTO>

滾動條每滾動到一個合適的位置,打印當前的序列元素。默認地,參數序列的當前元素在值域裏顯示。若是這樣不合適,你可使用:display關鍵字參數來提供一個相同長度的顯示序列的替代序列。:title關鍵字能夠用來爲窗體指定一個標題字符串。標記滑塊值的標籤能夠經過使用:text關鍵字來代替標籤字符串,其默認值是字符串"Value"。

    能夠向序列滑塊對話框對象發送:value消息,來設置或者獲取當前序列元素的下標;發送:action消息獲取或改變更做函數。

    滑塊的第二種形式能夠經過interval-slider-dialog函數來構造。這個函數須要一個參數,即滑塊區間的最大值及最小值的列表形式,返回一個區間滑塊對象。這個區間被離散成一個實數序列。使用的點的數量可使用:points關鍵字指定,其默認值爲30。若是參數:nice的值是nil的話,區間參數裏的點的數量和端點的值將被精確地使用。不然,它們將會稍微地改變以適應打印更加漂亮的打印值的目的。默認地,該參數的值是t。傳遞給該函數的:text, :title和:action關鍵字參數在產生序列滑塊時使用,如下表達式構造了一個區間滑塊,該滑塊滑過單位區間,離散出大約50個點,每次改變滑塊位置的時候打印當前值。

    能夠向區間滑塊對話框對象發送:value消息來設置和獲取當前值。一個新的值將會四捨五入到區間的離散過的點中裏它最近的那個點上。區間滑塊的動做函數也能夠經過:action消息來設置和獲取。

7.5 構造自定義對話框

由上節描述的函數產生的標準對話框對於大多數目的是足夠勝任的。可是偶爾咱們也可能由於特定的問題,想要構造一個更加複雜的對話框。例如,咱們可能想要使用圖7.4所示的對話框來指定一個迴歸模型。咱們可使用對話框和對話框項原型來構造這樣一個對話框。

7.5.1 對話框原型

有兩個對話框原型,dialog-proto是針對非模態對話框的,modal-dialog-proto是針對模態對話框的。這些原型都是繼承自window-proto原型的。一個新的對話框能夠這樣來構造:向合適的對話框原型發送:new消息,其參數是對話框項的列表,可能還有一些關鍵字參數。:title, :location, :size和:go-away關鍵字能夠用來對新的對話框設置響應的屬性。:go-away關鍵字對模態對話框是被忽略的。一個額外的關鍵字參數是:default-button,若是使用了該參數,它應該是一個對話框按鈕對象或者nil。默認值是nil,意思是沒有默認按鈕。對話框裏的默認按鈕可使用:default-button消息來設置和獲取。

圖7.4 創建一個迴歸模型的對話框

    傳遞給對話框的:isnew方法做爲第一個參數的對話框項列表,會指定對話框的佈局。若是列表只由對話框項組成,那麼這些對話框項將按列排列。出如今對話框項列表裏的一個列表表明一行。在一個行裏的一個列表是一個子列,等等。例如,用來構建圖7.3裏那個對話框的對話框項列表是這樣的:

(prompt-text editable-text (ok-button cancel-button))
這裏的一個列是由一個以提示爲目的的靜態文本、一個爲了輸入字符串的可編輯的文本項和一個有兩個按鈕的行。與菜單項相似,對話框項自己也是對象。對話框項列表可使用:items消息來獲取。

    當一個對話框初始化構造的時候,它表現爲非模態的風格,不管你用哪一個原型建立。當有用戶動做發生在一個特定的對話框項上的時候,系統將經過輸入一個字符串或者移動一個滾動條的形式來調節該項,而後向該對話框項對象發送:do-action消息。通常狀況,只有按鈕,滾動條和可滾動的列表須要從新執行這些消息。

    爲了使用模態對話框,你能夠發送:model-dialog消息。該消息的方法不須要參數,但能夠接受一個可選參數。除非該可選參數被置爲nil,在返回以前方法將向對話框發送:remove消息。:modal-dialog方法運行一個循環,它強制全部用戶動做都指向該對話框。該循環持續運行直到向對話框發送帶一個參數的:modal-dialog-return消息,該參數能夠是任何Lisp項,它將最爲:modal-dialog消息的結果返回。

7.5.2 對話框項

全部的對話框項都繼承自dialog-item-proto原型。與window-proto原型類似,dialog-item-proto不直接使用。它做爲六類指定的對話框項的共有功能的集合體的父類而使用,這六類對話框項是切換項(toggle items)、選擇項(choice items)、文本想(text items)、按鈕(button)、滾動條(scroll bars)和列表項(list items)。

    對話框項能夠應答:do-action和:action消息。:action消息獲取或者設置action槽的值。該槽應該使用nil或者一個帶0個或1個參數的函數,使用那個依靠項的類型。若是槽的值是nil的話,:do-action的默認方法將調用action槽的值。對話框項:isnew方法也接受一個:action關鍵字參數來設置動做函數。

    對話框項:isnew方法可使用:location和:size關鍵字參數。若是使用了這兩個參數,它們將定位該項在對話框中的位置。這兩個參數都應該是含有兩個整數的列表,單位是像素。若是沒有指定大小和位置,提供給對話框:isnew消息的項列表和對話框裏的項的數據,將被用來在這些項周圍構造一個對話框。你極可能歷來不用直接設置對話框項的位置,除非你須要將對話框項對其得很好。可是你可能須要:size關鍵字來設置可編輯項或滾動條的大小。尺寸參數的值應該是該項的長度與寬度組成的列表。若是及確實須要提供位置,那它應該是該項的左上角座標的列表,這是相對於對話框窗體的左上角來講的。

    爲了肯定模型是否包含一個截距或者一個特定的獨立變量,圖7.4所示的對話框包含一些複選框或者一些切換項。切換項繼承自toggle-item-proto原型。針對這個原型的:isnew方法須要一個字符串參數,該參數將被用做該項的標籤。除了標準的關鍵字參數,也可使用:value關鍵字。爲了切換對話框項的開與關,該值應該分別被置爲t貨值nil。能夠向一個切換項發送:value消息來設置和獲取當前值。咱們的對話框的截距項能夠這樣建立:

> (setf intercept
        (send toggle-item-proto :new "Intercept" :value t))
#<Object: 132e764, prototype = TOGGLE-ITEM-PROTO>
對話框項在初始化時是複選的,由於:value的值使用了t。該項沒有安裝動做函數,由於項的值是不須要的直到按下OK按鈕。該切換項須要的三個獨立變量能夠這要構造:
> (setf x-item-0 (send toggle-item-proto :new "X0"))
#<Object: 133d810, prototype = TOGGLE-ITEM-PROTO>
> (setf x-item-1 (send toggle-item-proto :new "X1"))
#<Object: 133d030, prototype = TOGGLE-ITEM-PROTO>
> (setf x-item-2 (send toggle-item-proto :new "X2"))
#<Object: 133c790, prototype = TOGGLE-ITEM-PROTO>
    經過在對話框中使用一個選擇項和一個單選集合,能夠設置迴歸模型的因變量。就像一個汽車收音機同樣,在給定時間只有一個按鈕時打開的。它的原型是choice-item-proto,:isnew方法須要一個字符串列表做爲按鈕的標籤。:value關鍵字能夠用來指定初始條件下被選中的那個項的下標,默認地,該下標值爲0。:value消息設置和獲取當前選中的項的下標。用來選擇因變量的選擇項能夠這樣產生:
> (setf y-item (send choice-item-proto :new
                     (list "Y0" "Y1" "Y2") :value 1))
#<Object: 133bd80, prototype = CHOICE-ITEM-PROTO>
該表達式初始選中項是"Y1"。

    咱們的對話框包含三個靜態文本項:一個用來標記因變量的選擇,一個用來標記自變量的選擇,還有一個做爲模型名稱的提示。還有一個可編輯的文本項用來輸入模型名稱。靜態文本項繼承自text-item-proto,可編輯文本項繼承自edit-text-item-proto。能夠經過向合適的原型發送帶初始化文本爲第一個參數的:new消息來建立新的文本項。除非你使用:text-length關鍵字參數來指定文本域的字符數量,系統將使用初始文本肯定文本項的大小。若是對話框字體是比例變距字體,將使用平均字符大小。靜態文本和可編輯文本項的文本均可以使用:text消息來設置和獲取。咱們的對話框裏的三個靜態哎文本項能夠這要構造:

> (setf x-label (send text-item-proto :new "X Variables"))
#<Object: 133b4f0, prototype = TEXT-ITEM-PROTO>
> (setf y-label (send text-item-proto :new "Y Variables"))
#<Object: 133ac00, prototype = TEXT-ITEM-PROTO>
> (setf prompt (send text-item-proto :new "Name:"))
#<Object: 133a4a0, prototype = TEXT-ITEM-PROTO>
可編輯文本項這樣設置:
> (setf name (send edit-text-item-proto :new "" :text-length 15))
#<Object: 1339d40, prototype = EDIT-TEXT-ITEM-PROTO>
咱們的對話框須要的最後的項是兩個按鈕。按鈕繼承自button-item-proto,能夠經過向該原型發送一個帶字符串參數的:new消息,即它的標籤爲參數,來構造一個按鈕。按鈕一般用來初始化一個動做,所以須要一個使用:action關鍵字的動做函數,或者一個指定的:do-action方法。例如,一個開始仿真的按鈕能夠定義成這樣:
> (send button-item-proto :new "Start"
        :action #'(lambda (x) (send simulation :start)))
#<Object: 13395f0, prototype = BUTTON-ITEM-PROTO>
    在模態對話框中按鈕一般用來移除對話框,所以應該向對話框發送帶合適參數的:modal-dialog-return消息。對於一個Cancel按鈕其參數一般是nil。所以,針對咱們的迴歸對話框的Cancel按鈕能夠定義成這樣:
> (setf cancel (send button-item-proto :new "Cancel"
                     :action
                     #'(lambda ()
                         (let ((dialog (send cancel :dailog)))
                           (send dialog
                                 :modal-dialog-return nil)))))
#<Object: 13387b0, prototype = BUTTON-ITEM-PROTO>
該:dialog消息返回了包含對話框項的對話框對象。

    爲了簡化模態對話框上按鈕的構造,modal-button-proto原型有一個:do-action方法,它能夠調用動做函數,而且將結果發送給帶:modal-dialog-return消息的對話框。若是action槽的值是nil,那麼nil將做爲返回方法的參數而傳遞。所以,Cancelannual可使用這個原型來構造:

> (setf cancel (send modal-button-proto :new "Cancel"))
#<Object: 1337fa0, prototype = MODAL-BUTTON-PROTO>
由於沒有使用動做函數,action槽的值默認爲nil。

    按下對話框裏的OK按鈕,應該返回對話框手機的信息。咱們能夠定義一個函數collet-values來收集對話框項裏的值到一個列表:

> (defun collect-values ()
    (list (send name :text)
          (send y-item :value)
          (which (list (send x-item-0 :value)
                       (send x-item-1 :value)
                       (send x-item-2 :value)))
          (send intercept :value)))
COLLECT-VALUES
OK按鈕能夠這樣建立:
> (setf ok (send modal-button-proto :new "OK"
                 :action #'collect-values))
#<Object: 1336ea0, prototype = MODAL-BUTTON-PROTO>
    如今咱們已經準備好全部咱們的迴歸對話框須要的項了。對話框自己能夠這樣設置:
> (setf reg-dialog
        (send modal-dialog-proto :new
              (list
               (list
                (list y-label y-item intercept)
                (list x-label x-item-0 x-item-1 x-item-2))
               (list prompt name)
               (list ok cancel))))
#<Object: 1336180, prototype = MODAL-DIALOG-PROTO>
這些項按三行爲一列排列,每一行有兩個子列,一個裏邊是y-label、y-item和intercept,還有一個裏邊是用來選擇自變量的。第二行包括提示符和用來輸入模型名字的可編輯文本域,第三行包含OK按鈕和Cancel按鈕。下邊這個表達式運行模態對話框循環,容許修改選擇項、切換項和文本項,直到其中一個按鈕點擊以前。這對圖7.4的那個配置,若是OK按鈕被點擊的話,那麼該表達式返回的結果是這樣的:
> (send reg-dialog :modal-dialog)
("m" 1 (0 1) T)
由於向對話框發送了不帶參數的:modal-dialog消息,對話框將在表達式返回以前自動移除。

    其它兩種沒在迴歸模型中使用的對話框項也是可用的,它們是滾動條和可滾動的列表。基礎滾動條項在必定整數範圍區間內滾動,經過向scroll-item-proto原型發送:new消息來構造。:isnew方法不須要參數,可是一些關鍵字參數是可用的。最小值和最大值能夠經過使用:min-value和:max-value關鍵字來設置,默認值是0和100。經過使用:min-value和:max-value消息能夠獲取和改變他們。滾動條滑塊的初始位置是最小值位置。能夠經過向:isnew方法發送:value關鍵字來指定滾動條滑塊的初始化位置,經過使用:value消息,滑塊位置的值能夠設置和獲取。在大多數系統上,滾動條能夠以一個單位爲增量或者一個更大的增量,這個有頁面有關。頁面增量的大小能夠經過向:isnew方法發送:page-increment關鍵字來指定,其默認值爲5。

    當鼠標在滾動條的活動部分按下的時候,滾動條一般會持續調整。每次自動滾動操做發生的時候,系統都會想滾動條對象發送一個不帶參數的:scroll-action消息。該消息的默認方法與:do-action方法是相同的。

    滾動條一般用來在一個序列或者一個區間內滾動。有兩個繼承自scroll-item-proto原型的原型被設計來處理這些標準實例。對於interval-scroll-item-proto原型,它的:isnew方法將表示一個區間的列表做爲本身的參數。另外,它還接受:points關鍵字參數來設置點數,接受:text-item參數來指定一個文本對象,該文本對象用來顯示當前值。對於sequence-scroll-item-proto原型,它的:isnew方法須要一個序列做爲參數。除了:text-item關鍵字,它還接受:display關鍵字,用來顯示一個備選的顯示序列。正對這些原型,經過默認的:do-action方法調用的動做函數帶有一個參數,當前序列元素或者區間裏當前的點。

    舉個例子,咱們可使用序列和區間滾動項來建立一個非模態對話框,它包含兩個滑塊,用來指定大約50次試驗的二項式分佈的參數。咱們能夠以兩個標籤開始,它們用來區分構造的可顯示值:

> (setf p-label (send text-item-proto :new "p"))
#<Object: 134321c, prototype = TEXT-ITEM-PROTO>
> (setf n-label (send text-item-proto :new "n"))
#<Object: 134292c, prototype = TEXT-ITEM-PROTO>

兩個用來顯示當前值的文本項這樣構造:

> (setf p-value (send text-item-proto :new "" :text-length 10))
#<Object: 1340d6c, prototype = TEXT-ITEM-PROTO>
> (setf n-value (send text-item-proto :new "" :text-length 10))
#<Object: 13403dc, prototype = TEXT-ITEM-PROTO>
滾動條項能夠這樣構造:
> (setf p-scroll (send interval-scroll-item-proto :new
                       '(0 1)
                       :text-item p-value
                       :action
                       #'(lambda (x) (format t "p = ~g~%" x))))
#<Object: 133f9ac, prototype = INTERVAL-SCROLL-ITEM-PROTO>
> (setf n-scroll (send sequence-scroll-item-proto :new
                       (iseq 1 50)
                       :text-item n-value
                       :action
                       #'(lambda (x) (format t "n = ~g~%" x))))
#<Object: 133e70c, prototype = SEQUENCE-SCROLL-ITEM-PROTO>
    最後一個對話框項類型是列表項。這種項表示表示文本項的一維或者二維的可滾動列表。每次至少一個單元格能夠選擇,選集能夠設置和獲取,單元格的文本能夠改變。另外,在單元格上的雙擊操做能夠被探測到。爲了建立一個列表項,能夠向list-item-proto原型發送一個帶一個序列和一個字符串的二維數組爲參數的:new消息。默認地,將構建一個單列的列表。可見列的數目可使用:columns關鍵字設置。:do-action方法默認狀況下帶一個值爲nil的可選參數,用來調用動做函數。系統爲每次點擊調用一次:do-aciton,若是點擊是雙擊的話將傳遞一個非nil參數,也就是說在前次點擊的位置兩次點擊時間足夠近。

    :set-text方法帶一個字符串和一個下標爲參數,而後將下標指定的單元格的文本改變成提供的字符串。若是該列表項是使用字符串序列構造的話,下標應該是一個數字,若是是使用數組的話,它應該是所選單元格的行與列的下標。

    :selection方法設置或者返回當前所選單元格的下標。nil下標意思是沒有單元格被選中。

練習 7.5
略。

7.6 額外的細節

screen-has-color函數能夠用來肯定顯示器是不是彩色顯示器,它沒有參數,返回t或者nil來指示顯示器是否支持彩色。

    能夠調用不帶參數的active-windows函數來得到當前顯示在屏幕上的或者臨時隱藏的全部的窗體對象的列表。

    4.5.2節裏描述的*features*變量,若是一個窗體系統接口是可用的,它將包含符號windows。若是顯示器支持彩色,那麼該特徵列表還包含符號color。若是系統支持層級菜單,還會定義一個hieratchical-menu特徵。這些特徵能夠經過使用#+和#-讀取宏來進行條件求值。

    Lisp-Stat系統被設計工做在一個單進程環境裏。結果,一些比較耗時的繪圖方法會阻止其它全部時間知道它自身完成。爲了解決這個問題,繪圖窗體容許一個空閒消息,該消息在沒有正在處理的時間可用時將向每一個窗體發送。一些系統可能還提供一個系統級別的空閒事件隊列,在沒有時間處理的時候,用以執行與特定窗體體無關的動做。

相關文章
相關標籤/搜索