How to pass a parameter using QSignalMapper, incompatible sender/receiver arguments - c++

Implementation:
void Test::addProcessToList(const QString &command, const QString &id, const BasicInfo &basicInfo) {
QProcess *console = new QProcess();
QSignalMapper* signalMapper = new QSignalMapper (this) ;
connect (console, SIGNAL(readyRead()), signalMapper, SLOT(map())) ;
connect (console, SIGNAL(finished(int)), signalMapper, SLOT(processFinished(int))) ;
signalMapper->setMapping (console, id) ;
connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(pidOut(QString))) ;
console->start(command);
}
void Test::registerProcess(QString id) {
QProcess *console = qobject_cast<QProcess*>(QObject::sender());
QByteArray processOutput = console->readAll();
int mainPID = parsePID(processOutput);
BasicInfo basicInfo;
qDebug() << "Registering id: " + id + " mainPID: " + mainPID;
if(mainPID != 0) {
Main::getInstance()->addProcessToList(mainPID, packageId, basicInfo);
} else {
qWarning() << "pidOut Error fetching mainPID";
}
}
void Test::processFinished(int exitCode) {
QProcess *console = qobject_cast<QProcess*>(QObject::sender());
QByteArray processOutput = console->readAll() + QString("Finished with code %1").arg(exitCode).toLatin1();
qDebug() << " processFinished: " + processOutput;
}
prototypes:
private
void addProcessToList(const QString &command, const QString &id, const BasicInfo &basicInfo);
private slots:
void registerProcess(QString);
void processFinished(int);
I get this errors when I call connect, which tells me I'm doing it wrong:
"QObject::connect: Incompatible sender/receiver arguments
QSignalMapper::mapped(int) --> Test::registerProcess(QString)"
I'm not understanding where I'm suppose to specify my parameter (QString id) so that registerProcess will receive it when it's called? I'm assuming I'm doing this part wrong, cut from above:
signalMapper->setMapping (console, id) ;
connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(pidOut(QString))) ;

QSignalMapper can emit either mapped(const QString & text) or mapped(int i) signals. The type is defined by setMapping(QObject * sender, int id) or setMapping(QObject * sender, const QString & text).
That led to confusion probably by autocompletion in
connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(pidOut(QString)));
The types of signal and slot must be the same for connection.
You set string mapping (QString &id), so the signal in the connection should be QString:
connect (signalMapper, SIGNAL(mapped(QString)), this, SLOT(pidOut(QString)));
Update
After deeper review of the code flow I suspect that you wanted to connect mapper to registerProcess() slot instead of pidOut(). In that slot you can have as an argument QString id that was passed to signalMapper in setMapping() call. That is the purpose of using QSignalMapper.
However, beside that id it is not possible to extract console pointer, since in that case sender() is signalMapper object. If it is the case, QSignalMapper cannot help you here. You should use direct connection of console and this on readReady (of course with slot of this with void argument as readReady()). To get the string id in that slot it is possible to use simple QMap<QProces*, QString> map stored as a Test class member.
// addProcessToList(...):
map[console] = id;
//registerProcess():
QString id = map[console];
//processFinished(...):
map.remove(console);
By the way, it is not needed to created a new instance of QSignalMapper for each map item.

Related

How to make slot work at time when calling function?

I have a function and connection between a slot and a signal.
Check the code below:
void NetworkAccessManager::sendPOST(QString url)
{
QNetworkCookieJar *cookieJar = new QNetworkCookieJar(manager_);
manager_->setCookieJar(cookieJar);
QNetworkRequest request(url);
QByteArray postData;
postData.append("j_username=admin&");
postData.append("j_password=admin");
manager_->post(request, postData);
connect(manager_, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinishedSlot(QNetworkReply *)));
}
void NetworkAccessManager::replyFinishedSlot(QNetworkReply *reply)
{
...
cookie = "...";
}
Code above owned by class NetworkAccessManager. Variable cookie is public and I need to change its value in replyFinishedSlot.
I try to use the function sendPOST() in constructer of another class and it works, but slot doing nothing and cookie varaible is empty. What do I do wrong?
Here's the code inside another class:
NetworkAccessManager *manager = new NetworkAccessManager();
manager->sendPOST("http://example.com");
qDebug() << "cookies: " << manager->cookies;
I guess that slot may not work because I never emit signal finished(), but I am not sure where should I emit this because my code shouldn't work with the user interface.
I have found a couple of problem solutions.
First one
I am saving the cookie in a file. Here's the code:
void NetworkAccessManager::replyFinishedSlot(QNetworkReply *reply)
{
...
QFile cookieFile("cookie.txt");
if (cookieFile.open(QIODevice::WriteOnly))
{
cookieFile.write(cookies.toUtf8());
cookieFile.close();
}
...
}
Here's the constructer of class where I use the function
NetworkAccessManager *manager = new NetworkAccessManager();
manager->sendPOST("http://example.com");
QString cookies = "";
QFile cookieFile("cookie.txt");
if (cookieFile.exists() && cookieFile.open(QIODevice::ReadOnly))
{
cookies = cookieFile.readAll();
cookieFile.close();
}
QMap<QString, QString> elements;
elements.insert("cookies", cookies);
Second one
I use a signal to send the info to QML (I transferred my NetworkAccessManager class to WebViewModel).
.h
class WebViewModel : public QObject
{
Q_OBJECT
public:
explicit WebViewModel(QObject *parent = nullptr);
~WebViewModel();
void sendPOST(QString url);
private slots:
void replyFinishedSlot(QNetworkReply *);
private:
QNetworkAccessManager *manager_;
signals:
void finished(QNetworkReply *);
void cookieReady(QString thisCookie);
};
.cpp
WebViewModel::WebViewModel(QObject* parent)
: QObject(parent)
{
manager_ = new QNetworkAccessManager(this);
sendPOST("http://example.com");
}
WebViewModel::~WebViewModel()
{
delete manager_;
}
void WebViewModel::sendPOST(QString url)
{
...
connect(manager_, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinishedSlot(QNetworkReply *)));
...
}
void WebViewModel::replyFinishedSlot(QNetworkReply *reply)
{
if(reply->error())
{
...
}
else
{
...
QString cookieString = cookie[0].name() + "=" + cookie[0].value() + "; domain=" + cookie[0].domain() + "; path=" + cookie[0].path();
emit cookieReady(cookieString);//the signal sends my variable to .qml
...
}
}
.qml
Connections {
target: WebViewModel
onCookieReady: {
testCookies = thisCookie
//thisCookie variable wasn't declared in the .qml file, thisCookie variable
//is accessible by signal void cookieReady(QString thisCookie) from .h C++
//and MUST have exactly the same name in .qml file as in .h file
}
}

How to replace Qthread with QtConcurrent

I have a SQL fetching class that connects to SQL and fetched the required data.
I wanted to this in another thread so I did that with QThread.
It is working as it should be.
But now I want to replace it with QTConcurrent. The problem I face with QTconcureent is that I need a connect command that initializes the database before the thread uses to do SQL query.
Here are my codes created is the public slot and qint64TotalSize is the public method of the SqlFetcher class.
Controller::Controller(QObject *parent) : QObject(parent)
{
SqlFetcher* m_Fetcher = new SqlFetcher();
qInfo() <<"Start"<< QThread::currentThread();
QFutureWatcher<void> watcher;
QFuture <void> future1 = QtConcurrent::run(m_Fetcher,&SqlFetcher::qint64TotalSize);
watcher.setFuture(future1);
//QThread* t1 = new QThread();
//m_Fetcher->moveToThread(t1);
//connect(t1, &QThread::started, m_Fetcher, &SqlFetcher::createDb);
//connect(t1, &QThread::started, m_Fetcher, &SqlFetcher::qint64TotalSize);
//t1->start();
qInfo() <<"Finish"<< QThread::currentThread();
}
void SqlFetcher::qint64TotalSize()
{
qint64 l_result= 0;
QSqlQuery l_query;
if (m_sqldb.isValid())
{
m_sqldb.open();
if ((m_sqldb.isOpen()))
{
l_query.prepare("SELECT COUNT(*) FROM table1");
l_query.exec();
if (l_query.next()) {
l_result= l_query.value(0).toInt();
}
m_sqldb.close();
}
}
qInfo() << l_result << QThread::currentThread();
}
void SqlFetcher::createDb()
{
m_sqldb = QSqlDatabase::addDatabase("QSQLITE");
m_sqldb.setDatabaseName("xyz.db");
qInfo() << "createDB" << QThread::currentThread();
}
My current output is
Start QThread(0x7feab4c0f060)
Finish QThread(0x7feab4c0f060)
0 QThread(0x7feab4d42070, name = "Thread (pooled)")
Expected Output or Output with Qthread is
Start QThread(0x7fe82140f060)
Finish QThread(0x7fe82140f060)
createDB QThread(0x7fe82155c840)
151 QThread(0x7fe82155c840)
Try executing the whole task in a run, e.g.
QtConcurrent::run([](){
SqlFetcher m_Fetcher;
m_Fetcher.createDb();
m_Fetcher.qint64TotalSize();
});
Since we're dealing with concurrency, it's safer to use named connections (otherwise the same default connection will be used every time, possibly by more than one thread). You can manage this by adding an argument to SqlFetcher::createDb:
void SqlFetcher::createDb(const QString & connectionName)
{
m_sqldb = QSqlDatabase::addDatabase("QSQLITE", connectionName);
// etc.
and to the lambda and the run function, as well:
QtConcurrent::run([](const QString & cn){
SqlFetcher m_Fetcher;
m_Fetcher.createDb(cn);
m_Fetcher.qint64TotalSize();
}, QString("TheConnectionName"));
In the other function, assign the database to the query, in construction:
void SqlFetcher::qint64TotalSize()
{
qint64 l_result= 0;
QSqlQuery l_query(m_sqldb);
//etc.

how to recognize which signal emitted in slot?

I connect two signals to same slot. like this:
check = new QCheckBox();
connect(check, SIGNAL(clicked()), this, SLOT(MySlot()));
connect(check, SIGNAL(toggled(bool)),this,SLOT(MySlot()));
I don't want to define an other slot. In MySlot is it possible to recognize which signal callbacks the slot?
How can I do this?
You might be able to use the QMetaObject/QMetaMethod data associated with the sender to get what you want (untested)...
void MyClass::MySlot ()
{
auto index = senderSignalIndex();
if (index == sender()->indexOfSignal("clicked()")) {
/*
* Got here as the result of a clicked() signal.
*/
} else if (index == sender()->indexOfSignal("toggled(bool)")) {
/*
* Got here as the result of a toggled(bool) signal.
*/
}
}
Rather than that, however, if you're using Qt5 I would suggest making use of the new signal/slot syntax along with lambdas...
check = new QCheckBox();
connect(check, &QCheckBox::clicked,
[this]()
{
MySlot(false);
});
connect(check, &QCheckBox::toggled,
[this](bool toggled)
{
MySlot(true, toggled);
});
Along with a change to the signature of MySlot...
/**
* #param from_toggled_signal If true this call was triggered by a
* QCheckBox::toggled signal, otherwise it's
* the result of a QCheckBox::clicked signal.
*
* #param toggle_value If from_toggled_signal is true then this was the
* value passed to QCheckBox::toggled, otherwise unused.
*/
void MyClass::MySlot (bool from_toggled_signal, bool toggle_value = false)
{
.
.
.
}
New slots can be defined on the fly using lambdas :)
class MyClass : public QWidget {
QSomeLayout m_layout{this};
QCheckBox m_check;
enum Signal { Clicked, Toggled };
Q_SLOT void mySlot(Signal);
public:
MyClass( ... ) : ... {
m_layout.addWidget(&m_check);
connect(&m_check, &QCheckBox::clicked, this, [this]{ mySlot(Clicked); });
connect(&m_check, &QCheckBox::toggled, this, [this]{ mySlot(Toggled); });
}
};
You can also add your own context to the signal if that helps. For instance I had a service that downloaded user avatars for multiple windows. I needed the window to only load the user it was interested in something so I would pass in the user's id as the context. Something like:
void UserService::downloadAvatar(const QString& url, const int context = 0) {
...// Make the http request, on finished:
emit onAvatarDownloaded(context, responseBody);
}

Passing parameters from QtMessageBox signals to buttonClick signal Handler slot

I am trying to create a popup that will (1) be non-modal, (2) carry context data that will be handled later when the user clicks the ok event. So far I have the code below which does pop up as a non-modal. I know that msgBox->open(this, SLOT(msgBoxClosed(QAbstractButton *)) and msgBoxClosed(QAbstractButton *button) work but when I added the QStringList collisionSections to the SLOT parameter. I get this error:
QObject::connect: No such slot MainWindow::msgBoxClosed(QAbstractButton *, collisionSections) in src\mainwindow.cpp:272
QObject::connect: (receiver name: 'MainWindow')
which I understand because it is declaring the SLOT there, but I don't know how to go about doing what I want which is passing in the QString as contents to my signal and have it play well with the buttonClicked() event that qmessagebox throws on the OK click. I could also be approaching this the wrong way, please let me know if so. Any help is much appreciated!
void MainWindow::do_showCollisionEvt(QStringList collisionSections)
{
QString info = "Resolve sections";
for (QString section : collisionSections)
{
if (!section.isEmpty())
{
info.append(" [" + section + "] ");
qDebug() << "Emitting node off for:" << section;
emit nodeOff(section);
}
}
QMessageBox *msgBox = new QMessageBox;
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setText("Collision event detected!");
msgBox->setInformativeText(info);
msgBox->setStandardButtons(QMessageBox::Ok);
msgBox->setDefaultButton(QMessageBox::Ok);
msgBox->setModal(false);
msgBox->open(this, SLOT(msgBoxClosed(QAbstractButton *, collisionSections)));
}
void MainWindow::msgBoxClosed(QAbstractButton *button, QStringList collisionSections) {
QMessageBox *msgBox = (QMessageBox *)sender();
QMessageBox::StandardButton btn = msgBox->standardButton(button);
if (btn == QMessageBox::Ok)
{
for (QString section : collisionSections)
{
if (!section.isEmpty())
{
qDebug() << "Emitting nodeON for:" << section;
emit nodeOn(section);
}
}
}
else
{
throw "unknown button";
}
}
First of all, the open() connects your slot to the finished() singnal with no arguments or to buttonClicked() signal if first slot argument is pointer (your case).
Second. you are not correctly passing param. You can't do it during declaration, the parameters that slot recieves are set in signal emission, which in your case you can't control. In SLOT you could only declare the arguments TYPES not their values (note the SLOT is just a macro, in reality result of SLOT(...) is just a string (char*)).
I suggest you to use the setProperty() method on message box, e.g.:
msgBox->setModal(false);
msgBox->setProperty("collisionSections", collisionSections);
msgBox->open(this, SLOT(msgBoxClosed(QAbstractButton *)));
And your slot could look like then:
void MainWindow::msgBoxClosed(QAbstractButton *button) {
QMessageBox *msgBox = (QMessageBox *)sender();
QStringList collisionSections = msgBox->property("collisionSections").toStringList();

Qt GUI becomes unresponsive emitting signals too fast

I've got a small chat client that stores all history in an sqlite database. When the user clicks on the history tab in my application my app fetches all the relevant history and displays it in a QWebView. Im fetching from a background thread dbThread below and then sending signals to update the QWebView accordingly.
This works fine, until the database grows. When the database gets larger the app starts to almost crashes. GUI is unresponsive for a few seconds until everything is loaded (4-6 secs) depending on the database size.
I've tried to add Qt::QueuedConnection on the signals and also like mentioned above I'm handling all database queries from a background thread.
I'm guessing that I'm emitting signals too fast. Any ideas how to solve this?
Signals
connect(dbtrad, SIGNAL(addAllHistoryMessage(QString, QString, QString, QString, QString)), this, SLOT(addAllHistoryMessage(QString, QString, QString, QString, QString)), Qt::QueuedConnection);
connect(dbtrad, SIGNAL(addAllHistoryMessageInner(QString, QString, QString, QString, QString)), this, SLOT(addAllHistoryMessageInner(QString, QString, QString, QString, QString)), Qt::QueuedConnection);
Code that fetches history from the sqlite database:
// Loads all local history
void dbThread::loadAllHistory(QString agentID, QString agentName) {
bool ret = false;
bool retInner = false;
QString retVal = "";
QDateTime dateTime = dateTime.currentDateTime();
QString dateTimeForTodayCheck = dateTime.toString("yyyy-MM-dd");
if (db.isOpen()) {
QSqlQuery query(db);
QSqlQuery queryInner(db);
ret = query.exec(QString("SELECT channelID, sender, time, message from chatHistory WHERE sender != 'ServerMessage' AND channelID NOT LIKE '%Agent%' GROUP BY channelID order by time DESC;"));
if (ret) {
while (query.next()) {
QString channelID = query.value(0).toString();
QString sender = query.value(1).toString();
QString time = query.value(2).toString();
QString msg = query.value(3).toString();
QString timeStr;
QString fmt = "yyyy-MM-dd hh:mm:ss";
QDateTime dt = QDateTime::fromString(time, fmt);
QDateTime dtCompare = QDateTime::fromString(time, fmt);
if(dateTimeForTodayCheck == dtCompare.toString("yyyy-MM-dd")) { // If today
timeStr = "Today " + dt.toString("hh:mm");
} else {
timeStr = dt.toString("dd MMM yyyy");
}
if(sender == agentID) {
sender = agentName;
}
// Grab all the tags
QString tempTagsForChannelID = getHistoryTagsString(channelID);
emit addAllHistoryMessage(channelID, sender, timeStr, msg, tempTagsForChannelID);
// Load sub-history
retInner = queryInner.exec(QString("SELECT * from chatHistory WHERE sender != 'ServerMessage' AND channelID = '%1' and message != '%2' order by time DESC;").arg(channelID).arg(msg));
if (retInner) {
while (queryInner.next()) {
QString channelIDInner = queryInner.value(0).toString();
QString senderInner = queryInner.value(1).toString();
QString timeInner = queryInner.value(4).toString();
QString msgInner = queryInner.value(2).toString();
QString timeStr2;
QString fmt = "yyyy-MM-dd hh:mm:ss";
QDateTime dt = QDateTime::fromString(timeInner, fmt);
QDateTime dtCompare = QDateTime::fromString(timeInner, fmt);
if(dateTimeForTodayCheck == dtCompare.toString("yyyy-MM-dd")) { // If today
timeStr2 = "Today " + dt.toString("hh:mm");
} else {
timeStr2 = dt.toString("dd MMM yyyy");
}
if(senderInner == agentID) {
senderInner = agentName;
}
emit addAllHistoryMessageInner(channelIDInner, senderInner, timeStr2, msgInner, tempTagsForChannelID);
}
}
}
}
}
}
My code to update:
void MainWindow::addAllHistoryMessageInner(QString channelIDInner, QString senderInner, QString timeStr2, QString msgInner, QString tempTagsForChannelID) {
ui->webViewHistory->page()->mainFrame()->evaluateJavaScript("$('#history tbody').append('<tr id=\"" + channelIDInner+ "\" class=\"hiddenRow\"><td>" + senderInner + "</td><td align=\"center\">" + timeStr2 + "</td><td align=\"center\" style=\"word-wrap:break-word;\">" + msgInner.remove(QRegExp("<[^>]*>")) + "</td><td align=\"center\">" + tempTagsForChannelID + "</td></tr>');undefined");
}
void MainWindow::addAllHistoryMessage(QString channelID, QString sender, QString timeStr, QString msg, QString tempTagsForChannelID) {
ui->webViewHistory->page()->mainFrame()->evaluateJavaScript("$('#history tbody').append('<tr id=\"" + channelID + "\"><td>" + sender + "</td><td align=\"center\">" + timeStr + "</td><td align=\"center\" style=\"word-wrap:break-word;\">" + msg.remove(QRegExp("<[^>]*>")) + "</td><td align=\"center\" style=\"word-wrap:break-word;\">" + tempTagsForChannelID + "</td></tr>');undefined");
}
Edit: Implementation of dbThread
thread = new QThread(this);
dbtrad = new dbThread();
dbtrad->moveToThread(thread);
Edit 2: This is how I call loadAllHistory
I create a signal:
connect(this, SIGNAL(loadAllHistoryS(QString, QString)), dbtrad, SLOT(loadAllHistory(QString, QString)));
And call it like this:
emit loadAllHistoryS(agentID, agentName);
The problem is, that your main thread is interrupted for every single row in the innerQuery. This destroys the benefits of loading the data in separate thread. Probably the overhead of the signal/slot communication over thread boundaries is even higher than the costs of loading a single row from the database.
I would recommend to collect the rows in a QList instance the while loop. When done, push the complete result via one signal invocation to the main thread:
First declare a simple class for storing history items:
class HistoryItem {
public:
QString channelID;
/* additonal fields omitted for brevity */
/* also, private fields with getters and setters would be better */
}
Then, create a list of such objects before the while loop:
QList<HistoryItem*> innerResult;
while (queryInner.next()) {
/* snip */
HistoryItem* item = new HistoryItem();
item.channelId = channelIDInner;
/* more lines ommited */
innerResult.append(historyItem);
}
emit historyLoaded(innerResult);
Obviously, you also need a matching signal definition in your worker class:
Q_SIGNAL void historyLoaded(QList<HistoryItem*> result);
Also as noted in the comments, you have to start the background thread with QThread::start().
You may benefit from continuous loading:
You only load the elements that will be in view and only request the next set when they get scrolled into view (or just at a much lower pace in the background in general).
This can for example be done with Adding an object to the window object with the appropriate signal and letting the js trigger it when the end becomes visible.