ラーメンタイマーアプリ
今度は、テンプレートのページの変更ではなく、新しいページを追加してラーメンタイマーアプリを作ります。今回のアプリでは、初期状態では、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の解説を予定しています。
