I have two widgets that can be checked, and a numeric entry field that should contain a value greater than zero. Whenever both widgets have been checked, and the numeric entry field contains a value greater than zero, a button should be enabled. I am struggling with defining a proper state machine for this situation. So far I have the following:
QStateMachine *machine = new QStateMachine(this);
QState *buttonDisabled = new QState(QState::ParallelStates);
buttonDisabled->assignProperty(ui_->button, "enabled", false);
QState *a = new QState(buttonDisabled);
QState *aUnchecked = new QState(a);
QFinalState *aChecked = new QFinalState(a);
aUnchecked->addTransition(wa, SIGNAL(checked()), aChecked);
a->setInitialState(aUnchecked);
QState *b = new QState(buttonDisabled);
QState *bUnchecked = new QState(b);
QFinalState *bChecked = new QFinalState(b);
employeeUnchecked->addTransition(wb, SIGNAL(checked()), bChecked);
b->setInitialState(bUnchecked);
QState *weight = new QState(buttonDisabled);
QState *weightZero = new QState(weight);
QFinalState *weightGreaterThanZero = new QFinalState(weight);
weightZero->addTransition(this, SIGNAL(validWeight()), weightGreaterThanZero);
weight->setInitialState(weightZero);
QState *buttonEnabled = new QState();
buttonEnabled->assignProperty(ui_->registerButton, "enabled", true);
buttonDisabled->addTransition(buttonDisabled, SIGNAL(finished()), buttonEnabled);
buttonEnabled->addTransition(this, SIGNAL(invalidWeight()), weightZero);
machine->addState(registerButtonDisabled);
machine->addState(registerButtonEnabled);
machine->setInitialState(registerButtonDisabled);
machine->start();
The problem here is that the following transition:
buttonEnabled->addTransition(this, SIGNAL(invalidWeight()), weightZero);
causes all the child states in the registerButtonDisabled state to be reverted to their initial state. This is unwanted behaviour, as I want the a and b states to remain in the same state.
How do I ensure that a and b remain in the same state? Is there another / better way this problem can be solved using state machines?
Note. There are a countless (arguably better) ways to solve this problem. However, I am only interested in a solution that uses a state machine. I think such a simple use case should be solvable using a simple state machine, right?
After reading your requirements and the answers and comments here I think merula's solution or something similar is the only pure Statemachine solution.
As has been noted to make the Parallel State fire the finished() signal all the disabled states have to be final states, but this is not really what they should be as someone could uncheck one of the checkboxes and then you would have to move away from the final state. You can't do that as FinalState does not accept any transitions. The using the FinalState to exit the parallel state also causes the parallel state to restart when it is reentered.
One solution could be to code up a transition that only triggers when all three states are in the "good" state, and a second one that triggers when any of those is not. Then you add the disabled and enabled states to the parallel state you already have and connect it with the aforementioned transitions. This will keep the enabled state of the button in sync with all the states of your UI pieces. It will also let you leave the parallel state and come back to a consistent set of property settings.
class AndGateTransition : public QAbstractTransition
{
Q_OBJECT
public:
AndGateTransition(QAbstractState* sourceState) : QAbstractTransition(sourceState)
m_isSet(false), m_triggerOnSet(true), m_triggerOnUnset(false)
void setTriggerSet(bool val)
{
m_triggerSet = val;
}
void setTriggerOnUnset(bool val)
{
m_triggerOnUnset = val;
}
addState(QState* state)
{
m_states[state] = false;
connect(m_state, SIGNAL(entered()), this, SLOT(stateActivated());
connect(m_state, SIGNAL(exited()), this, SLOT(stateDeactivated());
}
public slots:
void stateActivated()
{
QObject sender = sender();
if (sender == 0) return;
m_states[sender] = true;
checkTrigger();
}
void stateDeactivated()
{
QObject sender = sender();
if (sender == 0) return;
m_states[sender] = false;
checkTrigger();
}
void checkTrigger()
{
bool set = true;
QHashIterator<QObject*, bool> it(m_states)
while (it.hasNext())
{
it.next();
set = set&&it.value();
if (! set) break;
}
if (m_triggerOnSet && set && !m_isSet)
{
m_isSet = set;
emit (triggered());
}
elseif (m_triggerOnUnset && !set && m_isSet)
{
m_isSet = set;
emit (triggered());
}
}
pivate:
QHash<QObject*, bool> m_states;
bool m_triggerOnSet;
bool m_triggerOnUnset;
bool m_isSet;
}
Did not compile this or even test it, but it should demonstrate the principle
The state machine you used above does not correspond to what you described. Using a final state is not correct because after enter a value greater zero I don't see anything that prevents the user from enter zero again. Therefore the valid states can't be final. As far as I can see from your code the user is allowed to change the state of the widgets in any order. Your state machine has to pay attention to this.
I would use a state machine with four child states (no valid input, one valid input, two valid inputs, three valid inputs). You obviously start with no valid input. Each widget can make a transition from no to one an back (same counts for two and three). When three is entered all widgets are valid (button enabled). For all other states the button has to be disabled when the state is entered.
I wrote a sample app. The main window contains two QCheckBoxes a QSpinBox and a QPushButton. There are signals in the main window the ease write down the transitions of the states. There are fired when the state of the widgets are changed.
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtGui>
namespace Ui
{
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
bool m_editValid;
bool isEditValid() const;
void setEditValid(bool value);
private slots:
void on_checkBox1_stateChanged(int state);
void on_checkBox2_stateChanged(int state);
void on_spinBox_valueChanged (int i);
signals:
void checkBox1Checked();
void checkBox1Unchecked();
void checkBox2Checked();
void checkBox2Unchecked();
void editValid();
void editInvalid();
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow), m_editValid(false)
{
ui->setupUi(this);
QStateMachine* stateMachine = new QStateMachine(this);
QState* noneValid = new QState(stateMachine);
QState* oneValid = new QState(stateMachine);
QState* twoValid = new QState(stateMachine);
QState* threeValid = new QState(stateMachine);
noneValid->addTransition(this, SIGNAL(checkBox1Checked()), oneValid);
oneValid->addTransition(this, SIGNAL(checkBox1Checked()), twoValid);
twoValid->addTransition(this, SIGNAL(checkBox1Checked()), threeValid);
threeValid->addTransition(this, SIGNAL(checkBox1Unchecked()), twoValid);
twoValid->addTransition(this, SIGNAL(checkBox1Unchecked()), oneValid);
oneValid->addTransition(this, SIGNAL(checkBox1Unchecked()), noneValid);
noneValid->addTransition(this, SIGNAL(checkBox2Checked()), oneValid);
oneValid->addTransition(this, SIGNAL(checkBox2Checked()), twoValid);
twoValid->addTransition(this, SIGNAL(checkBox2Checked()), threeValid);
threeValid->addTransition(this, SIGNAL(checkBox2Unchecked()), twoValid);
twoValid->addTransition(this, SIGNAL(checkBox2Unchecked()), oneValid);
oneValid->addTransition(this, SIGNAL(checkBox2Unchecked()), noneValid);
noneValid->addTransition(this, SIGNAL(editValid()), oneValid);
oneValid->addTransition(this, SIGNAL(editValid()), twoValid);
twoValid->addTransition(this, SIGNAL(editValid()), threeValid);
threeValid->addTransition(this, SIGNAL(editInvalid()), twoValid);
twoValid->addTransition(this, SIGNAL(editInvalid()), oneValid);
oneValid->addTransition(this, SIGNAL(editInvalid()), noneValid);
threeValid->assignProperty(ui->pushButton, "enabled", true);
twoValid->assignProperty(ui->pushButton, "enabled", false);
oneValid->assignProperty(ui->pushButton, "enabled", false);
noneValid->assignProperty(ui->pushButton, "enabled", false);
stateMachine->setInitialState(noneValid);
stateMachine->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
bool MainWindow::isEditValid() const
{
return m_editValid;
}
void MainWindow::setEditValid(bool value)
{
if (value == m_editValid)
{
return;
}
m_editValid = value;
if (value)
{
emit editValid();
} else {
emit editInvalid();
}
}
void MainWindow::on_checkBox1_stateChanged(int state)
{
if (state == Qt::Checked)
{
emit checkBox1Checked();
} else {
emit checkBox1Unchecked();
}
}
void MainWindow::on_checkBox2_stateChanged(int state)
{
if (state == Qt::Checked)
{
emit checkBox2Checked();
} else {
emit checkBox2Unchecked();
}
}
void MainWindow::on_spinBox_valueChanged (int i)
{
setEditValid(i > 0);
}
This should do the trick. As you yourself mentioned already there are better ways to achive this behaviour. Especially keep track of all transistions between the state is error prone.
When I have to do things like this I usually use signals and slots. Basically each of the widgets and the number box will all emit signals automatically when their states change. If you link each of these to a slot that checks if all 3 objects are in the desired state and enables the button if they are or disables it if they aren't, then that should simplify things.
Sometimes you will also need to change the button state once you've clicked it.
[EDIT]: I'm sure there is some way of doing this using state machines, will you only be reverting in the situation that both boxes are checked and you've added an invalid weight or will you also need to revert with only one checkbox checked? If it's the former then you may be able to set up a RestoreProperties state that allows you to revert to the checked box state. Otherwise is there some way you can save the state before checking the weight is valid, revert all checkboxes then restore the state.
Set up your weight input widget so that there is no way a weight less than zero can be entered. Then you don't need invalidWeight()
edit
I reopened this test, willing to use it, added to .pro
CONFIG += C++11
and I discovered that lambda syntax has changed... The capture list cannot reference member variables. Here is the corrected code
auto cs = [/*button, check1, check2, edit, */this](QState *s, QState *t, bool on_off) {
s->assignProperty(button, "enabled", !on_off);
s->addTransition(new QSignalTransition(check1, SIGNAL(clicked())));
s->addTransition(new QSignalTransition(check2, SIGNAL(clicked())));
s->addTransition(new QSignalTransition(edit, SIGNAL(textChanged(QString))));
Transition *p = new Transition(this, on_off);
p->setTargetState(t);
s->addTransition(p);
};
end edit
I used this question as exercise (first time on QStateMachine). The solution is fairly compact, using a guarded transition to move between 'enabled/disabled' state, and lambda to factorize setup:
#include "mainwindow.h"
#include <QLayout>
#include <QFrame>
#include <QSignalTransition>
struct MainWindow::Transition : QAbstractTransition {
Transition(MainWindow *main_w, bool on_off) :
main_w(main_w),
on_off(on_off)
{}
virtual bool eventTest(QEvent *) {
bool ok_int, ok_cond =
main_w->check1->isChecked() &&
main_w->check2->isChecked() &&
main_w->edit->text().toInt(&ok_int) > 0 && ok_int;
if (on_off)
return ok_cond;
else
return !ok_cond;
}
virtual void onTransition(QEvent *) {}
MainWindow *main_w;
bool on_off;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QFrame *f = new QFrame(this);
QVBoxLayout *l = new QVBoxLayout;
l->addWidget(check1 = new QCheckBox("Ok &1"));
l->addWidget(check2 = new QCheckBox("Ok &2"));
l->addWidget(edit = new QLineEdit());
l->addWidget(button = new QPushButton("Enable &Me"));
f->setLayout(l);
setCentralWidget(f);
QState *s1, *s2;
sm = new QStateMachine(this);
sm->addState(s1 = new QState());
sm->addState(s2 = new QState());
sm->setInitialState(s1);
auto cs = [button, check1, check2, edit, this](QState *s, QState *t, bool on_off) {
s->assignProperty(button, "enabled", !on_off);
s->addTransition(new QSignalTransition(check1, SIGNAL(clicked())));
s->addTransition(new QSignalTransition(check2, SIGNAL(clicked())));
s->addTransition(new QSignalTransition(edit, SIGNAL(textChanged(QString))));
Transition *tr = new Transition(this, on_off);
tr->setTargetState(t);
s->addTransition(tr);
};
cs(s1, s2, true);
cs(s2, s1, false);
sm->start();
}
Related
I have an overloaded QTreeWidget class, with my SIGNALS: I have promoted it in my UI and when I listen promoted QTreeWidget object with a lambda syntax I have an error.
QObject::connect: signal not found in CustomTreeWidget.
MY CustomTreeWidget looks like:
.h
class CustomTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
explicit CustomTreeWidget(QWidget *parent = 0);
~CustomTreeWidget() {
}
signals:
void currentNodeChanged(QSet<int> uids);
void deleteRequest(QVector<int> uids);
}
.cpp
CustomTreeWidget::CustomTreeWidget(QWidget *parent) : QTreeWidget(parent)
{
setAnimated(true);
connect(this, &CustomTreeWidget::customContextMenuRequested, this, [=](const QPoint &pos) {
this->m_bCustomMenuOpen = true;
const auto &&item = this->itemAt(pos);
QMenu myMenu;
bool ok = !(item) ? false : true;
if (ok) {
//თუ topLevelItem -ია მხოლოდ დამატების action -ი უნდა იყოს ჩართული.
if (item == this->topLevelItem(0) || item == this->topLevelItem(0)->child(0)) {
ok = false;
}
}
QAction *Removecnt = myMenu.addAction(tr("&წაშლა"), this, SLOT(DeleteNode()));
Removecnt->setIcon(QIcon(":/global_res/delete.png"));
Removecnt->setEnabled(ok);
myMenu.exec(this->mapToGlobal(pos));
});
}
void CustomTreeWidget::BFS(QTreeWidgetItem *item, QSet<int> &out)
{
std::queue<QTreeWidgetItem *> Q;
Q.push(item);
while (!Q.empty()) {
QTreeWidgetItem *now = Q.front(); Q.pop();
out.insert(this->m_mapUids[now]);
for (int i = 0; i < now->childCount(); i++) {
Q.push(now->child(i));
}
}
}
QSet<int> CustomTreeWidget::GetCurrentNodeUids()
{
QSet<int> uids;
if (!this->currentItem())
return uids;
this->BFS(this->currentItem(), uids);
return uids;
}
void CustomTreeWidget::DeleteNode()
{
QSet<int> nodes = this->GetCurrentNodeUids();
QVector<int> uids;
for (auto it : nodes) {
uids.push_back(it);
}
emit deleteRequest(uids);
}
My lambda looks like:
connect(ui->productTree, &CustomTreeWidget::deleteRequest, this, [=](QVector<int> uids) {
//logic
});
But this signal works with old syntax.
connect(ui->productTree, SIGNAL(deleteRequest(QVector<int>)), this, SLOT(checkSlot(QVector<int>)));
And this slot is.
void ProductForm::checkSlot(QVector<int> uids)
{
qDebug() << uids.size();
}
So what is problem lambda syntax?
This smells like the violation of the one definition rule (ODR) - perhaps due to a stale build folder. The problem is that the address of deleteRequest passed to the connect method is not the same as the address of the method visible from moc_CustomTreeWidget.cpp. Remove the build folder and try again. If it still doesn't work, start reducing your problem:
Create a minimization branch in the repository (if you're not using version control, you won't go anywhere with minimization).
Copy-paste the contents of the ui_CustomTreeWidget.h file into the CustomTreeWidget.cpp file, remove the #include line.
Inspect the pasted contents for instantiation of the productTree object with the correct type.
If that's correct, then remove everything else from the code, step-by-step, rebuilding and committing to the repository at each step that still reproduces. You should end up with a test case that is 20-30 lines long at most. And it'll be either obvious what's wrong, or you can modify the question with the test case.
I am trying to create a configuration menu box for an application, and have used a QDialog box to display the options that the user can change. This box contains QComboBoxes and QLineEdits, but a lot of them (7 combo boxes and 12 line edits). There is a QPushButton in the bottom called "Apply Changes" that should get enabled only when any property in the box gets changed.
Do I have to link every signal from each widget with a slot to enable the button individually or is there a signal that the QDialog box itself emits when there is a change in its constituent widgets?
Right now I have this:
connect(Combo1,SIGNAL(activated(QString)),this,SLOT(fnEnable(QString)));
connect(Combo2,SIGNAL(activated(QString)),this,SLOT(fnEnable(QString)))
followed by 17 more lines of these connections.
void MyClass::fnEnable(QString)
{
ApplyButton->setEnabled(true); //It is initialised as false
}
I was wondering if there was a shorter way of doing this, maybe (like I mentioned before) a signal emitted by QDialog (I couldn't find one in the documentation)
I know that this does not speed up the program, as only the required connection is called, but it would make any further attempts at making more ambitious dialog boxes easier.
Actually there is no such signal, but one approach is to create a list of QComboBox, and make the connections with a for, for example:
QList <*QCombobox> l;
l<<combobox1<< combobox2<< ....;
for (auto combo: l) {
connect(combo, &QComboBox::activated, this, &MyClass::fnEnable);
}
The same would be done with QLineEdit.
You can iterate over an initializer list of widgets boxes and leverage C++11 to do all the boring work for you:
MyClass::MyClass(QWidget * parent) : QWidget(parent) {
auto const comboBoxes = {Combo1, Combo2, ... };
for (auto combo : comboBoxes)
connect(combo, &QComboBox::activates, this, &MyClass::fnEnable);
}
You can also automatically find all the combo boxes:
MyClass::MyClass(QWidget * parent) : QWidget(parent) {
ui.setupUi(this); // or other setup code
for (auto combo : findChildren<QComboBox*>(this))
connect(combo, &QComboBox::activated, this, &MyClass::fnEnable);
}
Or you can automatically attach to the user property's change signal. This will work on all controls that have the user property. The user property is the property of a control that contains the primary data the control is displaying.
void for_layout_widgets(QLayout * layout, const std::function<void(QWidget*)> & fun,
const std::function<bool(QWidget*)> & pred = +[](QWidget*){ return true; })
{
if (!layout) return;
for (int i = 0; i < layout->count(); ++i) {
auto item = layout->itemAt(i);
for_layout_widgets(item->layout(), fun, pred);
auto widget = item->widget();
if (widget && pred(widget)) fun(widget);
}
}
class MyClass : public QWidget {
Q_OBJECT
Q_SLOT void MyClass::fnEnable(); // must take no arguments
...
};
MyClass::MyClass(QWidget * parent) : QWidget(parent) {
// setup code here
auto slot = metaObject()->method(metaObject()->indexOfMethod("fnEnable()"));
Q_ASSERT(slot.isValid());
for_layout_widgets(layout(), [=](QWidget * widget){
auto mo = widget->metaObject();
auto user = mo->userProperty();
if (!user.isValid()) return;
auto notify = user.notifySignal();
if (!notify.isValid()) return;
connect(widget, notify, this, slot);
});
}
You can also keep the combo boxes in an array, by value. This minimizes the costs of indirect references and results in code that will take the least amount of memory possible and perform well:
class MyClass : public QWidget {
Q_OBJECT
QVBoxLayout m_layout{this};
std::array<QComboBox, 14> m_comboBoxes;
...
};
MyClass(QWidget * parent) : QWidget(parent) {
for (auto & combo : m_comboBoxes) {
m_layout.addWidget(&combo);
connect(&combo, &QComboBox::activates, this, &MyClass::fnEnable);
}
}
In the code above uitablewidget does not update using signal and slot.
It seems as if (ui->tableWidget->setItem(0,0,newItemx);) doesn't work.
Am I doing something wrong or is there a better way to update my qtablewidget from my class B?
Class_A::Class_A(QWidget *parent):QDialog(parent),ui(new Ui::Class_A)
{
ui->setupUi(this);
}
Class_A::~Class_A()
{
delete ui;
}
void Class_A::change_TableWidget(double x,double y) // this is the public slot
{
QTableWidgetItem *newItemx = new QTableWidgetItem(QString::number(x));
ui->tableWidget->setItem(0,0,newItemx);
QTableWidgetItem *newItemy = new QTableWidgetItem(QString::number(y));
ui->tableWidget->setItem(0,0,newItemy);
}
Class_B::Class_B(QWidget *parent) :
QGLWidget(parent)
{
Class_A *t=new Class_A;
connect(this,SIGNAL(mySignal(double,double)),t,SLOT(change_TableWidget(double,double)));
}
void Class_B::mousePressEvent(QMouseEvent *event)
{
double x = event->x();
double y = event->y();
emit mySignal(x,y);
}
You don't have a SLOT(change_TableWidget(double,double)) - yours takes 3 doubles, not two.
You should check that connect() returned true. I like to write
if (!connect(....)) Q_ASSERT(false);
or if (!connect(....)) Q_ASSERT(!"connect");
Also, connect prints out messages to the debug output when it fails to match the signals and slots. You should look for that output.
(Or use the new Qt 5 connect(), which is all checked at compile time.)
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"
I am trying to create a program that waits for the user to input something into a line edit widget, and when they hit enter, I want to compare the value to some predefined one (for example "1"). The problem I seem to be having is that I cannot find a way to make this work with the QStateMachine. At the moment, it will wait for the user to press enter and it just switches over to the next state, but I want it to only go to the next state if the input is "1". Here is the code I am using and thank you for any help that you can offer.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->lineEdit, SIGNAL(editingFinished()), this, SLOT(someSlot()));
setupStateMachine();
}
...
void MainWindow::setupStateMachine()
{
QStateMachine *machine = new QStateMachine(this);
QState *s1 = new QState();
QState *s2 = new QState();
QState *s3 = new QState();
s1->assignProperty(ui->label, "text", readFile("intro.txt"));
s2->assignProperty(ui->label, "text", "In state s2");
s3->assignProperty(ui->label, "text", "In state s3");
s1->addTransition(this, SIGNAL(editing()), s2);
s2->addTransition(this->ui->pushButton, SIGNAL(clicked()), s3);
s3->addTransition(this->ui->pushButton, SIGNAL(clicked()), s1);
machine->addState(s1);
machine->addState(s2);
machine->addState(s3);
machine->setInitialState(s1);
machine->start();
qDebug() << "State Machine Created";
}
...
void MainWindow::someSlot()
{
if(ui->lineEdit->text() == "1")
{
emit editing();
}
}
In the header file:
{
...
signals:
void editing();
...
private slots:
void someSlot();
...
};
PS: I realize that the signal does not do what I want, but I can't figure out which signal to use.
Perhaps you can connect editingFinished to your own slot. In that slot, check if the input is "1". if so, emit a new signal you pass into addTransition instead of editingFinished
To add a signal to a class, change the class like this (make sure there is a Q_OBJECT declared at the very top of the class):
signals:
void mySignalName();
Signals are guaranteed protected. You don't write the body of the function. That's what MOC does. So, when you want to call the signal in your class, just call:
emit mySignalName();
emit is just for code documentation. It's #defined to nothing. MOC will generate the body of mySignalName and boil down to calls to the slots you connect it to using QObject::connect.
To add a new slot to your class, add this:
private slots:
void mySlotName();
Note that you will have to write the body of a slot.
void MainWindow::mySlotName()
{
if(myLineEdit->text() == "1")
emit mySignalName();
}