Qt pass additional argument to slot AND keep emitted signal data - c++

I have searched the web on this issue and I've repeatedly got answers referring to the use of QSignalMapper. But my problem is pretty clear, QSignalMapper automatically gets rid of whatever is originally emitted and replaces it with basically nothing, plus the new data that is set via setMapping().
The problem here is simple.
I have a QNetworkAccessManager that parses html and updates a vector containing text data:
void DataManager::startHttpRequest(QString url, int index)
{
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
//QSignalMapper* signalMapper = new QSignalMapper(this);
//connect(manager,SIGNAL(finished(QNetworkReply*)), signalMapper,SLOT(map()));
//signalMapper->setMapping(manager, index);
//connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(insertUpdate(int)));
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(finishHttpRequest(QNetworkReply*)));
manager->get(QNetworkRequest(QUrl(url)));
qDebug() << index;
}
and here is what happens when the request is finished, the normal way:
void DataManager::finishHttpRequest(QNetworkReply *reply)
{
QString html = QString(reply->readAll()).simplified();
QString info;
int start = html.indexOf("<span id=\"SalePrice\" >");
if(start != -1)
{
QString price = html.mid(start + 23, 30);
int end = price.indexOf("</span>");
info = price.mid(0, end - 1);
qWarning() << price.mid(0, end - 1);
}
else
{
info = "NA";
}
// Do more stuff
}
Using the normal way of signals and slots, I would not be able to know the index of the vector I am updating,
Or,
If I am using QSignalMapper, I know the index, but not the data that comes with it.
How do I get BOTH working (index + data)?
(something like mySlot(QNetworkReply *reply, int *index), but we all know that won't work)
Many thanks in advance.

While it's probably not the best,
sender()->setObjectName(const QString & name) allows the sender to name itself.
The sender's name can be accessed from the receiving slot via sender()->ObjectName()
As documented on http://qt-project.org/doc/qt-5/qobject.html#objectName-prop.

Related

How to get data for a custom widget using an async source

I'm new to QT and I would like some help. If any of you could help me I would really appreciate it.
QUESTION:
I have an asynchronous class that makes an HTTP request and it's going to receive some data into a JSON format and from there I will extract the necessary information which should be passed to my custom widget. How can I do that? Because I don't know when the information will arrive.
WHAT I'VE DONE SO FAR:
My HTTP request and parsing JSON class:
WeatherAPI::WeatherAPI(QObject *parent) : QObject(parent) {
manager = new QNetworkAccessManager(this);
QObject::connect(manager, SIGNAL(finished(QNetworkReply * )), this, SLOT(readData(QNetworkReply * )));
}
void WeatherAPI::readData(QNetworkReply *reply) {
if (reply->error() == QNetworkReply::NoError) {
QString strReply = (QString) reply->readAll();
QJsonDocument jsonResponse = QJsonDocument::fromJson(strReply.toUtf8());
QJsonObject jsonObject = jsonResponse.object();
weatherObject.city = jsonObject["name"].toString();
weatherObject.temperature = QString::number(jsonObject["main"].toObject()["temp"].toDouble() - 273.15);
int ts = jsonObject["dt"].toInt();
weatherObject.time = QDateTime::fromSecsSinceEpoch(ts).toString("hh:mm");
auto weatherData = jsonObject["weather"].toArray()[0].toObject()["main"].toString();
if (weatherData == "Clouds") {
weatherObject.icon = "Sun.png";
}
} else {
qDebug() << "ERROR";
}
}
void WeatherAPI::requestDataForCity(const QString &city) {
QString link = linkTemplate.arg(city, key);
QUrl url(link);
manager->get(QNetworkRequest(url));
}
const WeatherObject &WeatherAPI::getWeatherObject() const {
return weatherObject;
}
Now here is my custom Widget:
void WeatherButton::initStyle(const QJsonValue &json) {
PolygonButtonWidget::initStyle(json);
auto cities = json.toObject()["cities"].toArray();
api = new WeatherAPI(this);
for (auto c: cities) {
QString city = c.toString();
api->requestDataForCity(city); // HERE I'm making the http request
WeatherObject data = api->getWeatherObject();//HERE I'm getting the DATA
m_title = data.city;
m_time = data.time;
m_icon = data.icon;
m_temperature = data.temperature;
}
}
In that function from WeatherButton::initStyle I'm going to make an HTTP request and also I'm going to place the data into the necessary variable. Now my question is... How can I wait for that data to be received and just after that to place them into those variables?
So far the only solution I know so far is to use a QEventLoop, but at that moment I'm going to basically convert an async call into a sync one, which is not quite what I want. I want to be fully async.
WeatherObject data = api->getWeatherObject(); //HERE I'm getting the DATA
No, you do not get the data here. WeatherAPI::readData is where you get the data.
That is the point of the Signal - Slot mechanism. You do not wait for an event to happen, but react to it via callback, i.e. slot.
Having that in mind, you have to rethink and extend your code. Here is one way to do this:
In the WeatherAPI class define a dataReady(const WeatherObject &weatherObject) signal
Emit this signal in WeatherAPI::readData like this:
void WeatherAPI::readData(QNetworkReply *reply) {
if (reply->error() == QNetworkReply::NoError) {
// the processing of the http response remains unchanged
// ...
emit dataReady(weatherObject);
} else {
qDebug() << "ERROR";
}
}
In the WeatherButton class define a onDataReady slot with the following implementation:
void WeatherButton::onDataReady(const WeatherObject &weatherObject) {
m_title = weatherObject.city;
m_time = weatherObject.time;
m_icon = weatherObject.icon;
m_temperature = weatherObject.temperature;
}
Connect the newly created signal and slot in WeatherButton::initStyle like this:
void WeatherButton::initStyle(const QJsonValue &json) {
PolygonButtonWidget::initStyle(json);
auto cities = json.toObject()["cities"].toArray();
api = new WeatherAPI(this);
connect(api, &WeatherAPI::dataReady, this, &WeatherButton::onDataReady);
for (auto c: cities) {
QString city = c.toString();
api->requestDataForCity(city); // HERE I'm making the http request
}
}
As a sidenote I should say, that initStyle is probably not the best place to instantiate the WeatherAPI. api seems to be an attribute of WeatherButton, hence it should be initialized in the constructor of the class.

Wait till request finished Qt

My program is getting data from mysql table via json format through php script, that generating json.
My function sends to server post request (with some params) and getting response. With that all okay. Works fine.
But, when I want to get specified cell (data from json array), it takes empty string (program works fast and taking data from a string before it sets up)
Here's the code with post request:
void dbase::requestor(QString option)
{
curr_js = ""; //nulling string
QString urla = host+"transfer.php";
// /////////
QNetworkAccessManager * manager = new QNetworkAccessManager(this);
QUrl url(urla);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery params;
params.addQueryItem("api", key);
params.addQueryItem("option", option);
//QEventLoop loop;
connect(manager, SIGNAL(finished(QNetworkReply *)),this, SLOT(replyFinished(QNetworkReply *)));
//loop.exec(); tryed eventloop, but bcuz of this my replyFinished is not accessible. error code under this one
manager->post(request, params.query().toUtf8());
}
If using eventloop
QObject::connect: No such slot QEventLoop::replyFinished(QNetworkReply *)
replyFinished function (setting up variable)
void dbase::replyFinished(QNetworkReply *reply)
{
curr_js = reply->readAll();
}
Usage (where is problem)
QString req = "SOME REQUEST";
database.requestor(req);
if (database.db_cell (0,0) == "")
{
qDebug()<<database.db_cell (0,0) << " - EMPTY - "<<database.curr_js;
}
So in this case I'm getting
"" - EMPTY - ""
But if I getting data from string (created button for test), it's there:
{"0":["1", "Admin","hashpwd"],"1":["2", "Albert","hashpwd"]}
db_cell function
QString dbase::db_cell (int indexrow, int indexcols)
{
QJsonDocument sd = QJsonDocument::fromJson(curr_js.toUtf8());
QJsonObject setobj = sd.object();
QJsonValue qqq = setobj.value(QString(QString::number(indexrow))).toArray()[indexcols];
return qqq.toString();
}
As I see, problem is that program need to wait before getting a data from json-string.

Extract menu action data in receiving function or slot

In my menu, I am setting data to the menu actions. How can I extract that data in my slot? Or even better, instead of connecting a slot, can I also connect a member function that is able to extract the action data (like in the 1st connect)? The action data is meant to identify each action. As a sidenode, I am not sure if I can use several menu action entries on only one openNote-action.
void Traymenu::createMainContextMenu() {
QAction *actionNewNote = m_mainContextMenu.addAction("Neue Notiz");
actionNewNote->setIcon(QIcon("C:\\new.ico"));
actionNewNote->setIconVisibleInMenu(true);
QObject::connect(actionNewNote,&QAction::triggered,this,&Traymenu::newNote);
QString menuEntryName;
QAction *openNote;
QVariant noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
connect(openNote,SIGNAL(triggered()),this,SLOT(s_showNote()));
noteID.setValue(m_noteList[i].data()->getID());
openNote->setData(noteID);
}
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
And the slot:
void Traymenu::s_showNote() {
QObject* obj = sender();
//int noteID = data.toInt();
//Search all notes in noteList for that ID and show it
}
Using QObject::sender()
You can use QObject::sender() to get the signal's sender, followed by qobject_cast to cast the sender pointer to the right type.
void Traymenu::s_showNote()
{
QAction* act = qobject_cast<QAction *>(sender());
if (act != 0)
{
QVariant data = act->data();
int noteID = data.toInt();
showNote(noteID); // isolate showNote logic from "get my ID" stuff
}
}
void Traymenu::showNote(int noteID)
{
// Do the real work here, now that you have the ID ...
}
As the Qt documentation warns, "This function violates the object-oriented principle of modularity." It's still a fairly safe and standard practice, though — just one with some shortcomings. In particular, note that you're committing to having a s_showNote method that only works when it's accessed as a slot (otherwise sender is 0).
Using QSignalMapper
Alternatively, you can use the QSignalMapper class to return a pointer to teh item or to associate a unique identifier (int or QString) with each item.
Something like this:
void Traymenu::createMainContextMenu()
{
signalMapper = new QSignalMapper(this); // (or initialize elsewhere)
// ... (create your newNote here same as before) ...
QString menuEntryName;
QAction *openNote;
int noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
noteID = m_noteList[i].data()->getID();
openNote->setData(QVariant(noteID)); // (if you still need data in the QActions)
signalMapper->setMapping(openNote, noteID);
}
connect(signalMapper, SIGNAL(mapped(int)),
this, SLOT(showNote(int)));
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
void Traymenu::showNote(int noteID) {
// Now you have the ID ...
}
This pattern has the benefit of isolating all the ugly "Wait, how do I get my identifier?" stuff in one spot, instead of having both the initialization code and the slot function having code for associating actions and IDs.
I would write it like:
void Traymenu::s_showNote() {
QObject* obj = sender();
QAction *action = qobject_cast<QAction *>(obj);
int id = action->data().toInt();
for (int i = 0; i < m_noteList.count(); i++) {
if (m_noteList[i].data()->getID() == id) {
[..]
}
}
}

how to return an Qt object from the result of an thread (Qtfutur)

i'm trying to load some data from a server and fill a Qt list. i want to run the dowloanding in a thread. so there is the code of:
principal function in the App.cpp
loadInterestPlaces(QString& urlInterestPlaces) {
LoadData* Data = new LoadData(urlInterestPlaces);
QFuture< list <InterestPlace *> > future = QtConcurrent::run(Data,
&LoadData::startLoading);
// Invoke our onLoadingFinished slot after the loading has finished.
bool ok = connect(&m_watcher, SIGNAL(finished()), this,
SLOT(onLoadingFinished()));
Q_ASSERT(ok);
Q_UNUSED(ok);
// starts watching the given future
m_watcher.setFuture(future);
}
void ApplicationUI::onLoadingFinished() {
qDebug() << "Loading finished";
interestPlacesList = m_watcher.future().result();
qDebug() << "List size= " << interestPlacesList.size();
}
}
the LoadData.cpp file : this is the code of the startloanding function :
std::list<InterestPlace *> LoadData::startLoading()
{
QNetworkAccessManager* netManager = new QNetworkAccessManager(this);
const QUrl url(_URL);
QNetworkRequest request(url);
QNetworkReply* reply = netManager->get(request);
netManager->moveToThread(this->thread());
netManager->setParent(this);
bool ok = connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
qDebug() << reply->isFinished();
Q_ASSERT(ok);
Q_UNUSED(ok);
qDebug() << "load data: liste size" <<interestPlacesList.size();
return interestPlacesList;
}
Finally inside the SLOT onreplyfinished i parse the data and fill the list.
But the problem here, the QFuture is finished before the downloading so that the list is always empty.
How could i return the list filled just after the execution of the onReplyFinished ?
You may be making this more complex than you need to. Here is what I do to get data off the web. In my case I'm downloading a large ZIP file, writing it out and then unziping it, but the basic steps are the same (I have omitted a lot of my specific code for clarity):
void HtmlDownloader::startDownload() {
// Creates the network request and sets the destination URL
QNetworkRequest request = QNetworkRequest();
request.setUrl(mUrl);
// Creates the network access manager and connects a custom slot to its
// finished signal. Checks the return value for errors.
QNetworkAccessManager *networkAccessManager =
new QNetworkAccessManager(this);
bool c = connect(networkAccessManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(requestFinished(QNetworkReply*)));
Q_ASSERT(c);
// Indicate that the variable res isn't used in the rest of the app, to prevent
// a compiler warning
Q_UNUSED(c);
// Sends the request
QNetworkReply *reply = networkAccessManager->get(request);
c = connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(onDownloadProgress(qint64,qint64)));
Q_ASSERT(c);
}
void HtmlDownloader::requestFinished(QNetworkReply *reply) {
// Handle the reply data...
// Check the network reply for errors
if (reply->error() == QNetworkReply::NoError) {
// use reply->readAll() or reply->read() to get the returned data
// and process into a list. The list can't be returned but could
// be sent as a payload of a signal, or stored in a data member and
// a signal sent to indicate it is available.
}
reply->deleteLater();
}
void HtmlDownloader::onDownloadProgress(qint64 bytesRecieved, qint64 bytesTotal) {
emit downloadProgress(bytesRecieved, bytesTotal);
}

Convert data from QLineEdit to QString when a QPushButton is clicked.

This site is trully amazing I would like to thanks anyone who answers my post. You would probably find my post a repost of a repost since it's again about retreive data from a QLineEdit. I've been trying to fix the problem for 2 entire days looking throught the hudge stackoverflow database but I can't find a answer.
Basically I want to retreive an ip address from a QLineEdit and a port number from a QLineEdit to do so I do :
myclass::myclass(QWidget *parent = 0)
{
_mainuilayout = new QGridLayout();
ipAddress = new QLineEdit();
portnumber = new QLineEdit();
QFormLayout *connect2adress = new QFormLayout();
connect2adress->addRow("Ip Adress : ", ipAddress);
connect2adress->addRow("Port number : ", portnumber);
_launch = new QPushButton("Launch server");
_mainuilayout->addWidget(_launch);
_mainuilayout->addLayout(connect2adress);
QObject::connect(_launch, SIGNAL(clicked()), this, SLOT(setipAddress()));
QObject::connect(_launch, SIGNAL(clicked()), this, SLOT(setportnumber()));
server->connectTo(thisaddress,thisport);
QObject::connect(_launch, SIGNAL(clicked()), server, SLOT(launchserver()));
}
Here you got the code to store the ip address in a QString thisaddress is a QString defined in the header as well as thisport
void myclass::setipAddress()
{
thisaddress = ipAddress->text();
}
void myclass::setportnumber()
{
thisport = portnumber->text().toShort();
}
What I want is when I click on launch button it store the data from QLineEdit in thisaddress and in thisport so I can launch the server, here is the code for connectTo
void server::connectTo(QString ipAdress,quint16 port)
{
if(!ipAdress.isEmpty() && port != 0 )
{
ipAddress = ipAdress;
portnumber = port;
}
}
The problem is really coming when I convert QLineEdit to QString because when I directly assign thisAddress and thisportnumber like that it's working
thisAddress = "127.0.0.1"
thisportnumber = 5855
Overwise I got this error :
Unsupported socket Operation
So guys do you have any answers to help me ?
I've tried to follow the solution given in that stack post Store QLineEdit's data into a QString upon a QPushButton click
but it still not working I'm still working on it if I find something I let you know !
Thanks for the replies !
It has been ages since I used Qt, but I see nobody has answered your question so I'll add my two cents.
I don't think you can expect a specific call order when you connect an event to multiple slots. You are connecting clicked() event on the _launch button to populate your IP address and port number, and also to call launchserver(). But you require the other two slots to be called first.
It would be better if you can just connect it to a single slot, which will update the appropriate member values and then do the connect.
It looks like I'm either missing something, or there is confusion about what exactly is happening in the connect calls. The connect function only tells Qt what to do when a signal is emitted (usually when the user does something). It does not wait for that event to happen.
myclass::myclass(QWidget *parent = 0)
{
// <snipped construction and layout>
// Tell Qt what to do when the clicked signal happens:
QObject::connect(_launch, SIGNAL(clicked()), this, SLOT(setipAddress()));
QObject::connect(_launch, SIGNAL(clicked()), this, SLOT(setportnumber()));
// At this point, nothing has been set, because the user hasn't had time to
// fill in anything -- you've only told the application what to do when they
// do so.
server->connectTo(thisaddress,thisport);
// As pointed out previously, even though you're telling the application what
// to do, the application says nothing about the order, so you don't know if
// this will be run before or after the slots to set the ip address and port
// number.
// What you do know is that it won't be run before the previous line of code,
// which does the first connection.
QObject::connect(_launch, SIGNAL(clicked()), server, SLOT(launchserver()));
}
The following is a more straightforward implementation:
myclass::myclass(QWidget *parent = 0)
{
_mainuilayout = new QGridLayout();
_ipAddress = new QLineEdit();
_portnumber = new QLineEdit();
QFormLayout *connect2adress = new QFormLayout();
connect2adress->addRow("Ip Adress : ", ipAddress);
connect2adress->addRow("Port number : ", portnumber);
_launch = new QPushButton("Launch server");
_mainuilayout->addWidget(_launch);
_mainuilayout->addLayout(connect2adress);
QObject::connect(_launch, SIGNAL(clicked()), server, SLOT(launchserver()));
}
myclass::launchserver()
{
server->connectTo(_ipAddress->text(),_portnumber->text().toShort());
}