はじめに
前回、家計簿アプリの設計を行いました(AIがやりました)
今回はその設計内容を元に取りあえず動く状態にします。
プロンプト
家計簿アプリの開発を行います。
最低限画面を表示したいため、コードを出力してください。
各画面の機能等は後ほど実装します。
要件は以下です。
・機能要件 -開発:C#.net(windows form) -データ管理:SQLite3 ・画面設計 -メニュー画面 -支出登録画面 -支出一覧画面 ・機能概要 -メニュー画面 -ボタン押下で各画面を表示する。 -支出登録画面 -支出情報を登録します。 レシート等支出情報を画像で入力、OCR機能で各項目に自動入力を行います。 OCRより入力された情報を人間が目視チェック、修正を行い登録します。 -支出内保持情報 -支出情報の一覧を表示します。 ・支出情報内保持項目 -日付 -金額 -支払方法 -備考 -商品一覧 -商品名 -商品区分 -金額 -数量 -税率 -備考
回答
以下は、まず「画面が起動し遷移できる最低限構成」のサンプルです。
機能本体(SQLite保存、OCR、一覧表示等)は後続実装を前提に、画面と土台クラスのみ作成しています。
構成は以下です。
KakeiboApp ├─ Program.cs ├─ Forms │ ├─ MenuForm.cs │ ├─ ExpenseEntryForm.cs │ └─ ExpenseListForm.cs │ ├─ Models │ ├─ Expense.cs │ └─ ExpenseItem.cs │ ├─ Services │ ├─ DatabaseService.cs │ └─ OcrService.cs │ └─ App.config
NuGet
まず以下をインストールしてください。
- SQLite
- OCR
推奨:
System.Data.SQLite Tesseract OpenCvSharp4 OpenCvSharp4.runtime.win
Program.cs
using System;
using System.Windows.Forms;
using KakeiboApp.Forms;
namespace KakeiboApp
{
internal static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MenuForm());
}
}
}
Models/Expense.cs
using System;
using System.Collections.Generic;
namespace KakeiboApp.Models
{
public class Expense
{
public int Id { get; set; }
public DateTime ExpenseDate { get; set; }
public decimal Amount { get; set; }
public string PaymentMethod { get; set; }
public string Note { get; set; }
public List<ExpenseItem> Items { get; set; }
= new List<ExpenseItem>();
}
}
Models/ExpenseItem.cs
namespace KakeiboApp.Models
{
public class ExpenseItem
{
public int Id { get; set; }
public string ProductName { get; set; }
public string Category { get; set; }
public decimal Amount { get; set; }
public int Quantity { get; set; }
public decimal TaxRate { get; set; }
public string Note { get; set; }
}
}
Services/DatabaseService.cs
using System.Data.SQLite;
namespace KakeiboApp.Services
{
public class DatabaseService
{
private readonly string _connectionString
= "Data Source=kakeibo.db;Version=3;";
public void Initialize()
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string expenseTable = @"
CREATE TABLE IF NOT EXISTS Expenses (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ExpenseDate TEXT,
Amount REAL,
PaymentMethod TEXT,
Note TEXT
);";
string itemTable = @"
CREATE TABLE IF NOT EXISTS ExpenseItems (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ExpenseId INTEGER,
ProductName TEXT,
Category TEXT,
Amount REAL,
Quantity INTEGER,
TaxRate REAL,
Note TEXT
);";
using (var command = new SQLiteCommand(expenseTable, connection))
{
command.ExecuteNonQuery();
}
using (var command = new SQLiteCommand(itemTable, connection))
{
command.ExecuteNonQuery();
}
}
}
}
}
Services/OcrService.cs
namespace KakeiboApp.Services
{
public class OcrService
{
public void ExecuteOcr(string imagePath)
{
// TODO:
// Tesseract OCR実装予定
// 例:
// ・店舗名取得
// ・商品一覧取得
// ・金額取得
// ・日付取得
}
}
}
Forms/MenuForm.cs
using System;
using System.Windows.Forms;
namespace KakeiboApp.Forms
{
public class MenuForm : Form
{
private Button btnExpenseEntry;
private Button btnExpenseList;
public MenuForm()
{
InitializeComponent();
}
private void InitializeComponent()
{
Text = "家計簿アプリ - メニュー";
Width = 400;
Height = 250;
StartPosition = FormStartPosition.CenterScreen;
btnExpenseEntry = new Button();
btnExpenseEntry.Text = "支出登録";
btnExpenseEntry.Width = 200;
btnExpenseEntry.Height = 40;
btnExpenseEntry.Top = 40;
btnExpenseEntry.Left = 80;
btnExpenseEntry.Click += BtnExpenseEntry_Click;
btnExpenseList = new Button();
btnExpenseList.Text = "支出一覧";
btnExpenseList.Width = 200;
btnExpenseList.Height = 40;
btnExpenseList.Top = 100;
btnExpenseList.Left = 80;
btnExpenseList.Click += BtnExpenseList_Click;
Controls.Add(btnExpenseEntry);
Controls.Add(btnExpenseList);
}
private void BtnExpenseEntry_Click(object sender, EventArgs e)
{
var form = new ExpenseEntryForm();
form.ShowDialog();
}
private void BtnExpenseList_Click(object sender, EventArgs e)
{
var form = new ExpenseListForm();
form.ShowDialog();
}
}
}
Forms/ExpenseEntryForm.cs
using System;
using System.Windows.Forms;
namespace KakeiboApp.Forms
{
public class ExpenseEntryForm : Form
{
private TextBox txtDate;
private TextBox txtAmount;
private TextBox txtPaymentMethod;
private TextBox txtNote;
private DataGridView dgvItems;
private Button btnSelectImage;
private Button btnOcr;
private Button btnSave;
public ExpenseEntryForm()
{
InitializeComponent();
}
private void InitializeComponent()
{
Text = "支出登録";
Width = 1000;
Height = 700;
StartPosition = FormStartPosition.CenterScreen;
Label lblDate = new Label();
lblDate.Text = "日付";
lblDate.Top = 20;
lblDate.Left = 20;
txtDate = new TextBox();
txtDate.Top = 40;
txtDate.Left = 20;
txtDate.Width = 200;
Label lblAmount = new Label();
lblAmount.Text = "金額";
lblAmount.Top = 80;
lblAmount.Left = 20;
txtAmount = new TextBox();
txtAmount.Top = 100;
txtAmount.Left = 20;
txtAmount.Width = 200;
Label lblPayment = new Label();
lblPayment.Text = "支払方法";
lblPayment.Top = 140;
lblPayment.Left = 20;
txtPaymentMethod = new TextBox();
txtPaymentMethod.Top = 160;
txtPaymentMethod.Left = 20;
txtPaymentMethod.Width = 200;
Label lblNote = new Label();
lblNote.Text = "備考";
lblNote.Top = 200;
lblNote.Left = 20;
txtNote = new TextBox();
txtNote.Top = 220;
txtNote.Left = 20;
txtNote.Width = 300;
dgvItems = new DataGridView();
dgvItems.Top = 280;
dgvItems.Left = 20;
dgvItems.Width = 920;
dgvItems.Height = 300;
dgvItems.ColumnCount = 6;
dgvItems.Columns[0].Name = "商品名";
dgvItems.Columns[1].Name = "商品区分";
dgvItems.Columns[2].Name = "金額";
dgvItems.Columns[3].Name = "数量";
dgvItems.Columns[4].Name = "税率";
dgvItems.Columns[5].Name = "備考";
btnSelectImage = new Button();
btnSelectImage.Text = "画像選択";
btnSelectImage.Top = 600;
btnSelectImage.Left = 20;
btnSelectImage.Click += BtnSelectImage_Click;
btnOcr = new Button();
btnOcr.Text = "OCR実行";
btnOcr.Top = 600;
btnOcr.Left = 140;
btnOcr.Click += BtnOcr_Click;
btnSave = new Button();
btnSave.Text = "登録";
btnSave.Top = 600;
btnSave.Left = 260;
btnSave.Click += BtnSave_Click;
Controls.Add(lblDate);
Controls.Add(txtDate);
Controls.Add(lblAmount);
Controls.Add(txtAmount);
Controls.Add(lblPayment);
Controls.Add(txtPaymentMethod);
Controls.Add(lblNote);
Controls.Add(txtNote);
Controls.Add(dgvItems);
Controls.Add(btnSelectImage);
Controls.Add(btnOcr);
Controls.Add(btnSave);
}
private void BtnSelectImage_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "画像ファイル|*.png;*.jpg;*.jpeg";
if (dialog.ShowDialog() == DialogResult.OK)
{
MessageBox.Show($"選択画像: {dialog.FileName}");
}
}
private void BtnOcr_Click(object sender, EventArgs e)
{
MessageBox.Show("OCR実行(未実装)");
}
private void BtnSave_Click(object sender, EventArgs e)
{
MessageBox.Show("登録処理(未実装)");
}
}
}
Forms/ExpenseListForm.cs
using System.Windows.Forms;
namespace KakeiboApp.Forms
{
public class ExpenseListForm : Form
{
private DataGridView dgvExpenses;
public ExpenseListForm()
{
InitializeComponent();
}
private void InitializeComponent()
{
Text = "支出一覧";
Width = 1000;
Height = 700;
StartPosition = FormStartPosition.CenterScreen;
dgvExpenses = new DataGridView();
dgvExpenses.Dock = DockStyle.Fill;
dgvExpenses.ColumnCount = 5;
dgvExpenses.Columns[0].Name = "日付";
dgvExpenses.Columns[1].Name = "金額";
dgvExpenses.Columns[2].Name = "支払方法";
dgvExpenses.Columns[3].Name = "備考";
dgvExpenses.Columns[4].Name = "商品数";
Controls.Add(dgvExpenses);
}
}
}
App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> </configuration>
次に実装すべき機能
次段階では以下順序がおすすめです。
- SQLite初期化
- 支出登録保存
- 支出一覧取得
- OCR実装
- OCR→DataGridView自動反映
- Excel出力
- 商品カテゴリマスタ
- 検索機能
- 月別集計
- グラフ表示
OCR実装時のおすすめ構成
OCRは以下構成が扱いやすいです。
画像 ↓ OpenCvSharp ・傾き補正 ・ノイズ除去 ・二値化 ↓ Tesseract OCR ↓ 正規表現解析 ↓ Expenseへ反映
レシートOCRは「画像前処理」が精度にかなり影響します。
単純OCRより、OpenCV併用を推奨します。
起動確認
最後に
次回はOCR取り込みを作成していこうと思います。