ウィンドウを使った2つの画面の間でデータを受け渡すアプリを作ろうと思います。
以前こちら「【C# WPF】WPFウィンドウを使った画面遷移の方法」で2つの画面を作成して行き来できるようにしたので、今度は、この画面2つの間でデータを受け渡しできるようにしようと思います。この記事はデータの受け渡しに焦点をあてて書くので、画面遷移の部分で分からないことがあれば上で以前の記事の方を見てください。
※ボタンに表示されている文字や間隔など見栄えについて若干の修正をしましたが、基本的な部分は同じです。
片方の画面でデータを入力して、もう片方の画面に移るとそこに入力した内容が表示される仕様にします。
それぞれの画面に「TextBox」を用意して、そこに受け渡すデータを入力したり、受け渡されたデータを表示するようにするわけです。
実際に動かしてみると次のようになります。
(gif 画像なのでブラウザによってはアニメーション再生機能で動しながら見てください)
使用環境
・Window 11
・Visual Studio 2022
・.NET 8.0 (C# 11.0)
はじめに
全体の仕組みをザックリ説明すると、片方の画面のコードから、もう片方の画面の項目に直接データを代入して受け渡すことはしません。
別の画面のコードから表示を操作されると往々にして収拾がつかない状態になり、表示内容を正確に保つことが難しいからです。また、入力チェックを行う場合などは似たようなコードが散在することになりアプリの品質も下がります。
そこで、画面以外にデータを格納しておく場所を用意し、そこを介してデータを受け渡すことにします
データを格納しておく場所を用意
まず、次の手順で新しくクラスを作り、そこにデータを格納しておく場所を用意します。
Visual Studioのソリューション エクスプローラを表示し、プロジェクトの行を選択して右クリックするとメニューが開くので「追加」→「新しい項目」と選びます。
「新しい項目の追加」という小窓が開くので、左側の列で「C#の項目」を選び、中央の列で「クラス」を選びます。
下端の「名前」欄に任意の名前(この例では “Model.cs” としておきます)を入力して「追加」をクリックします。
そうするとクラスファイル “Model.cs” ができるので、その中にクラスを作成し、項目を定義します。
今回は “Prop1” という名前のプロパティを用意しましょう。
クラスの外、つまり画面のコードからデータを出し入れするので、プロパティにします。
内容は次のようになります。
2つの画面間で、この “Prop1” を介してデータを受け渡すことにします。
このクラスを使える状態にするため、アプリケーションが起動したところで生成しておきます。
画面に受け渡すデータを表示するためTextBoxを追加
それぞれの画面に受け渡すデータを入力したり、受け渡されたデータを表示したりする箇所が必要なので、TextBoxを追加します
場所は「StackPanel」の中にします。
それぞれの画面のXAMLファイルの内容は次のようになります。
画面に入力されたデータを受け渡す
さて、これで、「(表示中の画面の) TextBox1.txt」→「ValueObject1.Prop1」→「(次の画面の) TextBox1.txt」とデータを受け渡すことができれば、完成するわけですが、受け渡す方法がいくつかあります。
- 直接代入して受け渡す方法
- データ バインディングで受け渡す方法
などです。
直接代入して受け渡す方法
まずは、あまりオススメではないのですが、分かりやすい方法を見てみましょう。
1番の「直接代入して受け渡す方法」です。
この方法は、各画面に、データを渡すための代入と、受け取るための代入を追加します。
データを渡すための代入は、画面を切り替える直前に追加します。
受け取るための代入は、画面を表示する時に動作させるので、「Initialize()」というメソッドを追加して、そこで代入します。
MainWindow.xaml.csの内容は次のようになります。
Window1.xaml.cs の方も同様に、画面の名前を入れ替えたコードを追記します。
これで、2つの画面の間でデータを受け渡すことができるように、なりました。
片方の画面のTextBoxに文字を入力してボタンをクリックすると、もう片方の画面のTextBoxに入力した文字が表示されるはずです。
データ バインディングで受け渡す方法
2番目の「データ バインディングで受け渡す方法」に修正して見ましょう。
まず、橋渡しをするクラスを定義して、その中で「ValueObject1」を生成し、生成したオブジェクトを返すプロパティを用意します。
「App.xaml.cs」の中に作ってもいいのですが、画面変更などの改訂が多い箇所なので、メンテしやすいように新しいクラスを作成した方がよいでしょう。ここでは「ViewModel.cs」という名前で作成します。
このクラスを使える状態にするため、アプリケーションが起動したところで生成しておきます。
各画面では、上の 1番の方法で行った、データを渡すための代入と、受け取るための代入は削除します。
代わりに、データ・バインディングで参照される「DataContext」というプロパティに、上で生成した橋渡しをするクラスのオブジェクトを代入します。
具体的には、各画面のコンストラクタに「DataContext = App.MyViewModel;」という一文を追加します。
最後に各画面の「TextBox1」にデータ・バインディングを定義します。
下の例のようになります。
「Binding」の後ろには、バインディングの相手先を指定します。
「Mode=TwoWay」は「更新を双方向で行う」という指定で、画面の入力内容が更新されても、相手先の内容が更新されても、相手側に更新を反映します。
これで、2つの画面の間でデータを受け渡すことができるように、なりました。
片方の画面のTextBoxに文字を入力してボタンをクリックすると、もう片方の画面のTextBoxに入力した文字が表示されるはずです。
通知の仕組みを追加
さて、このままでもアプリが動作するのですが、将来、他のロジックを追加するとこのアプリは動作しなくなる場合があります。
そこで、そうならないように通知という機能を追加します。
次の図で説明します。
今回のアプリでは、キーボードから「TextBox1」に入力された内容を「ViewModel」を経由して「Model」に連携させています。
青い矢印の流れですね。データ・バインディングと呼ばれる機能です。
キーボードから入力する機能しかないので、これでも動作しトラブルは起こりません。
ところが、緑の矢印のように、他のロジックで直接「Model」を変更する処理を加えた場合、このままでは画面に変更内容が反映しません。そして画面の表示内容と実際のデータが異なると様々なトラブルの原因になります。
青い矢印は一方通行で反対側への連携はできないのです。
そこで「通知」という機能、赤い矢印の流れを追加して「Model」で変更があった場合でも画面に内容が反映されるようにしておきます。
では実際に「通知」を実装して行きましょう。
まず、XAMLに「UpdateSourceTrigger=~」という記載を追加します。通知を受け取れるようにする指定です。
ここに「PropertyChanged」というイベント名を指定します。これでこの名前のイベントが発行されるとXAMLで通知を受け取り「TextBox1」の内容が画面に反映させるようになります。
次に「Model」側が、内容が変更されたときに通知を送る、つまり、「PropertyChanged」というイベント発行するようにコードを改訂します。
通知を発行する方法は、いつか書き方があります。
ここでは画面の項目が増えた場合でも対応しやすいように、「イベントの定義と発行するメソッド」そして「発行するメソッドを呼び出すコード」に分けることにします。前者を使い回しできるようにするわけです。
前者を記載するためにファイルとクラスを1つ追加します。
名前は任意でいいので、今回は「Notification.cs」というファイルに「NotificationBase」という名前で追加します。
次のように記載します。
「INotifyPropertyChanged」というインターフェイスを実装して「PropertyChanged」というイベントを追加。
イベントの名前は、XAMLの「UpdateSourceTrigger=~」で指定したイベント名と一致させます。
イベント発行するメソッドは「RaisePropetryChanged」という名前にします。
最後に「ValueObject1」を次のように変更し、「発行するメソッドを呼び出す」ようにします。
先程追記した「NotificationBase」を継承します。
そして、プロパティのアクセサを書き換えます。
アクセサの中でプロパティを呼び出すと、自己呼び出しになり正常に動作しないので、作業用に「_prop1」という変数を定義して使います。
getterは、動作は変わっていません。単にsetterにつきあって自動実装をやめただけです。
setterは、イベント発行するメソッド「RaisePropetryChanged」を呼び出すようにします。このとき引数にはプロパティ名を使いますが、nameofで代入した変数を使うと、今後のメンテナンスが楽になります。
まとめ
2つの画面の間でデータを受け渡す方法を2つ紹介しました。
オススメは「データ バインディングで受け渡す方法です」が、この方法の難点は何と何がどの機能で連動しているのかが、分かりにくい点です。
そこで、今回は「画面の間でデータを受け渡す」ことだけに焦点をあてるために、シンプルなコーディングにとどめています。
このままアプリを作成して、ある程度のコーディング量になると、メンテナンスが難いアプリになってしまうので、別途アプリの構成を整理する方法もまとめたいと思います。
コメント