今回は私が作成した【食品管理アプリ】の概要とソースコードの公開をします。
開発環境などは以下の通りです。
開発言語:C#
開発環境:Microsoft Visual Studio Community 2019
詳しく解説していますので参考にしてみて下さい。
ではここから実際にアプリの説明とコードの解説をしていきます。
食品管理アプリの概要
ここからアプリの概要について解説していきます。
このアプリを作ろうとしたきっかけ
きっかけは以下のような理由でした。
特に冷蔵庫の奥の商品は見ることが少なく期限切れとなってしまうことが多かったので作ろうと思いました。
アプリの説明
本アプリは食品の在庫を管理するシステムとなっています。
本アプリを使えば食品ロスを減らすことができる
を目標に取り組みました。
そのために以下を管理できるものとしました。
・食品一覧を管理
・期限を管理
・数量の管理
カテゴリー分けをし何がどれだけあるのかを分かりやすくしました。
在庫データはINIファイルとして以下の管理で管理
こちらでないと読み込まないので注意です。
解決できる問題点
まず食品管理アプリによって解決できる問題がこちらです。
- 何の食品があるのか把握できる
- 期限を把握できる
- カテゴリーを分けて見れるため分かりやすい
- 名前検索機能で直ぐにあるかわかる
- 数量を把握できる
以上が問題解決の機能になります。
ユーザインタフェースの紹介
ここからユーザが操作するところの説明に入ります。
ホーム画面
まずはこちらがホーム画面となります。

こちらがホーム画面です。
今は在庫が表示されている状態となっています。
ユーザが操作する箇所は以下になります。
- IDのテキストボックスに入力して検索する
- 名前のテキストボックスに入力して検索する
- 分類ラジオボタンをチェックして検索する
- 全検索ですべてのデータの表示、検索で絞り込み表示となります
続いて下のほうにあるボタンのを説明していきます。
削除(F1):データグリッドビューを選択して押下で選択行のデータが削除されます
追加(F2):押下すると追加画面のウィンドウが表示されデータを追加できます
更新(F3):押下すると更新画面のウィンドウが表示され選択行のデータの編集ができます
確定(F4):本画面では使用しません
戻る(F5):画面が閉じられアプリが終了します
これらのボタンはファンクションキーで操作可能となります。
追加画面
続いて追加画面となります。
追加画面はデータを追加する役割を持っています。

追加画面ではの操作がこちらです。
- 任意のIDと名前を入力(IDは今後自動連番に変更の可能性あり)
- 追加する食品の分類を選択
- 期限を選択
- 数量とその食品の単位(お肉ならgなど)を追加
続いて下のほうにあるボタンのを説明していきます。
削除(F1):本画面では使用しません
追加(F2):本画面では使用しません
更新(F3):本画面では使用しません
確定(F4):データの追加をします
戻る(F5):画面が閉じられホーム画面に戻ります(注意:入力内容は消えます)
これらのボタンはファンクションキーで操作可能となります。
更新画面
続いて更新画面となります。
ホーム画面のデータグリッドビュー選択行のデータを編集します。

更新画面ではの操作がこちらです。
- 名前を入力
- 追加する食品の分類を選択
- 期限を選択
- 数量とその食品の単位(お肉ならgなど)を追加
続いて下のほうにあるボタンのを説明していきます。
削除(F1):本画面では使用しません
追加(F2):本画面では使用しません
更新(F3):本画面では使用しません
確定(F4):データの編集をします
戻る(F5):画面が閉じられホーム画面に戻ります(注意:入力内容は消えます)
これらのボタンはファンクションキーで操作可能となります。
機能説明
ここからはソースコードをもとに機能の説明をしていきます。
ホーム画面の機能
こちらがホーム画面の機能一覧です。
・ファイルを読み込みデータを表示
・すべてのデータを表示
・絞り込み表示
・削除ボタンによるデータ削除
・追加ボタンによる追加画面ウィンドウ呼び出し
・更新ボタンによる更新画面ウィンドウ呼び出し
ではここからソースコードを公開しながら解説していきます。
ファイルを読み込みデータを表示
こちらはホーム画面が起動時の処理でファイルの読み込みを行いデータグリッドビューに表示します。
/// <summary>
/// 検索処理
/// </summary>
private void DisplaySerch()
{
FileImport fileImport = new FileImport(CommonFunc.INIPath, null);
CommonFunc commonFunc = new CommonFunc();
string[] dataArray = null;
try
{
// INIファイルから在庫データを読み込み
if (fileImport.CheckIniFile())
{
// INIファイルからデータを取得
this.IniFileData = fileImport.GetIniFle(CommonFunc.IniSection, CommonFunc.IniKey, CommonFunc.INIPath + CommonFunc.IniFileName);
this.IDArray = new string[this.IniFileData.Length];
// 取得データをデータグリッドビューに表示
for (int gridrow = 0; gridrow < this.IniFileData.Length; gridrow++)
{
if (this.IniFileData[gridrow] == string.Empty)
{
// データ数を保持しておく
this.DataNum = gridrow + 1;
break;
}
// カンマ区切りデータをばらす
dataArray = this.IniFileData[gridrow].Split(',');
// 数量の生データをとるためにdataArray.Length + 1する
for (int gridcell = 0; gridcell < dataArray.Length + 1; gridcell++)
{
if (gridcell != 6)
{
this.dgvSerchStock.Rows[gridrow].Cells[gridcell].Value = dataArray[gridcell];
}
if (gridcell == 0)
{
// IDは追加画面で使うので保持する
this.IDArray[gridrow] = this.dgvSerchStock.Rows[gridrow].Cells[CommonFunc.DGV_NUMBER_ID].Value.ToString();
}
// 種類は変換して表示
if (gridcell == CommonFunc.DGV_NUMBER_TYPE)
{
this.dgvSerchStock.Rows[gridrow].Cells[gridcell].Value = commonFunc.TypeChangeValueToName(this.dgvSerchStock.Rows[gridrow].Cells[gridcell].Value.ToString());
}
// 日付けは"/"区切りで表示
else if (gridcell == CommonFunc.DGV_NUMBER_DATE)
{
//this.dgvSerchStock.Rows[gridrow].Cells[gridcell].Value = commonFunc.ChangeDate(this.dgvSerchStock.Rows[gridrow].Cells[gridcell].Value.ToString());
this.dgvSerchStock.Rows[gridrow].Cells[gridcell].Value = this.dgvSerchStock.Rows[gridrow].Cells[gridcell].Value.ToString();
}
// 数量はカンマ区切り
else if (gridcell == CommonFunc.DGV_NUMBER_NUM)
{
this.dgvSerchStock.Rows[gridrow].Cells[gridcell].Value = String.Format("{0:#,0}", Convert.ToInt64(this.dgvSerchStock.Rows[gridrow].Cells[gridcell].Value));
}
// 数量に単位をつける
else if (gridcell == CommonFunc.DGV_NUMBER_TANI)
{
this.dgvSerchStock.Rows[gridrow].Cells[CommonFunc.DGV_NUMBER_NUM].Value = this.dgvSerchStock.Rows[gridrow].Cells[CommonFunc.DGV_NUMBER_NUM].Value.ToString() + dgvSerchStock.Rows[gridrow].Cells[CommonFunc.DGV_NUMBER_TANI].Value.ToString();
}
// 数量(生データ)
else if (gridcell == CommonFunc.DGV_NUMBER_DATA_NUM)
{
this.dgvSerchStock.Rows[gridrow].Cells[CommonFunc.DGV_NUMBER_DATA_NUM].Value = dataArray[4];
}
}
}
}
else
{
// INIファイルが存在しません
MessageBox.Show(CommonFunc.NoIniFile);
}
}
catch
{
throw;
}
}
/// <summary>
/// INIファイルから在庫データを取得
/// </summary>
public string[] GetIniFle(string iniSection, string iniKey, string iniPath)
{
string[] stockDataArry = new string[CommonFunc.Maxkeep];
int datarow = 1;
try
{
for (int row = 0; row < CommonFunc.Maxkeep; row++)
{
// 空の場合はそこでデータがないとみなし終了
stockDataArry[row] = GetIniString(iniSection, iniKey + datarow.ToString(), iniPath);
if (stockDataArry[row] == string.Empty)
{
break;
}
datarow++;
}
}
catch
{
throw;
}
return stockDataArry;
}
/// <summary>
/// 種類を値→名前に変換
/// </summary>
/// <param name="value">種類の値</param>
public string TypeChangeValueToName(string value)
{
string name = string.Empty;
try
{
switch (value)
{
case MeetValue:
name = MeetName;
break;
case FishValue:
name = FishName;
break;
case VegetableValue:
name = VegetableName;
break;
case FruitValue:
name = FruitName;
break;
case SnackValue:
name = SnackName;
break;
case OtherValue:
name = OtherName;
break;
default:
break;
}
}
catch
{
throw;
}
return name;
}
こちらが表示する処理のソースコードです。
それぞれのソースコードの役割から説明します。
SerchStock.cs:ホーム画面のソースコード
FileImport.cs:ファイル読み書きクラス
CommonFunc:共通定義関数&共通定数
DisplaySerch関数の処理の流れは以下です。
- INIファイルからデータを読み込みます
- データをプロパティとして宣言した配列に格納
- データの数だけ繰り返してデータグリッドビューに表示していきます
簡単に説明するとこのような感じで処理をしています。
コメントアウトの中に、
// 数量の生データをとるためにdataArray.Length + 1する
があると思います。
これは、
データグリッドビューでは数字+単位で表示するが、
データをINIファイルに追加する場合は数字と単位を別に登録する
が理由で生データ列をとっています。
次にINIファイルからデータを取得する処理を解説します。
INIファイルからデータを取得するには
GetIniString(セクション名, キー名, INIファイル名);
で取得します。
INIファイルにはセクション名とキー名が存在します。
[stockdata]
data1=0001,トマト,2,2021年10月11日,2000,個
data2=0002,さんま,1,2021年9月15日,5,匹
data3=0003,りんご,3,2021年9月30日,10,個
data4=0004,せんべい,4,2021年10月20日,200,枚
data5=0005,豆腐,5,2021年10月7日,111,aaa
data6=0006,豚肉,0,2021年9月21日,100,g
data7=0007,たまねぎ,2,2021年9月30日,3,個
data8=0008,ジャガイモ,2,2021年10月19日,1000,個
[stockdata]がセクション名、data〇がキー名です。
CommonFunc.csではカテゴリーの値を名前に変換する処理をしています。
INIファイルでカテゴリは以下のように管理
肉:0
魚:1
野菜:2
果物:3
お菓子:4
その他:5
このように文字列の数字で管理しています。
なのでデータグリッドビューに表示する際は変換をしています。
追加画面の機能
- 新規のデータを追加できる
追加機能はデータを追加する機能のみとなります。
データ追加処理
データ追加は以下の順で行います。
- ID重複チェック
- 入力データをINIファイルに書き込む
まずID重複チェックはホーム画面からコンストラクタで全IDの配列を受け取り、その値とテキストボックスの値を比較しています。
/// <summary>
/// 重複チェック
/// </summary>
private bool CheckDuplicate()
{
bool duplicateFlag = true;
try
{
for (int index = 0; index < this.IDArray.Length; index++)
{
if (this.txtID.Text == this.IDArray[index])
{
duplicateFlag = false;
break;
}
}
}
catch
{
throw;
}
return duplicateFlag;
}
次にIDの重複がなければINIファイルに入力されているデータを追加します。
/// <summary>
/// 確定処理
/// </summary>
private void Fix()
{
FileImport fileImport = new FileImport(CommonFunc.INIPath, null);
try
{
// ID重複チェック
if (CheckDuplicate())
{
// INIファイルが存在するか
if (fileImport.CheckIniFile())
{
// INIファイルへ追加
fileImport.CreateIniFile(txtID.Text, txtName.Text, this.ProType, dateTimePicker1.Text, txtNum.Text, txtTani.Text, this.DataNum);
}
else
{
// INIファイルが存在しません
MessageBox.Show(CommonFunc.NoIniFile);
return;
}
}
else
{
// IDが重複しています
MessageBox.Show(CommonFunc.IDDuplicate);
return;
}
}
catch
{
throw;
}
finally
{
this.Close();
}
}
/// <summary>
/// INIファイルにデータを追加
/// </summary>
/// <param name="id">ID</param>
/// <param name="name">名前</param>
/// <param name="type">種類</param>
/// <param name="date">日付</param>
/// <param name="num">数量</param>
/// <param name="tani">単位</param>
public void CreateIniFile(string id, string name, Type type, string date, string num, string tani, int datanum)
{
string data = string.Empty;
CommonFunc commonFunc = new CommonFunc();
try
{
// カンマ区切りで変数に格納
data = "data" + datanum.ToString() + "=" + id + "," + name + "," + commonFunc.TypeChangeTypeToValue(type) + "," + date + "," + num + "," + tani;
// カンマ区切りでINIファイルに追加
System.IO.StreamWriter sw = new System.IO.StreamWriter(@"C:\StockSystem\INI\stock.ini", true, System.Text.Encoding.GetEncoding("shift_jis"));
sw.Write(data + sw.NewLine);
//閉じる
sw.Close();
}
catch
{
throw;
}
}
引数で値を一つずつ受け取りカンマ区切りに変換してファイルに追加しています。
追加後フォームを閉じてホーム画面に戻ればアクティブイベントで再表示します。
/// <summary>
/// Formアクティブイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DgvSerchStock_Actived(object sender, EventArgs e)
{
try
{
// 全てを表示
DisplaySerch();
AllDisplay();
}
catch
{
throw;
}
}
更新画面の機能
- 既存のデータを更新できる
以上が更新の機能になります。
更新機能
以下の順で更新を行います。
- 更新するデータを入力
- 既存のINIファイルを一度削除
- 新しくINIファイルを生成
INIファイルを新しく作り直すのでホーム画面からデータをすべて受け取ります。
あとインデックスを受け取って更新対象のデータを取得します。
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="id">ID</param>
/// <param name="name">名前</param>
/// <param name="type">種類</param>
/// <param name="date">期限</param>
/// <param name="num">数量</param>
/// <param name="tani">単位</param>
/// <param name="changeRow">単位</param>
/// <param name="dataArray">データ</param>
/// <param name="index">行位置</param>
public ChangeStock(string id, string name, Type type, string date, string num, string tani, int dataNum, int changeRow, string[] dataArray, int index)
{
InitializeComponent();
// 初期化
this.ID = id;
this.StockName = name;
this.Type = type;
this.Date = date;
this.Num = num;
this.Tani = tani;
this.DataNum = dataNum;
this.ChangeRow = changeRow;
this.DataArray = dataArray;
this.Index = index;
// 種類によりラジオボタンを分岐
switch (this.Type)
{
case Type.meet:
rdbType0.Checked = true;
break;
case Type.fish:
rdbType1.Checked = true;
break;
case Type.vegetable:
rdbType2.Checked = true;
break;
case Type.fruit:
rdbType3.Checked = true;
break;
case Type.snack:
rdbType4.Checked = true;
break;
case Type.other:
rdbType5.Checked = true;
break;
default:
break;
}
}
次に実際にINIファイルを削除してからもう一度作り直す処理です。
/// <summary>
/// 確定イベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnF4_Click(object sender, EventArgs e)
{
FileImport fileImport = new FileImport(CommonFunc.INIPath, null);
CommonFunc commonFunc = new CommonFunc();
try
{
// ラジオボタンのプロパティセット
SetType();
// 変更したインデックスのデータを書き換えて配列に格納
for (int index = 0; index < this.DataArray.Length; index++)
{
// Indexが一致したものをカンマ区切り
if (index == this.Index)
{
this.DataArray[index] = this.txtID.Text + "," + this.txtName.Text + "," + commonFunc.TypeChangeTypeToValue(this.type) + "," + this.dateTimePicker.Text + "," + this.txtNum.Text + "," + this.txtTani.Text;
break;
}
}
// ファイル更新
fileImport.DelIniFile(this.DataArray);
// フォームを閉じる
this.Close();
}
catch
{
throw;
}
}
/// <summary>
/// INIファイルのデータを削除
/// </summary>
/// <param name="dataArray">全データ</param>
public void DelIniFile(string[] dataArray)
{
string data = string.Empty;
CommonFunc commonFunc = new CommonFunc();
try
{
// 指定ファイルを削除
System.IO.File.Delete(@"C:\StockSystem\INI\stock.ini");
// ファイルパス
string path = @"C:\StockSystem\INI\stock.ini";
// FileInfoのインスタンスを生成する
System.IO.FileInfo fileInfo = new System.IO.FileInfo(path);
// ファイルを作成する
System.IO.FileStream fileStream = fileInfo.Create();
// ファイルを閉じる
fileStream.Close();
// セクション名を挿入
System.IO.StreamWriter sw = new System.IO.StreamWriter(@"C:\StockSystem\INI\stock.ini", true, System.Text.Encoding.GetEncoding("shift_jis"));
sw.Write("[stockdata]" + sw.NewLine);
// ファイルにデータを追加
for (int count = 1; count <= dataArray.Length; count++)
{
if (dataArray[count - 1] != null)
{
// key名をつける
data = "data" + count.ToString() + "=" + dataArray[count - 1];
sw.Write(data + sw.NewLine);
}
else
{
break;
}
}
// ファイルを閉じる
sw.Close();
}
catch
{
throw;
}
}
以上が更新処理になります。
今後の拡張予定
今後時間があれば以下の機能を拡張していきます。
- ID自動連番振り分け
- 検索機能のワイルドカード対応
- 期限3日前になるとメッセージ送信
以下は、Gmail送信アプリの記事になりますのでご覧ください。
ポートフォリオ作成方法が知りたい方は以下の記事をご覧ください。
コメント