ラーメンタイマーアプリ
今度は、テンプレートのページの変更ではなく、新しいページを追加してラーメンタイマーアプリを作ります。今回のアプリでは、初期状態では、3分経過するとメッセージを表示します。また、設定する時間は、変更できるようにしました。
開始ボタンをクリックすると、表示されている残り時間が減っていきます。そして残り時間が0になると、メッセージが表示されます。
新しいページの追加
最初に、あらためて新規プロジェクトを作成しましょう。ここでは、RamenTimerという名前のプロジェクトにしました。前回解説したように、テンプレートのアプリでは、Shellコントロールの中にMainPageビューを定義しています。ただ1画面だけの単純なアプリでは、もっとシンプルな構成も可能です。
新しい空のページを追加して、そのページを直接表示する構成に変更してみます。Visual Studioのメインメニューから、[プロジェクト]ー[新しい項目の追加]を選択します。次のようなダイアログが表示されるので、左欄から、.NET MAUIを選択します。そして、表示された一覧から、ContentPage(XAML)を選びます。ここでは名前を、TimerPage.xamlとしました。
追加ボタンをクリックすると、プロジェクトにTimerPage.xamlとTimerPage.xaml.csが追加され、TimerPage.xamlの内容が表示されます。(もしx:Class属性が、プロジェクト名.TimerPageになっていない場合は、次のように修正し、TimerPage.xaml.csの1行目にあるnamespace定義も、namespace RamenTimer;に修正してください)
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="RamenTimer.TimerPage" Title="TimerPage"> <StackLayout> <Label Text="Welcome to .NET MAUI!" VerticalOptions="Center" HorizontalOptions="Center" /> </StackLayout> </ContentPage>
次に、追加したページを表示するように変更します。App.xaml.csのAppクラスのコンストラクターで、MainPageプロパティの設定をTimerPageに変更します。
public App() { InitializeComponent(); // MainPage = new AppShell(); MainPage = new TimerPage(); }
ここで一旦、アプリをビルドして確認してみましょう。次のような画面が表示されるはずです。
コントールの配置
それでは、タイマーアプリのパーツをページに配置していきましょう。追加したページには、すでに<StackLayout>が定義されていますので、このレイアウトを利用します。StackLayoutのLabelコントロールを変更し、さらにProgressBarコントロールを追加します。
<StackLayout Spacing="10" VerticalOptions="Center"> <Label Text="00:00" FontSize="36" FontAttributes="Bold" HorizontalTextAlignment="Center" x:Name="MinutesLabel"/> <ProgressBar x:Name="PrgBar" Progress="0" ProgressColor="Orange" /> </StackLayout>
StackLayoutのVerticalOptionsプロパティは、垂直方向の位置を設定します。Labelコントロールの、HorizontalTextAlignmentは、水平方向の位置になります。いずれも中央に設定しています。Spacingプロパティは、子コントロール間のスペースです。
プロパティについては、Visual StudioのIntelliSense機能によって、候補が表示されるので、そこから選択することで入力できます。細かいスペルまでは覚える必要はありません。また、ホットリロード機能のおかげで、アプリを起動したままXAMLを編集して、表示具合を確認することができます。
ボタンの配置
次は、タイマー開始(停止)のボタンと、設定値を変更するためのボタンを追加してみましょう。今回は、3つのボタンを横にならべるレイアウトにしました。次のように、StackLayoutの中に、ボタン用のStackLayoutを定義します。
<StackLayout ~略~ <StackLayout Orientation="Horizontal" HorizontalOptions="Center" Spacing="10" > <Button WidthRequest="100" x:Name="M1Button" Clicked="OnMinusButton" Text="-1分" /> <Button WidthRequest="100" x:Name="StartButton" Clicked="OnStartButton" Text="開始" /> <Button WidthRequest="100" x:Name="P1Button" Clicked="OnPlusButton" Text="+1分" /> </StackLayout> ~略~
真ん中のボタンがタイマーを開始するボタンになります。その左右に、設定時間を1分単位で増減するボタンを配置しています。なお、ボタンのWidthRequestプロパティは、ボタンの横幅を設定するプロパティです。
ソースコードの追加
画面の編集が完了したら、次はタイマーの処理を追加しましょう。アプリを起動していたら、いったんここで終了します。まずは、TimerPageクラスにフィールドを追加します。
~略~ public partial class TimerPage : ContentPage { // 設定時分(初期値は3分) private TimeOnly StartTime = new(0, 3); // 残り時分(初期値は3分) private TimeOnly LeftTime = new(0, 3); // 実行中ならtrue private bool IsLive = false; ~略~
StartTimeは、タイマーの設定値、LeftTimeは、タイマー開始後の残り時間としています。System.TimeOnly構造体は、時刻のみを保持する構造体です。ここでは、TimeOnly構造体のコンストラクター(時間と分の指定)を利用して初期化しています。次に、タイマーの残り時間を示すラベルと、プログレスバーの更新処理を追加しましょう。
~略~ // ラベル、プログレスバーの表示更新 private void SetLabel() { MinutesLabel.Text = LeftTime.ToString("mm分ss秒ff"); PrgBar.Progress = (StartTime - LeftTime).TotalSeconds / (StartTime - new TimeOnly(0, 0)).TotalSeconds; } public TimerPage() { InitializeComponent(); SetLabel(); } ~略~
SetLabelメソッドでは、変数LeftTimeを、0.01秒単位まで表示できるようにしています。また、プログレスバーのProgressプロパティには、変数LeftTimeが設定値と同じなら0、残り時間が0になったら、1となるように設定しています。
タイマー(時刻の減算)処理
次は、タイマー(時刻の減算)処理を追加しましょう。
~略~ private void Toggle() { IsLive = !IsLive; // 実行中フラグを反転 // ボタン名を変更する StartButton.Text = (IsLive) ? "停止" : "開始"; } // 残り時分を減算する private async void DecreaseTime() { var span = 10; // 10ミリ秒 while (IsLive) { await Task.Delay(TimeSpan.FromMilliseconds(span)); LeftTime = LeftTime.Add(TimeSpan.FromMilliseconds(-span)); SetLabel(); if ((LeftTime.Minute | LeftTime.Second | LeftTime.Millisecond) == 0) { Toggle(); await DisplayAlert("終了", "ラーメンができました", "OK"); } } } ~略~
Toggleメソッドは、実行中フラグを反転して、ボタン表示をフラグに合わせて変更する処理です。ボタンは、最初は「開始」と表示されますが、タイマー開始後は、「停止」となります。つまり、タイマー開始後は、開始ボタンから停止ボタンに変更する、ということです。
DecreaseTimeメソッドが、タイマーのメイン処理となります。whileループでは、実行中フラグがtrueの間、Task.Delayで一定期間処理を停止させ、その後に、LeftTimeから同じ停止した時刻分の値を減算しています。LeftTimeが0になれば、DisplayAlertメソッドで、画面にメッセージを表示します。
なお、Task.Delayは、非同期処理となりますので、先頭にawaitをつけ、Task.Delayを呼び出しているメソッドDecreaseTimeには、asyncを付加する必要があります。
ボタンイベント
最後に、ボタンがクリックされた時に実行されるメソッドを追加します。メソッド名は、TimerPage.xamlで定義した名前にします。
// 開始(停止)ボタンのクリック private void OnStartButton(object sender, EventArgs e) { Toggle(); DecreaseTime(); } // +1分ボタンのクリック private void OnPlusButton(object sender, EventArgs e) { LeftTime = LeftTime.Add(TimeSpan.FromMinutes(1)); StartTime = LeftTime; // 開始時分の設定 SetLabel(); } // -1分ボタンのクリック private void OnMinusButton(object sender, EventArgs e) { LeftTime = LeftTime.Add(TimeSpan.FromMinutes(-1)); StartTime = LeftTime; // 開始時分の設定 SetLabel(); } ~略~
OnStartButtonメソッドでは、実行中フラグを反転して、DecreaseTimeメソッドを呼び出します。OnPlusButton、OnMinusButtonメソッドでは、時間を増減して、変数StartTimeに設定しています。なお、LeftTime、StartTimeとも、構造体なので、=演算子では、値のコピーとなります。
最後に
今回は、.NET MAUIでラーメンタイマーアプリを作成しました。.NET MAUでは、豊富なUIコントロールを利用すれば、簡単に画面を作成することができます。もちろん、画像を駆使するようなゲームアプリは難しいのですが、GUIフォームが主体となるアプリでは、手軽にマルチプラットフォームに対応したアプリが作成できます。次回は、.NET MAUI Blazorの解説を予定しています。