見出し画像

MATLAB でマルチウィンドウアプリを作る (1)


まえがき


MATLAB では、App Designer を使って簡単に GUI アプリを作ることができます。

今回は、複数のウィンドウを持つアプリの作り方を解説します。

続きがある予定です。たぶん。


もちろん公式ドキュメントにも説明があるのですが、最初は分かりにくいと思いますし、「ダイアログ ボックスでしかできない?」とか思ってしまいそうなので、順を追って説明していきます。

公式サンプルはコマンドウィンドウで以下を実行すれば開くことができます。

> openExample('matlab/AppsThatShareDataExample')


また Web アプリでの複数アプリウィンドウ作成はサポートされていないので、その場合はタブ等を使うことになります。


App Designer


簡単な例として以下のようなアプリを作ってみます。

・app1
1. 各 n1 点のデータを持つ n2 種類のデータセットのグラフを表示
2. ”Parameters” に n1, n2 を表示
3. ”Option” ボタンクリックで app2 起動
4. ウィンドウを閉じると app2 も閉じる

app1

・app2
1. app1 の n1, n2 をスピナーの初期値として設定
2. スピナーで n1, n2 変更可能
3. 変更した n1, n2 を app1 の ”Parameters” にリアルタイム反映
4. "OK" ボタンクリックで app1 のグラフ更新

app2


これを実現するには、基本的には
app1:
app2 構造体宣言
それを戻り値に必要な引数付けてapp2 呼び出し

app2:
app1 構造体宣言
startupFcn に引数追加して構造体と共に受取り
app1 の GUI 操作、app1 の public 関数呼び出し

とこれだけです。

複雑そうに思えるかもしれませんが、一度分かってしまえばシンプルです。


アプリは、デフォルトでは左上に MATLAB アイコン と"MATLAB App" が表示されます。

変更したい場合は、背景の UIFigure をクリック、コンポーネントブラウザー一番下の「識別子」で Name にアプリ名を入れ、 Icon でアイコンファイルを指定します。

以下の記事を参照してください。


それでは、実際に詳しく見ていきましょう。


・app1 設計ビュー


まずは「新規 → アプリ」で App Designer を立ち上げます。 

App Designer の起動


左ペインのコンポーネントライブラリから、「座標軸」「ボタン」「テキストエリア」を適当なところにドロップして適当にサイズ変更等を行います。

app1 設計ビュー


「ラベル」で名前を変更できます。コンポーネント名も、ダブルクリックすれば変更でき、ソースコードに自動で反映されます。

ここでは、テキストエリアのラベルは "Parameters”、コンポーネント名も "Parameters” に変更しています。


同一アプリ内では自動更新
されますが、他アプリから参照している場合は注意しましょう。

テキストエリアの設定

「テキストエリア」のラベルは、ラベルだけを選択すれば好きな場所に移動できます。

同様に、「ボタン」は Text を "Option" に変更しました。

ボタンの設定


ここで一旦ファイルを保存しましょう。ファイル名がアプリ名になり、ソースコードも自動的に更新されます

今回はそのまま app1 としています。


以降は、MATLAB 上で mlapp ファイルをダブルクリックすれば App Designer で開かれ、MATLAB コマンドウィンドウで > app1 と打つか、エクスプローラーでダブルクリックすればアプリが実行されます。

(エクスプローラーからの場合は MATLAB が立ち上がり、起動済みであれば即アプリが実行されます)


・app1 コードビュー


それではコードの方を書いていきます。


1.プロパティの追加

コードブラウザーのプロパティで、プライベートプロパティを追加します。

アプリ内で共通に使える変数等の宣言です。

プロパティの追加

以下のように書き直します。

    properties (Access = private)
        propApp2  % for sub app
        n1 = 10;  % num of data points per dataset
        n2 = 3;   % num of datasets
    end


2.関数の追加

次は app2 から呼ぶグラフ更新関数を定義します。

アプリ外からも呼べるように、パブリック関数として宣言します。

関数の追加

以下のように書き直します。

    methods (Access = public)   
        function updateplot(app, p1, p2)
            plot(app.UIAxes, rand(p1,p2), '-o')
        end
    end


3.StartupFcn コールバックの追加

アプリ名を「右クリック → コールバック → StartupFcn コールバックの追加」で、アプリ起動時に実行される関数を追加します。

StartupFcn コールバックの追加

以下のようにして、グラフの初期描画、”Parameters” テキストエリアへの表示を行います。

        function startupFcn(app)
            rng(0,"twister");  % for reproducibility
            updateplot(app, app.n1, app.n2)
            app.Parameters.Value = string(app.n1) + ", " + string(app.n2);
        end


4.OptionButtonPushed コールバックの追加

Option ボタンをクリックしたときの動作を追加します。

ボタンを右クリック → コールバック → ButtonPushed コールバックの追加 でコールバックが追加されます。

OptionButtonPushed コールバックの追加


以下のコードを追加します。

        function OptionButtonPushed(app, event)
            app.propApp2 = app2(app, app.n1, app.n2);
        end

app2 を引数付きで呼び出すと共に、app2 の構造体を保存します。

以降は、同じ操作でコールバックコードへの直接移動ができます。

コールバックに移動


5.UIFigureCloseRequest コールバックの追加

最後に、app1 を閉じたときに app2 が開いたままなら閉じたいのでその処理を入れます。

コールバック → ➕ で CloseRequestFcn を選択して追加します。

CloseRequest コールバックの追加

以下のように、app2 の delete を追加します。

        function UIFigureCloseRequest(app, event)
            delete(app.propApp2)
            delete(app)
        end

app 自体は app1 内で宣言されているので、app2 の状態にかかわらず(開いているか閉じているかチェックせずに)実行が可能です。


・app2 設計ビュー

それでは app2 の方に移ります。

app1 タブの右の + をクリックすれば、新たなアプリが作成されます。

そこに、「スピナー」x 2、「ボタン」をドロップし、右ペイン下の「コードオプション」引数 "app1,n1,n2" を追加します。

StartupFcn コールバック が自動で追加されます。

これでアプリ起動時に引数を受け取ることができるようになります。

app2 設計ビュー

ボタンの "Text" は "OK" に、スピナーの "Limits" は "1,100" に変更しています。

スピナーの設定

これらはコード中からも、例えば以下のように設定・変更可能です。

app.n1Spinner.Limits = [1,10];


・app2 コードビュー

1.プロパティの追加

先ほどと同様にプロパティを追加します。

    properties (Access = private)
        propApp1  % for main app
        n1New, n2New
    end


2.StartupFcn コールバックへの追加

コールバック自体は既に追加されているので、以下のように書き換えます。

        function startupFcn(app, app1, n1, n2)
            app.n1Spinner.Value = n1;
            app.n2Spinner.Value = n2;
            app.n1New = n1;
            app.n2New = n2;
            app.propApp1 = app1;
        end

app1 から受け取った初期値を各スピナーと変数にセット、app1 の構造体も受け取ります。

これで、app2 から app1 のパブリック関数呼び出しapp1 の GUI 操作もできるようになります。


3.各コールバックの追加

各コンポーネントを右クリックしてコールバックを追加し、それぞれ以下のようにコードを追加します。

        function n1SpinnerValueChanged(app, event)
            app.n1New = app.n1Spinner.Value;
            app.propApp1.Parameters.Value = string(app.n1New) + ", " + string(app.n2New);
        end
        function n2SpinnerValueChanged(app, event)
            app.n2New = app.n2Spinner.Value;
            app.propApp1.Parameters.Value = string(app.n1New) + ", " + string(app.n2New);
        end
        function OKButtonPushed(app, event)
            updateplot(app.propApp1, app.n1New, app.n2New)
        end

スピナーで数値を変更したら app1 のテキスト表示をリアルタイムで変更、"OK" でapp1 の表示更新関数を変更された引数付きで呼び出します。

アプリの動作として app1 のテキスト表示をリアルタイム変更する意味はありませんし、お互い GUI パラメーターも直接参照できますが、あくまでも例として。


また、プロパティ、関数・コールバック、入力引数の設定は、コードビューのエディターツールバーからも行えます。

エディターツールバー

こちらでも、「アプリの入力引数」をクリックすると自動で StartupFcn が追加されます。


・実行

app1 を実行します。

app2 からは引数不足で実行できません。

nargin で引数の数を参照できるので、app2 の startupFcn にチェックを入れておいてもよいでしょう。


コード

R2024b で作りました。


あとがき


書きたいことがいっぱいあるのですが、今回はこの辺で・・。


いいなと思ったら応援しよう!