MATLAB App Designer 人機介面設計 December 25, 2020 MATLAB 已經全面更新了 GUI 的設計方式, 從 GUIDE(Graphical User Interface DEsigner) 升級為 AppDesigner, 朝向更模組化 物件化導向的現代程式設計風格, 名稱借用 App 也是接地氣 AppDesigner 比 GUIDE 簡約且具備自動化的智能輔助設計, 在許多細節處提供貼心的提醒, 對初學者非常友善 因此儘早揚棄 GUIDE 的古樸風格, 絕對有必要 為彌補作者所著書籍 Coding Math 寫 MATLAB 程式解數學 出版時採用 GUIDE 的缺憾, 在此介紹 AppDesigner 以饗宴讀者 本章關於 MATLAB 的指令與語法 指令 :delete, questdlg, readtable, startupfcn, uigetfile 1
1 環境介紹 圖 1 指示了 AppDesigner 從何開始 1 圖 1: 開啟 AppDesigner 1.1 控制元件的佈局 Canvas Layout AppDesigner 的控制元件比 GUIDE 豐富 美觀, 圖 2 嘗試製作與書本相同的範例, 功能一樣但佈局刻意展示 AppDesigner 的優勢, 以示比較之意 這個 App 利用幾個基本的控制元件讓使用者選擇三種不同的分配與參數, 並繪製該分配的 PDF 或 CDF 圖 圖 2: AppDesigner 的預設佈局 (Layout) 圖 2 左側稱為 COMPONENT LIBRARY, 列出可供拖曳使用的控制元件 右側稱為 COMPONENT BROWSER, 除列出目前使用的元件名稱外, 也是設定 修改每個控制元件特性 (Properties) 的地方 AppDesigner 預設中間位置為 Design View 與 Code View 即 App 的畫面設計與反應程式撰寫 使用者可以拖曳這幾個區域到適合的位置, 已滿足自己的習慣或螢幕的大小, 如圖 1 在 2019a 版本前, 可在命令視窗輸入 appdesigner 2
3 所示, 將 COMPONENT BROWSER 與 COMPONENT LIBRARY 上下疊放, 以騰出更大的空間給佈局設計與程式撰寫 另一個做法是隱藏左右兩側的 COMPONENT LIBRARY 或 COMPONENT BROWSER, 隱藏的開關分別在兩個視窗的底角, 如圖 2 兩側底角的三角形按鈕 圖 3: AppDesigner 的佈局重新配置 在本範例中, 共使用幾個控制元件, 對照圖 2 或 3, 從左而右 從上而下分別是 : Panel ( 參數設定 ) Drop Down ( 分配 ) Edit Field(Numeric) ( 平均數 標準差 ) Radio Button Group ( 函數類型 ) Button ( 結束 ) Label ( 常態分配 : 機率密度函數 ) Axes ( 繪圖區 ) 分別介紹如下 1.1.1 鑲板 (Panel) 鑲板 (Panel) 是一塊布景, 用來黏貼一些元件, 通常這些元件是為同一個目的設定的, 如圖 3 左邊標題為 參數設定 的長方形框 使用鑲板的優點如 : 讓畫面整齊 編輯時可以同時移動裡面的所有元件 簡單說, 鑲板的作用猶如一控制台, 主導整個 app 的運作 1.1.2 下拉式選單 (Drop Down) 下拉式選單 Drop Down 的設定方式較之過去更簡單明嘹, 如圖 4 當點選圖左上角的 Drop Down 元件時, 右邊 COMPONENT BROWSER 隨即反映代表該元件的變數 App.DropDown 及其內含的特性 ( 右下角 ), 其中最主要的特性當然是下拉式選單所需要的幾個選項文字, 如圖的 常態 貝他 卡方, 這些 3
選項文字屬於 Items 這個特性 設計者按到 Items 自然知道如何一一輸入 (Items 右邊一條細長型, 上有三個小點的按鍵 ) 之後, 在 Value 輸入哪一項 作為第一選擇 ( 內定為第一項 ) 圖 4: Drop Down 的設定方式 1.1.3 編輯元件 (Edit Filed) 編輯元件在互動式 App 裡的角色是提供使用者輸入資料 AppDesigner 特別區別了輸入資料的屬性, 分成數字與文字兩種, 如圖 5 的 Edit Field(Numeric) 與 Edit Field(Text) 做出區分表示將在使用階段主動維護輸入資料的正確性, 此舉減輕了設計者這項繁雜的工作 圖 5: AppDesigner 的兩種編輯元件 除了區分屬性外, 每個 Edit Filed 元件其實包含了兩個附屬元件: 標籤文字 (Label) 與編輯空格 (EditField) 這也是 AppDesigner 另一項貼心的設計, 因為任何編輯空格都需要標籤文字帶頭, 指示使用者該輸入什麼資料 圖 6 展示編 4
輯空格處的特性設定 因為是數字類型, 所以必須設定範圍 ( 也列入自動維護項目 ) 另外, 在圖右上方箭頭所指處, 為了程式撰寫方便, 將內定的變數名稱改為 app.para1, 另一個改為 app.para2, 讓程式碼使用這兩個輸入值時, 容易辨識它的意義 ( 程式碼的可讀性較高 ) 2 其他的特性選擇不再贅述, 讀者可以很清楚的理解 圖 6: AppDesigner 的編輯元件設計 1.1.4 群組式單選按鈕 (Radio Button Group) 群組式單選按鈕的設定與下拉式選單類似, 只要填上文字便大功告成, 如圖 7 所示 同時為了程式寫作的識別性, 這裡也更動了兩個按鈕的變數名稱, 以符 合按鈕本身的意義 如果要增加按鈕選項, 在元件上方按右鍵便會發現 Add Radio Button 的功能 刪除按鈕只需按鍵盤的刪除鍵即可 1.1.5 按鈕 (Button) 標籤 (Label) 區塊 (Panel) 與圖像區 (Axes) 圖 2 畫面的按鈕 (Button) 標籤 (Label) 與圖像區 (Axes), 在設定的選擇相對 單純許多, 不再贅述 安排好了畫面, 重頭戲才是下一節的程式撰寫 1.2 反應函數 Callback functions AppDesigner 提供智慧型的 App 設計環境, 當畫面佈置完成後, 系統自動生成 一支以 mlapp 為副檔名的程式 程式採物件導向 (Object Oriented) 結構, 不過 AppDesigner 透過控制元件可以直接連結到程式段落 ( 稱為反應函數,callback 2 Appdesigner 會自動給予使用中的元件一個變數名稱, 而命名的方式則一致地採取元件的功能屬性, 譬如 app.editfield, 相同的元件便在後面加上流水號區別, 其中最前面的 app 代表從屬關係中的上層 這樣的變數名稱並不具備所代表的意義, 在程式撰寫時比較不方便, 常要回到畫面去對照變數名稱 因此在本範例刻意修改了變數名稱, 做為示範 5
圖 7: AppDesigner 群組式單選按鈕 (Radio Button Group) functions), 對不熟悉物件導向式設計的讀者來說, 應該可以適應得很好, 甚至慢慢學到物件導向設計的方法, 並領會其優勢 首先這個範例 App 在開啟後, 安排呈現一個標準常態的機率密度函數圖, 接著才讓使用者選擇分配 該分配的參數及機率函數的類型 (PDF 或 CDF) 這裡刻意運用了幾個技術 : 1. App 啟動後, 立即繪製圖形 代表設計者必須知道 App 經啟動後的流程 2. 選擇下拉式選單的分配後, 在它下面的參數數量與名稱必須跟著分配走 譬如, 選擇 卡方 分配時, 參數部分只留一個, 名稱改為 自由度, 以符合卡方分配的參數名稱 3. 更動參數值或函數類型時, 圖像區必須立即反應正確函數圖, 同時圖像區上的標籤文字立刻更改為正確的分配與函數名稱 4. 結束 按鈕的處置方式 譬如, 開啟詢問對話視窗, 應答後關閉 App 上述的設計以繪製函數圖為目標, 而且任何控制元件的更動都必須立即啟動繪圖機制, 因此繪圖的程式碼必須被安排為內部呼叫的函數, 以便在每個控制元件變動時, 可以呼叫同一個函數 3 以下分幾個小節示範程式的做法 3 避免繪圖的程式碼在每個元件的反應函數中重複出現 6
1.2.1 StartupFcn 函數我們希望使用者在開啟 App 後, 立刻看到一張標準常態的 PDF 圖 因為這個動作不與任何元件的互動相關, 必須在 App 啟動的過程中, 讓系統自動執行 AppDesigner 建議使用內建的函數 startupfcn 使用步驟, 第一先轉換到 Code View, 第二, 在左上角的 Code Browser 下的 callbacks 按 + 圖示後, 在跳出的對話視窗中選擇 startupfcn, 接著便可以在 Code View 視窗看到新增的函數, 如圖 8 所示 Code Browser 的 callbacks 存放著所有系統建立的反應函數 (Callback Functions) 圖 8: startupfcn 的建立與程式碼 圖 8 的程式碼展現物件導向的特質, 以繪圖指令 fplot 為例, 第一個參數必須明確指定要將圖繪製到哪個物件, 譬如,app.UIAxes 代表畫面中的圖像區 即便是指令 title 也必須指定繪圖物件 此外, 為了彰顯參數的效應, 將座標軸移到 (X, Y ) 的原點, 設定的方式只須令物件 app.uiaxes 的兩個特性 XAxisLocation 與 YAxisLocation 分別為 origin 即完成設定 這個做法是物件導向式程式設計的典型 ; 以物件為中心, 每個物件包含許多特性 4 從圖 8 的 function startupfcn(app) 的前一行註解, 可以清楚了解這個函數的執行時機 ; 這行字寫著 : Code that executes after component creation, 當所有元件都一一呈現在畫面時, 接著執行這函數, 呈現如圖 9 的畫面 4 這裡因為先介紹啟動程式的關係, 寫了簡單的繪圖程式碼, 但實際上應該換上如圖 11 的一 行專責繪圖的函數 updateplot(app) 7
圖 9: 範例 App 的開啟畫面 1.2.2 群組式單選按鈕的反應函數與共同函數 App 程式的控制元件的反應函數經常會共用程式碼, 因此必須拉出來成為一獨立的函數供其他反應程式呼叫, 否則會發生重複程式碼的不良結果 本節以群組式單選按鈕的反應函數為例, 帶出與其他元件共用的函數 updateplot 在 AppDesigner 欲新設或編輯控制元件的反應函數, 都是以右鍵點選控制元件, 如圖 10, 點選第一個建議的反應函數 ButtonGroupSelectionChanged, 系統自動產生反應函數並帶到 Code View 的函數位置, 如圖 11 反應函數 ButtonGroupSelectionChanged 的名稱反應了其啟動機制, 就是當群組中的按鈕選項改變時 圖 10: 控制元件的反應程式編輯 AppDesigner 自動產生函數的頭尾, 預留中間給設計者填入適當的程式碼 一般 8
圖 11: 自動生成的反應函數與建議做法 而言, 設計者會先在此寫幾行程式, 先試著掌握程式的流動並取得其他控制元件的內容 測試成功後, 即可建立新函數提供共用的功能, 譬如根據其他控制元件的內容繪製圖形 圖 11 展示呼叫共用函數 updateplot 當然公用函數 upadteplot 必須被建立起來, 而最方便的方式當然還是由 AppDesigner 代勞 同樣在左上角的 Code Browser 選擇 Functions 並點選 + 號新增函數,AppDesigner 會自動在 Code View 新增程式碼, 設計者可以自行取名, 譬如, updateplot, 而程式內容一開始, 不外乎從其他控制元件擷取繪圖所需的參數資訊, 最後再繪圖至 app.uiaxes 元件 新增方式如圖 12 所示, 程式碼如圖 13 與 14 圖 12: 新增公用函數 updateplot 圖 13 的 updateplot 函數前五行程式碼, 分別取得不同控制元件的內容, 說明如下 : 1. distname=app.dropdown.value 取得下拉式選單的選項 其中 app.dropdown 代表下拉式選單這個物件的變數, 而 Value 是這個物件裡面的特性, 代表選項的文字內容, 譬如 常態 貝他 或 卡方 2. p1=app.para1.value 與 p2=app.para2.value 分別取得兩個編輯元件的內容 3. selectedbutton=app.buttongroup.selectedobject 取得群組式單選按鈕中被選取那項的文字 4. disttype=app.pdfbutton.text 取得群組式單選按鈕中變數為 app.pdfbutton 那個按鈕的文字, 準備與前一項 selectedbutton 的 Text 比較 一旦取得繪圖所需的內容, 圖 13 後續的程式碼與圖 14 的程式碼便不難理解 9
圖 13: 公用程式 updateplot 的程式碼 ( 一 ) 不外乎先判斷哪個分配被選到, 再判斷要繪製 PDF 還是 CDF 圖, 依序做好設定 後便可以繪製, 最後再更新圖像區上面的標籤文字 :app.label3.text=str 5 此外, 在程式碼中有一行判斷使用者選擇的按鈕 (radiobutton) 是 PDF 還是 CDF:if strcmp(selectedbutton.text, disttype) 適合用來比對文字串, 特別是二選一 的情況 1.2.3 編輯元件的反應函數 通常 編輯元件 只提供使用者輸入資料, 再由其他元件 ( 譬如按鈕元件 ) 啟動 反應函數執行特定的任務 本範例希望做到當使用者輸入新的參數資料後按下 ENTER 鍵, 即可立即執行更動後的結果 因此必須處理屬於 編輯元件 自 己的反應程式 由於本範例的功能只有單純地根據畫面上的參數繪製圖形, 所 以所有參數相關元件的反應程式都是一樣, 也就是上一節介紹 群組式單選按 鈕 的反應函數的作法 因為這層關係, 讓建立這些控制元件的反應程式簡單 許多, 圖 15 示範典型的編輯元件反應函數的做法, 也就是由剛剛為 群組式單 選按鈕 設好的反應函數代勞, 所有的事情都交給 updateplot 6 這裡有個細節必須提醒, 圖 15 箭頭所指的位置是選擇反應函數的地方, 圖 中顯示的反應函數是上一節的所指定的函數 ButtonGroupSelectionChanged, 而 非原先內定的第一選擇 ValueChangedFcn callback, 如圖 16 箭頭所指之處 5 每個物件的變數名稱, 如 app.label3 可以從 COMPONENT BROWSER 對應到 至於每個元件內的特性名稱, 如 app.label3 的 Text, 則在輸入 app.label3 後, 系統會自動提示, 當然也可以從 COMPONENT BROWSER 裡查到 6 編輯元件反應函數的啟動依賴使用者輸入後按 ENETR 鍵 10
圖 14: 公用程式 updateplot 的程式碼 ( 二 ) 我們選擇了圖中方框的 Select existing callback 選擇已經編好的反應函數 ButtonGroupSelectionChanged, 讓不同元件共用同一個反應函數 從圖 11 上的 ButtonGroupSelectionChanged 函數第一行的註解看到 ButtonGroup, para1, para2 三個元件共用同一個反應函數 1.2.4 下拉式選單的反應函數下拉式選單的反應函數設定, 原本也可以如編輯元件般簡單, 但本示範 App 特地加入更動佈局的技巧, 譬如, 當使用者選擇貝他分配時, 下方的編輯元件的名稱將變更為 A 與 B 以符合貝他分配的習慣名稱, 如圖 17 所示 11
圖 15: 編輯元件的反應程式設定 圖 16: 編輯元件原來的的反應程式設定內容 圖 17: 下拉式選單依選項更動元件的結果 12
於是除了繪圖之外, 尚須一些更動編輯元件的動作, 最後再呼叫 updateplot 執行繪圖 由於卡方分配僅有一個分配參數, 因此當選擇卡方分配時, 也必須讓第二個編輯元件消失 整個動作如圖 18 的程式碼所示 這段程式碼很平實, 容易解讀, 不外乎更動編輯元件的文字標籤內容及控制元件的 存在 (Visible on) 與 消失(Visible off) 特別留意的是, 程式最後一行呼叫了 updateplot 函數, 還是回到繪圖得目的 寫這段程式碼唯一遭遇麻煩是找不到編輯元件上的標籤文字 (Label) 的變數名稱 這個標籤文字與編輯元件一起被放進來, 但 AppDesigner 卻把他的變數名稱隱藏起來的 雖然如此, 物件導向的設計原理會先定義每個物件, 因此很快在程式的最頂端發現了這兩個標籤文字的變數名稱 : Label4 與 EditField_2Label, 另外在程式後端的元件製作程式碼裡, 也可以找到這兩個標籤文字所屬的變數名稱, 如圖 19 圖 18: 下拉式選單依選項更動元件的技巧 1.2.5 結束 App 的典型動作 App 的開啟需要通過 startupfcn 執行先期作業, 而其結束要執行 delete(app), 交給系統關閉視窗 結束 App 的典型方式是按壓一個如圖 20 的 結束 按鈕, 而 13
圖 19: 編輯元件裡的標籤文字的變數名稱 結束 按鈕的反應程式一般將啟動對話視窗(questdlg), 詢問使用者是否要結束 App, 之後再呼叫 delete(app) 執行關閉視窗的工作, 到此 App 的設計也告一段落 7 7 有興趣的讀者可以拉到程式碼的最後一段, 從 App 的生到死, 都在寫在這裡 (App creation and deleteion) 14
圖 20: 結束 App 的典型動作 1.3 其他控制元件的特性與反應函數的撰寫 前一節介紹基本的 App 架構 佈局與反應程式撰寫, 本節從不同的功能出發, 介紹幾個控制元件的使用方式, 包括讀取檔案資料寫入表格 兩個以上視窗的資料傳遞技巧 1.4 讀取檔案資料寫入表格 讀取外界檔案, 如 EXCEL 資料檔, 並顯示內容是很普遍的應用 這涉及各種檔案類型的讀取與表格元件的使用 在此僅採用 EXCEL 與 TXT 兩種常見的資料檔案類型, 其中 EXCEL 檔的內容通常含標題欄與列, 對資料的說明較清楚 而 TXT 文字檔, 通常只含數字資料 在設計 App 讀取檔案時, 必須限制檔案的類型與內容格式, 譬如不允許 TXT 檔含有標題列, 否則檔案內的格式可能五花八門, 程式處理起來非常瑣碎麻煩, 還是讓使用者負一點檔案格式的責任, 在送到 App 處理前先做必要的前置作業 圖 21 展示讀檔與資料表格的 App, 重點在按鈕 瀏覽檔案 的反應函數 圖 21 呈現表格元件 (Table) 的預設模式, 包含四欄及預設的標題 (Column Name) 表格元件的設定主要是圖右邊標示從 ColumnName 到 RowName 的特 15
圖 21: 表格 (Table) 控制元件的佈置 性 如果欄與列的標題固定, 就可以直接輸入, 否則依檔案本身的標題, 從反應程式設定 這個示範 App 只展示 瀏覽檔案 按鈕的反應程式, 如圖 22 這段程式碼有幾個重點分述如下 : 1. 指令 uigetfile 將帶出檔案管理視窗供使用者選擇檔案 後面的參數則是指定副檔名的型態, 做過濾篩選之用 之後再根據檔案名稱的副檔名, 決定適合開檔指令與後續的處置方式 2. 當檔案型態為 EXCEL 檔時,MATLAB 建議用指令 readtable 開檔 8 readtable 將傳回的內容安排在名為 table 的結構,MATLAB 將 EXCEL 檔內容安排在 table 結構裡面, 其中欄與列的標題存放在 Properties 下的 VariableNames 與 RowNames 圖 23 呈現如何查看 table 結構的 Properties table 結構下的數字資料則是直接以 cell 的方式擷取, 如 X=T{:,:}, 選取所有的數字資料, 或是指定某些行列 3. 透過 readtable 讀入 EXCEL 檔案後, 接著將標題與數字分別放入表格控制元, 方式如圖 22 方框內的三個指令, 分別填入表格控制元件的數字資料 (app.uitable.data), 欄標題 (app.uitable.columnname) 與列編題 (app.uitable.rowname), 其中若無欄標題則填入空值 ( 即無標題 ), 若無列標題則以數字填入 4. 文字檔 txt 的處理模式比較單純, 直接以指令 load 讀入並放入表格元件的資料區 讀者也可以試著嘗試用 readtable 讀取 txt 檔, 並允許有標題列 圖 24 為 App 的示範結果, 圖中的表格資料來自讀入一 EXCEL 檔 8 之前專門用來開啟 EXCEL 檔的指令 xlsread 已經不敷使用 16
圖 22: 表格元件的反應程式 圖 23: 表格 (table) 結構下的 Properties 利用指令 uigetfile 讀取檔案並陳列資料後, 必須執行一個小動作將 App 視窗帶 回最上層, 如圖 25 所示 17
圖 24: 資料讀取與表格的示範 App 2 資料共享 圖 25: 讓 App 視窗回到最上層 ( 成為 Active 視窗 ) 在 App 的應用程式裡, 程式的流動不是從投跑到尾的模式, 而是停滯著等待使用者對某個物件的動作, 再藉由被啟動的物件的反應程式做出適當的表達 因此當某些資料必須被保留 更新, 隨時能被其他物件取得時, 這必須有特別的做法 在 MATLAB 的 AppDesigner 裡稱為物件之間的資料共享 (Share Data among objects) 2.1 單一視窗的共享資料傳遞 (1) 前一節介紹資料檔案的讀入與呈現, 通常是 App 的前段功能而已, 接著便是對這些資料進行處裡 由於讀取檔案資料的 function 已經結束, 所有變數隨之消失, 也就是資料雖然呈現在表格上了, 但是實際已經不見了, 其他控制元件也無法進一步處理這些資料 譬如, 前述的 App 可以擴展為圖 26 所示的功能, 讓使用者能觀察每一個欄位資料的直方圖 這個功能牽涉到兩件事 : 1. 檔案資料必須被保留 ( 共享 ), 使其他控制元件能取得並且能變更 18
2. 由於檔案資料並非事先預知, 下拉式選單的內容必須被動態更動, 如圖 26 範例中的下拉式選單的個數與名稱, 都必須在讀入資料後才進行變更的 上述第一項共享資料的設定與第二項下拉式選單的變更, 指令如圖 27 的方框所示, 其中方框的第一行的變數 app.data 被事先設定為 App 的 property 之一 這是利用 App 的 property 為內部共用的功能, 拿來作為資料共享的變數 好比所有的控制元件都有自己的 property, 可以隨時被更動的道理 這也是第二行變更下拉式選單的方式 ( 設計時, 可以用 Column1, Column2... 等暫時作為欄位名稱 ) 新增一個 property 的方式如圖 28 所示, 在左方 Code Browser 內的 Properties 選擇 Private Property 並給定名稱後, 在 App 程式中將新增一段設定的程式碼 一開始設定為空矩陣 請讀者自行處理按鈕 直方圖 的反應程式, 先取得 App.Data 的內容, 再繪製直方圖到座標圖即可 圖 26: 加入下拉式選單以選取個別欄位資料 圖 27: 設定共享資料並變更下拉式選單的選項 19
圖 28: 設定做為共享資料的 private property: app.data 2.2 單一視窗的共享資料傳遞 (2) 本節特地設計了一個 打香腸 的小遊戲, 來展示這個技術 打香腸 來自街坊的彈珠台博弈, 賭具是一台彈珠台, 賭客付賭金, 贏則得烤香腸一支 遊戲規則很多種, 其中之一俗稱 過五關 賭客打彈珠, 累積彈珠進洞的分數 ( 皆為 10 的倍數 ), 只要積分恰為 50 的倍數, 則闖關失敗, 如果一路闖關順利超越 250( 共闖五關 50, 100, 150, 200, 250), 則成功 圖 29: 打香腸遊戲之概念 App 依照這個概念, 設計了如圖 29 的 App, 雖然沒有彈珠台的實景, 更沒有烤香 20
腸, 但體現了這個博弈遊戲的概念 遊戲概念如下所述 1. 左上角的按鈕負責彈珠的進洞分數, 在 0, 10, 20, 30, 50 的五個分數隨機抽樣 2. 按鈕下方的數字與右側的 Gauge( 測速表 ) 代表累積分數 3. 下方綠色燈號代表遊戲進行中, 若轉為紅色代表遊戲結束 4. 遊戲開始或再玩一次, 都由右下角的 START 按鈕啟動 圖 29 的 App 用了兩個新控制元件, 分別是 Gauge 與 Lamp, 都是簡單的控制元件, 用來顯示累積分數與最後的結果燈號 這個 App 的觀察重點如下 1. App 初啟動, 等待使用者按 START 前, 左上角的 Hit 按鈕必須處在 Disable 狀態, 也就是在這個元件的 Enable 特性必須先設定為 off 這個動作可以在 Design View 的佈局設計中直接在 COMPONENT BROWSER 的 INTERACTIVITY 的 Enable 指定為不打勾 之後, 再由其他控制元件將狀態改回 Enable 2. 這個 App 想表達的資料共享方式, 由一個 Property 來完成 圖 30 呈現如何建立一個名稱為 Total 的新特性(Properties), 作為 App 裡面所有函數都可以直接存取 做法是先在圖左邊的 Code Browser 的 Properties 按 + 按鈕, 此時在 Code View 區將新增一 Properties 的設定 在此放置一變數 Total, 並設定初始值為 0, 將來可被其他物件以 app.total 存取內容 圖 30: App 共用的特性 Total 3. 主要的動作都發生在 Hit 按鈕, 包括從五個數字裡均勻抽樣 累加分 數 設定 Gauge 的指針位置, 最後檢查成功或失敗 若失敗, 設定燈 號為紅色, 若闖關成功, 則連續閃動五次綠燈, 最後再度關閉 Hit 按 21
鈕 等待下一個 START 的動作 圖 31 的反應程式做了這些事 圖 31: 打香腸遊戲的主要反應程式 圖中的程式碼 app.total=app.total+n(d); app.label.text=num2str(app.total, %d ); app.gauge.value=app.total; 執行了共享資料 app.total 的累加 並將內容轉換為文字後, 貼到顯示分數的標籤元件, 最後寫到 Gauge 更動指針 2.3 兩個視窗間的資料傳遞 較複雜的 Apps 經常需要兩個或以上的視窗, 但只能有一個視窗是 Active, 其他視窗即便同時開啟, 也是處在 Inactive 狀態 第一個開啟的視窗稱為主視窗, 其餘為子視窗 視窗間的開啟與關閉, 經常牽涉資料的傳遞, 比起單一視窗內資料在物件間的傳遞複雜些, 必須仔細設定, 方能順利執行任務 本節探討如何啟動子視窗 如何在兩個視窗間傳遞資料 22
MATLAB 在使用手冊裡建立一示範案例, 9 簡單 容易模仿與複製, 在此重做一次該範例並說明關鍵地方 圖 32 展示這個範例執行的樣子 左視窗為主視窗, 顯示的圖形有兩個參數來自右邊的子視窗 當需要改變參數時, 便開啟子視窗, 設定新的參數後, 再返回主視窗重新繪製圖形 這個過程牽涉到兩個參數資料的傳遞, 與視窗控制權的轉移, 有些額外的細節要處理 圖 32: 兩個視窗的資料傳遞示範 App 以下大略依據程式執行的順序依步驟說明需要特別注意的地方 STEP 1:Layout 分別建立主視窗與子視窗的佈局畫面, 分屬兩個檔案 在此為方便討論, 假設主視窗稱為 callingapp, 子視窗稱為 subapp STEP 2:Properties 分別建立兩個視窗的特性變數 (Properties) 1. 主視窗必備的特性變數是子視窗的 app 代碼, 譬如本範例的 subapp, 其他特性設定視需求而定, 如圖 33 所示 2. 子視窗必備的特性變數是主視窗的 app 代碼, 譬如 callingapp 9 在 HELP 裡, 輸入 Creating Multiwindow App in App Designer, 便可找到該範例說明及原始程式 在此建議讀者按圖索驥, 一邊閱讀本文或 HELP 的說明, 一邊自行建立 App, 遇到困難再查看程式, 才不會錯失一些細節 23
圖 33: 兩個視窗必備的特性, 上圖為主視窗, 下圖為子視窗 STEP 3 撰寫啟動程式 startupfcn 主視窗與子視窗的啟動程式大不同, 分述如下 : 1. 主視窗負責自己啟動後要做的事, 沒有特別之處, 以本範例而言, 只是繪製 peaks 圖形 2. 子視窗由主視窗呼叫啟動並傳遞資料,startupFcn 的輸入參數, 除了代表子視窗的 app 外, 必須包括主視窗的 app 變數及其他傳遞過來的變數, 細節如圖 34 所示, 其中前兩個輸入參數, 分別代表子視窗的變數 (app) 與主視窗的變數 (appmain), 後兩個則是主視窗傳遞給子視窗的資料 sz 與 c( 代表樣本數與顏色對照表 ) 傳進來的參數在此放進之前設定的特性變數, 供後續控制元件使用 圖 34: 子視窗的 startupfcn 24
STEP 4 從主視窗撰寫呼叫子視窗的反應程式, 並撰寫從子視窗返回主視窗的程式 1. 呼叫子視窗 : 從主視窗啟動子視窗的方式是呼叫子視窗程式, 程式名即檔案名 值得注意的是參數的傳遞, 以圖 35 為例, 共傳遞了三個參數, 分別是代表主視窗 app 的變數及代表繪圖所需的兩個參數 傳遞主視窗 app 給子視窗的用意, 是讓子視窗回傳參數到主視窗的依據 另外兩個參數用來更新子視窗的控制元件內容 2. 返回主視窗 : 子視窗提供使用者輸入參數後, 返回主視窗 子視窗的工作主要有, 一 呼叫主視窗的函數並傳遞參數資料, 細節如圖 34 中間箭頭所指的函數, 其中的 updateplot 是位於主視窗的函數, 用來繪製主視窗的圖, 傳遞的參數包含了主視窗的 app 及其他兩個與繪圖相關的參數 這裡出現了關鍵的細節, 主視窗函數 updateplot 必須設定為 public 方能為其他視窗呼叫 二 關閉子視窗, 指令為 delete(app) 子視窗在呼叫完主視窗函數後, 必須關閉視窗, 將控制權交回主視窗 圖 35: 主視窗呼叫子視窗的反應程式 圖 36: 主視窗中設為 public 的反應程式 25
STEP 5 結束視窗的後處裡動作 不管子視窗的關閉回到主視窗, 或主視窗的關閉, 結束整個 app, 都必須執行 delete(app) 本示範 app 的主視窗並未使用按鈕結束, 而讓使用者按壓視窗右上角原生的結束鍵 如果有些額外的動作必須在視窗關閉前完成, 必須呼叫視窗的結束函數 圖 37 展示建立視窗結束函數 UIFigureCloseRequest 的方式 子視窗的 UIFigureCloseRequest 的內容如圖 34 第三個箭頭處所指的函數, 在執行 delete(app) 前, 先喚醒主視窗的按鈕 app.callingapp.button.enable= on 圖 38 則是主視窗關閉的結束函數, 在結束自己之前, 先關閉子視窗, 做一個保險動作 圖 37: 子視窗關閉時執行的結束函數 圖 38: 主視窗關閉時執行的結束函數 上述的步驟交代了兩個視窗的開啟 傳遞資料與結束的細節 讀者最好親手做 一次, 疏漏之處才能立刻明白 26
3 App 的符號運算技巧 MATLAB 的符號運算能力是否能運用在 App? 會發出這個疑問出於對符號運算 與 App 架構的理解 符號運算必須先將變數指定為符號, 而非當作一般變數 譬如, 下列指令對函數 f(x) = sin(2x) 進行一次微分與不定積分 syms x f=sin(2*x); df=diff(f,1); F=int(f); 其中變數 x 被設定為符號變數, 之後的與 x 相關的變數 f, df, F 也都是符號變數 當符號變數需要輸出為數字或文字時, 適當的變數轉變是必要的, 譬如接續上 述指令, fchar=char(f); fp=double(subs(f,2)); fpchar=char(subs(f,2)); Fvalue=double(int(f,0,1)); 其中變數 fchar 與 fpchar 都是文字型態的變數, 而變數 fp 與 Fvalue 則是數字型 態 當在 App 的架構下進行符號運算時, 必須考量使用者輸入的內容不是文字便是 數字, 如何轉為符號進行運算 以下程式碼展示一個可行的做法 : fchar= sin(2*x) ; syms x f=[ diff( fchar,1) ]; df=eval(f); 其中關鍵指令 eval 將字串的內容當作指令執行, 因此目標字串 f 必須預先建構出完整的指令 運用這個技巧便能完成如圖 39 所示的 App, 將使用者輸入的任意函數進行一次微分與不定積分, 並將結果寫回 App, 同時繪製這三個函數圖 關鍵程式碼如圖 40 27
圖 39: 利用符號運算功能的 App 圖 40: 利用符號運算功能程式碼 28