I'm new to Qt, C++ and signals and slots. I'm trying to load in a webpage. Then set a label_3's text to the title of the webpage. To do this I figured I had to connect the loadFinished signal to my custom function. But I'm having trouble doing just that.
I've read up on the manual, different examples and other questions, but I'm stuck.
This is a excerpt from code I have so far.
How do I properly connect the signal loadFinished() to my function labelSetText()?
main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.cpp
void MainWindow::on_pushButton_clicked()
{
QString webAdress = ui->lineEdit->text();
QWebView *view = ui->webView;
view->load(QUrl(webAdress));
QString taxt = view->title();
connect(&view, SIGNAL(loadFinished(bool)),
this, SLOT(labelSetText(taxt)));
QWebPage * webPage = view->page();
}
void MainWindow::labelSetText(QString titleStr)
{
ui->label_3->setText(titleStr);
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
namespace Ui {
class MainWindow;
}
class MainWindow : public QWidget
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_pushButton_clicked();
void labelSetText(QString titleStr);
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
EDIT:
This is the error I get
E:\_Programming\C++\playAround\mainwindow.cpp:37: error: no matching function for call to 'MainWindow::connect(QWebView**, const char*, MainWindow* const, const char*)'
this, SLOT(labelSetText(taxt)));
^
That's not how connections work. A signal-slot connection can only pass data from the signal into the slot. It can't pass arbitrary variables like you do. The only way you could write your connect statement is as follows (the this argument is unnecessary):
connect(view, SIGNAL(loadFinished(bool)), SLOT(labelSetText(QString)));
This of course doesn't work, because the signal and slot are incompatible. You of course don't need the intermediate slot, since a label already has the slot you want, but it doesn't help:
connect(view, SIGNAL(loadFinished(bool)), ui->label_3, SLOT(setText(QString)));
Note that you should not have connect(&view, ... since view is already a pointer-to-QObject.
To do it, you need to leverage C++11:
connect(view, &QWebView::loadFinished, [=,this](){
this->ui->label_3->setText(taxt);
});
The lambda syntax translates into a functor class instance with copies of taxt and this as members. The compiler essentially creates the following, on the fly:
class Functor_1 {
MainWindow * _this;
QString taxt;
public:
MyFunctor_1(MainWindow * a1, const QString & a2) : _this(a1), taxt(a2) {}
void operator() {
_this->ui->label_3->setText(taxt);
}
}
...
connect(view, &QWebView::loadFinished, Functor_1(this, taxt));
Of course this means that if you want to use Qt 4 signals and slots, you need to add the taxt member to your MainWindow class, and create a slot to do what the functor does. So, for Qt 4:
class MainWindow : public QMainWindow {
Q_OBJECT
QString m_taxt;
Q_SLOT void loadFinished() {
ui->label_3->setText(m_taxt);
}
...
Q_SLOT void on_pushButton_clicked() {
QString webAdress = ui->lineEdit->text();
QWebView *view = ui->webView;
view->load(QUrl(webAdress));
m_taxt = view->title();
connect(view, SIGNAL(loadFinished(bool)), SLOT(loadFinished());
...
}
};
Note that you shouldn't be connecting repeatedly. For Qt 4 style connection, move the connect to MainWindow's constructor. For Qt 5 style connection, you need to break the connection once it fires.
Related
I have a Qt project (in Win10) that requires a non-member function in my MainWindow cpp file. It's a callback function that is triggered automatically from an external library when data from an attached sensor arrives. I like to display this data on a MainWindow widget (a plainTextEdit), but of course I cannot access any MainWindow elements from my callback function, and also no signal-slot concept works so far. Below is a minimal example of the problem:
MainWindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMutex>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void displayData();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
MainWindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
// define global variables
QMutex mutex;
bool new_data_arrived = false;
QString newDataStr;
void newData() {
// This is a callback function triggered when data from a sensor attached via USB arrives
// I want to pass the result as a QString to MainWindow::displayData() to display it on screen
mutex.lock();
new_data_arrived = true;
newDataStr = "new data";
mutex.unlock();
};
void MainWindow::displayData()
{
mutex.lock();
if (new_data_arrived == true) {
new_data_arrived = false;
ui->plainTextEdit->appendPlainText(newDatastr);
}
mutex.unlock();
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
The function MainWindow::displayData() is then running in a loop (but that’s unimportant for my problem, so I removed it from the minimal example above…).
As mentioned, newData() cannot access any elements of MainWindow, which is clear. The only workaround I found so far is to use global variables as shown above, to get data from the non-member function newData() into the member function displayData(). I also included a mutex to avoid possible share violation (no idea if that's necessary, but it doesn't harm...).
Is there a more elegant way? In principle, what I need is either a signal-slot concept, but newData() cannot emit signals of MainWindow, or to get this into the function newData(), so that ui elements can be accessed.
You should use QMetaObject::invokeMethod to call a slot of a QObject from a non QObject slot (your callback funtion).
Here is an example:
class MainWindow : public QMainWindow
{
Q_OBJECT
//...
public slots:
void displayData( QString szNewData );
};
//Cpp MainWindow.cpp file
static MainWindow * mainWindowObj = nullptr;
void setMainWindowObj( MainWindow * ptr ){
mainWindowObj = ptr;
}
void newData() {
// This is a callback function triggered when data from a sensor attached via USB arrives
// I want to pass the result as a QString to MainWindow::displayData() to display it on screen
if( mainWindowObj == nullptr )
return;
QMetaObject::invokeMethod( mainWindowObj, //pointer to MainWindow instance (see main.cpp)
"displayData", //slot name
Qt::QueuedConnection, //connection type
Q_ARG( QString, newDataStr ) ); //Param passed to the slot
};
void MainWindow::displayData( QString szNewData )
{
ui->plainTextEdit->appendPlainText( szNewData );
}
//main.cpp file or where MainWindow instance is initialized
void setMainWindowObj( MainWindow * ptr );
int main( int argc, char *argv[] ){
//....
QApplication app(argc, argv);
//....
MainWindow mainWindow;
setMainWindowObj ( &mainWindow );
//....
}
I have a MainWindow and then a SecondWindow class that opens when a button is clicked in MainWindow. There is a slider in this SecondWindow class I want to control the music in both the MainWindow and SecondWindow classes.
my main function:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
SecondWindow s;
//this connects MainWindow to SecondWindow to open
QObject::connect(...);
//plays background music
Music m;
QObject::connect(&s, SIGNAL(Volume(int)), &m, SLOT(setVol(int)));
return a.exec();
}
I have a music object that plays a function defined as such:
class Music
{
public:
Music();
public slots:
void setVol(int value);
private:
QMediaPlayer* music;
};
my SecondWindow is defined as such:
class SecondWindow: public QMainWindow
{
Q_OBJECT
public:
explicit SecondWindo(QWidget *parent = nullptr);
~SecondWindow();
signals:
void VolumeChange(int value);
void Volume(int value);
public slots:
void ShowSettingsWindow();
void changeVolume(int value);
private:
void Connections();
int volume; //holds the volume value based on the music_slider
QSlider* music_slider;
};
At the bottom of my SecondWindow's default constructor I have the following connect statement, with the SLOT definition:
QObject::connect(music_slider, SIGNAL(valueChanged(int)), this, SLOT(VolumeChange(int)));
void SecondWindow::VolumeChange(int value){
emit Volume(value);
}
then music has the SLOT defined as such:
void Music::setVol(int value){
music->setVolume(value);
}
I am currently trying to make it so that everytime the slider changes value, VolumeChange is called for the value that the slider currently has. Then, the signal Volume is called causing music to call the function setVol and thus set the volume. But I recieve an error on the second connect statement in main saying it could not convert all argument types.
How an I fix these connect statements to work or is there a better way to do this?
According to your code, this connect QObject::connect(music_slider, SIGNAL(valueChanged(int)), this, SLOT(VolumeChange(int))); in the second window is invalid because it doesn't have a SLOT called VolumeChange. In fact, it's a SIGNAL. However, it has a SLOT called changeVolume, which I think you really mean.
Playing with QMetaObject::invokeMethod method :
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::setText( int value)
{
QString s = QString::number(value);
ui->textEdit->setText(s);
}
void MainWindow::on_pushButton_clicked()
{
QGenericArgument genericArg = Q_ARG(int, 321);
bool inv = QMetaObject::invokeMethod( this,"setText",Qt::BlockingQueuedConnection, genericArg);
qDebug("inv = %d\n", inv);
}
QMetaObject::invokeMethod returns false.
I'm not sure regarding slot "setText". I took it from function name and I suppose it might be related. Where I can find list of slots at all? Should I create special slot for "setText"?
Maybe it is related to fact I run it from the same thread?
UPD:
I have added public slot instead of public method:
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
//void setText( int value);
private slots:
void on_pushButton_clicked();
public slots:
void setText(int value);
private:
Ui::MainWindow *ui;
};
And this helped, but why I'm getting 0 in setText value ?
Your button-event is handled by the event loop that owns the MainWindow object and this same object also contains the method you wish to invoke (setText()). That means that caller and callee (or signal and slot) in your case live on the same thread, and you must not use Qt::BlockingQueuedConnection! To quote the manual: Using this connection type to communicate between objects in the same thread will lead to deadlocks.
If you intent to do processing in your on_pushButton_clicked() after the setText() method has finished, use a Qt::DirectConnection instead, then your setText() will be called as if it was a simple function, and control returns to your clicked() function after setText() finished.
If you intent to finish processing all code in your on_pushButton_clicked() before processing of the setText() function starts, use a Qt::QueuedConnection.
If you wish to execute setText() in parallel to on_pushButton_clicked(), then move your setText() method to another object (which is owned by another thread). Only in this scenario a Qt::BlockingQueuedConnection makes sense.
I'm trying to set the connect() like this:
QObject::connect(&webControl,
SIGNAL(Ui::MainWindow::loadFinished(bool)),
&w,
SLOT(Ui::MainWindow::loadFinished(bool)));
in main() function but it give the error:
QObject::connect: No such signal
QWebView::Ui::MainWindow::loadFinished(bool)
w and webControl are declared like this:
MainWindow w;
QWebView webControl;
And here's my files:
mainWindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void loadFinished(bool arg1);
private:
Ui::MainWindow *ui;
};
mainWindow.cpp
void MainWindow::loadFinished(bool arg1)
{
}
Why I'm getting this error and how do I fix this?
You need to add QWebView *webView; to your mainwindow.h:
mainwindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QWebView *getWebView() const;
public slots:
void loadFinished(bool arg1);
void setWebView(QWebView *webControl);
private:
Ui::MainWindow *ui;
QWebView *webView;
};
mainwindow.cpp
...
QWebView *MainWindow::getWebView() const
{
return webView;
}
void MainWindow::setWebView(QWebView *webControl)
{
webView = webControl;
QObject::connect(webControl,
SIGNAL(loadFinished(bool)),
this,
SLOT(loadFinished(bool)));
}
If you really need declaration of QWebView in main.cpp then pass pointer to setWebView() function:
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
QWebView webControl;
w.setWebView(&webControl);
w.show();
return a.exec();
}
Your problem is that QWebView webControl; webcontrol is a qwebview and your signal is not in QWebview, but in MainWindow. You need that signal in QWebView. That is why its complaining about a signal that can't be found.
EDIT
You have a problem knowing what is a slot, and what a signal. Thy are two different things. A signal is like an alarm. An slot is the receiver and it works as a normal function.
If you want your webControl var to be the sender, then you have to declare the signal like this in y our QWebView.h class :
signals:
void yourSignalName(bool arg1);
and use the connect like this:
QObject::connect(&webControl,
SIGNAL(yourSignalName(bool)),
&w,
SLOT(loadFinished(bool)));
I'd suggest the new Qt5 syntax (which is optional):
QObject::connect(&webControl, &Ui::MainWindow::loadFinished,
&w, &Ui::MainWindow::loadFinished);
more on the new syntax: http://wiki.qt.io/New_Signal_Slot_Syntax
What I simply want to do is connect a signal inside a thread to a slot in the main thread to handle UI changes.
This is basically the current state of my thread, nothing fancy but it's just for testing purposes atm:
// synchronizer.h
class Synchronizer : public QObject
{
Q_OBJECT
public:
Synchronizer();
signals:
void newConnection(std::wstring id);
private:
QTimer timer;
private slots:
void synchronize();
}
// synchronizer.cpp
Synchronizer::Synchronizer()
{
connect(&timer, SIGNAL(timeout()), this, SLOT(synchronize()));
timer.start();
}
void Synchronizer::synchronize()
{
emit newConnection(L"test");
}
And here's how my MainWindow looks:
// mainwindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
Synchronizer synchronizer;
private slots:
void addConnection(std::wstring id);
}
// mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(&synchronizer, SIGNAL(newConnection(std::wstring)),
this, SLOT(addConnection(std::wstring)));
QThread *thread = new QThread;
// The problems starts here?
synchronizer.moveToThread(thread);
thread->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::addConnection(std::wstring id)
{
// Add a new connection to QListWidget
ui->connectionList(QString::fromStdWString(id));
}
If I remove there lines:
synchronizer.moveToThread(thread);
thread->start();
everything seems to work as expected, that is a new item is added every second to a QListWidget but as soon as I move the synchronizer object to thread it simply stops working. I'd presume it has something to do with the connect context but I'm not really sure how something like this should be achieved as I'm quite new to Qt.
It seems that the in this case was simply the fact that I am using std::wstring as an argument in the signal without registering the type first and after adding the following line qRegisterMetaType<std::wstring>("std::wstring"); to the code, everything worked as expected.
If I would have read the output console more carefully I would have solved the problem without too much hassle as it was clearly stated that:
QObject::connect: Cannot queue arguments of type 'std::wstring'
So simply speaking, read the compiler output and don't be stupid like me :)