Closing Root App without ending program

I have integrated Root into a program that reads data from a DAQ device. The program continuously collects data within a while loop. I want to implement a feature where pressing the “P” key toggles the display of a plot window. In other words, pressing “P” should open the plot window, and pressing “P” again should close it. This almost works but the challenge I’m facing is figuring out how to close the plot window without terminating the entire program. The outline of the code looks like this:

TApplication app("app", nullptr, nullptr);   
TCanvas* canvas = nullptr;
bool isCanvasOpen = false;

while(true){ // data readout loop 

if (p_key_active){
    
    if (!isCanvasOpen){
            canvas = new TCanvas("canvas", "My Canvas", 800, 600);
            isCanvasOpen = true;
        }

    ///////////////////
    // PLOTTING STUFF //
    ////////////////////

} else {
  if (isCanvasOpen) {
      canvas->Close();
      isCanvasOpen = false;
  }

This closes the canvas of the plot properly; however, it leaves a blank Root application window. If I use the Terminate() function to close this window, it shuts down the entire program, which is not the desired behavior. I’m now considering whether I actually need to use the TApplication component. But without it, nothing is displayed at all. I’m looking for guidance on how to properly handle the closing of the plot window without affecting the rest of the program’s functionality.

EDIT: This is the full plotting loop:

  TApplication app("app", nullptr, nullptr);   
  TCanvas* canvas = nullptr;
  bool isCanvasOpen = false;

while(true){
if (p is active){

    if (!isCanvasOpen){
        canvas = new TCanvas("canvas", "My Canvas", 800, 600);
        isCanvasOpen = true;
    }

    TMultiGraph* multiGraph = new TMultiGraph(); 
        
      for (int i = 0; i < numberofplots; i++){

            TGraph* graph = new TGraph(......); 
            
            for (int j = 0; j < numberofpoints; j++){
               graph->SetPoint(...........);

            } 

            multiGraph->Add(graph);

        }

        multiGraph->Draw("A PLC");
        
        canvas->Modified();
        canvas->Update();

        delete multiGraph;
    }

} else {
    if (isCanvasOpen) {
        canvas->Close();
        isCanvasOpen = false;
    }
...
    

Now this shows the plots. Removing TApplication, the code still runs but no plots are displayed. Also adding gSystem->ProcessEvents(); does not help. I assume the TApplication sets some important things automatically which I have to set manually.

Dear @Tim_Buktu ,

I think @bellenot can help you here.

Cheers,
Vincenzo

Try with

((TRootCanvas *)canvas->GetCanvasImp())->CloseWindow();

instead of

canvas->Close();

Hi, thanks for the answer. Unfortunately this does not resolve the issue. This does not close the plotting window and the last plotted multigraph stays on display.

Then please provide a fully functional reproducer

I am trying to get a minimal working example but funny enough this program doesn’t even draw the plots, so again a different behavior. But maybe if this program is fixed I’ll have an answer:

#include <TApplication.h>
#include <TCanvas.h>
#include <TMultiGraph.h>
#include <TGraph.h>
#include <TRootCanvas.h>
#include <chrono>
#include <thread>
#include "TPad.h" 


int main() {

TApplication app("app", nullptr, nullptr);
TCanvas* canvas = nullptr;
bool isCanvasOpen = false;

    while (true) {
        if (!isCanvasOpen) {
            isCanvasOpen = true;
			canvas = new TCanvas("canvas", "My Canvas", 800, 600);
        }

        TMultiGraph* multiGraph = new TMultiGraph();

        for (int i = 0; i < 5; i++) {
            TGraph* graph = new TGraph(500);

            for (int j = 0; j < 500; j++) {
                double random = 1.0 + (rand() % 9); // Generate a random number between 1 and 9
                graph->SetPoint(j, j, random);
            }

            multiGraph->Add(graph);
        }

        multiGraph->Draw("A PLC");
        canvas->Modified();
        canvas->Update();
        
        std::this_thread::sleep_for(std::chrono::seconds(2));

        delete multiGraph;

        if (isCanvasOpen) {
            isCanvasOpen = false;
            canvas->Close();
        }
 
        std::this_thread::sleep_for(std::chrono::seconds(2));
    }

    return 0;
}



What I expect:
A plot of my graphs displayed for 2 seconds. The window closing and being closed for 2 seconds, before the next plot opens and is displayed for 2 seconds again and so on.

What i get:
A blank canvas displayed for 4 seconds, closing and immediately an new blank canvas showing and so on.

Well, that works for me. And BTW, you forgot to tell which platform you are working on and which version of ROOT you are using

Ok, interesting. Sorry, I use Linux Ubuntu 20.04 and ROOT 6.24.

Then I’ll have to try on Linux next week…

Ok thank you. I have also tried the newes root version now, still the same behavior.
Maybe in the meantime somebody using linux ubuntu could confirm?

I tried your “minimal” code on Ubuntu 22.04 on WSL (Windows 10) and I do get a plot on the canvas, it then closes and reopens, draws, etc. in a loop (to stop it I had to crtl+c to kill ROOT, and ROOT complained at the beginning with “Error in TApplication::TApplication: only one instance of TApplication allowed”).
I used ROOT 6.26/10.

//
// $(root-config --cxx --cflags) -O2 -Wall -Wextra -o main main.cxx $(root-config --libs) && ./main
//

#include "TApplication.h"
#include "TROOT.h"
#include "TSystem.h"
#include "TPad.h"
#include "TCanvas.h"
#include "TGraph.h"
#include "TMultiGraph.h"

#include <chrono>
#include <thread>

int main() {
  TApplication app("app", nullptr, nullptr);
  TCanvas* canvas = nullptr;
  bool isCanvasOpen = false;
  
  while (true) {
    TMultiGraph* multiGraph = new TMultiGraph();
    
    for (int i = 0; i < 5; i++) {
      TGraph* graph = new TGraph(500);
      for (int j = 0; j < 500; j++) {
        double random = 1.0 + (rand() % 9); // Generate a random number between 1 and 9
        graph->SetPoint(j, j, random);
      }
      multiGraph->Add(graph);
    }
    
    if (!isCanvasOpen) {
      isCanvasOpen = true;
      canvas = new TCanvas("canvas", "My Canvas", 800, 600);
    }
    
    gROOT->SetSelectedPad(canvas->cd(0)); // for the DrawClone below
    multiGraph->DrawClone("A PLC");
    canvas->Modified(); canvas->Update();
    // gSystem->ProcessEvents(); gSystem->ProcessEvents();
    
    delete multiGraph; // no longer needed
    
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    if (isCanvasOpen) {
      isCanvasOpen = false;
      delete canvas;
      gSystem->ProcessEvents(); gSystem->ProcessEvents();
    }
    
    std::this_thread::sleep_for(std::chrono::seconds(2));
  }
  
  return 0;
}

Hey thank you. It is very strange. I have tested @Wile_E_Coyote 's solution on two different distros now.

On my Ubuntu machine it still does not work. At least now the blank canvas is shown for 2 seconds, then it closes, reopens after 2 seconds and so on. But no plots to be seen.
Funnily, when I run the macro in a root session it still does not work, but when I kill the macro with Ctrl + C suddenly one plot gets drawn and stays on display.

I also tried it on an older Debian distro and there it works as intended.

When testing my earlier minimal example on the debian machine, it again shows a different behavior: The canvas with the plot opens, the plot disappears after 2 seconds leaving a blank canvas. The plot redraws on the canvas and after 2 seconds and so on.

Maybe it depends if you use Xorg/X11 or Wayland (and/or the used window manager).
Maybe @couet knows some tricks.

Ok unfortunately I do not know what this means and/or how to change something.

But I found something else interesting.
I suspect ist has something to do with the fact that the canvas opens and the plot only is drawn on it AFTER 2 seconds. Because when I run this macro:

void plotmacro() {
  
TCanvas* canvas = new TCanvas("canvas", "My Canvas", 800, 600);
TMultiGraph* multiGraph = new TMultiGraph();

for (int i = 0; i < 5; i++) {
  TGraph* graph = new TGraph(500);
  for (int j = 0; j < 500; j++) {
    double random = 1.0 + (rand() % 9); // Generate a random number between 1 and 9
    graph->SetPoint(j, j, random);
  }
  multiGraph->Add(graph);
}

gROOT->SetSelectedPad(canvas->cd(0)); // for the DrawClone below
multiGraph->DrawClone("A PLC");
canvas->Modified(); canvas->Update();

std::this_thread::sleep_for(std::chrono::seconds(2));
  
}

It is exactly what happens. So somehow this sleep command messes things up. Also when I remove the first 2 second sleep command from @Wile_E_Coyote 's solution no canvas at all is showing up. Very strange stuff happening.

Then you create the canvas (draw something) and immediately delete it (no wonder nothing is displayed).

Try to use:

    canvas->Modified(); canvas->Update();
    gSystem->Sleep(100); gSystem->ProcessEvents(); gSystem->Sleep(100); gSystem->ProcessEvents();`
      delete canvas;
      gSystem->Sleep(100); gSystem->ProcessEvents(); gSystem->Sleep(100); gSystem->ProcessEvents();

YES that did the trick! And actually I only need

canvas->Modified(); canvas->Update();
gSystem->Sleep(100); 
gSystem->ProcessEvents();
delete canvas;
gSystem->Sleep(100); 
gSystem->ProcessEvents();

So only calling each once. Do you have an idea what the problem was? Thank you very much!

I recalled some ancient unexplainable hack.

Note: maybe you need this hack only in one place (either after “Update” or after “delete”, try it).

I remember that sometimes two “ProcessEvents” were needed (probably with only a single “Sleep”) with the Linux X11 and especially with the macOS Cocoa backends.

Yes … with Cocoa backend.