MATLAB App Designerは、MATLABでGUIアプリを作成するためのツールです。MATLABの基本機能なので、MATLABのユーザであれば誰でも利用できます。MATLABの豊富な関数群をそのまま利用できるため、MATLABに慣れた人がApp Designerの使い方を習得すれば、高機能のGUIアプリケーションを開発することができます。
この記事では、MATLAB自体は少々使ったことがあるものの、App Designerは使ったことがないのでどんな風に開発を進めていくかの感覚をざっくりと把握したい、という方向けに、App Designerの基本的な使い方を解説します。段階的にアプリケーションを作りこんでいく形で説明を進めますので、実際にApp Designerを動かしながら読み進めていただくのが効果的です。
この記事は、大学教員である執筆者の研究室(大阪大学大学院工学研究科感情アンドロイドダイナミクス研究室)に配属された学生に、最終的には下図のようなGUIアプリを開発できるようになってもらうために作成したチュートリアル資料を基にして書いています。チュートリアルは9項目あります。1週間程度かけた自主演習課題として取り組んでもらうことを想定したチュートリアルです。MATLAB R2023bを使用していたときに作成した資料の転載なので、使用している関数が少々古かったりする恐れはありますが、基本的な使い方は変わらないはずです。
1.AppDesigner起動法と操作画面
まずは使い慣れたMATLABを起動させましょう。MATLABを起動すると次の画面が開きます。現在は「ホーム」のタブが選ばれており、上部のメニューには通常のMATLABコードを書いていくために使うメニューが上部に並んでいます。
「ホーム」の2つ隣の「アプリ」のタブを選ぶと、GUIアプリ開発に使うツールが上部メニューに現われます。
一番左の「アプリの設計」を選ぶと、次のようなApp Designerの初期画面が開きます。すでに開発を始めているアプリの開発を再開する場合は、左のカラムからアプリを選びます。新規で開発する場合は、右のカラムの左上の「空のアプリ」を選びます。右のカラムで「例を表示」を押すと、様々なサンプルアプリのテンプレートを選んで開発を始めることもできます。開発したいアプリがどのように実現できるのかを知りたい場合にはこれらのアプリを選んでコードを確認してみるのもよいでしょう。
今回は「空のアプリ」を選びましょう。すると、次のような開発画面が開きます。この画面中央に薄グレーで表示されている四角い部分が、開発したアプリを実行したときの外観です。今は何もないのっぺりした画面です。
この状態でもアプリとして実行できるので、試してみましょう。上部メニューの緑▶の実行ボタンをおすと、現段階のアプリがどのように動作するかを試すことができます。初めて実行しようとしたときには、まずはアプリのコードファイルをどこかに保存することを求められます。下のような「名前を付けて保存」の画面になるので、アプリに名前を付けて管理しやすい場所に保存しましょう。
Matlabアプリは、「.mlapp」という一つのファイルとして保存されるのですが、のちに外部ファイルを読み込んで起動するようにしたり、「.exe」のような実行ファイルに変換することも考慮すると、アプリ名を付けたフォルダを作っておいて、そのフォルダの中に「.mlapp」を保存しておくのがよいです。
ここでは、「Samples」というフォルダの中に、「tutorial1」というフォルダを作成し、そこに「tutorial1.mlapp」という名前でアプリを保存することにします。保存すると、次のようなウィンドウが起動します。これが、tutorial1.mlappの実行結果です。左上にアイコンとタイトル(MATLAB App)の記載が見えますが、画面内には何もなく、どこを押しても何も起きません。画面の端のクリック&ドラックで画面サイズを変更したり、右上のボタンで最小化、最大化、終了、を指示することはできます(OSに依存します)。いったん、終了ボタンを押してウィンドウを消しましょう。
開発画面に戻りましょう。基本的には、開発画面は左カラム、中央カラム、右カラムに分かれています。中央カラムの右上には「設計ビュー」と「コードビュー」を切り替えるボタンが付いています。現在選ばれている「設計ビュー」は、アプリの外観を設計していくための表示モードです。ここに、左カラムのコンポーネントライブラリにある各種コンポーネントをクリック&ドラッグして画面を作りこんでいくことになります。
右カラムのコンポーネントブラウザーの上部には、現時点でこのアプリにどのようなコンポーネントが実装されているかが表示されています。「tutorial1」のタブの中に、「app.UIFigure」というものだけが入っていることを確認してください。これは、「UIFigure」という変数名のコンポーネントだけが実装されていることを示しています。変数名の冒頭の「app.」は、「このアプリ内で、アプリ内の色々なところで使いまわせるように定義された変数」であることを示しています。このコンポーネントブラウザーの下部には、コンポーネントブラウザーで選ばれているコンポーネントの設定値が表示されます。下の画面では「tutorial1」が選ばれています。「tutorial1」はアプリ全体を指しますので、アプリの名前やバージョン、作成者などを設定できる状態になっています。必要に応じて自由に書き換えられます。
続いて、コンポーネントブラウザーで「app.UIFigure」をクリックして選択してみましょう。選択すると次の画面に切り替わります。中央カラムの薄グレーの領域の縁が青くなったことを確認してください。これは、「app.UIFigure」が画面内のどこに該当するかを示しています。この確認によって、app.UIFigureは、実行すると開くウィンドウそのもののことだということが分かります。右カラムの下部分は、このウィンドウの設定画面に変わっています。ウィンドウの色やスタイル、起動したときに表示される画面内位置(Position)、ウィンドウのサイズ変更をユーザに許可するかどうか(Resize)などの設定をここで変えることができます。ここではいったんこのままにしておきましょう。
2.設計ビューとコードビュー
この段階で、このアプリがどのようなMatlabコードによって実現されているのかを確認しておきます。コードを見るために、中央カラムの右上ボタンで「コードビュー」を選びます。そうすると、中央画面にコードが表示されます。コードの左側にはコードの行数が書かれています。この番号が飛び飛びになっているかもしれません。飛び飛びになっているとすれば、それは、いくつかのコードのブロックが先頭行だけを残して省略された状態になっているためです。省略された部分には、行番号の横に「+」ボタンが付いていて、これを押すと省略された部分が展開されて読めるようになります。「-」ボタンを押すと、省略表示に切り替えられます。注目したい部分だけ展開して、そうでない部分は省略すると開発が捗ります。省略されている部分のブロックの先頭行の右端には、「…」も表示されており,これをクリックすることでも展開することができます。
すべて展開した画面を次に示します。1行目のclassdefのブロックの中に、properties (Access = public)、methods (Access = private)、methods (Access = public)の3つのブロックがあることを確認してください(ブロックの終わりはendです)。propertiesは変数を、methodsは関数を配置していくブロックです。Access = publicとなっている方は、propertiesもmethodsも基本的には触る必要はありません。publicのpropertiesには、実装されているコンポーネントの変数名が配置されるのですが、これは設計ビューの方でコンポーネントを追加したり名前を変更したときにMatlab側が自動で書き換えてくれるためです。publicのmethodsには、アプリ自体を起動したり終了したりするための関数が配置されており、これも必要なものはMatlab側で書いてくれていますので、開発者の方で変える必要はありません。
開発者側で必要なのは、Access = privateとなっている方のproperties(まだコードに含まれていません)とmethodsの部分のコーディングです。その話に入る前に、まずはどこまでをMatlabが勝手に書き換えてくれるのかを確認してみましょう。設計ビューに戻り、左のコンポーネントライブラリに載っている「ラベル」のアイコンを、中央カラムのアプリ画面領域内のどこかにクリック&ドラッグしてみましょう。そうすると、下の画面のように、画面内に「Label」という文字列が埋め込まれるはずです。
このとき、このラベルのコンポーネントは、UIFigureというコンポーネントの中に含まれる形で実装されたことになります。つまり、コンポーネントは階層構造を持ちます。右ラベルのコンポーネントブラウザを見ると、app.UIFigureの項目の下に、少し右寄りでapp.labelが追加されていることを確認してください。この場合、app.UIFigureとapp.labelは親子の関係にあるという言い方をします。app.UIFigureが、app.labelの親コンポーネントということです。このことは後で重要になるので覚えておきましょう。
ここでは、アプリ画面領域内の「Label」の文字をクリックするとどうなるかの確認を進めます。クリックすると、Labelの文字の周りに次のような青線の囲みが現れます。この青線がこのコンポーネントが占める画面内領域を示しています。青線をよく見ると、角や中央に四角がいくつかついています。この青線や四角のところをクリック&ドラッグしてみてください。パワーポイントの文字列と同じように、場所を移動させたり、サイズを変えたりできるはずです。このようにして、どのコンポーネントを画面内のどこにどのサイズで置くかを直観的に設計できます。次に、右カラム上のコンポーネントブラウザーではapp.labelが選択された状態になり、右カラム下の部分が、このラベルコンポーネントの設定画面に変わっていることも確認してください。この設定画面には、Textという項目と、FontSizeという項目が含まれています。このText欄に設定されたものが、アプリ画面内に表示される文字列です。FontSize欄の数値がそのフォントサイズになります。
例として、Textを「Initial State」に、FontSizeを24に変更してみましょう。すると次のような画面になるはずです。中央カラムのアプリ画面でも修正が反映されています。このように、左カラムからクリック&ドラッグして配置した各種コンポーネントの内容を、右カラム下の設定欄を利用して作りこんでいけます。
このとき、いろいろな部分がMatlabによって勝手に書き換えられますので、確認していきましょう。まず、右カラム上のコンポーネントブラウザーをみてください。ここで、さきほどまでは「app.label」だったコンポーネント名が「app.InitialStateLabel」に変わっています。これは、今後たくさんのラベルコンポーネントが実装されていくことを見越して、Matlab側の方でTextの設定値に基づいて違う名前に変えた結果です。Text名が同じ別のラベルコンポーネントが追加されたときには、追加された方のコンポーネント名の末尾に「_2」のような番号が付されます。そのままでもよいのですが、これが気に入らない場合には、コンポーネントブラウザのコンポーネント名の部分をダブルクリックすれば好きな名前に変更できますので、必要に応じて書き換えてください。
次に、コードビューの方をみてみましょう。次の画像のようなコードになっているはずです。まず、publicのpropertiesにはInitialStateLabelが追記されています。また、privateのmethods内のcreateComponents(app)という関数(functionとendで挟まれた部分)には、「%Create InitialStateLabel」に続く4行のコードが追加されています。ここには、app.UIFigureの中にapp.InitialStateLabelというラベルコンポーネントをつくり、その文字サイズ、画面位置、テキスト内容を書き換えるコードが記載されています。これらの内容はすべて、先ほど設計ビューの方で決めたものになっているはずです。このようにして、MATLAB側で色々とコードを書いたり変えたりしてくれます。このようにしてMatlab側が書いたコードを、開発者の方で(意図せず)変えてしまうとおかしなことになってしまいますので、コードビューでは編集できないようになっています。編集できない部分は薄いグレーの背景で示されます。現状、すべてのコードをMatlabが書いたので、すべて薄いグレーです。コードをクリックするとカーソルは出ますが、編集はできません。
コードを直接編集するのはもう少し後にして、設計ビューの方でもう少しコンポーネントを追加しておきましょう。設計ビューに戻って、左カラムからボタン(状態ボタンではないもの)のアイコンを探してクリック&ドラッグでアプリ画面内に配置してください。このボタンコンポーネントでもTextやFontSizeが設定できますので、良いように調節してください。次の画面では、FontSizeを24に変えました。
ここで、このボタンをクリックすると、ラベルの文字列が書き換わるような機能を実装したいとします。この時点では、ボタンとラベルはありますが、紐づけられていないので、その機能は当然ありません。試しに、上部メニューの実行ボタンを押して挙動を確認してみましょう。実行すると次のような画面が立ち上がり、ボタンをクリックするとボタンを押せたような動きはします。しかし、何も起きません。
3.コンポーネントへの機能の実装
それでは、ここに「ボタン押しによる文字列変更機能」を追加してみましょう。実行されているアプリを終了して、設計ビューにしてください。機能追加も設計ビューから開始するのが楽です。機能を実装するうえでまず考えることは、「どのコンポーネントにどんな操作が加えられたタイミングで、どんな機能を発動するか」ですが、操作については選択肢は限定的です。Matlab Appのコンポーネントには、それぞれ受付可能な操作が決まっており、その中から選ぶことになるためです。たとえば、ボタンコンポーネントでは「ボタン押し」の操作のみ受付可能です。
というわけで、ボタンコンポーネントがこの操作を受け付けたときに、何か処理が動くようにしてみましょう。設計ビューのアプリ画面内で、今回機能を追加したいコンポーネントである「ボタン」を右クリック(Windowsの場合)してください。そうすると、次の画面のようにメニューが現れますので、「コールバック」内の「ButtonPushedFcnコールバックの追加」を選びましょう。コールバックというのは、アプリ外のソフトウェアにあらかじめ渡しておいて、特定のイベントが生じたときに実行してもらう関数のことです。ここでいうアプリ外のソフトウェアというのは、WindowsなどのOSに含まれているものです。マウスカーソルがPC画面内のどこにあって、マウスがクリックされたかどうかもOS側が把握しており、アプリ側としては不要なときに知る必要もないので、「このボタンの位置にカーソルがあたっているときにマウスクリックされたら、この関数を実行してね」というようにOSに任せておくわけです。
ButtonPushedFcnコールバックを追加すると、自動的にコードビューに切り替わり、次のように、ButtonPushed(app,event)という関数のブロック内に白背景の行(15行目)が追加されているはずです。ボタンがクリックされたときには、この関数内に書かれているコードが実行されることになります。この関数自体は自動で追加されたものですので、関数名の部分は薄グレーです。関数の中身は開発者自らが決めないといけないので、白背景になっていて、自由に編集できる状態になっています。この部分を編集する前に、左カラムのコードブラウザーの部分をみてみましょう。ここの「コールバック」というタブに、「ButtonPushed」が表示されています。ここには、今後追加されていくコールバックがどんどん並んでいきます。コードの中で編集できない、コールバック関数の名前(ここではButtonPushed)や関数の記載順の変更は、このコードブラウザーで行えます。
それでは、ButtonPused(app,event)関数の中の白い部分を編集して、ボタンが押されたときに実行される部分を作っていきましょう。ここでは、通常のMatlabなどのプログラムと同じで、どんな変数を利用して、どんな処理をさせるかをまず考えます。ここでは、「InitialStateLabelに表示されている文字列」を「別の文字列に変える」ことをしたいわけです。前者の文字列は、「InitialStateLabel」というコンポーネントの「Text」という設定値の中身でしたよね。これを表す変数名は、「app.InitialStateLabel.Text」です。冒頭の「app.」はこのアプリ内で定義され、アプリ内であればどこからでも利用できる変数(グローバル変数)にアクセスすることを指し、その次の「InitialStateLabel」は変数名(ここではコンポーネント変数)を指し、末尾の「.Text」はその変数が所有する設定値を指します。つまり、すでに実装済みのコンポーネントに属する値に対して何か変更を加えたい場合には、すでに設定されている名前の組み合わせでその値を表現することになります。ですので、その名前を調べて組み合わせることになります。
この検索に便利な機能として、Matlabには自動候補表示機能が搭載されています。すでにアプリ内で定義されている値を探して記載したいときには、まず編集可能な白背景の部分に「app.」と打ち込みます。そうすると次の画面のようにいくつか候補が出てきます。ここでは、上から2番目に「InitialStateLabel」が出てきていますので、これを選びましょう。そうすると、「app.InitialStateLabel」までコード内に記載された状態になります。
この状態で、「app.InitialStateLabel」の末尾にピリオドをつけてみます。そうすると、次の画面のように、InitialStateLabelが保持する値の一覧がでてきます。ここに「Text」がありますので、それを探して選びましょう。そうすると、「app.InitialStateLabel.Text」まで入力されます。このように検索機能を使って入力すると、記載ミスを防ぐことができますので、積極的に使っていきましょう。
ここまで入力できたら、この変数に、所定の文字列を代入すればやりたいことができそうです。Matlab内では、文字列はシングルクォーテーションで囲んで定義することができます。ここでは、「Button was pushed」という文字列に書き換えることにしましょう。次の画像で示すように、さきほどの変数名に続けて、「=’Button was Pushed’」と書いて、末尾に「;」を付ければ完了です。下の画像内では「Button is Pushed」になっていますが、isをwasに読み替えてください。
ここまでできたら、上部メニューの実行ボタンを押してみましょう。アプリが起動したらボタンを押してみてください。そうすると、ラベルの文字列が切り替わるはずです。
このように、コールバックを利用して、ボタンに機能を埋め込むことができました。ですが、上の画像のように、「Button w…」のように文字列が途切れてしまっているかもしれません。これは、ラベルに割り当てられた領域が狭すぎたことに起因します。これを解決するために、設計ビューに戻って、下の図のようにラベルの青枠をクリック&ドラッグして右に引き伸ばしてみてください。
十分引き伸ばしていれば、下の図のようにすべての文字列が表示されるようになるはずです。このようにして、「設計ビューでコンポーネントの追加と位置決め」をし、「設計ビューでコールバックを追加」し、「コードビューでコールバック関数の中身を書いていく」という形でアプリ開発を進めます。
他にもコンポーネントと機能を実装していってみましょう。チェックボタンを用意して、チェックの有無でボタン操作の有効と無効が切り替わるようにしてみましょう。まずは設計ビューでチェックボックスコンポーネントを追加します。このコンポーネントのText値は「有効化」にしてみましょう。そうすると次のような画面になります。
ここで、このチェックボックスコンポーネントの設定項目をみると、「Value」という欄があり、四角い枠が載っているはずです。ここをクリックすると、設定項目の方でも、アプリ画面内の方でも、四角にチェックが入るはずです。これが、このチェックボックスのチェックの有無の初期値となります。今回は、チェックが入っていなかった既定の設定状態に戻しておきましょう。後で、このチェックの有無を条件判定してコードを書いていくことになりますので、ここで、このチェックの有無でValueの値がどう変わるのかも確認しておきます。設定項目の「Value」にマウスカーソルを合わせると、次の画面のように、「チェックボックスの状態 0(既定の設定)|1」という解説が現れます。これを見ると、値は0か1で、チェックが入っていなかった既定の設定では0だということが分かります。つまり、この変数を調べて、0ならチェックが入っていないことを、1ならチェックが入っていることを判断できることになります。
というわけで、このチェックの有無を利用して、ボタン操作で実行される機能の有効化と無効化を実現してみましょう。この実現の手段は2つあります。1つは、ButtonPushedFcnコールバック関数が実行されたタイミングでチェックの有無を判断して有効無効を切り替える方法と、このチェックボタンのチェックが切り替えられたことをコールバックで検知(ValueChengedFcnコールバック)して、そのタイミングで有効無効を切り替える方法です。両方試してみましょう。まずは1つめの方法です。ButtonPushedFcnコールバックの関数を編集します。このコールバック関数の位置は、コードビューで探してもよいですが、設計ビューでボタンを右クリックして開くメニューでコールバックを選ぶと、すでに実装済みの「ButtonPushedコールバックに移動」という選択肢が出るので、それを選ぶのが楽です。
コードビューのButtonPushedコールバックの関数には、Textを書き換えるコードをすでに記載しているはずです。チェックボックスのチェックが入っている場合にだけ、このコードが実行されるように、ifブロックでこのコードを挟みましょう。Textを書き換えるコードの前に「if」を置き、コードの後に「end」を書きます。ifの後に半角スペースを空けて、「app.CheckBox.Value == 1」の条件文を書けば、チェックボックスにチェックが入っている場合にifブロックの中身のコードが実行されます。書いた例は次の画像のようになります。
これで実行結果を確認しましょう。実行して、チェックを入れていない状態でボタンをクリックして、文字列が変わらないことを確認してください。その後、チェックを入れてボタンをクリックすると、文字列が変わることを確認してください。これが1つめの方法です。
続いて、2つめの方法を試してみましょう。その準備として、今追加したコードの部分はコメントアウトして一旦無効化しておきましょう。Matlabでコメントアウトをするには、各行の先頭に「%」を記載します。記載して、文字の色が緑色になったら、その部分が無効化されたことを示します。次の画像の16行目の「ifの条件文」と18行目の「end」に対応する部分をコメントアウトして、ButtonPushedコールバックの中で有効なのは文字列を書き換えるコードだけになるようにしておきましょう。このチュートリアルではもうこれらの行は使いませんので、削除してしまっても大丈夫です。
2つめの方法というのは、チェックボックスのチェックが入っていない状態だと、ボタンがそもそも押せないようになり、チェックが入ったタイミングでボタンが押せるようにするものです。まず、チェックボックスのチェックが切り替えられたタイミングで実行されるコールバックを実装する必要があります。設計ビューでチェックボックスを右クリックしてメニューを開き、コールバックの項目で「ValueChangedFcnコールバックを追加」を選びましょう。
すると、コードビューでこのコールバックを編集するための場所に飛べます。次の画像では、23行目と24行目です。ここの白い部分には、すでに「value = app.CheckBox.Value;」のコードが記載されています。Value(つまりチェックの有無)が変わったら実行される関数なので、そのValueの値(0か1)はよく使うだろうということで、Matlab側が書いてくれています。これは、消してもよいし、活用してもよいです。今回は活用してみましょう。その前に、このvalueという変数名には「app.」という接頭語がついていないことも確認しておきましょう。つまり、これはグローバル変数ではありません。これは、この関数の中でだけ有効なローカル変数です。他の関数内では利用できません(グローバル変数にするための方法は後ほど説明します)。
また、valueの下にはオレンジ色の波線もついています。これは、「この変数はここで定義されているけどこの後活用されていないよ」ということを知らせてくれる親切な表示です。
このvalueには0か1の値が入っています。チェックが切り替えられたタイミングでこの関数が実行されるので、チェックが入るように切り替えられた場合には、valueは1になっています。チェックを消すように切り替えられた場合にはvalueは0になっています。ですので、もしvalueが1ならボタンを使える状態にして、そうでないなら使えない状態にするようにしてみましょう。
ボタンコンポーネントには、「Visible」という可視性の状態値と、「Enable」という操作可能性の状態値が備わっています。設計ビューで確認できるように、どちらも’on’もしくは’off’の状態値を持っており、規定の状態ではどちらも’on’です。Visibleを’off’にするとボタン自体が見えなくなります。Ebableを’off’にすると、見えてはいるけど押せない状態になります。今回はボタンは見えていた方がよいので、Enableを切り替えるようにしましょう。if,else,endを書いて、ifの横には「value == 1」を書きましょう(これを書くと、23行目のvalueの波線は消えます)。ifとelseの間に「app.Button.Enable = ‘on’;」と書いてもよいですし、Matlabにおいて、’on’は trueもしくは1と等価ですので、「app.Button.Enable = true;」でも「app.Button.Enable = 1;」でもよいです。elseとendの間には、「app.Button.Enable = ‘off’;」を書きましょう。’off’は,falseでも0でもよいです。
これでアプリを実行すると、一見うまく動きそうですよね。ということで実行してみてください。実行すると、次のような状態でアプリが開きますので、ボタンを押してみてください。そうすると、まずいことに気が付きます。次の画像のように、「有効化」のチェックボックスが入っていないのに、ボタンが押せて文字列が切り替わってしまったはずです。これは想定していた動作ではありませんね。
ここで大事なことは、チェックボックスのチェック状態の切り替わりのコールバックは、チェックが切り替わるまでは実行されない、ということです。ですので、有効化チェックボックスのチェック切り替えが行われる前に、ボタンが押されることもありえます。設計ビューで定めたコンポーネントの設定値が、アプリ起動時の初期状態になりますので、有効化チェックが入っていない状態(CheckBoxのValueのチェックを外した状態)を初期状態にしているのであれば、ボタンの方のEnable値も次の画像のようにチェックを外して’off’にしておきましょう。
これで再びアプリを実行してみると、次の画像のように、ボタンが薄くなった状態でアプリが起動することがわかります。このままボタンを押しても、押せたようなアニメーションにはなりません。これが,Enableが’off’の状態です。文字列も「Initial State」のままです。
次に、有効化のチェック部分をクリックしてチェックを入れてみましょう。すると、次の画像のように、ボタンがはっきり表示されるようになり、ボタンを押すと文字列も変わります。ここまでで、各コンポーネントの機能の連携は、初期状態の設定の影響を受けることを確認できたと思います。何かのコンポーネントの初期状態設定を変える場合には、それによって何か不具合が生じないかをしっかり確認してから行うことが大切です。
4.GUIからの数値読取とtableへの反映
ボタンを押してGUIの表示を変えることができるようになったので、もう少し発展的な機能の実装にも取り組んでみましょう。ボタンを押したときに、何かの設定値を読み込んで、その結果を表としてGUIに表示する機能です。設計ビューに戻って、左カラムのコンポーネントライブラリから、「編集フィールド(数値)」をクリック&ドラッグでアプリ画面に配置してください。次の画面のようになります。このコンポーネントは、ラベルと数値入力欄がグループ化(パワーポイントでのグループ化と同じ)されたものです。アプリ画面内にコンポーネントが増えてごちゃごちゃしてきましたが、後で整理しますので、今のところはコンポーネント同士が重ならなければそれで充分です。
この編集フィールドには、初期値として0が入力されています。ここの数値は、アプリのユーザが書き換えることができます。右カラムの設定値欄には、「Value」という項目があり、そこが0になっていると思います。つまり、この編集フィールドに入力された値は、Valueを指定すれば取得できます。ほかにもいろいろと設定値(コンポーネントプロパティ)がありますが、ここはいったんこのままにしておいて、表(table)も配置しておきましょう。左カラムのコンポーネントライブラリから「テーブル」をクリック&ドラッグしてアプリ画面に配置しましょう。4列分の見出しがついた表が配置されます。この表の名前は「UITable」です(右カラム上のコンポーネントブラウザー参照)。
各列の見出しは、右カラムの下側の設定値欄のColumnNameで変更できます。ここにカンマで各列の見出しを並べます。並べた数が列の数になります。現状「Column 1,Column 2,Column 3,Column 4」になっていますので、ここを「1倍,2倍,3倍」に変えてみましょう。そうすると次の画面のように各列の見出しと数を変えることができます。カンマは半角でないといけないことに注意してください。
ここで、アプリ画面のボタンが押されたら、編集フィールドに入力されている値の1倍、2倍、3倍の数が表に表示されるようにすることを考えます。この場合、ボタン押しに対するコールバック関数の中で編集フィールドの値を読み取るのがよさそうですよね。というわけで、「ButtonPushedコールバックに移動」しましょう。
今のところ、ButtonPushed(app,event)の関数は次の画面のようになっています。文字列を変更するコードです。これは残しておいても構いません。今回はこのコード(20行目)の下にコードを追加していきましょう。
読み取った値をいったん保存しておくために「inputvalue」という名前の変数を用意して、そこに「app.InputEditField.Value」の値を代入します。これで、ボタンが押されたときに、編集フィールドの値がinputvalueに入ります。
次に、テーブル(UITable)にデータを追加することを考えます。テーブルコンポーネントのデータは、「Data」プロパティとして扱うことができ、そこに入る値は行列の形式で指定できます。このことを確認するために、一旦、次の画像の24行目のようにして、2行3列の行列を「app.UITable.Data」に代入することにします。Matlabでは角カッコ[]で行列を作ります(Matlabでの変数はすべてこのような角カッコを用いた配列として扱われます。行列も配列で作ります。)。列はカンマで、行の切り替えはセミコロンで指定します。カンマで横に並べていって、セミコロンで改行するイメージです。これで、アプリをいったん実行してみましょう。
「有効化」のチェックボックスにチェックを入れて「Button」のボタンを押してみると、次の画面のように、2行3列の行列の値がテーブルに入るはずです。このように、コード側での行列操作によるDataプロパティの変更で、GUIのテーブルの表示を変更することができます。
これを踏まえて、編集フィールドの値に応じてテーブルの表示が切り替わるようにしてみましょう。ここでは、テーブルの列の見出しの通り、編集フィールドの値の1倍、2倍、3倍の値がテーブルに入るようにすることを考えます。その場合、コードは次の画像の24行目のようにすればよいです。2行共区別なく、1列目には編集値の1倍、2列目には2倍、3列目には3倍の値が入るように変えました。
こうしたうえでアプリを実行して、「有効化」にチェックを入れたうえで「Button」のボタンを押すと、次のような画面になるはずです。テーブルの値はすべて「0」です。今のところ想定通りの結果になっています。
ここで、編集フィールドの値を「5」に変えてから、「Button」のボタンを押してみましょう。そうすると、次のような画面になるはずです。こちらも想定通りの結果になっています。
次は編集フィールドに負の数や小数の桁を含む数を入れてみましょう。値を「-5.2」とすると、次の画像のように、小数も含めた結果を表示してくれます。このように、意図的に代入された値の形式(整数なのか実数なのか)に合わせて結果を調整してくれるところはMatlab(やpython)のひとつの特徴です。
実用的な場面を考えたとき、「負の数は入力されてほしくないな」「100までに制限したいな」とか「小数第二位以下は無視したいな」という要求がでてきますので、その対応についても見ておきましょう。この場合、編集フィールドの方で、受け付ける値域と数値の形式を限定することができます。ここで、「正の」「小数第1位まで」の数だけ受け付けるようにしてみましょう。この場合、設計ビューに戻って編集フィールドをクリックして選択し、右カラム下のプロパティ欄の「Limits」と「ValueDisplayFormat」を変更すればOKです。Limitsには、編集フィールドの最小値と最大値をカンマでつなげて記載します。現状、「-Inf,Inf」になっており、負の無限大から正の無限大までの入力を受け付けてしまいます。これを0から100までにするために、「0,100」に変えましょう。
続いて、小数第1位までの値に限定するために、「ValueDisplayFormat」の右側の縦の三点リーダ(…の縦版)をクリックして開く下の図のメニューで、「厳密」を選び、「1」を入力しましょう。これで、小数第1位までが受け付けられるようになります。
ここまでの設定をしたものを実行し、Inputの編集フィールドに先ほどと同じ「-5.2」を入力してみます。すると、次の画像のように、編集フィールドが赤い色になり、「値は0と100との間でなければなりません」の警告がでるようになりました。このようにして、対応可能な数値だけが入力されるように制限をかけることができます。また、値も小数第1位までに制限していますので、もし「4.2222」を入力しようとすると、小数第2位以下は打ち切られて「4.2」が入力値として採用されます。
続いて、GUIでテーブルの値を編集できるようにしてみましょう。まず、デフォルトではテーブルの値はGUI上で直接編集できないようになっていますので、この設定を書き換えます。設計ビューで、テーブルのプロパティ欄をみてみると、「ColumnEditable」という欄があり、空欄になっているはずです。「ColumnEditable」という文字列にカーソルをあてると出てくる解説を見ると、「1行n列のlogical配列」を入れればよいことがわかります。ここで、この n は列の数です。今回は3なので、1行3列です。logocalは、trueとfalseの値を持ちますので、これを3つ並べます。
falseだと編集不可、trueだと編集可になりますので、今回は1列目だけ編集できるようにするという想定で、次の画像のように「true,false,false」を入力してみましょう。
これでアプリを実行すると、1列目(1倍の列)のセルだけ、ダブルクリックすれば値が編集できるようになっているはずです(有効化にチェックを入れてボタンを押した後で試しましょう)。
つぎに、セルの値が書き換えられたら実行されるコールバックを追加しましょう。設計ビューでテーブルを右クリックして、「CellEditコールバックの追加」を選びます。
コードの方には次のようにUITableCellEditが作成されます。関数の中には、indicesとnewDataという変数が自動的に用意され、それぞれ、関数の引数になっているeventという何かの「.Indices」と「.NewData」というプロパティ値が代入されるようになっています。eventというのは、Matlab アプリのコールバック関数の引数です。何が渡されているかはコールバックに依存します。このCellEditコールバックの場合は、ユーザが書き換えたセルが渡されています。そして、「event.Indices」とすることで、書き換えられたセルの行番号と列番号を保持する1行2列の行列にアクセスでき、「event.NewData」で、書き換えられた後のTableのDataを取得できます。
ここでは、すでに配置しているラベルに、編集されたセルの行番号と列番号を表示させるようにしてみます。その場合、次の画像の41行目のように書けば終わりです。num2str()というのは、数値を文字列に変換するための関数です。number to stringの略なので覚えやすいです。編集されたセルの行と列の番号は数値というデータ型ですが、ラベルコンポーネントのTextに数値データ型の値をそのままでは代入できないため、文字列のデータ型に置き換えてやる必要があるのです。
試しに、41行目(下の画像では42行目です。改行が入ってしまいました。)を「app.InitialStateLabel.Text = indices;」にして実行すると、セルを書き換えたタイミングでコードビューに次の画面のようなエラーメッセージが出ます。
というわけで、num2strを使ったコードで進めましょう。アプリを実行して、1行1列目のセルを編集すると、次のようにラベルのところに「1 1」と表示されるはずです。2行1列目のセルを編集すると、「2 1」になるはずですので、こちらも試してみてください。このように、セルの値を取得できることがわかります。
次に、newDataの中身をラベルに表示させてみましょう。42行目のnum2strの関数の引数をnewDataに変えるだけです。
これでアプリを実行して、セルの値を3に変えてみてください。そうすると、ラベルには「3」が表示されるはずです。セルの値を別の数値にしても、その値が表示されるはずです。これで、書き換えられた値も取得できることがわかりました。このように、IndicesとNewDataのプロパティを読めば、「どこのセルがどの値に書き換えられたか」を特定できます。基本的にこの情報は大切ですので、CellEditコールバックを追加した時点でその値を読み取る変数が自動で追加されるようになっています。
ついでに、このようにある行の1列目のセルに編集が加えられたら、その行の2列目と3列目の値も自動的に書き換わるようにしてみましょう。2列目には2倍の数、3列目には3倍の数が来るようにすることを目指します。単純に実現するには、次の画像の43行目と44行目を書き足せば十分です。ここで、app.UITable.Data(1,2)は、テーブルに入っている値の行列(.Data)の1行2列目の要素のことを指します。配列の後に丸カッコをつけて、カンマつきで二つ整数を並べると、配列の要素を指定できます。ここに、newDataの2倍の値を入れています。Data(1,3)には3倍の値を入れています。
これで実行して、テーブルの1行1列目のセルの値を3に変えると、次の画像のように、1行目の数字が「3 6 9」になるはずです。
ただし、このアプリには現状問題があります。試しに、2行1列目のセルの値を4に変えると、2行目の2,3列が書き換わって「4 8 12」になるのではなく、次の画像のように、1行目の2,3列目の値が書き換わってしまいます。
これは、1倍、2倍、3倍の値を並べるテーブルとしてはおかしいので、直しましょう。1行目のセルが編集されたらData(1,2)とData(1,3)を書き直し、2行目のセルが編集されたらData(2,2)とData(2,3)を書き直せばよいですよね。そうなると、n行目のセルが編集されたら、Data(n,2)とData(n,3)が書き換わるようにできればよいということになります。ここで、このnは、編集されたセルのIndices配列の1番目の要素として取得できますよね。ですので、nの部分はindices(1)として、次のようにすればよいです。
これでアプリを実行して、2行1列目のセルを4にすると、次のようにちゃんと2行目の値が書き換わるようになりました。
5.CSVからのデータ読取と編集&保存
今度は、アプリ外で指定された値を読み込んでtableコンポーネントに反映させられるようにしましょう。値を保存しておくファイルとして、今回はcsv形式のファイルを採用します。
csvはカンマや半角スペースなどの区切り記号と改行を組み合わせてテーブル状にデータを記す方式です。Excelなどの表計算ソフトで開くと、データはセルに入り、Excelのファイルと同じように扱えます(ただし、シートは一枚のみ)。csvファイルはテキスト形式のファイルなので、Wordやメモ帳アプリでも開いて編集することもできます。
まずは、チュートリアルで開発しているアプリのtableコンポーネントに読み込ませるcsvファイルを作りましょう。3行3列の対角行列のデータをそこに書き込むことにします。Excelで左上の3行C列のところまでの領域に1と0を入力して対角行列成分を記入して、csv形式(UTF-8,コンマ区切り)を指定して保存してみましょう。保存先はtutorial.mlappを置いているフォルダにして、ファイル名は「digonalmatrix.csv」にしておきましょう。これで読み込ませるファイルを用意できました。中身が気になる場合は、メモ帳やWordで開いて、カンマ(あるいは半角スペース)区切りのテキストファイルになっているかを確認してみてもよいでしょう。
それでは、アプリの方でこのファイルを読み込む機能をつけていきます。アプリの設計ビューに、もう一つボタンをクリック&ドラッグで配置して、そのTextプロパティを「Read csv」にしましょう。そして、このボタンにButtonPushedコールバックを追加してください。
コードビューのButtonPushedコールバックの関数の中で、csvファイルを読み込んで、そこに入っているデータを配列に格納します。csvも含めて、各種データファイル(.txt、.dat、.xlsなど)から数値だけが並んだデータを読み込んで、各種数値計算に使うような行列として保存するときは、readmatrix関数が便利です。引数でファイル名を指定すると、読み取った行列を返してくれますから、今回はこれをtable_readfromcsvという変数に入れて受け取りましょう。table_readfromcsv=readmatrix(‘diagonalmatrix.csv’)と書けばよいです。mlappと同じフォルダに置いているファイルを読み込ませる場合には、ファイル名指定だけで大丈夫です。一重引用符(’)でファイル名を挟んでから関数の引数に入れているところも大事なポイントです。Matlabでは、一重引用符で囲まれた部分を文字の配列である「文字ベクトル」として認識します。文字ベクトルであると教えてあげないと、「diagonalmatrix.csvという名前の変数がコードのどこかで定義されていて、それを使おうとしているのかな」とMatlabが勘違いしてしまい、エラー(実際には変数が定義されていないので)が出てしまいます。こういう文字の並びだ、ということを指定するときには、一重引用符で囲むようにしましょう。ちなみに、Matlabで文字列はstring変数としても扱うことができます。string変数にする場合は、文字の並びを二重引用符で囲みます。string変数の方が、文字列の処理(連結や並び替えなど)をしやすい一方で、コマンドとの組み合わせによってはエラーが出やすいという短所もありますので、文字列は、場面に応じて文字ベクトルとstring変数の形式を相互に変換しながら扱うということをします。
とにかく、先ほどのコードの追加で、table_readfromcsvは3行3列の対角行列になることが見込まれます。ですので、次はこれをtableコンポーネントに反映させましょう。値を丸ごと上書きする場合には、ButtonPushedコールバック関数に、app.UITable.Data = table_readfromcsv;を追記すればよいですね(51行目)。
これでアプリを実行して、「Read csv」ボタンを押せば、次の画像のように、テーブルに対角行列の値が読み込まれますので確認してみてください。
ここで、Read csvボタンを押したときに、もしかすると次の画像のようなエラーが出てしまうかもしれません。’dagnalmatrix.csv’ファイルを読み込めという指示を50行目でしているのですが、見つからなかったよ、というエラーです。この場合、実はreadmatrix関数の引数に入れているファイル名を間違えてコードに書き込んでしまっているので、見つからないと言われてしまっています(diagの後のoが抜けています)。このように、コード中でファイル名を間違って書いてしまうとエラーになりますし、GUIを使っているときに違う名前のファイルを読み込ませたいなという要求も出てきますので、GUIでファイル名を指定できるようにしておきましょう。
その場合、設計ビューで「編集フィールド(テキスト)」のコンポーネントを配置すれば、ファイル名をユーザが入力できるようになります。編集フィールド(テキスト)をアプリ画面に配置して、ラベルプロパティを「File Name」に、Valueプロパティは「diagonalmatrix.csv」にしておきましょう。こうすると、次の画面のように、何をそこに書き込めばよいのか、初期値として何が設定されているのかをユーザが確認できるようになります。
Read csvボタンを押したら、この編集フィールド(テキスト)に入力されている文字ベクトルが読み取られて、それがreadmatrix関数の引数になるようにしましょう。次の画像のように、まず52行目で編集フィールドのValueをreadfilename変数で受け取って、53行目のreadmatrix関数の引数にはこの変数名を入力すればよいです。編集フィールド(テキスト)のValueプロパティは文字ベクトルですから、変換処理は不要で、そのまま入れられます。
もし、readfilenameという変数を再利用しないようなら、わざわざこの変数を経由せずとも、次の画像の52行目のように、readmatrix関数の引数として編集フィールドのValueを与えることもできます。
さらに、table_readfromcsv変数も再利用しないようなら、table_readfromcsv変数を設けずに、次の画像のように、一行で済ませることもできます。
ここまでで、ユーザが自由に読み込むファイル名を指定できるようにしましたが、そうすると、実際には用意されていないファイル名が指定されてしまうことがありえます。その時に、エラーでアプリが止まってしまったりするのはよくありませんし、ファイルが存在しないことをGUI上で知らせることも必要になります。このように、あるコード部分の実行が成功しない可能性がある場合には、「try-catch-end」の構文を使いましょう。これは、「やってみて、やってだめなときはこう対処して次に進む」という内容を指定できる構文です。今回は、ユーザが指定したファイルを読み込んでテーブルにデータを入れることをやってみて、だめなときはRead csvボタンを赤色にして失敗を知らせて終わる、という仕様にしてみましょう。先ほどの52行目のコードをtryとcatchの間に入れて、catchとendの間に、ReadcsvButtonのBackgroudColorプロパティに[1 0.2 0.2]の1行3列の配列を与えるコードを書きました。こうすると、53行目のコードを試して、成功すれば55行目は飛ばしてendまで進み、失敗すれば55行目を実行してからendまで進むということが実現できます。53行目は失敗することが想定されているので、失敗してもアプリが止まってエラーメッセージがでることもなくなります。
アプリを実行して、File Nameを用意していないものに適当に書き換えてからRead csvボタンを押すと、次の画像のように赤いボタンに変わることを確認してください。
ReadcsvButtonのBackgroudColorプロパティが何なのかは、設計ビューで確認できます。BackgroudColorのところにカーソルをあてると、RGB3成分の背景色だということがわかります。規定は[0.96 0.96 0.96]で、白に近いグレーが設定されています。さきほどのコードでは、これが[1 0.2 0.2]になるようにしたわけです。RGBそれぞれについて、値が1に近いほど多く、0に近いほど少ないような配合で混色されています(コンピュータの色表現は光で行うので、すべて1で配合すると白になります)。
このままでもよいように思うかもしれませんが、ここまでのコードでは、一度読み込みに失敗するとボタンが赤くなり、その後成功しても、赤いまま戻らない、ということが起きます。それを防ぐためには、次の画像の54行目のように、tryで成功した場合の部分にも色を変えるコードを挟めばよいです。今回は、[0.4 0.4 1]で、青色になるようにしてみました。
こうすると、読み込みが成功するとボタンが青くなります。失敗すると、赤になる機能も維持されています。一度赤か青の色がついたら、もともとの色には戻らないという仕様です。
ファイル名読み込みについてはここまでにしますが、tableコンポーネントへの値の反映についてはまだ不満が残ります。テーブルの「1倍」「2倍」「3倍」の列名(ColumnName)が意味のないものになってしまった点です。ですので、今度はこの列名もcsvファイルから読み取ってくるようにしましょう。まず、csvファイルをExcelやテキストエディタで開いて、1行目の上に新しい1行目を追加して、「X」「Y」「Z」の値を入れてください。できたら、csvファイルを上書き保存します。次の画像の例では、ワードパッドで編集しています。
このcsvファイルを読み込んできたいのですが、実はreadmatrixは、数値だけが並べられた「行列」という配列を読み込んでくる関数ですので、文字列を含む列名まで含まれた新しいcsvファイルを読み込んでも、先頭行は読み飛ばされてしまいます。これを避けて、csvに書き込んだ内容をすべて取り込みたいときには、readtable関数を使います。この関数は、列名や行名、あるいは文字列の要素などを持つ複雑な「テーブル配列」としてデータを読み込むために使うものです。テーブル配列は、行列よりも情報が多いために便利な反面、扱いが難しいのですが、今回はテーブル配列として読み込んでくることを試しましょう。先ほど、csvから読み込んだデータをそのままUItable.Dataに入れるという一行のコードにしましたが、今回は読み込んだデータから列名を取り出してUITableに設定するというコードと、数値だけの行列の部分を取り出してくるというコードを書くことになるので、いったんcsvの中身はtable_readfromcsv変数に取り込んでから、その後のコードでその変数を使いまわす形に戻します。
このようにして書いたコードを次の画像に示します。53行目で、csvファイルをreadtable関数で読み込んでtable_readfromcsvに保存しました。このとき、このtable_readfromcsvは「テーブル配列」という型になっており、csvの先頭行は、テーブル配列のProperties.VariableNamesというプロパティに格納されます。それにアクセスして、UITable.ColumnNameに代入しているのが54行目です。55行目では、table_readfromscvというテーブル配列を、UITable.Dataに代入しています。UITable.Dataもテーブル配列なので、これで列名以外のデータ部分も問題なく入ってくれます(54行目を書かずとも、55行目だけで列名も代入されてほしいところですが、どうやらそうはならないようです)。
これでアプリを実行して、csvを読み込むと、UITableの列名もcsvファイルの記載内容に合わせて変わるようにできました。
ここまで出来たら、テーブルの値をアプリ上で編集して、編集結果をcsvとしてPCに保存する機能の実装に入れます。まず、テーブルの全てのセルを編集可能にしておきましょう。テーブルのセルの編集の可不可は、ColumnEditableプロパティにfalseとtrueのロジカル値の配列を与えれば実現できることはすでに学びました。ただ、今回は全ての要素を編集可能にしたいので、trueをたくさん並べた配列を書く面倒を避けたいところです。こんな要望に答えて、ColumnEditableプロパティは、配列でないスカラ値としてtrueかfalseが与えられたら、配列要素全体にそれを適用するという仕様になっています。そして、ColumnEditableプロパティは、設計ビューの右カラムの下側で与える初期値設定欄で書き換えてもよいのですが、今回は、csv読み取りにはじめて成功したタイミングで全セルが編集できるようにするという想定で、ReadcsvButtonPushedコールバック関数のtryとcatchの間にコードを書くことにします。次の画像の57行目のように、app.UITable.ColumnEditable = true;と書けば終わりです。
ただし、このままだとこれまでに開発してきたほかの機能と干渉します。「UITableのセルの値が書き換えられたら、2列目は1列目の2倍の値に、3列目は1列目の3倍の値に自動的に修正する」というUITableCellEditコールバックがあり、その中でUITableのDataを使った数値演算(newData*2)を行ったうえで再度UITableに戻す部分です。このことの何がまずいのかを理解するために、試しに、この段階でアプリを起動して、どこかのセルの値を変更してみてください。すると、次のようなエラーメッセージがでます。
このエラーメッセージは、UITable.Dataへの代入の仕方に不備があることを示しています。readmatrix関数でcsvを読み込んでいたときには出なかったエラーが、readtable関数で読むようにしたら出るようになりましたので、エラーの原因は行列とテーブル配列の扱いの違いにありそうです。今回の場合、実は、47行目と48行目のUITable.Dataの後の丸カッコを、波カッコに変えるとうまくいくようになります。行列の後に丸カッコ(a,b)を添えた場合、a行b列にある一つの行列要素を指定できるのですが、テーブル配列の後に(a,b)を添えた場合、「a行目の要素の中でbという名前が付いた変数(列名は重複可能なので複数ありうる)を抜粋して小さいテーブルを取り出す」という操作になってしまうのです(参考MATLAB公式ヘルプ)。単一の値でない可能性があるのに、単一の値である「newData*2」を入れようとしているので怒られたわけです。テーブル配列はいろいろと機能があってややこしいというのは、こういうことも含みます。テーブル配列の行列番号を指定して要素を取り出す際には、丸カッコでなく波カッコを使うという仕様になっています(参考MATLAB公式ヘルプ)。ですので、次の画像の47行目と48行目のように、波カッコにすれば問題は解消されます。
アプリを実行して、UITableの値を書き換えると、その行の値が1列目(X)の2倍と3倍の値になるようになっているはずです。このように、UITableを扱う場合には、入っている変数が、テーブル配列(table)なのか、それとも行列(matrix)なのかをしっかり区別して、要素へのアクセスをする必要があります(入っているのがすべて数値でも、テーブル配列にはなりえます)。テーブル配列の機能が煩わしくなり、「テーブル配列で変数を扱っているけど、この後の処理はどうせ数値だけで行列みたいに使うから、テーブル配列でない方がいいな」と思ったときには、table2array関数を使って、テーブル配列を行列に変えるという手が使えます。列ラベルなどの情報などが失われますが、厄介なことを考えずに行列計算に使えるようになりますので、この変換を使いこなせると便利です。
続いて、保存したいときに押すボタンを作りましょう。設計ビューでボタンを新しく配置して、Textプロパティは「Save csv」にしましょう。そして、このボタンにButtonPushedコールバックを追加してください。
このコールバック関数には、次の画像のように、writetable関数を使う1行のコードを書くだけです。第1引数には保存したいテーブル変数を与え、第2引数にはファイル名を指定します。このようにファイル名だけを指定した場合、ファイルはMatlabが「現在のフォルダ」として認識しているフォルダに作成されます。ですので、アプリを起動してSave cvsボタンを押しても、mlappがあるフォルダにはファイルは作成されないかもしれません。
Matlabが認識している「現在のフォルダ」の確認と変更の方法には色々ありますが、App Designerではない、Matlabの開発画面の左カラムで確認と変更をすることができます。次の画面の左側に「現在のフォルダー」の文字があり、その下にはそのフォルダの内容が表示されています。上にはフォルダを移動するためのアイコンやバーがあります。ここが、tutoriala.mlappがあるフォルダになっている場合には、画像のように、editedtable.csvもあるはずです。見つけられたら、中身を確認してみてください。
現時点ではアプリをmlapp形式で開発していますが、完成したものは「.exe」のような実行ファイル形式にコンパイルして書き出すこともできます(ただし、MATLAB Compilarという有償アドオンが必要です)。そうなった場合には、この「.exe」があるフォルダが「現在のフォルダ」になるように動作します。また、テーブル配列でなく、行列をcsvとして書き出す際には、writematrix関数も使えます。writematrix(table_saveforcsv,’editedmatrix.csv’);
とすれば、第一引数の行列を、第二引数の名前(文字ベクトル)のファイルとして保存してくれます。
6.コンポーネントの整頓
ここまででいろいろなコンポーネントをアプリ画面領域に配置してきて、ごちゃごちゃしてきたので、一旦整えておきましょう。この作業はすべて設計ビューで実現できます。関連のあるものをラベルつきの枠で囲って、まとまりとして位置調整したりしたい場合には、「パネル」というコンポーネントが使えます。コンポーネントライブラリから「パネル」をクリック&ドラッグでアプリ画面に配置しましょう。この時点では、ほかのコンポーネントと重なってもよいですし、画面領域からはみ出していても大丈夫です。
このパネルの上に、ほかのコンポーネントをクリック&ドラッグして、パネル領域内部が青くなったときにマウスボタンを離すと、パネルの「上に載った」状態にすることができます。次の画像では、Read csvボタンと、File Name Editフィールドと、Save csvボタンをパネルの上に載せています。こうすると、パネルにカーソルを当てて、パネルの縁の色が青くなった時にクリック&ドラッグをすると、パネルの上に乗ったコンポーネントごと動かすことができるようになります。もし乗せたコンポーネントがパネルからはみ出すようなら、パネルの縁をクリック&ドラッグしてパネルのサイズを調整しましょう。
左上にあるInputの編集フィールドが邪魔なので左下に逃がして、右下にあったパネルを左上にもってきました。
パネル左上のラベルが「Panel」というのは意味がないので、「Files」に変えておきましょう。パネルコンポーネントのTitleプロパティの値を書き換えればよいですね。
このようにして、パネルの上にほかのコンポーネントを載せた場合、両者には親子の関係が発生します。右カラムのコンポーネントブラウザーを見ると、FilesPanel(パネルコンポーネント)の下に、右に少し寄せた形で「SavescvButton」「FileNameEditField」「ReadcsvButton」の3つのコンポーネントが並んでいることが確認できるはずです。このように、親子の関係が生じた場合、親コンポーネントへの設定変更の一部は、子コンポーネントにも自動的に反映されることに注意が必要です(パネルの位置を変えると、載せたコンポーネントの位置も変わることもその結果です)。たとえば、パネルコンポーネントの「Enable」のプロパティのチェックを外すと、パネルだけでなく、パネルに乗っているコンポーネントもすべて利用不可の状態になります。
コンポーネントを整頓するためのほかの方法として、「タブグループ」というコンポーネントも使えます。コンポーネントライブラリから「タブグループ」をアプリ画面に配置してみましょう。このタブグループも、複数の子コンポーネントを従えることができますが、それらの子コンポーネントをグループ分けして、グループごとに表示を切り替えることができるという特徴を有します。
このタブグループは、初期設定では「Tab」と「Tab2」という2つのタブを備えています。それぞれ、「TabGroup」を親とする子コンポーネントとなっていることを、コンポーネントブラウザーで確認してください。これらの子コンポーネントは、先ほどのパネルのように扱うことができます。まず、現時点では「Tab」というラベルが選択されている状態になっているはずですので、この「Tab」の方に「Button」「有効化チェックボックス」「InputEditField」をクリック&ドラッグして載せてみましょう。そうしたら、「Tab」のラベル部分をクリックしてTabコンポーネントが選択された状態にして、プロパティのTitleを「Settings」にしてみましょう。次の画面のように、アプリ画面でのタブのラベル部分が「Settings」になるはずです。
続いて、アプリ画面で「Tab2」を押して、Tab2コンポーネントが選択された状態にしてください。すると、先ほどタブグループに載せたコンポーネントは見えなくなるはずです。アプリを実行したときにもこのような切り替わりが起きます。
このTab2コンポーネントに、「Initial State」のラベルを載せてみましょう。ついでに、このTab2コンポーネントのTitleも「Results」に変えておきましょう。タブに載せる際にも、載せたい領域全体が青くなった時にドラッグをやめないとうまく載らず、タブの背後に隠れてしまうことがありますので気を付けてください。サイズが合わないようなら、タブコンポーネントの縁のクリック&ドラッグでサイズを大きくしてもよいですし、載せるコンポーネントを小さくしてもよいでしょう。ここまでで次のような画面になりました。
ここで、パネルやタブコンポーネントに載せた子コンポーネント達は、現状、載せる前と同じように機能してくれることを確認しておきましょう。アプリを実行して、Settingsタブの「有効化チェックボタン」にチェックを入れてみましょう。すると、Buttonはちゃんと有効化されて、次のような画面になるはずです。
ここで、Buttonはまだ押さずに、「Results」のタブを押してみてください。次の画像のように、「Initial State」の文字が表示されています。確認したら、再び「Settings」のタブに戻りましょう。
戻ったら、「Settings」のButtonを押してみましょう。すると、まずは右上の表に、しばらく前に実装した内容が表示されるはずです。しっかり機能が残ってくれています。
次に、「Results」のタブを押してみましょう。すると、文字列が「Button was Pushed」に変わっていると思います。これらのことから、タブが非選択の状態で見えなくなってしまっているコンポーネントも、見えてないだけで、実際には機能し続けてくれている、ということがわかります。
ここまでで、散らかったコンポーネントの整頓方法の基本を確認しました。タブグループを使えば、小さなアプリ画面でも多くのコンポーネントを配置することができます。また、見えなくできるということは、PCに与えられる描画のための計算負荷を削減できるということになりますので、表示に時間のかかる描画(プロット点数の多いグラフや解像度の高い画像等)がある場合に、ずっと表示させている必要がないようなら、こういったタブの中に収めておくとアプリの動作は軽くなるはずです(チェックボックスでVisibleを切り替えるという方法でもよいです)。
複数のコンポーネントの並びをもう少しきれいにしたい場合には、Ctrlキーで関連するコンポーネントを複数選択した状態で、App Designerの上部メニューの「キャンバス」タブの「整列」メニューを利用することも考えましょう。パワーポイントと同じように、複数の図形や文字列を左端や上端で揃えることができます。いったんそろえたものは、複数選択している状態で、「整列」メニューの近くにある「グループ化」でまとめておくと、配置がずれにくくなります。この「グループ化」は、共通の親コンポーネントの子同士である必要があります。具体的には、今作っているアプリで、Read csvとSave csvはグループ化することができますが、Read csvとButtonはグループ化することができません。
さらにこだわったコンポーネントの整頓をしたい場合には、「グリッドレイアウト」を使うという手段が取れますが、ちょっとややこしいですし、現時点では使わなくともチュートリアルは進められるので、グリッドレイアウトの練習は飛ばします。
7.グラフ描画とスライダによる調整
ここからは、アプリ画面にグラフ描画領域を用意して、GUI画面のスライダを操作することで、描画されるグラフがインタラクティブに変わるような機能の実装に挑みます。これまでのチュートリアルで、SettingsタブにButtonを実装していますので、まずはこのButtonが押されたらグラフが表示される機能を実装します。その後、スライダを実装して、スライダが操作されたら描画されるグラフが随時変わる、という仕様にしてみましょう。
まず、グラフの表示領域が必要なので、コンポーネントライブラリから「座標軸」を選んで、アプリ画面の空き領域に配置しましょう。空き領域が狭いとうまく配置されないので、他のコンポーネントを小さくしたり、アプリ画面全体を大きくして、ある程度の空き領域を確保してから配置すると、次の画像のように、グラフ描画のための座標軸が配置されます。アプリ画面全体を大きくするには、アプリ画面領域右下の三角形のつまみっぽい部分をクリック&ドラッグすればサイズを変更できます。
アプリの画面サイズを厳密に決めたいときは、アプリ画面全体が選ばれるように画面をクリックして、「app.UIFigure」が選ばれた状態にして(コンポーネントブラウザーで、一番上のapp.UIFigureを選んでもOK)、プロパティ欄のPosition値を編集するという手段が使えます。ここのPosition値は、カンマ区切りで4つの値を設定します。最初の2つが、アプリ画面左下のXY座標値です。このXY座標の原点は、モニタの左下で、Xは横、Yは縦です。ここを0,0にすると、アプリを実行したときに、アプリがモニタの左下に張り付く位置で表示されます。それらに続く2つが、アプリの横幅と立幅です。単位はPixelです。モニタの縦横の画素(Pixel)数を超えた値に設定すると、画面から一部がはみ出してしまうので、おおむね800,600くらいにしておくのがよいでしょう。次の画像に、そのように変更した例を示します。
ちなみに、ユーザの方での画面サイズ調整の可不可は、「Resize」のプロパティで切り替えられます。ただし、アプリ実行後に画面サイズが拡大された場合に、各コンポーネントも拡大するようにするのかや、コンポーネントがサイズを維持する場合、アプリ画面領域のどこのスペースが広がるようにするのかの設定がややこしいので、基本的には画面サイズ固定(Resizeをoff)にしておくほうが開発は楽です。もちろんユーザ側としては変わる方がありがたい場合もありますが、その場合の対応への解説は飛ばします。
この座標軸の見た目も色々と変えることができるのですが、今のところはこのままにしておきましょう。スライダを追加して値をGUI上で操作できるようにする前に、まずは決まった値でグラフを表示させるコードを作っていきましょう。
配置した座標軸は、現時点では「app.UIAxes」という名前のコンポーネントになっています。このコンポーネントに対して、「どんな種類のグラフで」「どんなデータを」描画するか、の2点を指定すれば、とりあえずのプロットを実現できます。グラフを描きたいときは、特定の種類のグラフをプロットするための関数の第一引数に「プロット先の座標軸コンポーネント名」を、第二引数に「プロットさせたいデータの横軸(x軸)の配列」を、第三引数に「プロットさせたいデータの縦軸(y軸)の配列」を与えます。Matlabで使用できるグラフの種類(つまりグラフ描画のための関数)はこのプロットギャラリーで紹介されています。ここでは、もっとも単純かつよく利用するplot関数を使ってみることにします。この関数は、各x座標に対して必ず1つのy座標の値が存在するグラフを描画する際に使えます。横軸を時間軸だとみなせば、時系列データもこの関数で描画できますので、よく使われます。
まずはデータ横軸の配列を作りましょう。今回は横軸を時間軸にするとして、0秒から30秒までの時間帯を対象にします。配列にするには、時間を刻まないといけないので、0.1秒間隔で刻んだ時刻で配列を作ります。この場合、横軸 x の配列は、x = linspace(0,30,301);で作れます。linspaceは線形に配置されたベクトルを作る関数で、第一引数は最初の値、第二引数は最後の値、第三引数は作成するベクトルの要素の数です。ボタンが押されたらグラフが描画されるようにしたいので、ButtonPushedコールバックの中にこの配列を作りましょう。次の画像では、35行目に追記しました。
続いて、データの縦軸の配列を作りましょう。横軸の値に応じた縦軸の値を計算して、それを配列に入れていきます。ここでは、正規分布関数を使って、時刻t=0であった値が時間とともにベルカーブ上に変化するようなデータにすることを想定します。パラメータとして、分散(σ)と中心(μ)が必要なので、事前にこれらの変数も定義して値を代入してから使うようにしておきます。次の画像では、36行目と37行目で σ と μ の値を決め、38行目でそれらのパラメータ値を使って y を計算しています。38行目の式の右辺に含まれる x が配列なので、すべての配列要素(つまり、0から30までの0.1刻みの値)について y が計算され、結果として y も配列になります。39行目で、app.UIAxesに対して、横軸を x 、縦軸を y としてグラフを描けと指示しています。
これで、アプリを実行してみましょう。Buttonを有効化してから押すと、次のような画面になるはずです。右下の座標軸に正規分布関数が描かれました。
このままではこの座標軸が何を表すものなのか、また横軸と縦軸が何かがわからないので、Title、X、Yのラベルも変えておきましょう。UIAxesコンポーネントを選んだ状態で、右カラム下側の「Title.String」「XLabel.String」「YLabel.String」のプロパティ値を書き換えれば、初期状態でのTitle、X、Yのラベルを編集することができますが、ここでは、ボタンが押されたら書き換わるようにしてみましょう。この場合、次の画像の40から42行目のように書けばよいです。
これでアプリを実行して、ボタンを有効化してから押すと、次の画像のように、グラフ描画と同時にラベルも書き換わるはずです。
X軸とY軸の値の範囲は自動で調整されますが、XLim、YLimのプロパティで指定できます。次の画像では、43行目と44行目で、X軸の範囲を0から50に、Y軸の範囲を0から1にしました。
これでアプリを実行してグラフを描画させると、次の画像のように、縦横軸の値の範囲を変更できていることを確認できます。plot関数にオプショナルな引数を追加することで、線の種類や色、太さなどの外観を変更することもできます。Matlabのplot関数の公式マニュアルでいろいろなオプション機能が紹介されていますので、試してみましょう。
この記事ではグラフはこのままの外観で進めます。続いては、グラフの形を変更するためのスライダを設置しましょう。設計ビューで、コンポーネントライブラリから「スライダー」を選び、次の画像のように、座標軸の下あたりに配置します。
このスライダーコンポーネントは、アプリ画面上でユーザがスライダのつまみをマウスのクリック、もしくはクリック&ドラッグで動かして値を指定することを可能にします。この値は、Valueのプロパティ値として取得できます。また、スライダのValueが変更されたときに実行される「ValueChangedFcnコールバック」も用意されています。そこで、このコールバックを利用して、スライダが操作されたら、そのタイミングでValueの値を読み取り、その値に応じてグラフを描きかえる、という機能を実装してみましょう。まずは、スライダコンポーネントに「ValueChangedFcnコールバック」を追加しましょう。
このコールバックでは、読み取ったスライダの値を保存するvalue変数がすでに用意されています。
すでにこの座標軸に何かのグラフがプロットされている場合は、それを消してから新しいグラフを描くのがよいので、座標軸をリセットする cla というコマンドを記載します。次の画像では、90行目に記載しました。リセットする座標軸を指定するために、app.UIAxesを引数として渡しています。これで、スライダを動かすと、すでにプロットされていたグラフが消えるようになりました。
この下に、先ほどButtonPushedFcnコールバックに記載したグラフプロットのためのコマンドをコピーして貼り付けましょう。次の画像では、92行目から101行目が、コピーして持ってきた部分です。
今回は、sigmaの値をスライダで指定できるようにすることにします。93行目のsigma = 5.0を、sigma = valueに書き換えて、次の画像のようにします。
この状態でアプリを実行して、スライダを動かしてみましょう。20より大きい値だと正規分布の裾野の部分しか見えないので縦軸の値がかなり小さくなってしまいますので、10辺りの値に設定してみるとよいでしょう。
これで、スライダ操作で描画するグラフを切り替えられるようになりました。横軸が50まであるのに、グラフは30のあたりで切れているのは、x軸の配列を30までしか作っていないためです。これを、50まで用意してやれば、横軸いっぱいまでグラフが描画されるようになります。また、スライダの操作範囲は0から100までですが、20以上は現状使わないので、0から20までにして、その範囲でより細かい値の調節ができるようにした方がよさそうですね。「Slider」というラベルも別のものに変えたいところです。これらの変更は、これまでのチュートリアルの内容が身についていたら簡単にできるはずですから、やってみてください。数か所変更すると、次の画像のようになります。
グラフをプロットする部分のコマンドが重複していたりしてコードがずいぶんごちゃごちゃしてきましたので、関数化するなどして整理整頓していきたいところですが、このチュートリアルではいったんこのままにしておきます。
8.タイマーを用いた周期処理
ここまで作成してきたアプリは、ユーザが何か操作をしたときにイベントを起こすという「不定期動作型」のアプリでした。ここからは、ユーザの操作を待つことなく、一定時間が経過したらイベントが自動で起きるような「定期動作型」のアプリを作っていきましょう。
まずは、これまで作ってきたアプリに、定期動作機能のON/OFFを切り替えるボタンを設置しましょう。設計ビューのコンポーネントライブラリで「状態ボタン」を選んで画面の空いているところに配置してください。次の画像では、左下に配置して、ボタンのラベル(Textプロパティ)を「Power」に変えました。この「状態ボタン」は、これまでに使用してきた「ボタン」に似ていますが、単に押したタイミングだけを検知する「ボタン」とは違い、「ボタンが押し込まれた状態」と「ボタンが押されていない状態」を持っています。電源のON/OFF用に都合がよいので、「状態ボタン」を使います。似たような機能を持つコンポーネントとして、「スイッチ」「スイッチ(トグル)」「スイッチ(ロッカー)」があります。外観上の違いがあるだけで、排他的な2つの状態の切り替えに使えるという点では同じですので、どれを用いてもよいでしょう。
この状態でアプリを起動して、Powerボタンを押してみましょう。押すまでは、次の画像のような外観です。
これを一度クリックすると、次の画像のように、ボタンの色が少し濃くなります。これで、押された状態であることを示しています。
このままでは、押された状態であることを判別しづらいので、変化がもっと目立つようにしておきましょう。設計ビューに戻って、PowerButtonにValueChangedFcnコールバックを追加してください。このコールバック関数の中で、押されている状態になったときには緑に、もとに戻ったときには薄いグレー(元の色)になるようにします。ボタンの文字の背景になっている色は、PowerButtonのBackGroundColorというプロパティで指定されます。プロパティ欄を見ると、元の色のRGB値が [0.96,0.96,0.96] になっていますので、押しこまれた状態になったらこれを [0.2,0.96,0.2]に変えて緑を強くして、もう一度押されたら[0.96,0.96,0.96]に戻るようにコードを追記しましょう。これは、これまでにチュートリアルで習った内容で実装できるはずです。次の画面のようにすればOKです。状態ボタンの状態はValueプロパティで設定されます。押し込まれた状態なら「1」、そうでないなら「0」です。ですので、この値が1かどうかをif構文で判定して色を切り替えるようにしました。
この状態でアプリを起動して状態ボタンを押すと、次のように緑色になり、もう一度押すと薄いグレーに戻ります。これで随分状態がわかりやすくなりました。
ではつぎに、この状態ボタンが押されている状態になったときには、定期動作が開始され、押されていないときには休止するような機能を実装していきます。この機能の実装のためには、「定期的に実行させる関数」をまず用意して、それをMatlabのtimer機能を使って定期的な実行スケジュールを組むことが必要です。まずは定期的に実行させる関数を用意してからtimer機能を使うという手順で進めましょう。自作関数は、matlabコードのmethodsのところで定義すれば使えるようになります。ここで作成する関数は、アプリ内でしか使わない関数なので、publicでなく、privateなmethodsのところで定義します。これまで作成してきたButtonPushedコールバックの関数なども、privateなmethodsのところで定義されていますので、同じところに同じように作ればよいということになります。次の画像では、27行目からprivateなmethodsを書く部分が始まっていて、その下にコールバック関数が定義されています。
自作関数をMatlabコードにはじめて追加するときは、コードに直接書き込むのではなく、Matlabの上部メニューのエディタータブの中の「関数」ボタンを押して、作る関数の種類を選ぶことから始めます。次の画像のように、種類の選択肢が出ますので、今回は「プライベート関数」を選びましょう。
そうすると、次の画像の26行目から33行目のように、methods (Access = private)とendのブロックと、その中に入った「functionとend」のブロックが作成されます。この部分はすべて白背景になっていますので、自由に書き換えることができます。デフォルトでは、appを引数としてresultsという値を返す、funcという名前の関数が作られます。引数も返り値も関数名も、このコード内で自由に変えられます。今後、別の関数を実装したいときには、このmethodsとendの中に、直接functionとendを書き込むことも可能です。
このようにして作成した自作関数は、画面左側の「コードブラウザー」の「関数」タブを選ぶと一覧で確認することができます。このコードブラウザー内で関数名を押すと、その関数が定義されたコード部分がコードビューに表示されるようになりますし、複数の関数を定義した後で、コードブラウザー内で関数の順序をクリック&ドラッグで入れ替えると、コード内でもその順序になるように並び替えができるので、大変便利です。コードビューで関数名を書き換えても、自動的にその変更は反映されます。
デフォルトで作成されたfunc関数を、定期動作する関数に修正していきましょう。まず、返り値は必要ないので無くします。また、関数名は、なんでもよいのですが、ここでは「CoreTimerFcn」にします。引数は、appだけだったのを、app,~,~というものに変えます。appは、Matlabアプリ内の様々な要素にアクセスするためのオブジェクトで、コールバック関数には必ず必要です。timerを使って定期的に実行する関数も、コールバック関数として運用されるため、appを引数に入れておきます。残りの2つの引数に入っている「~(チルダ)」は、関数の仕様上用意はされているものの、実際には使わない引数を受け取るときに、引数の変数名の代わりに置くものです。timerでこのコールバック関数を使うときに引数が3つ与えられるものの、今回は第1引数しか使わないので第2、第3引数は「~」で済ませています。
こうしておいて、この関数のfunctionとendの間に、定期実行させたいコードを書きます。ロボットの制御であれば、センサ信号を受信して、操作信号に変換して、それを送信する、といったコードを書けば、制御ループを作れます。ここでは暫定的に、描画するグラフをループ毎に更新していくものにしてみましょう。前回のチュートリアルで描画したグラフを題材として、まず始めは最初の20秒間のグラフをプロットして、その後ループ毎に、描画する20秒の範囲を0.1秒ずつ後ろにずらしていくという形で、グラフが時間とともに右から左に流れていくようにすることを考えます。横軸の配列は、0から0.1刻みで20までの値が並んだものにしましょう。前回のチュートリアルでは、linspace関数を使いましたが、実は、刻み幅が決まっている場合には、この関数を使わずとも、x = 0:0.1:20; とすれば[0, 0.1, 0.2, … 19.9, 20.0]という配列が作れますから、今回はその方法で作りましょう。横に流れていくグラフを描画する方法はいくつかありますが、今回は安直に、ループ毎に0.1ずつ増えていく time という変数を新たに用意して、x + time を使って 縦軸 y の値を計算する、ということにします。次の画像では、CoreTimerFcnの中に、前回のチュートリアルで作成したグラフ描画のコードをコピー&ペーストして、31行目に、ループ毎に0.1増える time という変数を用意し、35行目の y の計算に使う x を x+time に変えた例を示しています。32行目で x の配列を作るコードも 0:0.1:20に変えています。毎回配列を作り直すので効率は悪いのですが、ひとまずこれで実装を進めます。
ここで、この変数 time をどこかで 0 に初期化しておかないといけないことに気が付くと思います。この初期化をCoreTimerFcnの中で行うと、毎回初期化されてしまうので、どこかこの関数の外で、変数を定義して初期化しておくべきですよね。こんなときには、Matlabの上部メニューの「エディター」タブの「プロパティ」を選んで表示される「プライベートプロパティ」をまず選択します。
そうすると、コードの上部に、これまではなかった properties (Access = private)とendのブロックが挿入されます。下の図では、27行目から29行目です。ここのブロックの中に、アプリの中で使いまわしたい変数を定義(初期化含む)してやると、初期化後にほかの関数で利用できるようになります(C言語でいうところのグローバル変数)。
デフォルトでは、Propertyという変数が用意されていますが、これは使わないので、これを削除して、代わりに timeを 0として定義しましょう。次の画像では、28行目で定義しています。ただし、このままだと、先ほどCoreTimerFcn内に記載した timeの部分が黄色マーカー表示になり、そこにマウスカーソルを当てると、「appのプロパティを参照するには、app.timeを使用します。」というメッセージがでます。このように、propertiesで定義した変数をほかの部分で利用するときは、変数名の前に「app.」をつける必要がありますので、これまでに書いた「time」を「app.time」に差し替えましょう。
差し替えると、次の画像の通り、黄色い警告も消えました。
続いて、timer関数を使って、このCoreTimerFcnを定期実行させる部分を実装します。timerという関数は、「このイベントをこの時間で実行する」というスケジュールを定義する機能と、その開始や中断の指示を受け付ける機能を持っています。このスケジュールは、タイマーオブジェクト、という形式で管理され、いろいろな関数の中でそのタイマーオブジェクトに対して開始や中断の指示が与えられることが想定されるので、まずは、先ほどtime変数を定義したのと同じ、PrivateのPropertiesのところに、CoreTimerという名前の変数を一つ定義しておくことにします(この後でタイマーにするものに名前を付けているだけなので、名前は何でも良いです)。次の画像では、29行目に定義しました。初期値は必要ありません。
こうしたうえで、再びPowerButtonPushedコールバック関数の編集に戻ります。現状、PowerButtonが押されたら、その状態に応じてボタンの色が変わるだけでした。ここに、「ボタンが押し込まれた状態になったら、CoreTimerをタイマー機能を持ったオブジェクトにする」というコードを追加しましょう。次の画像では、136行目から140行目の部分を追加しています。timer関数にいろいろと引数を入れて、その返り値を CoreTimer に代入しています(CoreTimerの変数名にはやはり app. をつけます)。これで、CoreTimerは、タイマーの機能を持つもの(タイマーオブジェクト)になります。
「…」は、長いコードの視認性を上げるためにつかう省略記号(参考MATLAB公式ヘルプ)ですので、この記号を抜いた、app.CoreTimer = timer(’ExexutionMode’, ’fixedRate’, ‘Period’, 0.1, ‘BusyMode’, ‘queue’, ‘TimerFcn’, @app.CoreTimerFcn);というコードと実質同じです。これらの引数は、「設定項目」と「設定値」のペアが4つ並んだものなので、このような省略記号を用いて4分割しておくと、それぞれごとにコメントをつけることもできるので便利です。
TimerFcnという設定項目に対して、定期的に実行させたいコールバック関数の名前を指定します。今回は、app.CoreTimerFcnです(アプリの中で定義した関数にアクセスするときにも、関数名の頭にapp.をつけます)。ExecutionModeという設定項目では、どんなタイミングでそのコールバック関数を実行するかを指定します。fixedRateにすると、タイマーオブジェクトが開始された直後にまずコールバック関数が実行されて、その後一定時間ごとに繰り返し実行されるというスケジュールになります。Periodで、その周期を指定します。今回は0.1とします。BusyModeでは、コールバック関数内の処理をその周期内で実行しきれなかったときの挙動を指定します。多少周期が伸びたとしてもすべての処理を実行してほしい場合には、queueにします。ほかの設定については、Matlabの公式マニュアルのtimerのページを参照してください。
このままではタイマーオブジェクトというスケジュールを組んだだけで、開始されてはいませんので、この定義部分に続けて、開始させるコードも書いておきます。また、状態ボタンが押されていない状態になったらいったんスケジュールの実行を休止させたいので、その休止コードも用意します。次の画像では、CoreTimerをスタートさせるコードを141行目に、休止させるコードを144行目に記載しました。
これで、アプリを実行してみましょう。状態ボタンを押すと、グラフが右から左に流れていくはずです。状態ボタンを押すたびに、グラフの移動のON/Offを切り替えられることも確認してください。グラフの描画はPCに大きな負荷を与えるので、PCによっては滑らかには動かないかもしれません。その場合は、上記で設定した0.1の値を、0.5や1に変えることを試してみてください。
9.UDPを用いたアプリ間通信
ここまでで、単体で周期的に何か変化を起こすアプリを作ってきましたが、コードが随分複雑になってきました。より大規模なアプリを作っていく上では、一つのアプリを複雑にしていくよりも、機能ごとにアプリを分けて、アプリの間でデータ通信をさせて複雑な処理を実現する方が開発やメンテナンスやシステム拡張がしやすい場合があります。そこで、ここからは、アプリに他のアプリとの通信機能を持たせる方法について学びます。
データ通信の方式にはいろいろありますが、同じPCの内部で動作する定期(周期)動作アプリの間での通信(プロセス間通信)を考える場合には「UDP」という通信方式が便利です。この方式では、送り先のPCを特定するためのIPアドレス(同じPCの指定も可能)と送り先のアプリのポート番号を設定してデータを送り出せば、そのポートが用意されていようがいまいが、また通信経路途中でデータが壊れたり失われていようがいまいが、とにかく送信処理を終えてくれるので、「timerを使った制御ループの時間内で通信をとにかく高速に終えたい」「送り先にちゃんとデータが届いたかは確認せずとも構わない」という場合に都合がよいです。もちろん、インターネットを介して他のPCと通信をしようとしたりする場合にUDPを用いると、通信経路の複雑さやノイズの影響を受けてデータに乱れが生じます(ストリーミング配信動画で映像が止まったり乱れたりするのもUDPを採用した影響)が、同じPC内のアプリ間で通信をする場合には基本的に乱れませんので、UDPでも正確な通信が実現できます。
このチュートリアルでは、これまでに作ってきたアプリに、UDPでのデータ送信機能を搭載することに挑みます。送信するデータは、[100, 230, 3, 0, 0, 276] のように、0から255までの整数値がいくつか並んだ配列とします。アプリの制御ループ毎にこの配列の値を更新してどこかに送信するという機能を実装してみましょう。この機能の実装を終えた後で、このデータを受信するアプリを新たに作成して、実際にデータの通信ができているかを確認します。
MatlabにおいてUDP通信を利用する簡単な方法は、ストリーミング信号処理システムを組むために用意されている DSP System Toolbox を利用することです。Toolboxというのは、Matlab用のライブラリ(汎用性の高い関数を詰め合わせたもの)のことで、DSP System ToolboxはMatlabの公式Toolboxとして有償で配布されています。使っているMatlabにこのライブラリが入っているかどうかは、Matlabのアプリ(App Designerでない方)の上部メニューの「ホーム」の「アドオン」のサブメニューを開き、「アドオンの管理」を選ぶと調べることができます。
ちなみに、DSP System Toolboxを使うには、別途Signal Processing Toolboxという別の有償Toolboxもアドオンとして追加されている必要があります。
「アドオンの管理」を選ぶと「アドオンマネージャー」が開き、その時点でMatlabにインストール済みのToolboxを調べることができます。次の画像では、一番上に「DSP System Toolbox」が記載されています。
DSP System Toolboxがあれば、指定したIPアドレスの指定ポートにUDP送信を行うオブジェクトを簡単に生成して管理(休止させたり廃止したり)することができます。このオブジェクトも、前のチュートリアルで作成したタイマーオブジェクトと同様に、アプリ内のいろいろな関数で利用することが想定されるため、privateなpropertyとしてオブジェクト名を定義しておきましょう。名前は何でもよいのですが、UDPの送信用オブジェクトなので、udps_で始まる変数名にしておくことにします。次の画像では、30行目に「udps_generateddata」という名前でオブジェクト名を定義しました。
この段階では、「udps_generateddata」という名前の変数をprivate変数として用意しただけで、まだUDP送信の機能を持ったもの(オブジェクト)にはなっていません。というわけで、これを送信用のオブジェクトにします。アプリのボタンが押されたタイミングでオブジェクトにしてもよいのですが、今回は、アプリが起動したタイミングで自動的にオブジェクトになるようにしてみます。アプリが起動したタイミングで何かを実行させる場合には、StartupFcnコールバックを利用します。このコールバックは、次の画像のように、画面右カラムのコンポーネントブラウザーの一番上のアプリ名の部分(今回はtutorial1)を右クリックして開くメニューでコールバックを選ぶと、追加するための選択肢が出てきます。
追加すると、次の画像の57行目から59行目にstartupFcn関数が追加されました。この関数のブロックの中に書いたコードは、アプリが起動したタイミングで実行されますので、この中にUDP送信用オブジェクトを生成するコードを書いてみましょう。
DSP system toolboxを使う場合は、単に、dsp.UDPSenderという関数を実行すれば、返り値としてUDP送信用オブジェクトを得られます。つまり、app.udps_generateddata = dsp.UDPSender;と書けば、それが実行されたタイミングで、udps_generateddataがUDP送信用オブジェクトになります。ただし、それだけだと、どのIPアドレスのどのポートに送るのかが未定義ですので、引数に「設定項目」と「設定値」をペアで並べて設定することができます(タイマーオブジェクトのときと似ています)。dsp.UDPSenderでは、送り先のIPアドレスは「RemoteIPAddress」という設定項目で指定できます。規定値は同一PCを指す127.0.0.1なので、同じPC内にあるアプリ間での通信を実装する今回の場合は設定を変えなくてOKです。送り先のポート番号は、「RemoteIPPort」です。規定値は25000で、1から65535の間で設定できます。
個人で開発するアプリの間でUDP通信を行う場合には、基本的には50000番以降の番号を選ぶのがよいようです。それ以前の番号は、すでに動作しているほかのアプリに割り当てられている可能性が高いので、意図しないアプリにデータを送って挙動をおかしくさせる恐れがあるためです。次の画像では、送り先ポート番号を50000にしています。そのほかの設定項目についてはUDPSenderの公式マニュアルを参照してください。
これで、udps_generateddataという送信用オブジェクトが用意されるので、制御ループの中でこのオブジェクトを利用してデータをアプリ外部に送信することができるようになりました。今回は、下の画像の49行目のように、送信用のデータを格納する変数をdatasendという名前で用意して、そこに1から5までの整数値が入る5行1列の縦行列を入れることにします(各括弧の中のセミコロンは、「行列要素を指定する際の改行」を意味します。今回は1要素ごとに改行を入れているので、1列の行列になります。[1,0;0,1]とすれば、2行2列の単位行列となります。)。50行目のように、これをudps_generateddataの引数にすれば、同じPCの50000番ポートにこの変数の中身を送り出してくれます。ここで、引数はdatasendそのままでなく、uint8()という関数を一度嚙ませたdatasendとしています。
このuint8()は、引数に与えられた数を、0から255までの一番近い整数値に変換する関数です。現状では、datasendには1から5までの整数しか入っていないので、uint8()を噛ませても噛ませなくても変わりはありませんが、この後でdatasendにいろいろと計算をかけてから送信をすることを考慮してこの段階でuint8()をつけています。今回は、受信側で0から255の整数値だけを受け付けて処理をすることを想定しているのでuint8()を使いますが、受信側でこの範囲外の値や小数付きの値を利用する場合には、uint8()を使う必要はありません。
これでアプリを実行してPowerボタンを押すと、これまでと同様にグラフが右から左に流れる様子が描画されますが、データが本当にUDP送信できているかどうかは確認できません。そこで、送信するデータをいろいろといじる前に、データを受信して、受信した内容を確認するためのアプリを先に作っておきましょう。App Designerの上部メニューのデザイナータブを選び、一番左の「新規」メニューを押してください。
するとApp Designerスタートページが開くので、「空のアプリ」を選びます。
すると、app1.mlappといった名前でアプリが作成されます。まずはわかりやすい名前に変えて保存しておきましょう。上部メニューの「保存」を選び、ファイル名を「udp_receiver.mlapp」にして、さきほどまで開発していたtutorial1.mlappと同じフォルダに保存しておきます(ほかの場所でもよいです)。
このアプリの画面に、受信したデータを文字列にして表示する機能を実装します。まずは画面にラベルコンポーネントを配置して、Textプロパティを「Received Data」に変えておきましょう。
続いて、定期的にデータを受信するタイマーループを実装します。privateなpropertyのところに、タイマーオブジェクトと、UDP受信用オブジェクトにする変数を定義しておきましょう。udp_receiver.mlappのコードビューを表示させた状態で、上部メニューの「エディター」タブの「プロパティ」を開いて「プライベートプロパティ」を選びましょう。
コードに作成された properties (Access = private)のブロック内で2つの変数を定義します。下の画像では、受信ループのタイマーオブジェクトとして「ReceiverTimer」を、UDP受信用オブジェクトとして「udpr_bytedata」を用意しました。
次に、「StartupFcnコールバック」を設けましょう。このコールバックの中で、上記2つの変数に、タイマーオブジェクトとUDP受信用オブジェクトを与えることにします。
StartupFcnコールバックの中で、タイマーオブジェクトを作成してReceiverTimerにそのオブジェクトを与える方法は、送信用オブジェクトを作成した時と同じです。下の画像の21行目から25行目の部分で分かるように、今回も0.1秒周期で回るタイマーにしました。「CoreTimer」だった部分を「ReceiverTimer」に変えただけです。こちらのアプリで用いるタイマーの名前もCoreTimerにしている場合は、そのままCoreTimerでOKです。32行目で指定している、定期実行する関数の名前は、「ReceiverTimerFcn」としておきましょう。後でこの名前の関数を作成します。26行目で、このタイマーオブジェクトを開始して受信ループが回り始めるようにしています。28行目は、dsp system toolboxを使ってUDPの受信用オブジェクトを作っている部分です。dsp.UDPRecerverという関数の引数として、”LocalIPPort”という設定項目と、「50000」という設定値を与えています。この設定項目が、この受信用オブジェクトの受信ポート番号になります。今回、先に開発していた送信側のアプリで、50000番のポートにデータを送る設定にしていたので、それを拾えるように、受信用のポート番号も50000にしておきます。
つづいて、ReceiverTimerという定期実行される関数を作りましょう。上部メニューの「エディター」タブの「関数」で、「プライベート関数」を選びます。
すると、function results = func(app)という関数が作られるので、これを、次の図のように、function ReceiverTimerFcn(app,~,~)に変えましょう。ここは、ReceiverTimer(タイマーを管理するオブジェクト)ではなく、ReceiverTimerFnc(タイマーで定期実行される関数)であることに注意が必要です。
このReceiverTimerFcn関数の中で、50000番に送られてくるデータを受け取って、文字列に変換して、アプリ画面のラベルに表示する機能を入れていきます。次の画像の18行目から27行目までその部分ですが、いろいろとややこしいことをしているので、ひとつずつ確認していきます。まず、try-catch-endの構文で、とりあえず試みる処理と、途中で失敗した場合の処理を書いています。catchとendの間の26行目が、受信に失敗したときの処理ですね。アプリ画面の文字列を「no signal」にするようなコードにしています。19行目から24行目が、試みる処理です。大事なのは19行目です。ここで、app.udpr_bytedata()という受信用オブジェクト名の関数を呼び出すことで、返り値として受信したデータを得られますので、これをreceiveという変数に入れています。この時点で、もし受信に成功すれば、receiveには、送信した5行1列の行列が入ります。この行列をそのまま次の処理に使う場合には、この行列のまま使えます。20行目から24行目は、この行列を文字列に変換して、アプリ画面に表示させるための処理です。20行目で、1行1列目の行列要素をまず文字列に変換して、tmpという変数に入れます。num2str()は、引数として数値(の配列)を入れると、それを文字列に変換してくれる関数です。21行目から23行目までは、カウンタ変数 i を2から受信した行列の行の数まで変えながら、そのカウンタ変数の行にある1列目の要素を文字列に変換して、先ほどのtmpの文字列の後ろにカンマで繋げていく部分です。strcat(a,b)は、aとbが文字列の場合に、この順で文字列を結合してくれる関数です。strcat(a,b,c)で、aとbとcを結合します。こうして、tmpを、すべての行の値をカンマで繋げた文字列にしたうえで、24行目でアプリ画面のラベルに代入しています。
このコードを実行すると、次のように、まずはno signalの画面になるはずです。
つづいて、このアプリを起動したままで、tutorial1.mlappのアプリも起動します。起動したら、Powerボタンを押しましょう。すると、「no signal」の代わりに、「1,2,3,4,5」が表示されるはずです。これが、受信した行列receiveを文字列tmpに変換した結果です。
現状では送信内容が「1,2,3,4,5」のまま変わらないので、tutorial1.mlappに戻って、送信する行列の中身がループ毎に変わるようにしてみましょう。両方のアプリはいったん閉じて構いません。今、tutorial1で定期実行される関数の中身は、次の画像のようになっています。49行目で、datasend = [1;2;3;4;5]になっている部分を変えたいので、41行目で随時変わる変数 y を使うことにしましょう。
yは、201行1列の行列で、1行目には、座標軸の一番左の値が入っています。この値が、0付近からだんだんと0.4付近まで大きくなっていきますので、これを100倍した値を[1;2;3;4;5]の要素に乗じた値を送信することにします。次の画像の29行目が、変更した部分です。これで送信結果がどうなるかを見てみましょう。再び2つのアプリを起動して、tutorial1.mlappの方のPowerボタンを押してみてください。
そうすると、次の画像のように、受信される値が以前より大きくなり、時間とともにいったん増えてまた下がるようになります。この値の変化は、グラフの左端の値の変化と一致します。これで、実際に時々刻々とデータが送信されていることが確認できました。また、送信する際にデータにuint8()を噛ませているので、整数値になっていることも併せて確認してください。
おわりに
この基本チュートリアルはここまでです。App Designerには、ボタンやスライダ以外にも多くのコンポーネントが用意されており、それぞれに設定できる項目や、追加できるコールバックは異なるものの、このチュートリアルで理解した内容を踏まえてそれぞれのコンポーネントについての公式解説資料を読めば、使い方はわかると思います。是非思い通りに機能するGUIアプリを作ってみてください!




































































































































































