c++qtroot-frameworktcanvas

Why Will ROOT Only Let Me Set The Final Element of a TCanvas Vector to Another TCanvas Vector if I Pass the Vector to Another Class?


The Problem

I want to start dynamically passing a TCanvas from essentially the "Model" part of my program to the "View" part. The way I thought about doing this, was to simply create my TCanvas on the View at startup, then update this TCanvas with the View TCanvas once the graphs had been filled. I created a test bench to see if it would work.

I've displayed a working method and the broken method.

I'm using QT-ROOT, the TQtWidget is a custom widget which is essentially a back to the TCanvas.

Setting Up My Canvas

void DataTestTab::setupCanvas(int cNCbc) //I pass "2" to this for now to generate the below loop twice
{
    for (int i=0; i<cNCbc; i++)
    {
        m_vectorCanvas.push_back(new TQtWidget(this));

        //m_vectorCanvas[i]->GetCanvas()->SetFillColor(i);
        QHBoxLayout *loH = new QHBoxLayout(this);

        loH->addWidget(m_vectorCanvas[i]);
        m_vectorLayout.push_back(loH);

        QGroupBox *gbCanvas = new QGroupBox(this);
        QString title = QString("CBC %1").arg(i);
        gbCanvas->setTitle(title);
        gbCanvas->setLayout(m_vectorLayout[i]);
        m_vectorGroupBox.push_back(gbCanvas);

        ui->loCbcBox->addWidget(m_vectorGroupBox[i]); //adding the panels to the main layout
    }
}

This Works

void DataTestTab::drawTest()
    {
        static Int_t HistoID = 1;
        qDebug() << "in Testing env ";
        std::vector<TH1D*> graphs;
        std::vector<TCanvas*> vCanvas;

        TString name("h1_");
        Bool_t build = false;


        for (int i = 0; i <m_vectorCanvas.size() ; i++)
        {
            TCanvas *cCanvas = new TCanvas(build);
            name += HistoID++;

            vCanvas.push_back(cCanvas);
            vCanvas.at(i)->cd();


            TH1D *h1 = new TH1D(name.Data(),name.Data(),10,0, 10);
            graphs.push_back(h1);
            graphs.at(i)->Fill(i);

            graphs.at(i)->Draw();
            //graphs.at(i)->DrawCopy();

            m_vectorCanvas.at(i)->GetCanvas()->SetFillColor(i+5);
            m_vectorCanvas.at(i)->cd();

            qDebug() << i;

            m_vectorCanvas.at(i)->GetCanvas()->SetCanvas(vCanvas.at(i));
            m_vectorCanvas.at(i)->Refresh();
        }
    }

Corresponding output:

Working Method

Albeit the graphs are in the wrong order.

This Does Not Work

I transfer this method to another class and pass the TCanvas back over signal/slots.

 void DataTestWorker::doWork()
    {
        static Int_t HistoID = 1;
        qDebug() << "in Testing env ";
        std::vector<TH1D*> graphs;
        std::vector<TCanvas*> vCanvas;

        TString name("h1_");
        Bool_t build = false;

        for (int i = 0; i <2 ; i++)
        {
            TCanvas *cCanvas = new TCanvas(build);
            name += HistoID++;

            vCanvas.push_back(cCanvas);
            vCanvas.at(i)->cd();

            TH1D *h1 = new TH1D(name.Data(),name.Data(),10,0, 10);
            graphs.push_back(h1);
            graphs.at(i)->Fill(i);

            graphs.at(i)->Draw();

        }
        emit sendGraphData(vCanvas); //void sendGraphData(const std::vector<TCanvas*> &canvas);

The graph data is then sent to here:

void DataTestTab::drawGraph(const std::vector<TCanvas*> &canvas)
{
    for (int i=0; i<m_vectorCanvas.size(); i++)
    {
        canvas.at(i)->cd();
        m_vectorCanvas.at(i)->cd();
        m_vectorCanvas.at(i)->GetCanvas()->SetCanvas(canvas.at(i));
        m_vectorCanvas.at(i)->Refresh();
        //m_vectorCanvas.at(i)->GetCanvas()->Update();

    }
}

This is the output of this method:

Attempt 2

The only error I can see at the moment is I get this for the SetupTab:

QLayout: Attempting to add QLayout "" to GUI::DataTestTab "DataTestTab", which already has a layout
QLayout: Attempting to add QLayout "" to GUI::DataTestTab "DataTestTab", which already has a layout

Solution

  • I am trying to tackle thing one at a time. Your error message QLayout: Attempting to add QLayout "" to GUI::DataTestTab "DataTestTab", which already has a layout stems from the fact that you are calling this constructor for your QHBoxLayout:

    QHBoxLayout *loH = new QHBoxLayout(this);
    

    If you look at the documentation for QLayout (e.g. http://qt-project.org/doc/qt-4.8/qlayout.html#QLayout) then you are calling the constructor with a parent (in your case this) and the documentation says:

    QLayout::QLayout ( QWidget * parent )
    Constructs a new top-level QLayout, with parent parent. parent may not be 0.
    There can be only one top-level layout for a widget.
    

    So even if you don't explicitly set these layouts as the main layout of your DataTestTab, Qt tries to do this because this is how the constructor is written and this results in the message you are seeing.

    The obvious cure for this problem is to change

    QHBoxLayout *loH = new QHBoxLayout(this);
    

    to

    QHBoxLayout *loH = new QHBoxLayout;
    

    which then calls this constructor:

    QLayout::QLayout ()
    Constructs a new child QLayout.
    This layout has to be inserted into another layout before geometry management will work.
    

    i.e. a QLayout that can be added to another one (and has to in order to work).

    EDIT: I think you can simplify your way of handing over objects. There is no reason to construct a TCanvas for each histogram and pass it around, especially since the TCanvas will not take ownership of your histogram and I believe that when you issue TH1::Draw() even having cd()'ed into the canvas you want, this does not achieve what you are trying to do.

    I played with your code and I could achieve what you want by simply having:

    void DataTestWorker::doWork()
    {
      static Int_t HistoID = 1;
      std::vector<TH1D*> graphs;
      TString name("h1_");
      for (int i = 0; i <2 ; i++)
        {
          name += HistoID++;
          TH1D *h1 = new TH1D(name.Data(),name.Data(),10,0, 10);
          graphs.push_back(h1);
          graphs.at(i)->Fill(i);
        }
      emit sendGraphData(graphs); //void sendGraphData(std::vector<TH1D*>);     
    
    }
    

    and

    void DataTestTab::drawGraph(std::vector<TH1D*> hists)
    {
      for (size_t i=0; i<m_vectorCanvas.size(); i++)
        {
          TH1D *h = hists.at(i);
          m_vectorCanvas.at(i)->cd();
          h->Draw();
          m_vectorCanvas.at(i)->Refresh();
        }
    }
    

    I obviously do not perform any checks to make sure that the std::vector<TH1D*> and std::vector<TQtWidget*> have the same length etc. but with this code I was able to display two histograms on the two TCanvas as you showed in your This Works example.

    Does this help?