"おこづかいちょう"のからくり
おこづかいちょうのフォーム(UI)はC#で作りました。毎月の一覧にListView、日付の入力にDateTimePicker、そしておなじみのTextBok、Button、Labelを配置しています。

一覧には年月、摘要、収入、支出、残額のカラムを並べています。
C#で実装するUIはデータベースの存在をまったく意識せず、C++/CLIによるデータベースのラッパがUIの実現に必要なメソッドを提供し、UIはそれらを利用します。データベースのラッパに必要なのは以下の3つです。
- レコードの挿入
- (キーを指定した)レコードの削除
- (年/月を指定した)レコードの列挙
データベース上のテーブルには日付、摘要、金額(+なら収入/-なら支出)、さらに削除レコードを一意に特定するため、重複しないキーを与えることにしましょう。
ListViewへのレコードの列挙はUI側で用意したハンドラをcallbackすることで実現します。列挙の開始と終了時にはイベント引数を変えて開始/終了が検出できるようにします。
C++/CLIで実装するUIとのインターフェース「CashBookModel.h」を示します。
// CashBookModel.h #pragma once struct sqlite3; namespace CashBookModel { // Record : 列挙時のイベント引数を兼ねる public ref class Record : System::EventArgs { public: int key; // キー System::DateTime date; // 日付 System::String^ summary; // 摘要 int amount; // 金額 }; // Record列挙の最初/最後を知らせるマーカ public ref class EnumStart : System::EventArgs {}; public ref class EnumEnd : System::EventArgs {}; public ref class CashBook { private: sqlite3* db_; System::EventHandler^ notification_; System::String^ select_; void EnumRecords(); public: CashBook(); // コンストラクタ ~CashBook(); // デストラクタ !CashBook(); // ファイナライザ // Record列挙ハンドラの設定 void SetRecordListener(System::EventHandler^ notif); // Record挿入 void AddRecord(Record^ rec); // Record削除 void RemoveRecord(int key); // 月/日を指定してRecord列挙 void EnumRecords(int yyyy, int mm); }; }
SQLiteによる実装(C++/CLI)
それでは上記のCashBookの実装です。SQLite-APIを適宜呼び出してサクサクいきましょう。
#define _CRT_SECURE_NO_WARNINGS #include <cstring> #include <sqlite3.h> #include "CashBookModel.h" using namespace System; namespace CashBookModel { // System::String^ を wchar_t* に変換 wchar_t* dup_wcs(String^ s) { if ( s == nullptr || s->Length == 0 ) { return 0; } array<wchar_t>^ warray = s->ToCharArray(); pin_ptr<wchar_t> pin = &(warray[0]); wchar_t* result = new wchar_t[warray->Length+1]; wcsncpy(result, pin, warray->Length); result[warray->Length] = L'\0'; return result; } // dup_wcs で確保した文字列を解放する void free_wcs(void* p) { delete static_cast<wchar_t*>(p); } // wide-string → String^ inline String^ to_clistr(const void* p) { return gcnew String(static_cast<const wchar_t*>(p)); } // コンストラクタ CashBook::CashBook() { // データベースをopen sqlite3* db; sqlite3_open16(L"cashbook.db", &db); db_ = db; // テーブルをcreate sqlite3_stmt* statement; const wchar_t* command = L"CREATE TABLE IF NOT EXISTS cashbox " L"( key INTEGER PRIMARY KEY AUTOINCREMENT," L" date TEXT NOT NULL, summary TEXT, " L" amount INTEGER NOT NULL)"; sqlite3_prepare16_v2(db_, command, -1, &statement, 0); sqlite3_step(statement); sqlite3_finalize(statement); } // デストラクタ CashBook::~CashBook() { this->!CashBook(); } // ファイナライザ CashBook::!CashBook() { // データベースをclose if ( db_ != 0 ) { sqlite3_close(db_); db_ = 0; } } // レコードの挿入 void CashBook::AddRecord(Record^ rec) { sqlite3_stmt* statement; const wchar_t* command = L"INSERT INTO cashbox ( date, summary, amount ) VALUES ( ?, ?, ? )"; sqlite3_prepare16_v2(db_, command, -1, &statement, 0); sqlite3_bind_text16(statement, 1, dup_wcs(rec->date.ToString(L"yyyy/MM/dd")), -1, &free_wcs); sqlite3_bind_text16(statement, 2, dup_wcs(rec->summary), -1, &free_wcs); sqlite3_bind_int(statement, 3, rec->amount); sqlite3_step(statement); sqlite3_finalize(statement); EnumRecords(); } // レコードの削除 void CashBook::RemoveRecord(int key) { sqlite3_stmt* statement; const wchar_t* command = L"DELETE FROM cashbox WHERE key=?"; sqlite3_prepare16_v2(db_, command, -1, &statement, 0); sqlite3_bind_int(statement,1,key); sqlite3_step(statement); sqlite3_finalize(statement); EnumRecords(); } // 月/日を指定したレコードの列挙 void CashBook::EnumRecords(int yyyy, int mm) { System::DateTime dt(yyyy,mm,1); select_ = dt.ToString(L"yyyy/MM") + L"/%"; EnumRecords(); } // レコードの列挙 void CashBook::EnumRecords() { if ( notification_ == nullptr ) return; notification_(this, gcnew EnumStart()); // '列挙開始'を通知 // テーブルから該当する月/日のレコードを検索 sqlite3_stmt* statement; const wchar_t* command = L"SELECT * FROM cashbox WHERE date LIKE ? ORDER BY date"; sqlite3_prepare16_v2(db_, command, -1, &statement, 0); sqlite3_bind_text16(statement,1, dup_wcs(select_),-1, &free_wcs); while ( sqlite3_step(statement) == SQLITE_ROW ) { // Recordを生成して通知 Record^ rec = gcnew Record(); rec->key = sqlite3_column_int(statement,0); rec->date = DateTime::Parse(to_clistr(sqlite3_column_text16( statement,1))); rec->summary = to_clistr(sqlite3_column_text16(statement,2)); rec->amount = sqlite3_column_int(statement,3); notification_(this,rec); } sqlite3_finalize(statement); notification_(this, gcnew EnumEnd()); // '列挙終了'を通知 } // ハンドラの登録 void CashBook::SetRecordListener(EventHandler^ notif) { notification_ = notif; } } // リンク時のwarning抑止 struct sqlite3 {}; struct sqlite3_stmt {};
wchar_t*との相互変換ルーチンがありますが、Visual C++ 2008(VC++9)ならマーシャリング・ライブラリを使って楽に書けます。#include <locale> #include <iostream> #include <msclr/marshal.h> // marshal-lib. using namespace msclr::interop; using namespace std; int main() { wcout.imbue(locale("japanese")); marshal_context ctx; System::String^ input = L"本日は晴天なり"; wcout << ctx.marshal_as<const wchar_t*>(input) << endl; // ここで得られたwchar_t*の領域はctxのデストラクトと共に解放される }
