Open4

[.NET MAUI] サブ画面のモーダル表示

GomitaGomita

メイン画面(MainPage)からサブ画面をモーダル表示(メイン画面の上にサブ画面を重ねて表示)したい。
そんなときにMAUIではNavigation.PushModalAsyncが使える。ページ遷移に関するまとめは公式サイトのここ

サブ画面のビューをAnswerPage.xamlとすると、これだけモーダル表示で開くことができる。

await Navigation.PushModalAsync(new AnswerPage());

ところが、デフォルトの動作では、少なくともiOSでは下からスライドするように白い背景でサブ画面が現れるので、この動作は必ずしも良いとは思わない。PushModalAsyncのオーバーライドで第2引数animatedfalseにすれば、アニメーションを無効化できる。また、サブ画面のビューContentPageBackgroundColor="#aa000000"を設定することで、半透明の背景でサブ画面を重ねて表示できる。
このあたりは、以下のredth.codesの記事が参考になった。
https://redth.codes/popups-with-net-maui-no-plugin-nuget-needed

GomitaGomita

サブ画面の追加(読み込み中表示)

まずは [プロジェクト] > [新しい項目の追加] > [.NET MAUI ContentPage (XAML)] でAnswerPageを追加する。
AnswerPage.xamlは#aa000000で背景を半透明にして、メイン画面と大きさを合わせたGridをの中にActivityIndicatorを配置する。これは読み込み中を表すくるくるアイコンで、OSに依存した見た目となる。

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             BackgroundColor="#aa000000"
             x:Class="RentaProto.AnswerPage"
             Title="AnswerPage">
    <Grid RowSpacing="0" ColumnSpacing="0"
          AbsoluteLayout.LayoutFlags="PositionProportional,HeightProportional"
          AbsoluteLayout.LayoutBounds="0.5,0,375,1">
        <!-- ▼Loading表示 -->
        <ActivityIndicator x:Name="LoadingIcon"
                           IsRunning="True" Color="White" Scale="2" />
</ContentPage>

メイン画面側はこのような感じ。

MainPage.xaml
+                        <Button Text="検索" FontSize="20" TextColor="Black"
...
+                                Clicked="SubmitButton_Clicked" />
MainPage.xaml.cs
+	private async void SubmitButton_Clicked(Object sender, EventArgs e)
+	{
+		await Navigation.PushModalAsync(new AnswerPage(), false);
+	}

これで、検索ボタンをタップすると、半透明のモーダル表示でサブ画面が開き、くるくるアイコンが表示されるようになった!

GomitaGomita

サブ画面(読み込み完了)

読み込み完了と同時にくるくるアイコンを消してサーバのレスポンスに応じた内容を表示したい。今回は実際にサーバとの通信はせずに1秒のウェイト後に固定の内容を表示する。

AnswerPage.xamlの一番上の階層のGrid内に固定の内容を追加する。下記コードは大幅に省略しているが、「×」の形の画像ファイルで閉じるボタンも配置する。画像ファイルはResources\Imagesフォルダに入れること。

AnswerPage.xaml
    <Grid RowSpacing="0" ColumnSpacing="0"
          AbsoluteLayout.LayoutFlags="PositionProportional,HeightProportional"
          AbsoluteLayout.LayoutBounds="0.5,0,375,1">
        <!-- ▼Loading表示 -->
        <ActivityIndicator x:Name="LoadingIcon"
                           IsRunning="True" Color="White" Scale="2" />
+        <!-- ▼回答画面 -->
+        <Grid x:Name="AnswerContent" IsVisible="False"
+              Margin="30,100,30,30" Padding="14" WidthRequest="315"
+              Background="#F2F2F2">
...
+                    <Image Source="x_button.png" HorizontalOptions="End"
+                           WidthRequest="40" HeightRequest="40">
+                        <Image.GestureRecognizers>
+                            <TapGestureRecognizer NumberOfTapsRequired="1" Tapped="XButton_Tapped" />
+                        </Image.GestureRecognizers>
+                    </Image>
...

コードビハインド側は下記の通り。モーダル表示のサブ画面を閉じるのはNavigation.PopModalAsyncで。こちらも引数animatedfalseにする。

AnswerPage.xaml.cs
	public AnswerPage()
	{
		InitializeComponent();
+		this.Loaded += AnswerPage_Loaded;
	}

	private async void AnswerPage_Loaded(object sender, EventArgs e)
	{
+		this.Loaded -= AnswerPage_Loaded;
+		await Task.Delay(1000);
+		LoadingIcon.IsRunning = false;
+		AnswerContent.IsVisible = true;
	}

+	private async void XButton_Tapped(object sender, EventArgs e)
+	{
+		await Navigation.PopModalAsync(false);
+	}

結果はこのような感じ!

GomitaGomita

BindingContextによるメイン画面とサブ画面の値受け渡し

メイン画面に入力した値をサブ画面に引き渡して表示してみる。ページナビゲーションにおけるデータ受け渡し方法はこちら。今回はBindingContextを使ってみる。

若干やっつけだが、受け渡し用のRequestParamsクラスを作っておく。

MainPage.xaml.cs
+public class RequestParams
+{
+	public string carType { get; set; }
+	public string regionFrom { get; set; }
+	public string regionTo { get; set; }
+	public string gear { get; set; }
+	public string depMonth { get; set; }
+	public string depDay { get; set; }
+}

受け渡し方は以下の通り。

MainPage.xaml.cs
- 		await Navigation.PushModalAsync(new AnswerPage(), false);
+		var reqParams = new RequestParams
+		{
+			carType = CarTypeInput.InputText,
+			regionFrom = RegionFromInput.InputText,
+			regionTo = RegionToInput.InputText,
+			gear = GearSlide.GetCurrentText(),
+			depMonth = MonthEntry.Text,
+			depDay   = DayEntry.Text
+		};
+		await Navigation.PushModalAsync(new AnswerPage
+		{
+			BindingContext = reqParams
+		}, false);

受け取り側のXAMLはBindingから値を取り出してStringFormatにより書式を加工する。2つの値を取り出す場合はMultiBindingを使う。

AnswerPage.xaml
+                <Line Stroke="DarkBlue" StrokeDashArray="3,3" X2="300" />
+                <Label Text="<入力内容>" Margin="0,10" />
+                <Label Text="{Binding carType, StringFormat='車種 :{0}'}" />
+                <Label Text="{Binding regionFrom, StringFormat='乗車地:{0}'}" />
+                <Label Text="{Binding regionTo, StringFormat='降車地:{0}'}" />
+                <Label Text="{Binding gear, StringFormat='ギア :{0}'}" />
+                <Label>
+                    <Label.Text>
+                        <MultiBinding StringFormat="出発日:{0}月{1}日">
+                            <Binding Path="depMonth" />
+                            <Binding Path="depDay" />
+                        </MultiBinding>
+                    </Label.Text>
+                </Label>