如何在 ROOT TButton 中使用 C++ lambda 或函数回调

ROOT C++ TButton API 只允许你向 TButton 对象传递 method 字符串,该字符串由 CINT 解释器解释。虽然这在某些方面很灵活,但它不提供任何向按钮传递 lambda 或函数指针的方法。

解决方案是从 TButton 派生自定义类并重写其 TButton::ExecuteEvent() 函数。大部分事件处理程序应从原始 TButton::ExecuteEvent() 函数复制,只有

TButtonWithCallback.cpp
gROOT->ProcessLine(GetMethod());

被替换为

clickCallback_replacement.cpp
clickCallback()

作为回调成员,我们使用 std::function<void()> 对象,它与 C 函数指针不同,能够存储带上下文的 lambda,即向回调传递参数。

完整代码

TButtonWithCallback.hpp

TButtonWithCallback.hpp
#pragma once

#include <TButton.h>
#include <functional>

/**
 * Custom TButton class that allows setting a callback function
 * (instead of an interpreted string) to be called when the button is clicked.
 */
class TButtonWithCallback : public TButton {
    friend class TButton;
public:
    TButtonWithCallback(const char *title, std::function<void()> clickCallback, Double_t x1, Double_t y1, Double_t x2, Double_t y2);
    void ExecuteEvent(Int_t event, Int_t px, Int_t py) override;

private:
    std::function<void()> clickCallback;
    bool fFocused = false;
};

TButtonWithCallback.cpp

TButtonWithCallback_impl.cpp
#include "TButtonWithCallback.hpp"
#include <TROOT.h>
#include <TVirtualPad.h>
#include <TCanvas.h>

TButtonWithCallback::TButtonWithCallback(const char *title, std::function<void()> clickCallback, Double_t x1, Double_t y1, Double_t x2, Double_t y2)
    : TButton(title, "(void)0;", x1, y1, x2, y2), clickCallback(clickCallback) {}

void TButtonWithCallback::ExecuteEvent(Int_t event, Int_t px, Int_t py) {
    //check case where pressing a button deletes itself
    if (ROOT::Detail::HasBeenDeleted(this)) return;

    if (IsEditable()) {
        TPad::ExecuteEvent(event,px,py);
        return;
    }

    auto cdpad = gROOT->GetSelectedPad();
    HideToolTip(event);

    switch (event) {

    case kMouseEnter:
        TPad::ExecuteEvent(event,px,py);
        break;

    case kButton1Down:
        SetBorderMode(-1);
        fFocused = kTRUE;
        Modified();
        Update();
        break;

    case kMouseMotion:

        break;

    case kButton1Motion:
        if (px<XtoAbsPixel(1) && px>XtoAbsPixel(0) &&
            py<YtoAbsPixel(0) && py>YtoAbsPixel(1)) {
            if (!fFocused) {
                SetBorderMode(-1);
                fFocused = kTRUE;
                Modified();
                GetCanvas()->Modified();
                Update();
            }
        } else if (fFocused) {
            SetBorderMode(1);
            fFocused = kFALSE;
            Modified();
            GetCanvas()->Modified();
            Update();
        }
        break;

    case kButton1Up:
        SetCursor(kWatch);
        if (fFocused) {
            TVirtualPad::TContext ctxt(cdpad, kTRUE, kTRUE);
            clickCallback();
        }
        //check case where pressing a button deletes itself
        if (ROOT::Detail::HasBeenDeleted(this)) return;
        SetBorderMode(1);
        Modified();
        Update();
        SetCursor(kCross);
        break;
    }
}

完整使用示例

此示例将代码合并到单个文件中

button_example.cpp
#include <TApplication.h>
#include <TCanvas.h>
#include <TButton.h>
#include <iostream>
#include "TSystem.h"
#include <TROOT.h>
#include <functional>

using std::cout;
using std::endl;

/**
 * Custom TButton class that allows setting a callback function
 * (instead of an interpreted string) to be called when the button is clicked.
 */
class TButtonWithCallback : public TButton {
    friend class TButton;
public:
    TButtonWithCallback(const char *title, std::function<void()> clickCallback, Double_t x1, Double_t y1, Double_t x2, Double_t y2)
        : TButton(title, "(void)0;", x1, y1, x2, y2), clickCallback(clickCallback) {}

    void ExecuteEvent(Int_t event, Int_t px, Int_t py) override {
        //check case where pressing a button deletes itself
        if (ROOT::Detail::HasBeenDeleted(this)) return;

        if (IsEditable()) {
            TPad::ExecuteEvent(event,px,py);
            return;
        }

        auto cdpad = gROOT->GetSelectedPad();
        HideToolTip(event);

        switch (event) {

        case kMouseEnter:
            TPad::ExecuteEvent(event,px,py);
            break;

        case kButton1Down:
            SetBorderMode(-1);
            fFocused = kTRUE;
            Modified();
            Update();
            break;

        case kMouseMotion:

            break;

        case kButton1Motion:
            if (px<XtoAbsPixel(1) && px>XtoAbsPixel(0) &&
                py<YtoAbsPixel(0) && py>YtoAbsPixel(1)) {
                if (!fFocused) {
                    SetBorderMode(-1);
                    fFocused = kTRUE;
                    Modified();
                    GetCanvas()->Modified();
                    Update();
                }
            } else if (fFocused) {
                SetBorderMode(1);
                fFocused = kFALSE;
                Modified();
                GetCanvas()->Modified();
                Update();
            }
            break;

        case kButton1Up:
            SetCursor(kWatch);
            if (fFocused) {
                TVirtualPad::TContext ctxt(cdpad, kTRUE, kTRUE);
                clickCallback();
            }
            //check case where pressing a button deletes itself
            if (ROOT::Detail::HasBeenDeleted(this)) return;
            SetBorderMode(1);
            Modified();
            Update();
            SetCursor(kCross);
            break;
        }
    }

private:
    std::function<void()> clickCallback;
    // Clone of TButton::fFocused which is private
    bool fFocused = false;
};

int main(int argc, char **argv) {
    // Initialize the ROOT application
    TApplication app("ROOT Application", &argc, argv);

    // Create a canvas for displaying the button
    TCanvas *canvas = new TCanvas("canvas", "Button Example", 800, 600);

    // Create a button and set its callback function
    TButtonWithCallback *button = new TButtonWithCallback("Click Me", [&]() {
        cout << "Button clicked!" << endl;
    }, 0.1, 0.1, 0.3, 0.2);
    button->Draw(); // Draw the button on the canvas

    // Update the canvas to display the button
    canvas->Modified();
    canvas->Update();

    // Run the application event loop
    app.Run();

    return 0;
}

使用以下命令编译代码

compile_button_example.sh
g++ -o button_example button_example.cpp `root-config --cflags --libs`

ROOT button lambda example

当你点击按钮时,

button_click_output.txt
Button clicked!

将打印到控制台。


Check out similar posts by category: ROOT, C/C++