刹那(せつな)の瞬き

Willkömmen! Ich heiße Setsuna. Haben Sie etwas Zeit für mich?

Nanaのthreads::poolを利用してnanodbcの実行結果を表示する

Linux 環境でも ODBC 接続をスレッド内で利用可能なのは確認できました。

もう一歩進めるとスレッドプールやワーカースレッドの話が出てきますが、どうせならGUI を絡めてみたくなりました。

ウィンドウを表示して、そこに結果セットを表示したくなるのは自然だと思います。
今回は GUI framework に Nana C++ Library を利用してみました。

実行結果とソースコード

2 つのクエリを実行し、結果を表示するサンプルです。

開始ボタンをクリックすると、フォームに表示される2つの nana::listbox にそれぞれのクエリの実行結果を表示します。

f:id:infinity_volts:20201206155350g:plain

・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);  // 0.1 sec
        }
    };

    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);  // 0.1 sec
        }
    };

    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 について言及されてたので、それを参考にして、コードを書いてみた次第です。