Application with both GUI and continuously running elements

I’m new to root so please forgive me if the answer to this is obvious. I have been able to use example2b in the documentation as a starting point to create an application with buttons, text entry, etc., but the program doesn’t do anything unless there is user input. I have also written a program that continuously reads files and updates graphs, etc., but with no buttons or other input devices. I would like to combine the two elements. I would like to write an application which reacts to user input (button clicks, etc.), but while there is no input, it should be continuously updating graphs and performing other tasks instead of simply waiting for user input.

I’m pretty sure there is a way to do this, but I’m not sure how to go about doing it. Can someone please point me to a part of the manual which says how to do this, or provide an example of how I can modify example2b to make this work. Any hints would be greatly appreciated.

Thanks.

Hi,

You can take a look at the examples in $(ROOTSYS)/tutorials and $(ROOTSYS)/test.
The general rule is to create a TApplication and to call TApplication::Run() in order to process events. And to update graphs and/or perform any other tasks, you can use timers or threads.
If you need more help or if you have problem with your code, please post it here, so we can explain what is wrong and how to do it right :wink:

Cheers, Bertrand.

Here is an example based on example2b, using a timer:

#include "TApplication.h"
#include "TGClient.h"
#include "TCanvas.h"
#include "TF1.h"
#include "TRandom.h"
#include "TGButton.h"
#include "TRootEmbeddedCanvas.h"
#include "TH1F.h"
#include "TFormula.h"
#include "TTimer.h"
#include "TGFrame.h"

class MyMainFrame : public TGMainFrame {
private:
   TRootEmbeddedCanvas *fEcanvas;
   TH1F                *fH1f;
   TTimer              *fTimer;             // Timer used for update

public:
   MyMainFrame(const TGWindow *p, UInt_t w, UInt_t h);
   virtual ~MyMainFrame() { }
   void     DoDraw();
   Bool_t   HandleTimer(TTimer *);

   ClassDef(MyMainFrame, 0)
};

//______________________________________________________________________
MyMainFrame::MyMainFrame(const TGWindow *p,UInt_t w,UInt_t h)
      : TGMainFrame(p,w,h), fH1f(0), fTimer(0)
{
   // Creates widgets of the example
   fEcanvas = new TRootEmbeddedCanvas ("Ecanvas",this,200,200);
   AddFrame(fEcanvas, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY,
            10,10,10,1));
   TGHorizontalFrame *hframe=new TGHorizontalFrame(this, 200,40);
   TGTextButton *draw = new TGTextButton(hframe,"&Draw");
   draw->Connect("Clicked()","MyMainFrame",this,"DoDraw()");
   hframe->AddFrame(draw, new TGLayoutHints(kLHintsCenterX,5,5,3,4));
   TGTextButton *exit = new TGTextButton(hframe,"&Exit ", "gApplication->Terminate()");
   hframe->AddFrame(exit, new TGLayoutHints(kLHintsCenterX,5,5,3,4));
   AddFrame(hframe,new TGLayoutHints(kLHintsCenterX,2,2,2,2));
   // Sets window name and shows the main frame
   SetWindowName("Simple Example");
   MapSubwindows();
   Resize(GetDefaultSize());
   MapWindow();
   // create a timer firing every 100 ms
   if (!fTimer) fTimer = new TTimer(this, 100);
}

//______________________________________________________________________
void MyMainFrame::DoDraw()
{
   // Draws function graphics in randomly choosen interval
   TCanvas *c1 = fEcanvas->GetCanvas();
   c1->cd(1);

   if (!fH1f) {
      TFormula *form1 = new TFormula("form1","abs(sin(x)/x)");
      TF1 *sqroot = new TF1("sqroot","x*gaus(0) + [3]*form1",0,10);
      sqroot->SetParameters(10,4,1,20);
      fH1f = new TH1F("h1f","Test random numbers",100,0,10);
      fH1f->SetFillColor(45);
   }

   fH1f->Reset();
   fH1f->FillRandom("sqroot",10000);
   fH1f->Draw();
   // TCanvas::Update() draws the frame, after which one can change it
   c1->Update();
   fTimer->Reset();
   fTimer->TurnOn();
}

//______________________________________________________________________________
Bool_t MyMainFrame::HandleTimer(TTimer *)
{
   // timer handling.

   TCanvas *c1 = fEcanvas->GetCanvas();
   if (fH1f) {
      fH1f->Reset();
      fH1f->FillRandom("sqroot",10000);
   }
   c1->Modified();
   c1->Update();
   fTimer->Reset();
   return kTRUE;
}

#ifndef __CINT__
//______________________________________________________________________________
int main(int argc, char *argv[])
{
   TApplication theApp( "TestDatePlot", &argc, argv );

   new MyMainFrame(gClient->GetRoot(), 800, 600);

   theApp.Run();
}
#endif

you can compile it, or use ACLiC. To use ACLiC, start root, and then:

root [0] .L example2b.cxx+
root [1] new MyMainFrame(gClient->GetRoot(), 800, 600)

The GUI will popup. Pressing the “Draw” button will display an histo. Then the canvas will keep updating every 100 ms.

Cheers, Bertrand.

Thanks for the example. It works under root, but I am wondering how to compile it as a standalone. What command do I use for that? I’m getting errors when I try to compile using g++ root-config --cflags --glibs

Thanks.

Hi,

  1. Move the class definition in example2b.h:
#include "TGFrame.h" 

class TRootEmbeddedCanvas;
class TH1F;
class TTimer;

class MyMainFrame : public TGMainFrame { 
private: 
   TRootEmbeddedCanvas *fEcanvas; 
   TH1F                *fH1f; 
   TTimer              *fTimer;             // Timer used for update 
   
public: 
   MyMainFrame(const TGWindow *p, UInt_t w, UInt_t h); 
   virtual ~MyMainFrame() { } 
   void     DoDraw(); 
   Bool_t   HandleTimer(TTimer *); 
   
   ClassDef(MyMainFrame, 0) 
}; 
  1. Add #include “example2b.h” in example2b.cxx
  2. Create a LinkDef2b.h file:
#ifdef __CINT__ 

#pragma link off all globals; 
#pragma link off all classes; 
#pragma link off all functions; 

#pragma link C++ class MyMainFrame+; 

#endif 

Then you can create a a makefile (look the Makefile in $ROOTSYS/test) or do it by hand:

  • Generate the dictionary for the class MyMainFrame:
rootcint -f example2bDict.cxx -c example2b.h LinkDef2b.h

Compile & link your application:

g++ `root-config --cflags --glibs` -o example2b example2b.cxx example2bDict.cxx

Cheers, Bertrand.

Thanks very much for the example. I have been able to incorporate it into the application I am developing. The only trouble I am having is that I want to have 2 graphs. I have started out by adding a canvas called fEcanvas2 and defining it in a similar way to fEcanvas. However, when I do this, the plotting no longer works properly. I don’t understand what is wrong. Here is my modified code:

#include "TApplication.h" 
#include "TGClient.h" 
#include "TCanvas.h" 
#include "TF1.h" 
#include "TRandom.h" 
#include "TGButton.h" 
#include "TRootEmbeddedCanvas.h" 
#include "TH1F.h" 
#include "TFormula.h" 
#include "TTimer.h" 
#include "TGFrame.h" 

class MyMainFrame : public TGMainFrame { 
private: 
   TRootEmbeddedCanvas *fEcanvas; 
   TRootEmbeddedCanvas *fEcanvas2; 
   TH1F                *fH1f; 
   TTimer              *fTimer;             // Timer used for update 

public: 
   MyMainFrame(const TGWindow *p, UInt_t w, UInt_t h); 
   virtual ~MyMainFrame() { } 
   void     DoDraw(); 
   Bool_t   HandleTimer(TTimer *); 

   ClassDef(MyMainFrame, 0) 
}; 

//______________________________________________________________________ 
MyMainFrame::MyMainFrame(const TGWindow *p,UInt_t w,UInt_t h) 
      : TGMainFrame(p,w,h), fH1f(0), fTimer(0) 
{ 
   // Creates widgets of the example 
   fEcanvas = new TRootEmbeddedCanvas ("Ecanvas",this,200,200); 
   AddFrame(fEcanvas, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 
            10,10,10,1)); 
   fEcanvas2 = new TRootEmbeddedCanvas ("Ecanvas2",this,200,200); 
   AddFrame(fEcanvas2, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 
            10,10,10,1)); 
   TGHorizontalFrame *hframe=new TGHorizontalFrame(this, 200,40); 
   TGTextButton *draw = new TGTextButton(hframe,"&Draw"); 
   draw->Connect("Clicked()","MyMainFrame",this,"DoDraw()"); 
   hframe->AddFrame(draw, new TGLayoutHints(kLHintsCenterX,5,5,3,4)); 
   TGTextButton *exit = new TGTextButton(hframe,"&Exit ", "gApplication->Terminate()"); 
   hframe->AddFrame(exit, new TGLayoutHints(kLHintsCenterX,5,5,3,4)); 
   AddFrame(hframe,new TGLayoutHints(kLHintsCenterX,2,2,2,2)); 
   // Sets window name and shows the main frame 
   SetWindowName("Simple Example"); 
   MapSubwindows(); 
   Resize(GetDefaultSize()); 
   MapWindow(); 
   // create a timer firing every 100 ms 
   if (!fTimer) fTimer = new TTimer(this, 100); 
} 

//______________________________________________________________________ 
void MyMainFrame::DoDraw() 
{ 
   // Draws function graphics in randomly choosen interval 
   TCanvas *c1 = fEcanvas->GetCanvas(); 
   c1->cd(1); 

   if (!fH1f) { 
      TFormula *form1 = new TFormula("form1","abs(sin(x)/x)"); 
      TF1 *sqroot = new TF1("sqroot","x*gaus(0) + [3]*form1",0,10); 
      sqroot->SetParameters(10,4,1,20); 
      fH1f = new TH1F("h1f","Test random numbers",100,0,10); 
      fH1f->SetFillColor(45); 
   } 

   fH1f->Reset(); 
   fH1f->FillRandom("sqroot",10000); 
   fH1f->Draw(); 
   // TCanvas::Update() draws the frame, after which one can change it 
   c1->Update(); 
   fTimer->Reset(); 
   fTimer->TurnOn(); 
} 

//______________________________________________________________________________ 
Bool_t MyMainFrame::HandleTimer(TTimer *) 
{ 
   // timer handling. 

   TCanvas *c1 = fEcanvas->GetCanvas(); 
   if (fH1f) { 
      fH1f->Reset(); 
      fH1f->FillRandom("sqroot",10000); 
   } 
   c1->Modified(); 
   c1->Update(); 
   fTimer->Reset(); 
   return kTRUE; 
} 

#ifndef __CINT__ 
//______________________________________________________________________________ 
int main(int argc, char *argv[]) 
{ 
   TApplication theApp( "TestDatePlot", &argc, argv ); 

   new MyMainFrame(gClient->GetRoot(), 800, 600); 

   theApp.Run(); 
} 
#endif 

Could you please tell me what I have done wrong?

Thanks again.

Hi,

Just replace “c1->cd(1);” by “c1->cd();” in the code, as shown below:

//______________________________________________________________________ void MyMainFrame::DoDraw() { // Draws function graphics in randomly choosen interval TCanvas *c1 = fEcanvas->GetCanvas(); c1->cd(); // <----- and not c1->cd(1); !!! [...]
This was a copy & paste typo, sorry :blush:

Cheers, Bertrand.

Thanks for the correction. It works fine now. I just have one more question for now. Is it possible to set the timer running and have it do what it needs to do every so many milliseconds, even if there are other functions being started by other buttons which may take longer than the timer interval? Right now, the timer function seems to be suspended while the other button functions take priority. Is there a way to set the priority of the timer function so it does what it needs to do no matter what else is going on?

Thanks.

Hi,

You can use different threads, see example macros in $ROOTSYS/tutorials/thread. But this is a bit more complex and requires proper locking mechanism (as you can see in those examples).

Cheers, Bertrand.

Thanks for the advice. I will try to use threads, but I’d like to know more about how they work before I put them into my rather complex program. I found some examples on

www-linux.gsi.de/%7Ego4/HOWTOthr … sbody.html

I have downloaded the files for Example TMhs3, but when I try to compile, I get messages like:

Undefined symbols:
  "TCanvasImp::ShowMembers(TMemberInspector&, char*)", referenced from:
      vtable for TCanvasImpin TMhs3.o
      vtable for TCanvasImpin TMhs3Dict.o
  "TPad::TPad(char const*, char const*, double, double, double, double, short, short, short)", referenced from:
      TMhs3::CreateGraphics()      in TMhs3.o
      TMhs3::CreateGraphics()      in TMhs3.o
  "_G__getstructoffset", referenced from:
      G__TMhs3Dict_319_0_16(G__value*, char const*, G__param*, int)in TMhs3Dict.o
      G__TMhs3Dict_319_0_22(G__value*, char const*, G__param*, int)in TMhs3Dict.o
      G__TMhs3Dict_319_0_23(G__value*, char const*, G__param*, int)in TMhs3Dict.o
  "TThread::Lock()", referenced from:
      TMhs3::Func0()     in TMhs3.o
      TMhs3::Func0()     in TMhs3.o
      TMhs3::Func1()     in TMhs3.o
      TMhs3::Func1()     in TMhs3.o
      TMhs3::Func2()     in TMhs3.o
  "_G__tagtable_setup", referenced from:
      _G__cpp_setup_tagtableTMhs3Dict in TMhs3Dict.o
  "TCanvas::TCanvas(char const*, char const*, int, int)", referenced from:
      TMhs3::CreateGraphics()      in TMhs3.o
  "_G__tag_memvar_reset", referenced from:
      G__setup_memvarTMhs3()     in TMhs3Dict.o
  "TObject::operator delete(void*)", referenced from:
      TMhs3::CreateGraphics()      in TMhs3.o
      TMhs3::CreateGraphics()      in TMhs3.o
      TMhs3::CreateGraphics()      in TMhs3.o
      TMhs3::CreateGraphics()      in TMhs3.o
      TMhs3::Func0()     in TMhs3.o
      TMhs3::Func1()     in TMhs3.o
      TMhs3::Func1()     in TMhs3.o
      TMhs3::Func2()     in TMhs3.o
      TMhs3::Func2()     in TMhs3.o
      TMhs3::Func2()     in TMhs3.o
      TMhs3::Func2()     in TMhs3.o

That represents a small subset of all the error messages. I have tried running rootcint manually to create the Dict file, but I get the same messages. Is there something I’m missing? I am using “make -f Makefile.TMhs3” to compile.

Thanks.

Well, maybe your examples are not up to date, or not compatible with your version of ROOT… Anyway, you should first start with the ROOT examples. Just take a look at $ROOTSYS/tutorials/thread and $ROOTSYS/test/threads.cxx.

Cheers, Bertrand.

I have looked at the examples you mentioned and they were somewhat helpful, but unfortunately don’t contain much info about what each line does. I think I need to see something with some of the components in the program I have (such as the buttons) with the threads. That’s why I want to compile the examples I downloaded. They are linked to in the Threads chapter of the ROOT user guide. I have tried to compile them on two different computers but it doesn’t work. I just need to know why there are so many undefined symbols when I compile. Wouldn’t the latest version of ROOT be backward compatible with programs written for previous versions? Thanks.

Hi,

This is not a backward compatibility issue, but a makefile issue. Just try with the Makefile.TMhs3 in attachment.
And FYI, we are not maintaining all examples coming from external web sites (how could we?). And these examples you are talking about are about ten years old.
Then this TMhs3run example is doing the same than $ROOTSYS/test/threads is doing…
If you describe exactly what you want to achieve, I could prepare a simple example.

Cheers, Bertrand.

I can’t see any attachment. Where can I find that?

What I want to do is make an application with buttons and graphs. The buttons initiate communication with an outside device which switches other devices on and off. I have it so that while there is a connection to the device, the buttons are a certain colour depending on whether a given device is switched on or off. The program also reads quantities such as temperatures and voltages from the device, and these are plotted on the graphs.

So far I have the buttons switching the devices on and off, and changing colour as they should, but I’d also like something running continuously, retrieving and plotting the temperatures and voltages on the graphs. I almost had that using your previous example with the timers, but I have a couple of buttons which switch on a large number of devices at once, and they wait a second between each device (in order not to draw too much current at once and to allow each device to run through its initiation process). Therefore, these buttons take many seconds to execute, and while this is happening, the reading and plotting is suspended. I would like the reading and plotting to run continuously no matter what, and I would also like each button to turn green when the corresponding device is switched on, instead of all buttons turning green at once when the button is finished executing the “all on” command.

In your example, if you can replace the timer that does the plotting with a thread that can run continuously, and add a button to do something that takes a few seconds (even something simple like printing something to the screen then waiting 5 seconds then printing something else to the screen), while the plotting is still being done continuously, I can build on that and I think I will be able to achieve what I want. My attempts at adding threads to my program have so far not been entirely successful. Is what I want to do possible?

Thanks.

Sorry, there was a problem with the file extension… So here it is (hopefully)
And I’ll prepare a simple example and post it here.
Makefile.TMhs3.txt (2.46 KB)

See also: root.cern.ch/phpBB2/viewtopic.php?t=9634
And let me know if it is still not sufficient…

Cheers, Bertrand.

Thanks for the link. I downloaded myClass.C, but when I try to run it (with .x myClass.C) I get the following error:

Error: Symbol innerLoop is not defined in current scope myClass.C:117:
(class myClass)32468000
*** Interpreter error recovered ***

Some of it shows up, but instead of a button I just see a red rectangle that doesn’t do anything. Am I doing something wrong?

Thanks.

The script has to be compiled with ACLiC in order to run properly:

root [0] .L myClass.C+ root [1] new myClass()
– Bertrand.

This is what I get after the second of those commands:

Error in TMutex::UnLock: thread -1610160352 tries to unlock unlocked mutex
(class myClass*)0x28a55f0

The program seems to run, but then there is no way to stop it, as far as I can tell. I get a bunch of errors when I kill the window. There is no menu along the top.

Is the above error something I should be worried about? How should I stop the program?

Thanks.

OK, this macro has some problems (but apparently it works on MAC…).
Anyway, it is not a very good example anyway. Could you try the one in attachment instead, which is your modified original macro. It shows a quick and dirty implementation to show how to use a thread instead of a timer… Usage:

root [0] .L MyMainFrame.C+ root [1] new MyMainFrame(gClient->GetRoot(), 800, 600);
Cheers, Bertrand.
MyMainFrame.C (4.05 KB)