Linux 環境でも ODBC 接続をスレッド内で利用可能なのは確認できました。
もう一歩進めるとスレッドプールやワーカースレッドの話が出てきますが、どうせならGUI を絡めてみたくなりました。
ウィンドウを表示して、そこに結果セットを表示したくなるのは自然だと思います。
今回は GUI framework に Nana C++ Library を利用してみました。
2 つのクエリを実行し、結果を表示するサンプルです。
開始ボタンをクリックすると、フォームに表示される2つの nana::listbox にそれぞれのクエリの実行結果を表示します。
・main.cpp
nana::listbox に表示させたい処理をラムダ式で記述しておいて、それをスレッドプール(nana::threads::pool)に登録しつつ、開始ボタンのクリックイベントに割り当ててます。
#include <nana/gui.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/widgets/listbox.hpp>
#include <nana/gui/widgets/button.hpp>
#include <nana/threads/pool.hpp>
#include <nana/system/platform.hpp>
#include <nanodbc/nanodbc.h>
int main()
{
std::locale::global(std::locale(""));
std::string conn_str = "Driver={ODBC Driver 17 for SQL Server};Server=localhost;UID=sa;PWD=abcd1234$;Database=my_test_db;";
nana::threads::pool th_pool;
nana::form fm;
fm.caption("Nana - nanodbc");
fm.size(nana::size{ 450, 400 });
nana::label lbl1{ fm, "SQLServer接続テスト <bold blue size=12>MS版ODBCドライバ</>" };
lbl1.format(true);
nana::listbox list1{ fm };
list1.append_header("コード");
list1.append_header("氏名");
list1.append_header("入社日");
list1.show_header(true);
list1.auto_draw(true);
auto th1 = [ &conn_str, &list1 ] {
list1.clear();
nanodbc::connection conn(conn_str);
nanodbc::string sql_text(NANODBC_TEXT("SELECT コード,氏名,入社日 FROM 社員 WHERE コード<300 ORDER BY コード"));
nanodbc::result rs = nanodbc::execute(conn, sql_text);
for (const auto& row : rs) {
auto cat = list1.at(0);
auto val_int = row.get<nanodbc::string>("コード");
auto val_str = row.get<nanodbc::string>("氏名");
auto val_date = row.get<nanodbc::string>("入社日");
cat.append(std::initializer_list<nanodbc::string>{ val_int, val_str, val_date });
nana::system::sleep(100);
}
};
nana::listbox list2{ fm };
list2.append_header("コード");
list2.append_header("氏名");
list2.append_header("入社日");
list2.show_header(true);
list2.auto_draw(true);
auto th2 = [ &conn_str, &list2 ] {
list2.clear();
nanodbc::connection conn(conn_str);
nanodbc::string sql_text(NANODBC_TEXT("SELECT コード,氏名,入社日 FROM 社員 WHERE コード>199 ORDER BY 入社日"));
nanodbc::result rs = nanodbc::execute(conn, sql_text);
for (const auto& row : rs) {
auto cat = list2.at(0);
auto val_int = row.get<nanodbc::string>("コード");
auto val_str = row.get<nanodbc::string>("氏名");
auto val_date = row.get<nanodbc::string>("入社日");
cat.push_back(val_int);
cat.back().text(1, val_str);
cat.back().text(2, val_date);
nana::system::sleep(100);
}
};
nana::button btn_start{ fm, "開始" };
btn_start.events().click(nana::threads::pool_push(th_pool, th1));
btn_start.events().click(nana::threads::pool_push(th_pool, th2));
nana::button btn_close{ fm, "閉じる" };
btn_close.events().click([ &fm ] {
fm.close();
});
nana::place fm_place{ fm };
fm_place.div("<><weight=90% vertical<><weight=95% vertical<weight=25 text><weight=5><vertical listboxes gap=5><weight=5><weight=25 <>< weight=40% buttons gap=20><>> ><> ><>");
fm_place["text"] << lbl1;
fm_place["listboxes"] << list1 << list2;
fm_place["buttons"] << btn_start << btn_close;
fm_place.collocate();
fm.show();
nana::exec();
}
少ないコーディング量でフォーム表示までたどり着けるのは良いですね。
フォームのウィジェットはベタに書いてから整形してますが、他にも方法があります。
データベース接続失敗やクエリ失敗時の例外処理、クリック連打対策等、まだ実装してない部分もありますが、とりあえずの目的は達成できました。
・CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(gui_odbc CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Threads REQUIRED)
find_package(nanodbc REQUIRED)
add_executable(gui_odbc main.cpp)
target_link_libraries(gui_odbc PRIVATE Threads::Threads)
target_link_libraries(gui_odbc PRIVATE nana X11 Xft fontconfig)
target_link_libraries(gui_odbc PRIVATE nanodbc)
雑記
いつもなら Qt5 でコーディングするようなネタですが、今回は QODBC ではなくnanodbc を使ってるので、Qt5 である必要はない。それなら wxWidgets でも...と思いましたが、折角の機会なので、初めての Nana C++ Library に挑戦してみました。
これを選んた理由はいくつかありますが、 GUI プログラミングでありがちな「処理が busy 状態だと描画を妨げる」件について、ドキュメントを見つけたのが大きいかも。
ここに Nana の UI thread と nana::threads::pool について言及されてたので、それを参考にして、コードを書いてみた次第です。