狐好きぷろぐらまー

狐好きプログラマーのブログです。

食材管理アプリを作ってみる その2

そういえば、その1で伝えるべきだったゴールの完成品のイメージを書いていなかったので、ここに書いておきます。

 

完成品のイメージ

タブレットを冷蔵庫にペタッと貼って冷蔵庫を開け締めするときみたいに画面をタッチして使っていこうと考えています。

 

今のところのイメージ(開発途中です)

f:id:pregum_fox:20181017000250p:plain

 

 

実装する予定の項目

優先度高の機能

  1. 食材データ管理(CRUD
  2. カレンダー表示画面
  3. 食材データのエクスポート/インポート(Json形式)
  4. 食材データ管理画面
  5. 食材賞味期限表示
  6. ダッシュボード画面(内容固定)

 

優先度中の機能

  1. 食材の栄養素をレーダーチャートで表示
  2. ダッシュボード画面(内容可変)

 

優先度低の機能

  1. 食材の調理候補検索機能

 

今の所これぐらいです。

作業は基本、優先度高の1からやっていきますが、同時に作業する時もあります。

今後増減する可能性はありますが、そのときはまた書き直したいと思います。

 

では前置きはこれぐらいで、作業内容を書いていきますね。

 

1. 食材データ管理(CRUD

ここでは、食材データを管理するクラスを作成していきます。このクラスは、今回作成するプログラムで様々な箇所から参照されるため、修正する際は気をつけましょう。

 

まずは、ソリューションのフォルダ構成を整えていきます。

ソリューションのプロジェクト内にModel, ViewModel, View, Commonという名前のフォルダを作成します。

 

f:id:pregum_fox:20181017010326p:plain

 

Modelフォルダ内に食材オブジェクトを表すFoodクラスを作成します。

f:id:pregum_fox:20181017014238p:plain

 

 

 Food.cs

using Prism.Mvvm;
using System;
using System.Windows.Media.Imaging;

namespace Wpf_FoodManager.Model
{
public class Food : BindableBase
{
/// <summary>
/// Id
/// </summary>
private uint _id;
public uint Id
{
get { return _id; }
set { this.SetProperty(ref _id, value); }
}

/// <summary>
/// 名前
/// </summary>
private string _name;
public string Name
{
get { return _name; }
set { this.SetProperty(ref _name, value); }
}

/// <summary>
/// 賞味期限
/// </summary>
private DateTime _limitDate;
public DateTime LimitDate
{
get { return _limitDate; }
set { this.SetProperty(ref _limitDate, value); }
}

/// <summary>
/// 購入日
/// </summary>
private DateTime _boughtDate;
public DateTime BoughtDate
{
get { return _boughtDate; }
set { this.SetProperty(ref _boughtDate, value); }
}

/// <summary>
/// 画像
/// </summary>
private BitmapImage _image;
public BitmapImage Image
{
get { return _image; }
set { this.SetProperty(ref _image, value); }
}

 

private static uint _countId = 1;

 

public Food()
{
}

public Food(string name, DateTime limitDate, DateTime boughtDate, BitmapImage image)
{

this._id = Food._countId++;
this._name = name;
this._limitDate = limitDate;
this._boughtDate = boughtDate;
this._image = image;
}
}
}

  

Foodクラスに限らずModelのクラスにはINotifyPropertyChagedインターフェイスを実装し、各プロパティのsetアクセサでNotifyPropertyChangedイベントを発生させることを忘れないようしてください。でないとModeのプロパティ変更時ViewModelに通知できません。(今回はSetProperty()が上記の処理をやってくれています。SetProperty()は継承したBindableBaseクラス内に定義されています。)

 

次にViewModelフォルダにFoodViewModelクラスを作成します。FoodViewModelクラスでは、Modelの変更通知を受け取る処理と自身のプロパティが変更された時にViewに通知する処理が必要になります。(今回はReactivePropertyを使用して、2つの処理を行っています。)

 

using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.Reactive.Disposables;
using System.Windows.Media.Imaging;
using Wpf_FoodManager.Model;

namespace Wpf_FoodManager.ViewModel
{
public class FoodViewModel : IDisposable
{
// ReactiveProperty<T>にすることでTの型のデータの通知受け取り、通知発行の役割を持ったプロパティになります。
public ReactiveProperty<uint> Id { get; }
public ReactiveProperty<string> Name { get; }
public ReactiveProperty<DateTime> LimitDate { get; }
public ReactiveProperty<DateTime> BoughtDate { get; }
public ReactiveProperty<BitmapImage> Image { get; }

private CompositeDisposable Disposable { get; } = new CompositeDisposable();

 

// test用

  public FoodViewModel() : this(new Food("test1", new DateTime(2018, 10,17), new DateTime(2018, 10,19), new BitmapImage(new Uri("nothing", UriKind.RelativeOrAbsolute))))

{
}

public FoodViewModel(Food food)
{
this.Id = food.ObserveProperty(x => x.Id).ToReactiveProperty().AddTo(this.Disposable);
this.Name = food.ObserveProperty(x => x.Name).ToReactiveProperty().AddTo(this.Disposable);
this.LimitDate = food.ObserveProperty(x => x.LimitDate).ToReactiveProperty().AddTo(this.Disposable);
this.BoughtDate = food.ObserveProperty(x => x.BoughtDate).ToReactiveProperty().AddTo(this.Disposable);
this.Image = food.ObserveProperty(x => x.Image).ToReactiveProperty().AddTo(this.Disposable);
}

public void Dispose()
{
this.Disposable.Dispose();
}
}
}

 

ひとまず動作させるために、Mainwindows.xamlのDataContextにFoodViewModelクラスのインスタンスを設定し、各プロパティをTextBlockコントロールのTextプロパティにバインドさせていきます。(ImageプロパティはURIが表示されます)

 

<Window x:Class="Wpf_FoodManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wpf_FoodManager"
xmlns:vm="clr-namespace:Wpf_FoodManager.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:FoodViewModel />
</Window.DataContext>
<Grid Margin="10">
<StackPanel>
<TextBlock Text="{Binding Id.Value}"/>
<TextBlock Text="{Binding Name.Value}"/>
<TextBlock Text="{Binding LimitDate.Value}"/>
<TextBlock Text="{Binding BoughtDate.Value}"/>

<TextBlock Text="{Binding Image.Value}"/>
</StackPanel>

</Grid>
</Window>

 

 

Bindingしているプロパティに.Valueを付けているのはReactivePropertyを使用しているためです。 

 

またそのまま実行するとnugetのバージョンのエラーが発生してしまったので、System.Runtime.CompilerServices.UnsafeとSystem.ValueTupleのバージョンをそれぞれv4.5.1とv4.5.0に更新してください。

f:id:pregum_fox:20181017045021p:plain

 

ここで、Visual Studioから実行すると、ウィンドウが立ち上がり、画面にバインドした内容が表示されます。

 

f:id:pregum_fox:20181017044527p:plain

 

ここまでで、VMからVへプロパティをバインドさせる事ができました。

 

CRUD処理について書こうと思ったのですが、先にコレクションについて触れておこうと思いますので、次回にしたいと思います。

 

ここまでのソースコードを下に置いておきます。

github.com