How to use C++ lambda or function callback in ROOT TButtons
The ROOT C++ TButton
API only allows you to pass a method
string to a TButton
object which is interpreted by the CINT interpreter. While this is flexible in some regards, it doesn’t provide any means of passing a lambda or function pointer to the button.
The solution is to derive a custom class from TButton
and override its TButton::ExecuteEvent()
function. Most of the event handler should be copied from the original TButton::ExecuteEvent()
function, with only
gROOT->ProcessLine(GetMethod());
being replaced by
clickCallback()
As the callback member, we use a std::function<void()>
object, which is - in contrast to a C function pointer - able to store lambdas with context, i.e. pass parameters to the callback.
Full code
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
#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;
}
}
Complete usage example
This example has the code merged into a single file
#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 the code with
g++ -o button_example button_example.cpp `root-config --cflags --libs`
When you click the button,
Button clicked!
will be printed to the console.