ここでは、Uno Platform の開発環境の構築と実際にハロー ワールドを作成して Uno Platform の基本的な使い方について解説します。
Uno Platform の開発環境の構築
Uno Platform で開発を行うには Windows 10 version 1903 以降をお勧めします。そして Visual Studio 2019 を以下のワークロードと共にインストールします。
- ユニバーサル Windows プラットフォーム開発
- .NET によるモバイル開発
- ASP.NET と Web 開発
Visual Studio のインストーラーの画面で上記ワークロードを選択してインストールしてください。Visual Studio のインストールを既にしている人はスタートメニューから Visual Studio Installer を起動してから、Visual Studio 2019 の「変更」ボタンを選択するとワークロードを選択する画面が表示されます。
Android のエミュレーターが必要な場合は Visual Studio Installer の個別のコンポーネントのタブで以下のオプションを入れてください。
- Google Android Emulator
- Intel Hardware Accelerated Execution Manager (HAXM)
Hyper-V を有効化している場合は HAMX は使えないので以下のドキュメントの「Hyper-V による高速化」を参考にエミュレーターの設定をしてください。
エミュレーターのパフォーマンスのためのハードウェア高速化 (Hyper-V と HAXM): http://aka.ms/unobook-androidemulator
iOS 開発を行う場合は別途 Xcode と Visual Studio 2019 for Mac を macOS にインストールして、Windows の Visual Studio からのペアリングを行う必要があります。詳細な手順については以下のドキュメントを参照してください。
Windows に Xamarin.iOS をインストールする: http://aka.ms/unobook-installios
最後に、Uno Platform 用の拡張機能を Visual Studio にインストールします。
Visual Studio のメニューの「拡張機能」→「拡張機能の管理」でオンラインの項目から Uno Platform を検索します。そして Uno Platform Solution Templates を選択してダウンロードを選択します。
ダウンロードが完了したら Visual Studio を再起動してインストールをしてください。
最後に Windows 10 を開発者モードにします。Windows の設定を開いて「更新とセキュリティ」→「開発者向け」を選択して以下のように「開発者モード」を選択します。
以上で、Uno Platform の開発環境の構築は完了です。
ハロー ワールド
ここでは、ハロー ワールドの作成を通じて Uno Platform の開発の基本的な流れを解説します。
プロジェクトの新規作成
Visual Studio 2019 を起動して「新しいプロジェクトの作成」を選択します。検索ボックスに「Uno」と入力して「Cross-Platform App (Uno Platform)」を選択して「次へ」を選択します。
プロジェクト名に「HelloUnoPlatform」と入力して、場所になるべく短いパスのフォルダーを指定します。
パスが長いとビルド中に生成されるファイルのパスの長さが Windows でデフォルトで許容されている長さを超えてエラーになることがあります。
プロジェクト名と場所を設定したら「作成」ボタンを選択します。
作成が終わったらソリューションで右クリックをして「ソリューションの NuGet パッケージの管理」を選択してください。そして、Microsoft.Extensions.Logging.Console
パッケージ以外を最新に更新してください。
プロジェクトの構成
ここから少しプロジェクトの構造を見ていきます。
プロジェクトを新規作成をすると、ソリューションエクスプローラーに以下のような 5 つのプロジェクトが作成されます。
一つ一つ解説していきます。
HelloUnoPlatform.Droid プロジェクト
Android 用のビルドを行うプロジェクトです。
Xamarin の Android ネイティブ アプリ向けのプロジェクトに Uno のライブラリーへの参照と、後述する共有プロジェクトへの参照を含んでいます。
このプロジェクトをスタートアップ プロジェクトにして実行することで Android 向けのアプリの実行が出来ます。
Android 固有の API を使用するコードは、ここに記載します。
==== HelloUnoPlatform.iOS プロジェクト
iOS 用のビルドを行うプロジェクトです。
Xamarin の iOS ネイティブ アプリ向けのプロジェクトに Uno のライブラリーへの参照と、後述する共有プロジェクトへの参照を含んでいます。
iOS 固有の API を使用するコードは、ここに記載します。
このプロジェクトをビルドしたり実行するためには、Visual Studio のメニューの「ツール」→「iOS」→「Mac とペアリング」を選択して Visual Studio 2019 for Mac がインストールされた Mac との接続が必要です。
HelloUnoPlatform.Shared プロジェクト
iOS/Android/UWP/WebAssembly 用の各種プロジェクトから参照される共有プロジェクトです。
プラットフォーム間で共有する全てのコードがこの中に入っています。
このプロジェクトに含まれるソースコードは以下のようなものになります。
- App.xaml & App.xaml.cs
Uno Platform アプリケーションのエントリーポイント的な役割のクラス。このクラスが実際の各ネイティブ用のプロジェクトから呼び出されてアプリケーションが起動します。 - MainPage.xaml & MainPage.xaml.cs
アプリケーションの実行時に最初に表示される画面 - Assets フォルダー
画像などのアセットファイルを入れるフォルダー - Strings フォルダー
各種文字列リソースを入れるフォルダー
HelloUnoPlatform.UWP プロジェクト
UWP 用のビルドを行うプロジェクトです。
Uno Platform は UWP に対する互換レイヤーを提供するフレームワークなので、ここには互換レイヤーは含まれません。
参照設定がされている共有プロジェクトに含まれているソースコードが、普通に UWP アプリとしてビルドされます。
UWP 固有の API を使用するコードは、ここに記載します。
HelloUnoPlatform.Wasm プロジェクト
WebAssembly 用のビルドを行うプロジェクトです。
共有コードへの参照が含まれています。
WebAssembly 向けの固有の API を使用するコードはここに記載します。
ビルドして実行
実行したいプラットフォームのプロジェクトの右クリックメニューから「スタートアップ プロジェクトに設定」をして画面上部の再生ボタンを押すとデバッグ実行されます。(ショートカットは F5 キーです。)
デバッガーのアタッチが必要ない場合は、メニューの「デバッグ」→「デバッグなしで開始」を選択してください。
Android プロジェクトの実行
Android プロジェクトの実行は、開発者モードにして USB デバッグ機能をオンにした Android デバイスを Windows に接続するか、エミュレーターで実行可能です。
エミュレーターはメニューの「ツール」→「Android」→「Android Device Manager」で管理。
Android Device Manager の「新規」ボタンからエミュレーターの作成が可能です。
エミュレーターの作成の詳細ついては以下の「Android Device Manager による仮想デバイスの管理」を参照してください。
エミュレーターを作成すると、Visual Studio のツールバーにあるデバイスを選択する場所にエミュレーターの名前が表示されるようになります。
実機の場合は、実機を Windows に接続するとデバイスを選択する場所にデバイス名が表示されます。
実行したいエミュレーターを選択して実行すると、エミュレーターが起動してアプリケーションが実行されます。
iOS プロジェクトの実行
iOS プロジェクトの実行は Mac とペアリングが必要です。また、実機デバッグは Apple Developer Program に参加していない場合には以下の「Xamarin.iOS アプリの無料プロビジョニング」に記載の手順が必要です。
iOS のシミュレーターでの実行は、以下のように実行したいデバイスと OS のバージョンを選択することで Windows 上で実行が可能です。
ビルドとシミュレーターの実行自体は macOS 上で行われていますが画面操作は以下のようなウィンドウが出てきて Windows 上で可能です。
UWP プロジェクトの実行
UWP プロジェクトは、ターゲットプラットフォームに x86 か x64 を選択すると実行できます。Any CPU では実行出来ないので注意してください。
デバッグ実行を行うとローカル コンピューター上にアプリケーションがインストールされてアプリケーションが実行されます。
上記のようにローカルコンピューターで実行するように設定して実行すると、以下のようにアプリケーションがローカルで起動します。
WebAssembly プロジェクトの実行
WebAssembly プロジェクトは、実行すると Web ブラウザーが起動します。デバッグは Chrome で行う手順がドキュメントに記載されていますが私の環境上ではアプリからデバッガーの Proxy に WebSocket で繋ぎに行く箇所で全く別の IP アドレスに繋ぎに行くためデバッグが出来ませんでした。
WebAssembly のプロジェクトのデバッグについての手順は以下の公式ドキュメントを参照してください。
Using the WebAssembly C# Debugger
また、現在プレビューバージョンの Uno Platform 2.2 を使うと Visual Studio から WebAssembly のデバッグが出来るような機能が開発されています。詳細は以下のブログ記事にまとまっています。
Debug an Uno Platform WebAssembly Web app with Visual Studio
実際に確認してみたところ、ボタンクリックイベントハンドラーにブレークポイントをしかけてブレークポイントに止まることが確認できました。
デザイナーの表示
プロジェクトを作成した直後は MainPage.xaml を開いてもデザイナーが表示されないことがあります。
デザイナーが表示されていない場合は、MainPage.xaml の右クリックメニューから「ファイルを開くアプリケーションの選択」から「XAML デザイナー」を選択します。デザイナーで開けないという内容のメッセージが出た場合は、プロジェクトをビルドして Visual Studio を再起動してください。
XAML デザイナーで開くと左上にドロップダウンがあってプロジェクトを選択できます。ドロップダウンで UWP を選択してください。
そして、MainPage.xaml を開きなおす(ダメな場合はビルドをしてプロジェクトを開きなおす)ことでデザイナーが表示されます。
ハローワールドを変更してみよう
ここでは、新規作成されたプロジェクト(新規作成した時点でハローワールドとしては完成していますが)に少し手を入れて実際に動作を確認してみたいと思います。
ここで画面定義に使用する XAML については後程詳しく解説するので、まずは雰囲気をつかんでください。
最初の目標としては、モバイル画面と UWP や WebAssembly のような大きな画面で画面レイアウトを変えるようにしたいと思います。
画面の大きさによって画面レイアウトを変える機能の実装
共有プロジェクト(今回の場合は HelloUnoPlatform.Shared)の中にある MainPage.xaml を開きます。
まずは、画面サイズでのレイアウト変更機能は追加しない状態のものを作成します。XAML を以下のように編集します。
<Page
x:Class="HelloUnoPlatform.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HelloUnoPlatform"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Name="textBlockHelloWorld" Text="Hello, world!" Margin="20" FontSize="30" />
<Button x:Name="buttonAlert" Content="Click me" Click="ButtonAlert_Click"
FontSize="30"
Margin="20"
RelativePanel.RightOf="textBlockHelloWorld"/>
</RelativePanel>
</Page>
ここで使用している RelativePanel は、コントロール間の相対位置で配置を行うパネルです。
例えば、上記のコードでは Button
に {RelativePanel.RightOf="textBlockHelloWorld"}
という属性がついています。これは textBlockHelloWorld
の右に Button
を配置するという指定をしています。
RightOf
以外にも以下のプロパティがあります。
- LeftOf: 指定したコントロールの左に配置します
- Above: 指定したコントロールの上に配置します
- Below: 指定したコントロールの下に配置します
- AlignTopWith: 指定したコントロールの上の端に合わせます
- AlignRightWith: 指定したコントロールの右の端に合わせます
- AlignLeftWith: 指定したコントロールの左の端に合わせます
- AlignBottomWith: 指定したコントロールの下の端に合わせます
- AlignHorizontalCenterWith : 水平方向の真ん中に合わせます
- AlignVerticalCenterWith : 垂直方向の真ん中に合わせます
- AlignTopWithPanel: パネルの上の端に置きます(True か False で指定)
- AlignRightWithPanel: パネルの右端に置きます(True か False で指定)
- AlignLeftWithPanel: パネルの左端に置きます(True か False で指定)
- AlignBottomWithPanel: パネルの下の端に置きます(True か False で指定)
- AlignHorizontalCenterWithPanel: パネルの水平方向の中央に合わせます(True か False で指定)
- AlignVerticalCenterWithPanel: パネルの垂直方向の中央に合わせます(True か False で指定)
次に、先ほど追加したボタンのクリック時の処理を書くために MainPage.xaml.cs に ButtonAlert_Click メソッドを以下のように追加します。
using System;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace HelloUnoPlatform
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private async void ButtonAlert_Click(object sender, RoutedEventArgs e)
{
var dlg = new MessageDialog("Hello world");
await dlg.ShowAsync();
}
}
}
この時点で UWP で実行してボタンをクリックすると以下のような結果になります。
Android で実行すると以下のようになります。
特にはみ出たりはしてませんが、もう少しボタンを下に置いたりテキストをセンターに置いたりして、モバイルでも使いやすそうな配置にしてみたいと思います。Page タグの中の RelativePalen の中に以下のタグを追加します。
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="641" />
</VisualState.StateTriggers>
</VisualState>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<!-- テキストのレイアウト変更 -->
<Setter Target="textBlockHelloWorld.(RelativePanel.AlignTopWithPanel)" Value="True" />
<Setter Target="textBlockHelloWorld.(RelativePanel.AlignRightWithPanel)" Value="True" />
<Setter Target="textBlockHelloWorld.(RelativePanel.AlignLeftWithPanel)" Value="True" />
<Setter Target="textBlockHelloWorld.HorizontalAlignment" Value="Center" />
<!-- ボタンのレイアウト変更 -->
<Setter Target="buttonAlert.(RelativePanel.AlignBottomWithPanel)" Value="True" />
<Setter Target="buttonAlert.(RelativePanel.AlignRightWithPanel)" Value="True" />
<Setter Target="buttonAlert.(RelativePanel.AlignLeftWithPanel)" Value="True" />
<Setter Target="buttonAlert.HorizontalAlignment" Value="Stretch" />
<Setter Target="buttonAlert.(RelativePanel.RightOf)" Value="" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
XAML 全体は以下のようになります。
<Page
x:Class="HelloUnoPlatform.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HelloUnoPlatform"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
... 省略 ...
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="textBlockHelloWorld" Text="Hello, world!" Margin="20" FontSize="30" />
<Button x:Name="buttonAlert" Content="Click me" Click="ButtonAlert_Click"
FontSize="30"
Margin="20"
RelativePanel.RightOf="textBlockHelloWorld"/>
</RelativePanel>
</Page>
VisualStateManager という機能を使って画面のコントロールのプロパティの設定を条件によって変えることが出来ます。
その機能を使って RelativePanel のレイアウト系の設定を変更してボタンとテキストのレイアウトを変更しています。
AdaptiveTrigger を使うことで画面幅に応じて VisualState を切り替えることが出来ます。上の例では 641 pixel 以上の時は、そのままの値を使用して 0 pixel 以上の時は Setter タグを使って各種プロパティを変更しています。
UWP と Android で実行して見てみます。
画面が狭い Android ではレイアウトが変わっていることが確認できます。もちろん UWP でも画面サイズを小さくすることで Android のような画面の小さなプラットフォームで実行した時と同じレイアウトにすることが出来ます。
WebAssembly のプロジェクトで実行してみると UWP と同じように動くことが確認できます。
この機能を使うことで、モバイル端末などの小さな画面とデスクトップやタブレットのような大きな画面のレイアウトを 1 つの XAML で表現可能です。
セーフエリアへの対応
最後に、iPhone X 以降多くなってきた画面が矩形でないデバイスに対応します。
現時点で iPhone 11 などのノッチがある環境でアプリケーションを実行すると以下のように画面上部などがノッチなどに隠れてしまいます。
Uno Platform にはセーフエリアに対応するための機能が提供されています。Uno.UI の NuGet パッケージで提供されているので UWP プラットフォームのプロジェクトには Uno.UI の NuGet パッケージを追加します。UWP のプロジェクトの右クリックメニューから「NuGet パッケージの管理」を選択して Uno.UI パッケージをインストールします。
これで全てのプロジェクトで Uno.UI.Toolkit 名前空間の VisibleBoundsPadding クラスが使えるようになりました。
このクラスの PaddingMask プロパティをコントロールに設定することでセーフエリア内に収まるように Pading を自動的に設定してくれます。
設定可能な値は以下の値になります。
- All: 上下左右全ての Padding を設定します
- Top: 上の Padding を設定します
- Left: 左の Padding を設定します
- Right: 右の Padding を設定します
- Bottom: 下の Padding を設定します
ただ、残念ながら現在最新の Uno Platform 2.1.37 では RelativePanel にこのプロパティを設定しても有効化されないため、Grid と呼ばれるパネルで RelativePanel をラップして、そこに設定してみます。
<Page
x:Class="HelloUnoPlatform.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HelloUnoPlatform"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ios="http://uno.ui/ios"
xmlns:toolkit="using:Uno.UI.Toolkit"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d ios">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
toolkit:VisibleBoundsPadding.PaddingMask="All">
<RelativePanel>
<!-- 省略 -->
</RelativePanel>
</Grid>
</Page>
これで Grid の上下左右にセーフエリアに対応した余白が追加されます。iOS のエミュレーターで実行すると以下のようになります。
これで、広い画面と狭い画面(モバイルや UWP/WebAssembly で画面幅を狭めたとき)の両対応の画面に対応して、さらに iPhone などのセーフエリアに対応することが出来ました。
Uno Platform で使えるコントロール
Uno Platform で使えるコントロールは UWP のコントロールになります。
UWP のコントロールのドキュメントと Uno Platform で実装されているコントロールの一覧があるので、この 2 つを見ることで使い方が確認できます。
例えば、Uno Platform のドキュメントから CommandBar を選択すると、そのコントロールの Uno Platform での実装が確認できます。
特に各コントロールの Not implemented properties などの項目は実装されていない機能が書かれているので気を付けてください。
特に重要な UWP のドキュメント
Uno Platform での開発は、UWP アプリの開発を他の OS やプラットフォームでも動くようにするものになります。
そのため UWP の機能の理解が大切になります。ここでは、UWP のドキュメントで特に重要なものについて紹介します。
UWP のドキュメントのトップページ:https://docs.microsoft.com/ja-jp/windows/uwp/
ドキュメントのトップページの左にドキュメントの目次のツリーがあります。
この中で個人的に重要だと思うものは:
- 設計と UI > レイアウト > パネル の下のドキュメント
- 設計と UI > レイアウト > XAML を使ったレイアウト のドキュメント
- 設計と UI > コントロール の下のドキュメント
- アプリを開発する > データバインディング の下のドキュメント
の 4 つになります。最初のドキュメントは思った通りにコントロールをレイアウトするためのレイアウトパネル関係になります。2 つ目は、先ほど行った画面サイズに応じてレイアウトを変える方法が書かれています。
3 つ目が各種コントロールのドキュメントになります。最後のデータバインディングは重要なので、ここでも簡単に取り上げようと思います。
データバインディング
最近は、ほぼすべての UI を開発するプラットフォームで何らかのデータバインディングの仕組みが提供されています。
UWP も例外ではありません。UWP のデータバインディングは、画面のコントロールのプロパティと、任意のオブジェクトのプロパティの値の同期を取るために使います。(厳密には画面のコントロールでなくても条件を満たせばデータバインディングが使えますがレアケースなので、ここでは説明しません)
実行時データバインディング
Binding というマークアップ拡張でデータバインディングを指定します。この時コントロールにバインドするデータのソースは DataContext というプロパティに設定されているオブジェクトになります。DataContext は、ほぼ全てのコントロールが持っているプロパティで、DataContext の値はコントロールの親から子へ引き継がれていきます。
つまり、Page の DataContext に何かクラスを代入すると、その下に置いている Button や TextBlock などのコントロールの DataContext も Page で設定されたものと同じになります。
実際にやってみましょう。以下のような MainPageViewModel という名前のクラスを共有プロジェクトに追加します。
namespace HelloUnoPlatform
{
public class MainPageViewModel
{
public string Message { get; set; } = "Hello from MainPageViewModel";
}
}
そして、MainPage.xaml を以下のように編集して Message プロパティを TextBlock の Text プロパティに紐づけます。
<Page
x:Class="HelloUnoPlatform.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HelloUnoPlatform"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.DataContext>
<!-- Binding のソースを設定 -->
<local:MainPageViewModel />
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<!-- MainPageViewModel クラスの Message プロパティを TextBlock の Text と紐づける -->
<TextBlock
FontSize="32"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Message}" />
</Grid>
</Page>
このコードを実行すると、MainPageViewModel で定義している Message プロパティの値が画面に表示されます。{Binding Message}
の Message の部分が Path といって Binding のソースのプロパティ名を記載します。Path は Hoge.Foo.Bar
のように書いて Hoge プロパティの Foo プロパティの Bar プロパティの値のようにネストした形のオブジェクトのプロパティを指定することもできます。
データバインディングは、ソースからコントロールへの値の伝搬以外にも、コントロールで入力された値をソースに渡すことも出来ます。
この値の伝搬の方向を明示的に指定することができます。Binding マークアップ拡張に Mode というプロパティがあります。この Mode に以下の値を設定することで同期の方向を制御できます。
- OneTime: 一度だけソースからコントロールに値を同期する
- OneWay: ソースの値をコントロールに同期する
- TwoWay: ソースとコントロールの値を双方向に同期する
例えば TextBox の入力値をソースに渡したい場合は TwoWay を指定します。先ほどの XAML を少し書き換えて TextBox を追加してバインドします。
<Page
x:Class="HelloUnoPlatform.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HelloUnoPlatform"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.DataContext>
<!-- Binding のソースを設定 -->
<local:MainPageViewModel />
</Page.DataContext>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<!-- MainPageViewModel クラスの Message プロパティと TextBox の Text を双方向でバインドする -->
<TextBox Text="{Binding Message, Mode=TwoWay}" />
<!-- MainPageViewModel クラスの Message プロパティを TextBlock の Text と紐づける -->
<TextBlock
FontSize="32"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Message}" />
</StackPanel>
</Page>
この状態で実行すると初期状態で TextBox と TextBlock に Hello from MainPageViewModel が表示されます。
どちらも Message プロパティにバインドされているので TextBox の値を変えると TextBlock の値も変わってほしいのですが、現時点では変わりません。
これは TextBlock コントロールが MainPageViewModel クラスの値の変更を知る方法が無いからです。ソースの値が変わったときには、ソースの値が変わったということをコントロールに伝える仕組みが必要です。
そのための仕組みとして System.ComponentModel.INotifyPropertyChanged インターフェースがあります。このインターフェースを MainPageViewModel に実装すると TextBox の値がソースに伝えられた時に TextBlock の値も変わるようになります。
実際に INotifyPropertyChanged インターフェースを実装すると以下のようになります。
public class MainPageViewModel : INotifyPropertyChanged // インターフェースを実装
{
// プロパティに変更があったときに発行されるイベント
public event PropertyChangedEventHandler PropertyChanged;
private string _message = "Hello from MainPageViewModel";
public string Message
{
get => _message;
set
{
_message = value;
// プロパティの値が変わったことを PropertyChanged イベントで外部に通知
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Message)));
}
}
}
実際に実行して TextBox の値を書き換えて TextBox からフォーカスを外すと値が同期されます。
デフォルトでは、TextBox はフォーカスが外れたときに値の同期を行いますが、Binding マークアップ拡張の UpdateSourceTrigger プロパティに PropertyChanged を指定することで入力された値が変わったタイミングでリアルタイムに値が同期されるようになります。この設定をした TextBox の定義は <TextBox Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
のようになります。
最後にコンバーターについて説明します。コンバーターは Binding マークアップ拡張の Converter プロパティに指定するもので、名前のとおりバインドする値に変換処理を入れることが出来ます。
実際に TextBox に入力した値を大文字に変換するコンバーターを作ってみます。コンバーターは Windows.Ui.Xaml.Data.IValueConverter インターフェースを実装して作成します。
Convert でソースからコントロールでの変換処理、ConvertBack でコントロールからソースでの変換処理を実装します。
今回は、ソースからコントロールで大文字変換を行いたいので Convert メソッドで ToUpperInvariant を呼んで大文字に変換する処理を書きます。
using System;
using Windows.UI.Xaml.Data;
namespace HelloUnoPlatform
{
public class ToUpperConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language) =>
(value as string)?.ToUpperInvariant();
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
}
Converter は一般的に Page や App クラスの Resources で定義して StaticResource マークアップ拡張で Binding の Converter に設定します。今回は Page 内の Resources に定義します。コードを以下に示します。
<Page
x:Class="HelloUnoPlatform.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HelloUnoPlatform"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<!-- コンバーターのインスタンスを定義 -->
<local:ToUpperConverter x:Key="ToUpperConverter" />
</Page.Resources>
<Page.DataContext>
<local:MainPageViewModel />
</Page.DataContext>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBox Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<!-- 大文字変換のコンバーターを設定 -->
<TextBlock
FontSize="32"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Message, Converter={StaticResource ToUpperConverter}}" />
</StackPanel>
</Page>
実行すると以下のようになります。TextBlock に表示されている値が大文字になっています。
コンバーターは、以下のようなケースで利用されることが多い機能となります。
- ソースの値に応じてテキストの色を変えたい場合には ViewModel の値を色に変換するコンバーターを作ってコントロールの Foreground や Background プロパティとバインド
- ソースの値に応じてコントロールの表示・非表示を変えたい場合には ViewModel の値を Visibility 型に変換して、コントロールの Visibility プロパティとバインド
この他にも、画面のコントロール同士の値のバインドなども出来るのでデータバインディングの UWP のドキュメントを確認してみてください。
INotifyPropertyChanged プロパティの実装の省力化
INotifyPropertyChanged の実装を簡単にする方法として INotifyPropertyChanged インターフェースを実装した基本クラスを定義する方法があります。
これらの基本クラスは Prism や MVVM Light Toolkit などの多くのライブラリーで提供されています。例えば Prism で提供されている BindableBase クラスを継承するとプロパティの定義は以下のようになります。
private string _message;
public string Message { get => _message; set => SetProperty(ref _message, value); }
コンパイル時データバインディング
こちらは x:Bind というマークアップ拡張で指定するデータバインディングになります。Uno Platform では 2.1 以降でサポートされています。
x:Bind と Binding の主な違いは以下になります。
- Mode のデフォルトが OneTime (Binding は OneWay)
- Path の中で関数呼び出しが出来る
- ソースは Page クラス(Binding は DataContext の値がソース)
それでは、Binding と同じ動きをする TextBox に入力した文字を大文字にして TextBlock に表示するプログラムを x:Bind で作ります。
TextBox の入力を MainPageViewModel の Message プロパティに設定する部分は Binding を使います。そして x:Bind で大文字に変換して TextBlock に表示させます。
x:Bind で使用するために Page の DataContext を MainPageViewModel 型として返す ViewModel プロパティを定義します。
public sealed partial class MainPage : Page
{
private MainPageViewModel ViewModel => DataContext as MainPageViewModel
public MainPage()
{
this.InitializeComponent();
}
}
そして、XAML で x:Bind を使ってバインディングします。
<Page
x:Class="XBindApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:XBindApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.DataContext>
<local:MainPageViewModel />
</Page.DataContext>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBox Text="{x:Bind ViewModel.Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<!-- ToUpper 関数を直接呼び出して大文字に変換 -->
<TextBlock
FontSize="32"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{x:Bind ViewModel.Message.ToUpper(), Mode=OneWay}" />
</StackPanel>
</Page>
x:Bind の中で ToUpper 関数を直接呼び出しているのでコンバーターが不要になりすっきりしました。実行すると以下にようになります。
画面遷移
Uno Platform のアプリケーションはページ内の Frame プロパティに対して Navigate メソッドを呼ぶと画面遷移できます。
Navigate メソッドは、遷移先のページの型を typeof(XxxxPage)
のようにして指定します。第二引数に画面遷移のパラメーターを渡せます。
例えば MainPage と NextPage というページがあり MainPage から NextPage にパラメーターとして "1"
を渡すコードは以下のようになります。
Frame.Navigate(typeof(NextPage), "1");
Frame プロパティはページをホストしている Frame を返します。App.xaml.cs の OnLaunched メソッドを見ると Frame クラスのインスタンスが作成されて、現在の Window のコンテンツとしてセットされているコードがあります。プロジェクトテンプレートを使って作られたプロジェクトは、このように Frame の中で画面遷移するようになっています。そのため、基本的に全てのページで上記のようなコードで画面遷移が出来ます。
ページクラスで画面遷移の途中で呼ばれるメソッドがあります。以下の 3 つになります。
- OnNavigatedTo: 別のページから遷移してきたときに呼ばれる。
- OnNavigatedFrom: 別のページに遷移したときに呼ばれる。
- OnNavigationgFrom: 別のページに遷移する前に呼ばれる。画面遷移のキャンセルが出来る。
各メソッドの引数には以下のプロパティが定義されています。OnNavigatedTo と OnNavigatedFrom メソッドの引数には NaviationEventArgs 型のインスタンスが渡されます。OnNavigatingFrom メソッドには NavigationCancelEventArgs 型のインスタンスが渡されます。
どちらの型も以下のプロパティを持っています。
- Parameter: 画面遷移のパラメーター
- NavigationMode: NavigationMode の列挙型の値。New、Back、Forward、Refreshのいずれかの値。
- Conetnt: ターゲットページのルートコンテンツ
= SourcePageType: ナビゲーションのソースのページの型
NavigationCancelEventArgs 型には追加で以下のプロパティがあります。
- Cancel: 画面遷移をキャンセルするかを決める。true にすると、画面遷移をキャンセルする。
まとめ
ここでは Uno Platform の環境構築とプロジェクトの新規作成から各種プラットフォームでの実行方法と、プロジェクトに少し手を入れて色々なサイズの画面に対応する方法について説明しました。
さらに、後半では画面をデザインするときに使用する XAML という言語と基本的な機能やバインディング、画面遷移などについて解説しました。
次の章では、もう少し応用的な処理について説明します。