家計簿アプリを作成する中で、カレンダーの日付とその日に使った金額とカテゴリを紐づけて、データベースで管理しようと思っています。
データベースで管理する理由は、アプリを終了しても入力したデータを残しておきたいからです。
そして、月間の支出総額ややカテゴリ毎の支出金額も、このデータベースを利用することで計算することができます。
ここでは、家計簿アプリの操作を想定して、
QtでのSQLiteの使い方をプログラムをもとに解説していきます。
1.QtでSQLiteを使用したプログラム仕様とQtCreatorのデザイン画面
ここでのプログラムの大まかな仕様は、下記のようにします。
No | 仕様説明 |
---|---|
1 |
アプリ起動時に、SQLiteのデータベースを作成して初期データも作成する。 そして本日の日付のデータを表に表示する。 |
2 | 登録ボタンの押下で、表にデータを追加し、データベースにも追加する。 |
3 |
入力欄に削除したい行番号を入力後、削除ボタンの押下で表からその行を削除、 データベースからも対象のデータを削除する。 |
4 | コンボボックスで日付を選択すると、データベースからその日付のデータを取得して、表に表示する。 |
QtCreatorデザイン画面では、下図のようにウィジェットを配置します。
プログラムで主に使用するウィジェットのクラスは、下表のとおりです。
クラス名 | 用途 |
---|---|
QComboBox |
本日、昨日、一昨日の日付を選択できるようにして、その日付のデータを表に表示する。 |
QPushButton |
ボタンラベルに「登録」と表示して、ボタン押下時、データを表とデータベースに追加する。 |
QPushButton | ボタンラベルに「削除」と表示して、入力欄の番号をもとに、表からデータを削除する。更にデータベースからもそのデータを削除する。 |
QEditText |
削除したい行番号を入力する。 |
QTableWidget | データを表示する。 |
2.QtでのSQLiteの使い方をプログラムをもとに解説
全体のプログラムとしては、以下のような感じで、
プログラム中のコメントで①や②などの番号をクリックすると、そのプログラムを説明している項目に飛ぶことができます。
また、プログラム内には、デバッグ用で使用したログ出力のコードも入れていますので、参考にしてみてください。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QTime>
static QString DataBase = QString("KAKEIBO");
const QStringList headerlist = {"日付","カテゴリ","金額"};
static int iColNum = 3; // 表の列数
static int iRowNum = 0; // 表の行数
// <表の行番号, DBのid> 表にデータを表示する時に、毎回行番号とidを紐付ける
static std::map<int, QString> mRowNumID;
static QDate cmbDate; // comboBoxで指定した日付
static QSqlDatabase db;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->pushButton->setText("登録");
ui->pushButton_2->setText("削除");
// コンボBoxのアイテム追加
QDate date = QDate::currentDate();
for( int i=0; i < 3; i++){
ui->comboBox->insertItem(i, (date.addDays(-i)).toString(Qt::LocalDate));
}
ui->tableWidget->setColumnCount(iColNum);
ui->tableWidget->setHorizontalHeaderLabels(headerlist);
ui->tableWidget->setSelectionMode(QAbstractItemView::NoSelection);
① db = QSqlDatabase::addDatabase("QSQLITE",DataBase);
① db.setDatabaseName("./kakeibo.db");
① db.open();
QSqlQuery query(db);
// kakeibo.dbに支出テーブルを作成する
② query.exec("create table shisyutsu(id INTEGER PRIMARY KEY, date text, item text, money int)");
DBdataCreate();
DisplayDB(QDate::currentDate());
}
MainWindow::~MainWindow()
{
db.close();
delete ui;
}
// 疑似DBデータの作成
void MainWindow::DBdataCreate()
{
QSqlQuery query(db);
QDate date = QDate::currentDate();
QString arg1 = (date.addDays(-2)).toString(Qt::LocalDate);
QString arg2 = "スーパー";
int arg3 = 100;
for(int i=1; i <= 10; i++){
if( 4<=i && i<=7 ){
arg1 = (date.addDays(-1)).toString(Qt::LocalDate);
}
else if( 8<=i ){
arg1 = (date.addDays(0)).toString(Qt::LocalDate);
}
③ query.prepare("insert into shisyutsu (date,item,money) values (:date,:item,:money)");
query.bindValue(":date",arg1);
query.bindValue(":item",arg2);
query.bindValue(":money",arg3);
query.exec();
arg3 = arg3 + 100;
}
}
// 表のデータを全クリアする。 DBデータは削除しない。
void MainWindow::tableAllClear()
{
int rowCnt = ui->tableWidget->rowCount();
// 行数が0になるまでremoveRowする。
while( ui->tableWidget->rowCount() > 0) {
ui->tableWidget->removeRow(--rowCnt);
// qDebug() << rowCnt;
}
}
// 表のデータを全削除して、引数の日付のDBデータを表示する。
void MainWindow::DisplayDB(QDate date)
{
// qDebug() << "DisplayDB ST";
tableAllClear();
iRowNum = 0;
// DB内データ確認
// qDebug() << "shisyutsu DB 出力" << date.toString(Qt::LocalDate);
QSqlQuery query(db);
④ query.exec("SELECT * from shisyutsu where date = '" + date.toString(Qt::LocalDate) + "'");
④ while (query.next()) {
QString arg1 = query.value(0).toString(); // DB index
QString arg2 = query.value(1).toString(); // date
QString arg3 = query.value(2).toString(); // item
int arg4 = query.value(3).toString().toInt(); // money
qDebug() << arg1 << arg2 << arg3 << arg4 << iRowNum;
ui->tableWidget->insertRow(iRowNum);
for (int i=0; i< iColNum; i++){
QTableWidgetItem *item = new QTableWidgetItem();
item->setTextAlignment(Qt::AlignCenter);
switch (i){
case 0:
item->setText(arg2);
break;
case 1:
item->setText(arg3);
break;
case 2:
item->setText(QString::number(arg4));
break;
default:
break;
};
ui->tableWidget->setItem(iRowNum,i, item);
//qDebug() << "setItem=" << iRowNum;
//ここで、表の行番号とDBのidを紐づけておく。行番号は1から始まる。
// 行番号で削除をするときに、行番号に紐付いているidで、DBのデータを削除する。
⑤ mRowNumID[iRowNum+1] = arg1;
}
iRowNum++;
}
// qDebug() << "RowNum =" << iRowNum;
// qDebug() << "DisplayDB ED";
}
// DBに入力データを追加
static int money = 2000;
void MainWindow::on_pushButton_clicked()
{
qDebug() << "on_pushButton_clicked ST";
// 入力データは、ユーザー入力から受け取る
QString arg1 = cmbDate.toString(Qt::LocalDate); // コンボBoxの日付で追加する
QString arg2 = "スーパー";
int arg3 = money;
// DBに入力データを追加
QSqlQuery query(db);
query.prepare("insert into shisyutsu (date,item,money) values (:date,:item,:money)");
query.bindValue(":date",arg1);
query.bindValue(":item",arg2);
query.bindValue(":money",arg3);
query.exec();
⑥ QString dbid = query.lastInsertId().toString();
money++; // 追加データをわかりやすくするために、インクリメントする。
// 現在の表の行数を数えて、表の最後尾に新データの行を追加する。
int rowCnt = ui->tableWidget->rowCount();
ui->tableWidget->insertRow(rowCnt);
for (int i=0; i< iColNum; i++){
QTableWidgetItem *item = new QTableWidgetItem();
item->setTextAlignment(Qt::AlignCenter);
switch (i){
case 0:
item->setText(arg1);
break;
case 1:
item->setText(arg2);
break;
case 2:
item->setText(QString::number(arg3));
break;
default:
break;
};
ui->tableWidget->setItem(rowCnt,i, item);
// qDebug() << "setItem=" << iRowNum;
mRowNumID[rowCnt+1] = dbid;
}
qDebug() << "on_pushButton_clicked ED";
}
// TextEditで入力された値の行を削除して、DBからも削除する。
void MainWindow::on_pushButton_2_clicked()
{
// ここで、TextEditで入力された値の行を削除して、DBからも削除する。
QString inputText = ui->lineEdit->text();
// qDebug() << "inputText= " << inputText;
int rowCnt = ui->tableWidget->rowCount();
if( 0 < inputText.toInt() && inputText.toInt() <= rowCnt ){
QString dbid = mRowNumID[inputText.toInt()];
// qDebug() << "dbid= " << dbid;
QSqlQuery query(db);
⑦ query.exec("delete from shisyutsu where id = " + dbid);
ui->tableWidget->removeRow(inputText.toInt()-1);
}
DisplayDB(cmbDate);
}
void MainWindow::on_comboBox_currentTextChanged(const QString &arg1)
{
// qDebug() << arg1 ;
QString sDate = arg1;
cmbDate = QDate::fromString(sDate,"yyyy/MM/dd");
// qDebug() << "comb date" << cmbDate.toString(Qt::LocalDate);
⑧ DisplayDB(cmbDate);
}
QPushButtonクラスやQComboBoxクラスなどの解説については、ここでは省略して、
QSqlDatabaseクラスやQSqlQueryクラスの使い方をメインに解説していきます。
ただ、上記プログラム中にコメントを入れていますので、動作としては理解できると思います。
2.1. ① データベースを作成する。
まずは、SQLiteの使用とデータベース名を引数として、データベースを作成します。
db = QSqlDatabase::addDatabase("QSQLITE",DataBase);
そして、この関数の戻り値は、このプログラム内でデータベースを使用する際のハンドラとなりますので、
プログラム内のどこからでも使用できるように、グローバル変数にします。
次に、データベースのデータを保存するためのファイルを作成します。
db.setDatabaseName("./kakeibo.db");
ここでは、データベースファイルであること示すために、拡張子をdbとしていますが、特に決まりはありません。
また、ファイルが生成されるパスは、アプリ実行ファイルと同じフォルダにしており、
build-(プロジェクト名)-unknown-Debugフォルダに生成されます。
なので、データベースファイルを削除したい場合は、
コマンドラインから、このフォルダに移って、rmコマンドで手作業で削除します。
データベースファイルが作成できましたので、このファイルにアクセスするために下記を行います。
db.open();
アプリを起動している間は、ずっとオープン状態にしておきます。
アプリを終了する際に、MainWindowクラスのデストラクタで、クローズします。
2.2. ② データベースのテーブルを作成する。
データベースファイルが作成できましたので、データベース内にテーブルを作成します。
QSqlQuery query(db);
query.exec("create table shisyutsu(id INTEGER PRIMARY KEY, date text, item text, money int)");
データベースに対して、何かしらの操作を行う場合は、
QSqlQueryクラスのコンストラクタでデータベースのハンドラを引数にして変数宣言しておきます。
exec()関数で、SQLのコマンドを実行することができます。
また、SQLのcreateコマンドで、テーブル名をshisyutsu、int型のidをプライマリキーとすることで、
1行のデータを一意として扱うことができ、番号は1から順に割り当てられます。
そのテーブル内のデータとして、text型のdate、text型のitem、int型のmoneyとしてデータベースを作成します。
text型は、文字列で、int型は、整数としてデータを扱います。
テーブル内のデータについて、ここでは、日付、カテゴリ、金額としていますが、
自分で作成する場合には、どのようなデータにするか最初に検討しておくと良いです。
2.3. ③ データベースにデータを追加する。
このプログラムで使用するデータベースのデータをプログラムを実行した時点で作成し、DBdateCreate()関数で作成します。
1のデータにつき、3つの要素が必要なので、下記のようにある程度データ固定で作成します。
QDate date = QDate::currentDate();
QString arg1 = (date.addDays(-2)).toString(Qt::LocalDate);
QString arg2 = "スーパー";
int arg3 = 100;
ここでは、初期データとして、10個のデータをデータベースに追加しますので、
上記のデータをベースにarg1やarg3の値を変えて追加していきます。
そして、SQLのinsertコマンドを使用して、下記のようにデータを追加します。
query.prepare("insert into shisyutsu (date,item,money) values (:date,:item,:money)");
query.bindValue(":date",arg1);
query.bindValue(":item",arg2);
query.bindValue(":money",arg3);
query.exec();
この関数で作成されたデータベースの中身を見てみると下図のようになります
次に表にデータを追加する処理ですが、これはQTableWidgetクラスの使い方なので、
このブログで書いている、QTableWidgetクラスの使い方をプログラムをもとに解説 - 水瓶座列車
を参考にしてみてください。
2.4. ④ データベース内のデータを表示する。
DisplayDB()関数で、引数で渡された日付をもとにデータベースからその日付のデータを取得して、
QTableWidgetクラスで作成した表に、表示します。
データを表に表示する前に、まず、tableClear()関数で表のデータを全クリアします。
tableClear()関数では、現在表示されているデータの行数を取得して、行数が0になるまで、行を削除しています。
QTableWidgetクラスにclear()関数やclearContents()関数がありますが、
私がイメージする動作になりませんでしたので、tableClear()関数を自作しました。
データベース内から指定した日付のデータを全て取得するには、SQLのselectコマンドを使用します。
query.exec("select * from shisyutsu where date = '" + date.toString(Qt::LocalDate) + "'");
上記のdate.toString(Qt::LocalDate)の値は、例えば「2023/07/17」などの日付の文字列になるのですが、
SQLで実行する場合にはシングルクォーテーションで囲む必要があります。
そして、データベースからSELECTコマンドで抜き出したデータを1データつづ取得するために、query.nex()を行います。
while (query.next()) {
query.next()は、データが無い場合は、戻り値にfalseを返します。
なので、while文でデータが無くなるまでqueryクラスからデータを取得します。
queryクラスのデータ取得は、query.value()関数を使用しますが、データベースのカラム番号を引数に取得することができます。
イメージとしては、下図です。
そして、取得できたデータを順番に表に追加していきます。
表にデータを追加する処理の説明については、QTableWidgetクラスの使い方なので、説明は省略します。
2.5. ⑤ データベースのID番号と行番号を紐づけて管理する。
表からデータを削除する処理と連動して、データベースからもそのデータを削除しますが、
データベースからデータを削除する際には、このプログラムではデータベースのID番号を使用します。
なので、データベースのデータを表に表示する処理と同時に、行番号とデータベースのID番号を紐づけて、C++のコンテナのmap<>で管理します。
例えば、このプログラムを起動した時に表示されるデータでは、下図の赤枠の部分をmap<>で管理します。
ここで注意することは、行番号は1から、データベースのID番号も1から、
QTableWidgetの行番号は、0から始まるので、気を付けてください。
map<>変数は、下記のようにグローバル変数で宣言します。
static std::map<int, QString> mRowNumID;
map<>のキーは、行番号をint型で保持し、キーに対する値は、データベースID番号をQString型で保持します。
データベースID番号をint型ではなく、QString型で保持する理由は、
SQLのコマンド命令をexec()関数で実行する際に文字列にする必要があり、
int型にしたとしても結局、QString型に変換する必要があるためです。
mRowNumID[iRowNum+1] = arg1;
上記で、iRowNum変数は、QTableWidgetのセルの行番号ですが、
mRowNumIDでは行番号にしたいので、1を加算しています。
2.6. ⑥ 「登録」ボタン押下でデータを追加する。
作成しようとしている家計簿アプリでは、「登録」ボタンを押下した際にデータを追加します。
このプログラムでは、新規データ追加をon_pushButton_clicked()関数で行います。
追加するデータは、ここではユーザー入力からではなく、ボタン押下処理の中で、
下記のように、ある程度固定した値を追加します。
QString arg1 = cmbDate.toString(Qt::LocalDate); // コンボBoxの日付で追加する
QString arg2 = "スーパー";
int arg3 = money;
そして、③と同様の処理で、データベースと表にデータ追加を行い、同時にデータベースID番号と行番号を関連づけます。
ここで追加したデータは、データベースの最後尾に追加することになりますので、
下記の関数を使用して、データベースの最後尾のID番号を取得します。
QString dbid = query.lastInsertId().toString();
次に、表に表示されているデータの行数を取得して、上記で取得したデータベースのID番号と紐づけます。
そして、表の最後尾にデータを追加し表示します。
2.7. ⑦ 「削除」ボタン押下で、データを削除する。
入力欄に、行番号を入力して、「削除」ボタンを押下した際に、対象のデータを表とデータベースから削除します。
この処理は、on_pushButton_2_clicked()関数で行います。
「削除」ボタン押下時の処理としては、まず入力された値が表に表示されている行番号のみを処理するようにします。
そして、⑤のmap<>で管理しているデータから入力値をキーとして、データベースのID番号を取得します。
QString dbid = mRowNumID[inputText.toInt()];
取得したデータベースID番号を指定して、データベースから対象のデータを削除します。
query.exec("delete from shisyutsu where id = " + dbid);
表からも対象のデータを削除します。
ui->tableWidget->removeRow(inputText.toInt()-1);
表からデータを削除する場合、QTableWidgetクラスの行番号は0から始まるので、
入力値から1を引いた値をremoveRow()の引数に指定します。
データを削除すると、map<>内の行番号とデータベースのID番号の関連が変わりますので、
DisplayDB()関数をコールして、map<>の値を更新します。
2.8. ⑧ 指定した日付のデータを表示する。
作成しようとしている家計簿アプリでは、カレンダーから指定した日付のデータを表示する操作があります。
ここでは、カレンダーの代わりにコンボボックス(QComboBoxクラス)を使用して、
本日、昨日、一昨日の三日間の日付を選択できるようにしておきます。
そして、コンボボックスで日付を選択すると、指定した日付のデータをデータベースから取得して表に表示します。
コンボボックスの値に変更があった際に、処理を行うためのシグナル関数の
on_comboBox_currentTextChanged(const QString &arg1)関数を使用します。
この関数の引数で渡された日付を、DisplayDB()関数の引数にしてコールし、
データベースからのデータを取得後、表にデータ表示します。
3.QtでSQLiteを使用したプログラムの実行
2項のプログラムを実行すると下記のように表示されます。
表に表示されているデータは、初期データの本日(2023年7月17日)のデータのみが表示されています。
まずは、「登録」ボタンを押して、表にデータが追加されることを確認します。
次に、入力欄に削除したい行番号を入力して、「削除」ボタンを押します。
ここでは、2行目を削除するために、2を入力して「削除」ボタンを押して、表からデータが削除されることを確認します。
表ではデータの追加、削除が確認できますが、データベース上で追加・削除ができているか確認できませんので、
コンボボックスで、本日以外の日付を選択後、再度、本日の日付を選択します。
その表示が先ほどの操作後の表示と同じになっているか確認します。
コンボボックスから、一旦、過去の日付を選択します。
その後、コンボボックスから本日の日付を選択します。
先ほど追加や削除を行ったデータが表に表示されていることを確認します。
以上が、家計簿アプリの操作を考慮した、QtでのSQLiteの使い方になります。
プログラム実行中にデータベースの操作が正確に行われているかを確認するには、
デバッグログを入れて、動作確認をすると良いです。
4.最後に
QtでのSQLiteの使い方の解説について、QTableWidgetクラスも絡めたプログラムにしましたので、
かなり長文になりましたが、実際にデータベースを使用したアプリを作成する際には、このような感じの使い方になると思います。
<関連・おすすめ記事>
Linux勉強用の中古パソコンおすすめショップランキング - 水瓶座列車
無線で複数台接続できるトラックボールマウスのおすすめランキングベスト5 - 水瓶座列車
画面分割できるパソコンディスプレイのおすすめランキングベスト5 - 水瓶座列車
Bluetoothでマルチペアリング(複数台接続)できるマウスのおすすめランキングベスト5 - 水瓶座列車