.NET MAUIとCleanArchitectureで簡単なアプリを作成してみる4~実装3~の続き
Viewを作成する
.NET MAUI環境をつくるで追加した「.NET MAUI アプリ」プロジェクトをそのまま使います。
フォルダ構成はフォルダ構成を決める(MauiApp1)の通り
以下をプロジェクト参照しておく
MauiApp1.Domain
MauiApp1.SqlInfrastructure
MauiApp1.UseCase
MauiApp1直下のMainPage.xamlを「MauiApp1¥Views」へ移動させる
名前空間を「MauiApp1.Views」に変更する
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- x:Class="MauiApp1.MainPage"
+ x:Class="MauiApp1.Views.MainPage"
- namespace MauiApp1
+ namespace MauiApp1.Views
以下の箇所も変更が必要
<Shell
x:Class="MauiApp1.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- xmlns:local="clr-namespace:MauiApp1"
+ xmlns:local="clr-namespace:MauiApp1.Views"
Shell.FlyoutBehavior="Disabled"
Title="MauiApp1">
※AppShellはアプリのビジュアル階層を定義するクラスです。
「MauiApp1¥Views」配下にHealthEditPage.xamlを追加する
「.NET MAUI ContentPage (XAML)」を選択
最初はこんな感じ
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp1.Views.HealthEditPage"
Title="HealthEditPage">
<VerticalStackLayout>
<Label
Text="Welcome to .NET MAUI!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentPage>
レイアウトは後で実装します。
Modelを作成する
体調管理Modelを作成する
ハート数の変化に合わせてハートマーク1~5の表示/非表示を切り替えたいので、ObservableObjectを継承する
using CommunityToolkit.Mvvm.ComponentModel;
internal class Health : ObservableObject
{
/// <summary>
/// コンストラクタ
/// </summary>
public Health()
{
RecordDate = DateTime.Now;
HeartNumber = 3;
}
/// <summary>
/// ID
/// </summary>
public int Id { get; set; }
/// <summary>
/// 記録日付
/// </summary>
public DateTime RecordDate { get; set; }
private int _heartNumber;
/// <summary>
/// ハート数
/// </summary>
public int HeartNumber
{
get => this._heartNumber;
set
{
if (this._heartNumber != value)
{
this._heartNumber = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsVisibleHeart1));
OnPropertyChanged(nameof(IsVisibleHeart2));
OnPropertyChanged(nameof(IsVisibleHeart3));
OnPropertyChanged(nameof(IsVisibleHeart4));
OnPropertyChanged(nameof(IsVisibleHeart5));
}
}
}
/// <summary>
/// ハート1表示/非表示
/// </summary>
public bool IsVisibleHeart1 { get => HeartNumber >= 1; }
/// <summary>
/// ハート2表示/非表示
/// </summary>
public bool IsVisibleHeart2 { get => HeartNumber >= 2; }
/// <summary>
/// ハート3表示/非表示
/// </summary>
public bool IsVisibleHeart3 { get => HeartNumber >= 3; }
/// <summary>
/// ハート4表示/非表示
/// </summary>
public bool IsVisibleHeart4 { get => HeartNumber >= 4; }
/// <summary>
/// ハート5表示/非表示
/// </summary>
public bool IsVisibleHeart5 { get => HeartNumber >= 5; }
}
グラフ用のModelを作成する
LiveCharts2/samples/ViewModelsSamples/を参考に、グラフ用のデータを作成する
using CommunityToolkit.Mvvm.ComponentModel;
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
internal class HealthGraph : ObservableObject
{
/// <summary>
/// X軸ラベル
/// </summary>
public Axis[] XAxes => new Axis[]
{
new() { Labeler = d => string.Format("{0:dd}", DateTime.FromOADate(d)) }
};
/// <summary>
/// データ系列
/// </summary>
public ISeries[] Series { get; private set; } = Array.Empty<LineSeries<ObservablePoint>>();
/// <summary>
/// グラフデータを作成する
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <returns></returns>
public async Task CreateGraph(DateTime startDate, DateTime endDate)
{
// 指定した期間の体調情報を取得する
var interactor = ServiceProvider.GetService<IHealthGetListUseCase>();
var request = new HealthGetListRequest(startDate, endDate);
var response = await Task.Run(() => { return interactor.Handle(request); });
if (response == null) return;
var values = response.Healths.Select(h => new ObservablePoint(h.RecordDate.ToOADate(), h.HeartNumber)).ToArray();
// データ系列を作成する
Series = new LineSeries<ObservablePoint>[]
{
new() { Values = values }
};
OnPropertyChanged(nameof(Series));
}
}
ViewModelを作成する
メイン画面のViewModelを作成する
画面読み込み時に本日の体調情報の有無を確認したいので、Loadメソッドを作成する
→ コンストラクタで非同期処理を呼ぶのはどうかと思うので、Viewから呼ぶようにする
(これはどうあるべきなのだろう?)
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MauiApp1.UseCase.Healths.Create;
using MauiApp1.UseCase.Healths.GetList;
using System.Windows.Input;
internal class MainViewModel : ObservableObject
{
private Models.Health _health;
/// <summary>
/// 本日の体調情報
/// </summary>
public Models.Health Health
{
get => this._health;
}
private DateTime _startDate;
/// <summary>
/// グラフの開始日
/// </summary>
public DateTime StartDate
{
get => this._startDate;
set
{
if (this._startDate != value)
{
this._startDate = value;
OnPropertyChanged();
_ = RefreshGraph();
}
}
}
private DateTime _endDate;
/// <summary>
/// グラフの終了日
/// </summary>
public DateTime EndDate
{
get => this._endDate;
set
{
if (this._endDate != value)
{
this._endDate = value;
OnPropertyChanged();
_ = RefreshGraph();
}
}
}
private Models.HealthGraph _healthGraph;
/// <summary>
/// 体調情報のグラフデータ
/// </summary>
public Models.HealthGraph HealthGraph
{
get => this._healthGraph;
}
private DateTime _selectedDate;
/// <summary>
/// 編集する日付
/// </summary>
public DateTime SelectedDate
{
get => this._selectedDate;
set
{
if (this._selectedDate != value)
{
this._selectedDate = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// 保存コマンド
/// </summary>
public ICommand SaveCommand { get; private set; }
/// <summary>
/// 編集コマンド
/// </summary>
public ICommand EditCommand { get; private set; }
/// <summary>
/// コンストラクタ
/// </summary>
public MainViewModel()
{
this._health = new Models.Health();
this._healthGraph = new Models.HealthGraph();
this._startDate = this._health.RecordDate.AddDays(-5);
this._endDate = this._health.RecordDate;
SelectedDate = this._health.RecordDate;
SaveCommand = new AsyncRelayCommand(Save);
EditCommand = new AsyncRelayCommand(Edit);
}
/// <summary>
/// 体調情報を取得する
/// </summary>
/// <returns></returns>
public async Task Load()
{
if (await Load(Health) is HealthDto dto)
{
Health.Id = dto.Id;
Health.HeartNumber = dto.HeartNumber;
}
// グラフを更新する
await RefreshGraph();
}
/// <summary>
/// DBから体調情報を取得する
/// </summary>
/// <param name="health"></param>
/// <returns></returns>
private async Task<HealthDto> Load(Models.Health health)
{
var interactor = ServiceProvider.GetService<IHealthGetListUseCase>();
var request = new HealthGetListRequest(health.RecordDate.Date, health.RecordDate);
var response = await Task.Run(() => { return interactor.Handle(request); });
return response?.Healths?.FirstOrDefault();
}
/// <summary>
/// グラフを更新する
/// </summary>
/// <returns></returns>
private async Task RefreshGraph()
{
await HealthGraph.CreateGraph(StartDate, EndDate);
}
/// <summary>
/// 体調情報を保存する
/// </summary>
/// <returns></returns>
private async Task Save()
{
var interactor = ServiceProvider.GetService<IHealthCreateUseCase>();
var request = new HealthCreateRequest(Health.Id, Health.RecordDate, Health.HeartNumber);
var response = await Task.Run(() => { return interactor.Handle(request); });
Health.Id = response.Id;
// グラフを更新する
await RefreshGraph();
}
/// <summary>
/// 編集画面に遷移する
/// </summary>
/// <returns></returns>
private async Task Edit()
{
var health = new Models.Health() { RecordDate = SelectedDate };
if (await Load(health) is HealthDto dto)
{
health.Id = dto.Id;
health.HeartNumber = dto.HeartNumber;
}
// 編集画面に渡すパラメータ
var navigationParameter = new Dictionary<string, object>
{
{ "health", health },
};
// パラメータを渡して編集画面に遷移する
await Shell.Current.GoToAsync("HealthEditPage", navigationParameter);
}
}
編集画面のViewModelを作成する
メイン画面からパラメータを受け取るためにIQueryAttributableを実装する
(ApplyQueryAttributesで受け取っている)
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MauiApp1.UseCase.Healths.Create;
using System.Windows.Input;
internal class HealthEditViewModel : ObservableObject, IQueryAttributable
{
private Models.Health _health;
/// <summary>
/// 本日の体調情報
/// </summary>
public Models.Health Health
{
get => this._health;
}
private DateTime _selectedDate;
/// <summary>
/// 編集する日付
/// </summary>
public DateTime SelectedDate
{
get => this._selectedDate;
set
{
if (this._selectedDate != value)
{
this._selectedDate = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// 保存コマンド
/// </summary>
public ICommand SaveCommand { get; private set; }
/// <summary>
/// 戻るコマンド
/// </summary>
public ICommand ReturnCommand { get; private set; }
/// <summary>
/// コンストラクタ
/// </summary>
public HealthEditViewModel()
{
this._health = new Models.Health();
SaveCommand = new AsyncRelayCommand(Save);
ReturnCommand = new AsyncRelayCommand(Return);
}
/// <summary>
/// メイン画面からのパラメータを受け取る
/// </summary>
/// <param name="query"></param>
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.ContainsKey("health") && query["health"] is Models.Health health)
{
this._health = health;
SelectedDate = this._health.RecordDate;
OnPropertyChanged(nameof(Health));
}
}
/// <summary>
/// 体調情報を保存する
/// </summary>
/// <returns></returns>
private async Task Save()
{
var interactor = ServiceProvider.GetService<IHealthCreateUseCase>();
var request = new HealthCreateRequest(Health.Id, Health.RecordDate, Health.HeartNumber);
var response = await Task.Run(() => { return interactor.Handle(request); });
}
/// <summary>
/// メイン画面に戻る
/// </summary>
/// <returns></returns>
private async Task Return()
{
await Shell.Current.GoToAsync("///MainPage");
}
}
参考にしたURL
.NET MAUI チュートリアル
LiveCharts2を使ってグラフを表示する
LiveCharts2/samples/ViewModelsSamples/
まとめ
今回はModel、ViewModel、Viewを実装しました。
次はViewのレイアウトを実装します。
.NET MAUIとCleanArchitectureで簡単なアプリを作成してみる4~実装5~