Style Sheet ignored on class inherited from QWidget [duplicate] - c++

I have several custom widget in my current project. I wish to apply stylesheets to them and when I do so inside Qt Creator, it appears to work. However, when executing the program, no stylesheet is used. The stylesheets for the Qt widgets are working normally.
Does anyone have any advice?
WidgetUnits.h
#ifndef WIDGETUNITS_H
#define WIDGETUNITS_H
#include <QList>
#include <QWidget>
#include <QPainter>
#include <Widgets/JECButton.h>
#include <Unit.h>
#include <Time.h>
namespace Ui
{
class WidgetUnits;
}
class WidgetUnits : public QWidget
{
Q_OBJECT
public:
explicit WidgetUnits(QWidget *parent = 0);
~WidgetUnits();
void setNumTimes(const int& numTimes);
public slots:
void updatePictures(const Time* time);
protected:
void paintEvent(QPaintEvent *event);
private:
void checkNewQueue(const QList<QList<Unit*>*>* units);
Ui::WidgetUnits *ui;
const int pictureWidth; // The width of the Unit pictures.
const int pictureHeight; // The height of the Unit pictures.
QList<QList<JECButton*>*> buttonPictures; // The Units' pictures. The outer QList stores the QList of pictures for a given tick.
// The inner QList stores the JECButtons for the specific tick.
};
WidgetUnits.cpp
#include "WidgetUnits.h"
#include "ui_WidgetUnits.h"
WidgetUnits::WidgetUnits(QWidget *parent):
QWidget(parent),
ui(new Ui::WidgetUnits),
pictureWidth(36),
pictureHeight(36)
{
ui->setupUi(this);
}
WidgetUnits::~WidgetUnits()
{
delete ui;
}
void WidgetUnits::updatePictures(const Time *time)
{
// Only showing units that started to get built this turn.
checkNewQueue(time->getUnits());
checkNewQueue(time->getBuildings());
checkNewQueue(time->getUpgrades());
// Updating the position of the remaining pictures (after some were removed).
// Checking the maximum number of Units made in one tick.
int maxNewQueue = 0;
for (int a = 0; a < buttonPictures.length(); ++a)
{
if (buttonPictures.at(a)->length() > maxNewQueue)
{
maxNewQueue = buttonPictures.at(a)->length();
}
}
if (buttonPictures.length() > 0)
{
this->setGeometry(0, 0, buttonPictures.length() * 130,
maxNewQueue * (pictureWidth + 10) + 20);
QList<JECButton*>* tickButtons = 0;
for (int a = 0; a < buttonPictures.length(); ++a)
{
tickButtons = buttonPictures.at(a);
for (int b = 0; b < tickButtons->length(); ++b)
{
tickButtons->at(b)->move(a * 130, b * (pictureHeight + 10));
}
}
}
update();
}
void WidgetUnits::checkNewQueue(const QList<QList<Unit *> *> *units)
{
if (units != 0)
{
const Unit* currentUnit = 0;
JECButton* currentButton = 0;
for (int a = 0; a < units->length(); ++a)
{
buttonPictures.append(new QList<JECButton*>());
for (int b = 0; b < units->at(a)->length(); ++b)
{
currentUnit = units->at(a)->at(b);
// Verifying that there is an item in the queue and the queue action was started this turn.
if (currentUnit->getQueue() != 0 && currentUnit->getAction()->getTimeStart() == currentUnit->getAction()->getTimeCurrent()
&& (currentUnit->getAction()->getType() == Action::BUILD || currentUnit->getAction()->getType() == Action::TRAIN ||
currentUnit->getAction()->getType() == Action::UPGRADE))
{
buttonPictures.last()->append(new JECButton(this));
currentButton = buttonPictures.last()->last();
QImage* image = new QImage(currentUnit->getQueue()->getUnitBase()->getImage().scaled(pictureWidth, pictureHeight));
currentButton->setImage(*image);
currentButton->setGeometry(0, 0, currentButton->getImage().width(),
currentButton->getImage().height());
currentButton->setColorHover(QColor(0, 0, 225));
currentButton->setColorPressed(QColor(120, 120, 120));
currentButton->setImageOwner(true);
currentButton->setVisible(true);
}
}
}
}
}
void WidgetUnits::setNumTimes(const int &numTimes)
{
// Appending new button lists for added ticks.
for (int a = buttonPictures.length(); a < numTimes; ++a)
{
buttonPictures.append(new QList<JECButton*>());
}
}
void WidgetUnits::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
}
The widget is visible- I set a tooltip which it showed me (It's just the same color of the QScrollArea it's sitting in).

I had a similar problem and it was solved using jecjackal's comment. As sjwarner said, it would be much more noticeable in the form of an answer. So I'll provide it. For the benefit of any future viewers. Again, it isn't my answer! Appreciate jecjackal for it!
As it is said in the Qt's stylesheets reference, applying CSS styles to custom widgets inherited from QWidget requires reimplementing paintEvent() in that way:
void CustomWidget::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
Without doing it your custom widgets will support only the background, background-clip and background-origin properties.
You can read about it here: Qt Stylesheets reference in the section "List of Stylable Widgets" -> QWidget.

There is an answer much easier than writing your own paintEvent: subclass QFrame instead of QWidget and it will work right away:
class WidgetUnits : public QFrame
{
Q_OBJECT
....

For completeness, the same problem is present in PyQt. You can apply a stylesheet to a subclassed QWidget by adding similar code:
def paintEvent(self, pe):
opt = QtGui.QStyleOption()
opt.init(self)
p = QtGui.QPainter(self)
s = self.style()
s.drawPrimitive(QtGui.QStyle.PE_Widget, opt, p, self)

I had same problem with pyside. I post my solution just for completeness.
It is almost like in PyQt as Pieter-Jan Busschaert proposed.
only difference is you need to call initFrom instead of init
def paintEvent(self, evt):
super(FreeDockWidget,self).paintEvent(evt)
opt = QtGui.QStyleOption()
opt.initFrom(self)
p = QtGui.QPainter(self)
s = self.style()
s.drawPrimitive(QtGui.QStyle.PE_Widget, opt, p, self)
One other thing you need to make sure is that you define your custom widget in your css file the following way:
FreeDockWidget{...}
and not like often recommended
QDockWidget#FreeDockWidget{...}

Calling setAttribute(Qt::WA_StyledBackground, true) for the custom widget worked for me.

Setting Qt::WA_StyledBackground to true only works if you remember to add Q_OBJECT to your class. With these two changes you don't need to reimplement paintEvent.

Related

Is there a way to place a widget into more than one of the tabs of a QTabWidget?

The motivating scenario: I'm working on a Qt/QWidgets C++ app whose GUI is largely arranged into tabs, via a hierarchy of QTabWidgets. Many of these tabs are various agglomerations of the same content, (e.g. there's a tab with widget A and B, a tab with widget B and C, and so on).
I have one particular widget class W which is a fairly heavyweight GUI object and it appears in many (but not all) of the tabs. Currently I handle that by simply creating a separate object of the W class for each tab I want it to appear in, and that basically works, but it's not 100% satisfactory for a couple of reasons:
Since the widget is heavy, creating a number of instances of it slows down the GUI's creation on startup, and uses more system resources than I would like.
Every time the user changes the layout/state of the widget in one tab, I have to manually echo that change to all of its "clones" in the other tabs; otherwise the user will notice the different states of the different W widgets as he moves back and forth from one tab to another. This is doable, but it's a maintenance and testing headache.
So what I'd like to do is create just a single instance of W and have it magically appear in its expected location within whichever tab is currently visible. Since only one tab with W should ever be visible at one time, it seems like a single W instance ought to be enough to accomplish that.
I thought about making a lightweight proxy/container-widget of some sort, and overriding its showEvent() method to setParent() the real W object to be its child as necessary; I think that might work, or it might turn out to be full of gotchas, so I thought I'd ask first if anyone else knows of a more elegant or better-supported way to accomplish the same result.
With a little help I was able to make this technique work, as shown in the example code below. Note that the green label "Shared Widget!" is only created once, but it appears in all 5 tabs (along with various normal QLabels):
#include <QApplication>
#include <QLabel>
#include <QMap>
#include <QSet>
#include <QStackedLayout>
#include <QTabWidget>
#include <QWidget>
/** This is a special container-class that holds a single target widget so that the target widget can be placed
* into more than one QTabWidget at a time. This widget will handle moving the target widget around from proxy
* to proxy as tabs are shown, so that instead of having to create N identical widgets, we can just create one
* target-widget and have it jump from tab to tab as necessary.
*/
class TabProxyWidget : public QWidget
{
public:
/** Constructor
* #param optTargetWidget if non-NULL, this will be passed to SetTargetWidget(). Defaults to NULL.
*/
TabProxyWidget(QWidget * optTargetWidget = NULL)
: _layout(new QStackedLayout(this))
, _targetWidget(NULL)
{
SetTargetWidget(optTargetWidget);
}
virtual ~TabProxyWidget() {SetTargetWidget(NULL);}
/** Set the widget that we want to be a proxy for
* #param optTargetWidget the widget we will proxy for, or NULL to disassociate us from any target widget
* #note the same pointer for (optTargetWidget) can (and should!) be passed to multiple TabProxyWidget objects
*/
void SetTargetWidget(QWidget * optTargetWidget);
virtual void showEvent(QShowEvent *);
virtual bool eventFilter(QObject * o, QEvent * e);
private:
void AdoptTargetWidget();
void UpdateSizeConstraints();
QStackedLayout * _layout;
QWidget * _targetWidget;
};
static QMap<QWidget *, QSet<TabProxyWidget *> > _targetWidgetToProxies;
void TabProxyWidget :: SetTargetWidget(QWidget * targetWidget)
{
if (targetWidget != _targetWidget)
{
if (_targetWidget)
{
_targetWidget->removeEventFilter(this);
QSet<TabProxyWidget *> * proxiesForTargetWidget = _targetWidgetToProxies.contains(_targetWidget) ? &_targetWidgetToProxies[_targetWidget] : NULL;
if ((proxiesForTargetWidget == NULL)||(proxiesForTargetWidget->isEmpty()))
{
printf("TabProxyWidget::SetTargetWidget(NULL): can't proxies-table for target widget %p is %s!\n", targetWidget, proxiesForTargetWidget?"empty":"missing");
exit(10);
}
(void) proxiesForTargetWidget->remove(this);
if (proxiesForTargetWidget->isEmpty())
{
(void) _targetWidgetToProxies.remove(_targetWidget);
delete _targetWidget;
}
else if (dynamic_cast<TabProxyWidget *>(_targetWidget->parentWidget()) == this)
{
proxiesForTargetWidget->values()[0]->AdoptTargetWidget(); // hand him off to another proxy to for safekeeping
}
}
_targetWidget = targetWidget;
if (_targetWidget)
{
if (_targetWidgetToProxies.contains(_targetWidget) == false) _targetWidgetToProxies[_targetWidget] = QSet<TabProxyWidget *>();
_targetWidgetToProxies[_targetWidget].insert(this);
if ((isHidden() == false)||(_targetWidget->parentWidget() == NULL)||(dynamic_cast<TabProxyWidget *>(_targetWidget->parentWidget()) == NULL)) AdoptTargetWidget();
UpdateSizeConstraints();
_targetWidget->installEventFilter(this);
}
}
}
bool TabProxyWidget :: eventFilter(QObject * o, QEvent * e)
{
if ((o == _targetWidget)&&(e->type() == QEvent::Resize)) UpdateSizeConstraints();
return QWidget::eventFilter(o, e);
}
void TabProxyWidget :: UpdateSizeConstraints()
{
if (_targetWidget)
{
setMinimumSize(_targetWidget->minimumSize());
setMaximumSize(_targetWidget->maximumSize());
setSizePolicy (_primaryWidget->sizePolicy());
}
else
{
setMinimumSize(QSize(0,0));
setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
setSizePolicy (QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
}
}
void TabProxyWidget :: showEvent(QShowEvent * e)
{
AdoptTargetWidget();
QWidget::showEvent(e);
if (_targetWidget) _targetWidget->show();
}
void TabProxyWidget :: AdoptTargetWidget()
{
if ((_targetWidget)&&(_targetWidget->parentWidget() != this))
{
QLayout * layout = _targetWidget->layout();
if (layout) layout->removeWidget(_targetWidget);
_targetWidget->setParent(this);
_layout->addWidget(_targetWidget);
}
}
static void SetWidgetBackgroundColor(QWidget * w, const QColor bc)
{
QPalette p = w->palette();
p.setColor(QPalette::Window, bc);
w->setAutoFillBackground(true);
w->setPalette(p);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
QTabWidget * tabWidget = new QTabWidget;
tabWidget->setWindowTitle("Proxy Widget test");
QWidget * proxyMe = new QLabel("Shared Widget!");
SetWidgetBackgroundColor(proxyMe, Qt::green);
int counter = 0;
for (int i=0; i<5; i++)
{
QWidget * nextTab = new QWidget;
QBoxLayout * tabLayout = new QBoxLayout(QBoxLayout::TopToBottom, nextTab);
const int numAbove = rand()%3;
for (int i=0; i<numAbove; i++) tabLayout->addWidget(new QLabel(QString("Unshared label #%1 above").arg(++counter)));
tabLayout->addWidget(new TabProxyWidget(proxyMe));
const int numBelow = rand()%3;
for (int i=0; i<numBelow; i++) tabLayout->addWidget(new QLabel(QString("Unshared label #%1 below").arg(++counter)));
tabWidget->addTab(nextTab, QString("Tab %1").arg(i+1));
}
tabWidget->show();
return app.exec();
}

How do you change a QGraphicsScene from another function?

My program has a QGraphicsView called SimulatorGV and it has a QGraphicsScene called Simulation. In the main function I have drawn a few rectangles and ellipses. I want to be able to draw on the same scene in another function too.
In essence, how do you add shapes to a scene from another function? (Which is called when a button on the user interface is pressed)
So far I've tried to pass the QGraphicsView to the other function? Below is the main ui function (irrelevant statements cut out):
{
ui->setupUi(this);
Simulation = new QGraphicsScene(this);
ui->SimulatorGV->setScene(Simulation);
Simulation->addRect(0,415,20,50,noPen,greyBrush);
Simulation->addRect(425,230,10,420,noPen,goldBrush);
Simulation->addEllipse(80,90,700,700,greyPen,noBrush);
Simulation->addRect(72,215,90,450,noPen,blackBrush);
}
In the header file I declared this function in the private slots:
void DrawingSimulation(QGraphicsView *SimulationGV);
Should it be like this instead?
void DrawingSimulation(QGraphicsScene *Simulation);
I called the function in another function like this:
DrawingSimulation(ui->SimulatorGV);
Should it be like this instead?
DrawingSimulation(ui->SimulatorGV->Simulation);
or?
DrawingSimulation(ui->Simulation);
This is the function I want to be able to draw on the scene from:
void RASP::DrawingSimulation(QGraphicsView *SimulationGV)
{
for (int i = 0; i < DisplayAlphaParticleNumber; i++)
{
if (ParticleLocation[i*6+3] != 0 || ParticleLocation[i*6+6] != 0)
{
ui->SimulatorGV->setScene(Simulation);
Simulation->addEllipse(ParticleLocation[i*6+3],ParticleLocation[i*6+4],10,10);
}
}
}
SimulatorGV is the name of the QGraphicsView in my ui form. RASP is the name of the project. ParticleLocation[i*6+3] is the x coordinate and [i*6+4] is the y coordinate.
Was I right to pass the QGraphicsView onto the varibale instead of the QGraphicsScene Simulation?
Did I pass it correctly?
In the DrawingSimulation function did I add the ellipse correctly?
Edit:
In essence, how do you add shapes to a scene from another function? (Which is called when a button on the user interface is pressed)
When a button is pressed this function is called:
void RASP::on_Button1_clicked()
{
//some values set
//some other functions called then the main one that leads to the drawingsimulation
mainfunction();
}
Then inside the mainfunction():
void RASP::mainfunction()
{
DrawingSimulation(); //is called
}
Now the function DrawingSimulation() which I would like to draw on the original scene in the RASP::RASP() (MyClass::MyClass) is called.
My previous attempt was to have a boolean function that is set true by the button then the addEllipse:
MyClass::MyClass()
{
ui->setupUi(this);
Simulation = new QGraphicsScene(this);
ui->SimulatorGV->setScene(Simulation);
Simulation->addRect(0,415,20,50,noPen,greyBrush);
Simulation->addRect(425,230,10,420,noPen,goldBrush);
Simulation->addEllipse(80,90,700,700,greyPen,noBrush);
Simulation->addRect(72,215,90,450,noPen,blackBrush);
if (SimulationRun == true)
{
for (int i = 0; i < DisplayAlphaParticleNumber; i++)
{
if (ParticleLocation[i*6+3] != 0 || ParticleLocation[i*6+6] != 0)
{
ui->SimulatorGV->setScene(Simulation);
Simulation->addEllipse(ParticleLocation[i*6+3],ParticleLocation[i*6+4],10,10);
}
}
}
}
and then in the button clicked function setting SimulationRun = to true.
Let's keep it simple.
If you have:
MyClass::MyClass() : ui(new Ui::MainWindow)
{
ui->setupUi(this);
Simulation = new QGraphicsScene(this);
ui->SimulatorGV->setScene(Simulation);
Simulation->addRect(0,415,20,50,noPen,greyBrush);
Simulation->addRect(425,230,10,420,noPen,goldBrush);
Simulation->addEllipse(80,90,700,700,greyPen,noBrush);
Simulation->addRect(72,215,90,450,noPen,blackBrush);
}
If this works, then, this will work too:
MyClass::MyClass() : ui(new Ui::MainWindow)
{
ui->setupUi(this);
Simulation = new QGraphicsScene(this); // supposing MyClass has a Simulation attribute of type QGraphicsScene
ui->SimulatorGV->setScene(Simulation);
addItems(); // calling function below
}
void MyClass::addItems()
{
// declared "void addItems();" in header file
addItems( Simulation ); // calling function below that could be static
}
void MyClass::addItems( QGraphicsScene* simu )
{
// declared "static void addItems(QGraphicsScene* simu);" in header file
simu->addRect(0,415,20,50,noPen,greyBrush);
simu->addRect(425,230,10,420,noPen,goldBrush);
simu->addEllipse(80,90,700,700,greyPen,noBrush);
simu->addRect(72,215,90,450,noPen,blackBrush);
}
Then, if this works, you now know how to modify the secene from "another function".
Finally, you should also have:
void MyClass::DrawingSimulation()
{
// declared "void DrawingSimulation();" in header file
DrawingSimulation( Simulation );
}
void MyClass::DrawingSimulation(QGraphicsScene *simu)
{
// declared "void DrawingSimulation(QGraphicsScene *simu);" in header file
for (int i = 0; i < DisplayAlphaParticleNumber; i++)
{
if (ParticleLocation[i*6+3] != 0 || ParticleLocation[i*6+6] != 0)
{
simu->addEllipse(ParticleLocation[i*6+3],ParticleLocation[i*6+4],10,10);
}
}
}
Note that DrawingSimulation() could also be a slot (declare it using public slots: in your header file. Then, if you connect it to the clicked() signal of a QPushButton of your GUI (for instance), it will be called when the button is clicked and ellipse will be added.
Like this:
MyClass::MyClass()
{
...
connect( ui->pushButton, SIGNAL(clicked()), this, SLOT(DrawingSimulation()) );
}

C++/Qt Threads error without using threads

I am using a special form of the qt framework and it will look a bit complicated. But do not worry. It is the same as the original framework of qt. (And no I can't change it)
My intention of this program is to load a table in a tableview from the ui. The tableview should have as many rows and text as files that are in a directory. I take all files in the directory and put an filter on them. Only the selected one are counted and used afterwards.
And now to my problem:
I want to display the table like an Excel table: it should highlight the cell where my cursor (in my example it is an counter which goes up and down) stands right now (Currently I do not have a cursor, so I have to use a self-created "cursor". You will see it in the program what I mean).
I use a QStandardItemModel to display it and a QStandardItem to define it.
Because I want to change the color of the cells I use an QStandardItem array to save the cells one by one.
form.h:
#ifndef FORM_H
#define FORM_H
#include <QWidget>
#include <QTextTable>
#include <QFont>
#include <QFile>
#include <QDir>
#include <QTextStream>
#include <QString>
#include <QStringList>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QBrush>
namespace Ui {
class Form;
}
class Form : public QWidget
{
Q_OBJECT
public:
explicit Form(QWidget *parent = 0);
~Form();
void my_setCFGFromDirectory();
void my_Highlighted(int, bool);
void my_loadCFG(int);
private:
Ui::Form *ui;
QFile file;
QTextStream textstream;
QDir directory;
QString filename;
QStringList stringlist;
QStringList filter;
QStandardItemModel *model;
QStandardItem *tableitem;
QStandardItem* pointerTableitem[];
int max_files;
int w_counter;
bool check1;
bool check2;
bool check3;
};
#endif // FORM_H
and the
form.cpp
#include "form.h"
#include "ui_form.h"
#include "rbobject.h"
Form::Form(QWidget *parent) :
QWidget(parent),
ui(new Ui::Form)
{
ui->setupUi(this);
}
Form::~Form()
{
delete ui;
}
void Form::my_Highlighted(int actual_count, bool modus){
if(modus == 1){
w_counter = 0;
while(w_counter < max_files){
if(w_counter == actual_count){
pointerTableitem[w_counter]->setBackground(QBrush(QColor(130,180,255)));
}
w_counter++;
}
}
else
{
w_counter = 0;
while(w_counter < max_files){
pointerTableitem[w_counter]->setBackground(QBrush(QColor(255,255,255)));
w_counter++;
}
}
}
void Form::my_setCFGFromDirectory(){
directory.setPath("/etc/rosenbauer/CFG-Files");
filter << "*_CFG.ini";
stringlist = directory.entryList(filter); //takes the selected files and wriths them in a filelist
max_files = stringlist.count(); //Counts the selected files
pointerTableitem [max_files];
memset(pointerTableitem, 0, max_files*sizeof(int)); //The compiler can not see how many elements should be in the array at the time he constructs the array.
//So you have to use this to tell the compiler, how many elements it should create
model = new QStandardItemModel(max_files, 1, this); //Rows , Columns , this
model->setHorizontalHeaderItem(0, new QStandardItem(QString("CFG-Files"))); //Column , QStandarditem...name
for(w_counter = 0; w_counter != max_files; w_counter++){
tableitem = new QStandardItem();
tableitem->setBackground(QBrush(QColor(255,255,255)));
tableitem->setText(QString(stringlist[w_counter])); //Wriths text in the cell
qDebug() << tableitem->text();
pointerTableitem[w_counter] = tableitem; //Stacks the tableitems in a filelist
model->setItem(w_counter, 0, tableitem); //Row, Column , tableitem...text
}
w_counter = 0;
//pointerTableitem[2]->setBackground(QBrush(QColor(130,180,255))); //Test
ui->TableConfig->setModel(model); //Sets the table into the UI
}
void Form::my_loadCFG(int file_position){
check1 = 0;
check2 = 0;
directory.setPath("/etc/rosenbauer/etc/rosenbauer");
check1 = directory.remove("SA008_890560-001_CFG.ini");
check2 = directory.remove("reparsed/SA008_890560-001_CFG.ini_reparsed");
if(check1 && check2){
filename = pointerTableitem[file_position]->text();
check3 = QFile::copy("/etc/rosenbauer/CFG-Files/"+filename, "/etc/rosenbauer/SA008_890560-001_CFG.ini");
if(!check3){
qDebug() << "Error! Could not copy new CFG-file into directory";
}
}
else
{
qDebug() << "Error! Could not delete running CFG-file(s) from directory";
}
}
The main header rbobject45000.h
#ifndef RbObject45000_H
#define RbObject45000_H
#include "rbobject.h"
#include "form.h"
class RbObject45000 : public RbObject{
public:
RbObject45000();
RbObject45000(qint32 rbObjectId, RDataObject *dataobject, qint32 style, QObject *parent = 0);
RbObject45000(qint32 rbObjectId, qint32 style, qint32 prio, QObject *parent);
bool entered_ui;
bool entered_key;
short counter;
short w_count;
short delay_count;
short max_files;
short begin;
short end;
Form *f;
void exec();
~RbObject45000();
};
#endif // RbObject45000_H
And this is the main:
rbobject45000.cpp
RbObject45000::RbObject45000(qint32 rbObjectId, qint32 style, qint32 prio, QObject *parent):
RbObject(rbObjectId, style, prio, parent){
entered_ui = 0;
entered_key = 0;
counter = 0;
w_count = 1; //... whilecounter
delay_count = 0; //... delay for the deadtime
max_files = 0;
begin = 0;
end = 0;
f= new Form(); //f...name of the displayed UI
}
void RbObject45000::exec(){
//Opens the file on the display
if(getKey(RB_KEY_9) && getKey(RB_KEY_7) && entered_ui ==0){
f->show();
entered_ui = 1;
// Geting the Files from Directory
f->my_setCFGFromDirectory();
}
// Prescind them file in the table
if(entered_ui == 1 && delay_count == 2){
if(getKey(RB_KEY_7)){
counter--;
entered_key = 1;
if(counter < 0){
counter = 0;
}
}
else if(getKey(RB_KEY_9)){
counter++;
entered_key = 1;
if(counter > max_files){
counter = max_files;
}
}
if(entered_key == 1){
while(w_count <= max_files){
f->my_Highlighted(w_count, 0); //Cell , not highlighted
w_count++;
}
w_count = 0;
f->my_Highlighted(counter,1); //Cell , highlighted
entered_key = 0;
}
delay_count = 0;
}
else if(delay_count == 2){ //if delay == 2 and nobody hit a button
delay_count = 0;
}
delay_count++;
//Load the coosen file as programm-config in
if(entered_ui == 1){
if(getKey(RB_KEY_8)){
f->my_loadCFG(counter);
}
}
}
(RB_KEY_7-9 are buttons of a hardware-module, only have to click them)
So if I do it like this, the program will compile and start. If I press the buttons and the program run into the my_setCFGFromDirectory() it exits the window and stop running, but if the QStandardItem pointerTableitem is declared in the my_setCFGFromDirectory() everything works fine (but the pointerTableitem needs to be private).
The errors I get are:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is Form(0x167498), parent's thread is QThread(0x1414e8), current thread is QThread(0x15d2d8)
Segmentation fault
It appears to say I use a thread (I'm sure I don't). But I know it has something to do with pointerTableitem and the declaration in the header and cpp.
If I'm not mistaken the problem lies here:
model = new QStandardItemModel(max_files, 1, this);
You try to create a QStandardItemModel passing your Form instance this as a parent, the form was created on the main thread, this call apparently happens on another thread (the edits you made were not enough, it is still unclear if RbObject inherits QThread but you could check that yourself) and thus the error message.
Why not call the function on the main thread? Instead of doing this
f->show();
entered_ui = 1;
// Geting the Files from Directory
f->my_setCFGFromDirectory();
in a different thread, make a slot/signal pair for the Form class, put this code in the slot and fire the connected signal from RbObject45000::exec(). It is not correct to do UI manipulation from a non-UI trhead.

An issue when creating a pop-up menu w.r.t to a parametr via loop

I am trying to create pop-up menu depending on a variable as follows:
QMenu menu(widget);
for(int i = 1; i <= kmean.getK(); i++)
{
stringstream ss;
ss << i;
string str = ss.str();
string i_str = "Merge with " + str;
QString i_Qstr = QString::fromStdString(i_str);
menu.addAction(i_Qstr, this, SLOT(mergeWith1()));
}
menu.exec(position);
where:
kmean.get(K) returns an int value,
mergeWith1() is some `SLOT()` which works fine
Issue:
The loop creates an action on menu only for i=1 case, and ignores other values of i.
Additional information
When doing the same loop with casual int values (without convert) everything works fine. e.g. if I do in loop only menu.addAction(i, this, SLOT(...))) and my K=4, a menu will be created with four actions in it, named 1, 2, 3, 4 correspondingly.
What can be the problem caused by
I think the issue is in convert part, when I convert i to string using stringstream and after to QString. May be the value is somehow lost. I am not sure.
QESTION:
How to make the loop accept the convert part?
What do I do wrong in convert part?
In Qt code, you shouldn't be using std::stringstream or std::string. It's pointless.
You have a crashing bug by having the menu on the stack and giving it a parent. It'll be double-destructed.
Don't use the synchronous blocking methods like exec(). Show the menu asynchronously using popup().
In order to react to the actions, connect a slot to the menu's triggered(QAction*) signal. That way you can deal with arbitrary number of automatically generated actions.
You can use the Qt property system to mark actions with custom attributes. QAction is a QObject after all, with all the benefits. For example, you can store your index in an "index" property. It's a dynamic property, created on the fly.
Here's a complete example of how to do it.
main.cpp
#include <QApplication>
#include <QAction>
#include <QMenu>
#include <QDebug>
#include <QPushButton>
struct KMean {
int getK() const { return 3; }
};
class Widget : public QPushButton
{
Q_OBJECT
KMean kmean;
Q_SLOT void triggered(QAction* an) {
const QVariant index(an->property("index"));
if (!index.isValid()) return;
const int i = index.toInt();
setText(QString("Clicked %1").arg(i));
}
Q_SLOT void on_clicked() {
QMenu * menu = new QMenu();
int last = kmean.getK();
for(int i = 1; i <= last; i++)
{
QAction * action = new QAction(QString("Merge with %1").arg(i), menu);
action->setProperty("index", i);
menu->addAction(action);
}
connect(menu, SIGNAL(triggered(QAction*)), SLOT(triggered(QAction*)));
menu->popup(mapToGlobal(rect().bottomRight()));
}
public:
Widget(QWidget *parent = 0) : QPushButton("Show Menu ...", parent) {
connect(this, SIGNAL(clicked()), SLOT(on_clicked()));
}
};
int main (int argc, char **argv)
{
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}
#include "main.moc"

Refreshing with repaint() stops working

Today I encountered a problem with repaint() function from QT libraries. Long story short, I got a slot where I train my neural network using BP algorithm. I had tested the whole algorithm in console and then wanted to move it into GUI Application. Everything works fine except refreshing. Training of neural networks is a process containing a lot of computations, which are made in bp_alg function (training) and licz_mse function (counting a current error). Variable ilosc_epok can be set up to 1e10. Therefore the whole process may last even several hours. Thats why I wanted to display a current progress after each 100000 epochs (the last if contition). wyniki is an object of QTextEdit class used for displaying the progress. Unfortunately, repaint() doesnt work as intended. At the beginning it refreshes wyniki in GUI, but after some random time it stops working. When the external loop is finished, it refreshes once again showing all changes.
I tried to change frequency of refreshing, but sooner or later it always stops (unless the whole training process stops early enough because of satisfying the break condition). It looks like at some moment of time the application decides to stop refreshing because of too many computations. Imo it shouldnt happen. I was looking for a solution among older questions and managed to solve the problem when I used qApp->processEvents(QEventLoop::ExcludeUserInputEvents); instead of wyniki->repaint();. However, Im still curious why repaint() stops working just like that.
Below I paste a part of the code with the problematic part. Im using QT Creator 2.4.1 and QT Libraries 4.8.1 if it helps.
unsigned long int ile_epok;
double mse_w_epoce;
for (ile_epok=0; ile_epok<ilosc_epok; ile_epok++) { //external loop of training
mse_w_epoce = 0;
for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training
alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
mse_w_epoce += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
}
//checking break condition
if (mse_w_epoce < warunek_stopu) {
wyniki->append("Zakończono uczenie po " + QString::number(ile_epok) + " epokach, osiągając MSE: " + QString::number(mse_w_epoce));
break;
}
//problematic part
if ((ile_epok+1)%(100000) == 0) {
wyniki->append("Uczenie w toku, po " + QString::number(ile_epok+1) + " epokach MSE wynosi: " + QString::number(mse_w_epoce));
wyniki->repaint();
}
}
You're blocking your GUI thread, so repaints will not work, it's just plainly bad design. You're never supposed to block the GUI thread.
If you insist on doing the work in the GUI thread, you must forcibly chop the work into small chunks and return to the main event loop after each chunk. Nested event loops are evil, so don't even think you'd want one. All this has a bad code smell, so stay away.
Alternatively, simply move your computation QObject to a worker thread and do the work there.
The code below demonstrates both techniques. It's easy to notice that the chopping-up-of-work requires to maintain loop state inside of the worker object, not merely locally in the loop. It's messier, the code smells bad, again - avoid it.
The code works under both Qt 4.8 and 5.1.
//main.cpp
#include <QApplication>
#include <QThread>
#include <QWidget>
#include <QBasicTimer>
#include <QElapsedTimer>
#include <QGridLayout>
#include <QPlainTextEdit>
#include <QPushButton>
class Helper : private QThread {
public:
using QThread::usleep;
};
class Trainer : public QObject {
Q_OBJECT
Q_PROPERTY(float stopMSE READ stopMSE WRITE setStopMSE)
float m_stopMSE;
int m_epochCounter;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev);
public:
Trainer(QObject *parent = 0) : QObject(parent), m_stopMSE(1.0) {}
Q_SLOT void startTraining() {
m_epochCounter = 0;
m_timer.start(0, this);
}
Q_SLOT void moveToGUIThread() { moveToThread(qApp->thread()); }
Q_SIGNAL void hasNews(const QString &);
float stopMSE() const { return m_stopMSE; }
void setStopMSE(float m) { m_stopMSE = m; }
};
void Trainer::timerEvent(QTimerEvent * ev)
{
const int updateTime = 50; //ms
const int maxEpochs = 5000000;
if (ev->timerId() != m_timer.timerId()) return;
QElapsedTimer t;
t.start();
while (1) {
// do the work here
float currentMSE;
#if 0
for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training
alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
currentMSE += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
}
#else
Helper::usleep(100); // pretend we're busy doing some work
currentMSE = 2E4/m_epochCounter;
#endif
// bail out if we're done
if (currentMSE <= m_stopMSE || m_epochCounter >= maxEpochs) {
QString s = QString::fromUtf8("Zakończono uczenie po %1 epokach, osiągając MSE: %2")
.arg(m_epochCounter).arg(currentMSE);
emit hasNews(s);
m_timer.stop();
break;
}
// send out periodic updates
// Note: QElapsedTimer::elapsed() may be expensive, so we don't call it all the time
if ((m_epochCounter % 128) == 1 && t.elapsed() > updateTime) {
QString s = QString::fromUtf8("Uczenie w toku, po %1 epokach MSE wynosi: %2")
.arg(m_epochCounter).arg(currentMSE);
emit hasNews(s);
// return to the event loop if we're in the GUI thread
if (QThread::currentThread() == qApp->thread()) break; else t.restart();
}
m_epochCounter++;
}
}
class Window : public QWidget {
Q_OBJECT
QPlainTextEdit *m_log;
QThread *m_worker;
Trainer *m_trainer;
Q_SIGNAL void startTraining();
Q_SLOT void showNews(const QString & s) { m_log->appendPlainText(s); }
Q_SLOT void on_startGUI_clicked() {
QMetaObject::invokeMethod(m_trainer, "moveToGUIThread");
emit startTraining();
}
Q_SLOT void on_startWorker_clicked() {
m_trainer->moveToThread(m_worker);
emit startTraining();
}
public:
Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
QWidget(parent, f), m_log(new QPlainTextEdit), m_worker(new QThread(this)), m_trainer(new Trainer)
{
QGridLayout * l = new QGridLayout(this);
QPushButton * btn;
btn = new QPushButton("Start in GUI Thread");
btn->setObjectName("startGUI");
l->addWidget(btn, 0, 0, 1, 1);
btn = new QPushButton("Start in Worker Thread");
btn->setObjectName("startWorker");
l->addWidget(btn, 0, 1, 1, 1);
l->addWidget(m_log, 1, 0, 1, 2);
connect(m_trainer, SIGNAL(hasNews(QString)), SLOT(showNews(QString)));
m_trainer->connect(this, SIGNAL(startTraining()), SLOT(startTraining()));
m_worker->start();
QMetaObject::connectSlotsByName(this);
}
~Window() {
m_worker->quit();
m_worker->wait();
delete m_trainer;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"