retireSakiの日記

たぶん引退した?ソフトウエアエンジニアのブログ

GTKmm (3.0) と glade によるアプリ (9) - 子ウィンドウとインターバルタイマー

 今回は、前回の" GTKmm (3.0) と glade によるアプリ (8) - ダイアログのモダールとモードレス表示 "で作成したプログラムを使って、子ウィンドウを作成し、子ウィンドウ内でインターバルタイマーの起動と停止してみたいと思います。

retiresaki.hatenablog.com

最終的にこんな感じですね。

f:id:retireSaki:20181106045941j:plain

ソースコードは最後に両方の機能を含めたものを公開。(zipダウンロードできます)


■ 子ウィンドウの作成

 基本的に、aboutダイアログで作成した方法と同じです。

1) glade で子ウィンドウをデザインします。

f:id:retireSaki:20181106063356j:plain

 タイマー表示に使うラベル。
 その下はボタングループがあります。
 ボタングループは、左からタイマーのON/OFFボタン、何もしないボタン、子ウィンドウを閉じるボタン

 ※ 詳細はgladeファイルを確認してください。

2) 子ウィンドウクラス Child_Window の作成 (Child_Window.cc, Child_Window.h)

 glade で作成したウィンドウは GtkWindow をベースにしたので、Gtk::Window から Child_Window クラスを派生。
詳細は、ソースコードを見たほうが早いと思います。

3) メインウィンドウクラスの修正

(1) glade ファイルの修正 (paned_test.glade)

 今回は、ボタン4を使いますので、ボタン4のラベルを "child window" に変更。

(2) メインウィンドウ のソースファイルとヘッダーファイルを修正 (MainWindowcc , MainWindow.h)

 ボタン4に関するメンバーの取得とイベント処理の追加


● MainWindow.h

protected:
…
    virtual void on_child_window();
…
    Gtk::Button *m_btn4 = nullptr;
…


● MainWindow.cc

void MainWindow::setInit_display()
{
…
    refBuilder->get_widget("btn4", m_btn4);
…
    m_btn4->signal_clicked().connect(
        sigc::mem_fun(*this, &MainWindow::on_child_window));
…
}
…
void MainWindow::on_child_window()
{
    Child_Window *child_window = nullptr;

    refBuilder->get_widget_derived("childWindow", child_window);
    child_window->set_transient_for(*this);
    child_window->show();
}

 ボタンのクリック処理関連は、今まで通りなので説明は不要でしょう。
 今回追加した子ウィンドウの所持関数が、on_child_window() です。
やってることは、aboutダイアログのときと同じです。


■ インターバルタイマーを使う

 タイマー関数にはいくつかありますが、今回はインターバルタイマー関数を使います。
sigc::connection Glib::SignalTimeout::connect (
const sigc::slot< bool > & slot,
unsigned int interval,
int priority = PRIORITY_DEFAULT
)
  slot は、タイマーイベントを受取る関数のアドレス
  interval は、インターバル時間をミリ秒単位で指定
  priority は、プライオリティですが、とくに変更するひsつようがなければ未指定で構いません

 connect の代わりに connect_once を使うと、ワンショットタイマーになります。
タイマーイベントを受取る関数で false を返すとワンショットタイマーとタイマーと同等の機能を実現できます。

 connect の代わりに connect_seconds を使うと、インターバル時間を秒単位で指定することができ、長時間のインターバル処理を実現できます。

 connect の代わりに connect_seconds_once を使うと、長時間のワンショットタイマーになります。
 
 注意点としては、
  インターバルタイマーのときは、イベント処理の関数は bool で戻します。true なら継続、false なら中止です。
  ワンショットrタイマーのときは、イベント処理の関数の戻り値ありません。void です。


● Child_Window.h

public:
…
    virtual ~Child_Window()
    {
        if ( !m_timers.empty() )
            m_timers[m_timers.begin()->first].disconnect();
    }
protected:

    Gtk::Label *m_labeldisp1 = nullptr;
    Gtk::Button *m_btntimer = nullptr;

…
    std::map<int, sigc::connection> m_timers;

private:

    unsigned int untimer_interval = 200; // interval (msec)
    long ntimer_cnt = 0;                 // loop cntvoid on_btntimer();
    bool on_timeout();
…


● Child_Window.cc

void Child_Window::setInit_display()
{
…
    refBuilder->get_widget("btn_timer", m_btntimer);
…
    m_btntimer->signal_clicked().connect(
        sigc::mem_fun(*this, &Child_Window::on_btntimer));
…
    m_btntimer->set_label("start timer");
}
…
void Child_Window::on_btntimer()
{
    if ( m_timers.empty() ){
        // start timer
        ntimer_cnt = 0;
        sigc::connection conn = Glib::signal_timeout().connect(
            sigc::mem_fun(*this, &Child_Window::on_timeout), untimer_interval);
        m_timers[untimer_interval] = conn;

        m_btntimer->set_label("stop timer");
    }
    else{
        // end timer
        m_timers[m_timers.begin()->first].disconnect();
        m_timers.clear();
        m_btntimer->set_label("start timer");

    }
}

bool Child_Window::on_timeout()
{
    // display
    ntimer_cnt++;
    stringstream sstr;
    sstr << ntimer_cnt;
    string str = sstr.str();
    m_labeldisp1->set_text( str );

    // timer true = continuation , false = stop
    return true;
}

 Child_Window.ccは、ほぼ全てのコードがタイマー処理関連ですので、タイマー処理がなければ、ヘッダーファイルにまとめることができます。

 btn_timer に関する部分の説明は不要でしょう。

 作成したタイマーは、
  std::map m_timers;
に保存しています。
これは、m_timers が empty か確認することで、タイマーが稼働中かどうか判断でき、終了時にタイマーが残っていたら削除します。

 void Child_Window::on_btntimer()
はタイマー開始/停止ボタンの処理関数です。

m_timers が empty であれば
 Glib::signal_timeout().connect で、on_timeout() をタイマーイベント関数として、インターバルタイマーを起動。
 タイマーボタンのラベルを "stop timer" に変更。

empty でなければ
タイマーを停止。
 タイマーボタンのラベルを "start timer" に変更。

 bool Child_Window::on_timeout()
は、タイマーイベントで呼び出される関数です。
ntimer_cnt をカウントアップして、label_disp1 に表示しています。
戻り値を true にすることで、インターバルタイマは継続し、false で中止します。


以上です。
インターバルタイマは見た目、面倒くさい感じもしますが意外に簡単に実装できます。

最後に、全てのファイルを圧縮したファイル(zip)を載せます。

●ソース一式のダウンロード

www.mediafire.com

「Amazon.co.jpアソシエイト」または「[乙の名称を挿入]は、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイトプログラムである、Amazonアソシエイト・プログラムの参加者です。