QPointer for accessing object methods - qt

have a strange problem using QPointer for accessing object method from a different, not related, object.
I've prepared a little example to explain it better.
I created two QWidget, Widget_A and Widget_B in a simple QDialog.
I need to access from Widget_B a public method of Widget_A: I passed widget_a pointer to a method of widget_b for QPointer assignment. Widget_A contains a QLineEdit that I want to clear from Widget_B.
The problem is when hitting pushButton_B nothing happens to lineEdit_A. Console doesn't show any problem, so the most obvious reason is Widget_B is working on a different Widget_A object, not the one I passed.
I also created a connection from QWidget_A to QWidget_B so when editing lineEdit_A a label_B text is changed accordingly: this works.
Before asking, I need a QPointer since in my real project Widget_A could be deleted. Can you explain where I'm wrong? Should I choose a different way? Thanks. Follow some snippets
widget_a.h
class Widget_A : public QWidget
{
Q_OBJECT
public:
explicit Widget_A(QWidget *parent = 0);
~Widget_A();
void clearLineEdit_A();
signals:
void lineEdit_A_changed(const QString &);
private slots:
void on_lineEdit_A_textChanged(const QString &arg1);
private:
Ui::Widget_A *ui;
};
widget_a.cpp
Widget_A::Widget_A(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget_A)
{
ui->setupUi(this);
ui->lineEdit_A->setText("write something here...");
ui->lineEdit_A->selectAll();
}
Widget_A::~Widget_A()
{
delete ui;
}
void Widget_A::clearLineEdit_A()
{
ui->lineEdit_A->clear();
}
void Widget_A::on_lineEdit_A_textChanged(const QString &arg1)
{
emit lineEdit_A_changed(arg1);
}
widget_b.h
class Widget_B : public QWidget
{
Q_OBJECT
public:
explicit Widget_B(QWidget *parent = 0);
~Widget_B();
void controlWidget(QWidget *w);
private slots:
void changeLabel_B(const QString &text);
void on_pushButton_B_clicked();
private:
Ui::Widget_B *ui;
QPointer<QWidget> qPtrWdgt;
};
widget_b.cpp
Widget_B::Widget_B(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget_B)
{
ui->setupUi(this);
}
Widget_B::~Widget_B()
{
delete ui;
}
void Widget_B::controlWidget(QWidget *w)
{
qPtrWdgt = w;
connect(qPtrWdgt, SIGNAL(lineEdit_A_changed(const QString &)),
this, SLOT(changeLabel_B(const QString &)));
}
void Widget_B::changeLabel_B(const QString &text)
{
ui->label_B->setText(text);
}
void Widget_B::on_pushButton_B_clicked()
{
Widget_A(qPtrWdgt).clearLineEdit_A();
}
dialog.h
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
Widget_A *widget_a;
Widget_B *widget_b;
};
dialog.cpp
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
widget_a = new Widget_A(this);
widget_b = new Widget_B(this);
widget_b->controlWidget(widget_a);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(widget_a);
mainLayout->addWidget(widget_b);
setLayout(mainLayout);
}

Widget_A(qPtrWdgt).clearLineEdit_A();
This creates a new Widget_A by using the Widget_A(QWidget *) constructor, then calls clearLineEdit_A() on it.
If you define qPtrWdgt as QPointer<Widget_A> instead, and since QPointer overloads operator->, then you can use:
qPtrWdgt->clearLineEdit_A();
You'll need to include Widget_A's header in Widget_B's header too.

Related

How to deal with signal and slot in different classes in Qt

My UI looks like following diagram:
Inside MainWindow, there is a big red QStackedWidget area where i will use to put different custom stackedwidgets, current StackedWidget1 have three CardWidgets, (StackedWidget and CardWidget are both cpp class), inside each CardWidget, there is a pushbutton, what i want to do is when i click the pushbotton of CardWidget, the CardWidget send signal to MainWindow, the MainWindow then switch from current stackedwidget to another custom stackedwidget, meanwhile, the signal will carry some variables in cardwidget to the new custom stackedwidget.
Now the problem is after i click one pushbutton, the cardwidget1 emit out signal,the slot function in mainwindow didn't react.
I searched out one question similiar to my situation,link, maybe just as Eeli K and dheerendra said, here the cardwidget-sender instance used in connect(...) in mainwindow.cpp is different from the cardwidget-sender instance in StackedWidget1.cpp, so mainwindow actually didn't receive the signal.
Another thing, i construct the cardwidget-sender instance without any argument in mainwindow.cpp, but i construct the cardwidget-sender instance with 5 arguments in StackedWidget1.cpp.
I write a minimal,reproducible example Qt project - download link.
For those who would like to see the code directly, i extract some of its code:
# mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
stackedwidget1 = new StackedWidget1(ui->stackedWidget);
ui->stackedWidget->addWidget(stackedwidget1);
CardWidget* card = new CardWidget();
bool c_res = connect (card, &CardWidget::open_panel_signal, this, &MainWindow::open_panel);
qDebug()<<"Connect open_panels signal and slot ? :"<<c_res;
//If i emit signal from here instead of from cardwidget, it can trigger the slot function, uncomment to see it
// int dummy_int = 0;
// QString dummy_string = "abcd";
// emit card->open_panel_signal(dummy_int, dummy_string);
}
void MainWindow::open_panel(int index, QString text)
{
qDebug()<<"test text from cardwidget"<<index<<"is"<<text;
stackedwidget2 = new StackedWidget2(ui->stackedWidget);
ui->stackedWidget->addWidget(stackedwidget2);
ui->stackedWidget->setCurrentWidget(stackedwidget2);
}
#mainwindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void open_panel(int index, QString text);
private:
Ui::MainWindow *ui;
StackedWidget1* stackedwidget1;
StackedWidget2* stackedwidget2;
};
# stackedwidget1.cpp
StackedWidget1::StackedWidget1(QWidget *parent) :
QWidget(parent),
ui(new Ui::StackedWidget1)
{
ui->setupUi(this);
for(int col = 0; col< 3; col++){
CardWidget* card = new CardWidget(this,col);
ui->tableWidget->setCellWidget(0, col, card);
ui->tableWidget->update();
}
}
# cardwidget.cpp
CardWidget::CardWidget(QWidget *parent, int index) :
QWidget(parent),
ui(new Ui::CardWidget),
index_(index)
{
ui->setupUi(this);
}
void CardWidget::on_pushButton_clicked()
{
QString text = "text from cardwidget";
emit open_panel_signal(index_, text);
qDebug()<<"emit signal from cardwidget"<<index_;
}
# cardwidget.h
class CardWidget : public QWidget
{
Q_OBJECT
public:
explicit CardWidget(QWidget *parent = nullptr, int index = 0);
~CardWidget();
signals:
void open_panel_signal(int, QString);
private slots:
void on_pushButton_clicked();
private:
Ui::CardWidget *ui;
int index_;
};
# stackedwidget2.cpp
StackedWidget2::StackedWidget2(QWidget *parent) :
QWidget(parent),
ui(new Ui::StackedWidget2)
{
ui->setupUi(this);
}
Is there any good way to solve this problem? Thanks in advance!
The Basic idea should be :
Signal (card Widget) -> Signal (stackedwidget1) -> SLOT(Mainwindow)
You can follow the general approach:
mainwindow.h
public slots:
void open_panel(int, QString);
cardwidget.h
signals:
void open_panel_signal(int, QString);
cardwidget.cpp
void CardWidget::on_pushButton_clicked()
{
QString text = "text from cardwidget";
emit open_panel_signal(index_, text);
qDebug()<<"emit signal from cardwidget"<<index_;
}
stackedwidget1.h
signals:
void open_panel_signal(int, QString);
stackedwidget1.cpp
StackedWidget1::StackedWidget1(QWidget *parent) :
QWidget(parent),
ui(new Ui::StackedWidget1)
{
ui->setupUi(this);
for(int col = 0; col< 3; col++){
CardWidget* card = new CardWidget(this,col);
ui->tableWidget->setCellWidget(0, col, card);
ui->tableWidget->update();
connect(card,SIGNAL(open_panel_signal(int, QString)),this,SIGNAL(open_panel_signal(int, QString)));
}
}
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
stackedwidget1 = new StackedWidget1(ui->stackedWidget);
ui->stackedWidget->addWidget(stackedwidget1);
connect(stackedwidget1,SIGNAL(open_panel_signal(int, QString)),this,SLOT(open_panel(int, QString)));
}

Qt window resize aspect ratio 1:1

I want to resize the app window proportionally 1:1. I tried to change it inside the ResizeEvent, but then I got the window flickering. Now my code looks like this, but it doesn't work.
filterobject.h:
class FilterObject:public QObject{
public:
QWidget *target = nullptr;//it holds a pointer to target object
int goalHeight=0;
FilterObject(QObject *parent=nullptr):QObject(parent){}//uses QObject constructor
bool eventFilter(QObject *watched, QEvent *event) override;//and overrides eventFilter function
};
widget.h:
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
virtual int heightForWidth ( int w ) const { return w*9/16;}
//virtual void resizeEvent(QResizeEvent *event) override;
~Widget();
private:
Ui::Widget *ui;
};
widget.cpp:
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void QWidget::resizeEvent(QResizeEvent *event){
FilterObject *filter = new FilterObject();
QWidget *targetWidget = new QWidget();
filter->target=targetWidget;
targetWidget->installEventFilter(filter);
}
bool FilterObject::eventFilter(QObject *watched, QEvent *event) {
if(watched != target){//checks for correct target object.
return false;
}
if(event->type() != QEvent::Resize){//and correct event
return false;
}
QResizeEvent *resEvent = static_cast<QResizeEvent*>(event);
goalHeight = 9*resEvent->size().width()/16;
if(target->height()!=goalHeight){
target->setFixedHeight(goalHeight);
}
return true;
};
Perhaps this code will work, but my condition if(event->type() != QEvent::Resize) does not work .. Any ideas?
You have some problems in your code. First of all you should install event filter once e.g in your constructor. You create an object of event filter and install it every time resizeEvent is triggered which is wrong. Also you are installing event filter on the wrong object (a new QWidget). So remove the resizeEvent function and insert in the constructor of Widget:
FilterObject *filter = new FilterObject();
filter->target=this;
installEventFilter(filter);

QSerialPort in QThread

I was trying to read the serial output from a thread. The Serial port is opened in main program and passed the QSerialPort variable to thread.
Then I called serial.waitForReadyRead() function in thread, But its not getting any value from serial port. I confirmed that serial port is sending data by reading the same from main program.
Can anyone knows why this behavior is happening?
Adding the code below:
mainwindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QSerialPort *serial;
private slots:
void onProgressChanged();
private:
Ui::MainWindow *ui;
};
class WorkerThread : public QThread {
Q_OBJECT
public:
QSerialPort *thread_serial;
void run() {
int ret = thread_serial->waitForReadyRead(60000); //thread is not getting any serial data.
//so it will not emit progressChanged.
if (ret)
{
qDebug()<<"Data Rate Read done";
QByteArray recvData = thread_serial->readAll();
qDebug()<<recvData;
emit progressChanged();
}
}
// Define signal:
signals:
void progressChanged();
};
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
serial = new QSerialPort(this);
openSerialPort( );
}
void MainWindow::openSerialPort( )
{
serial->setPortName("COM 4");
serial->setBaudRate(QSerialPort::Baud115200);
serial->setDataBits(QSerialPort::Data8);
serial->setParity(QSerialPort::NoParity);
serial->setStopBits(QSerialPort::OneStop);
serial->setFlowControl(QSerialPort::NoFlowControl);
if (serial->open(QIODevice::ReadWrite)) {
qDebug()<<"Connected";
WorkerThread *workerThread = new WorkerThread();
workerThread->thread_serial = serial;
connect(workerThread, SIGNAL(progressChanged()),
SLOT(onProgressChanged()));
workerThread->start();
}
}
void MainWindow::onProgressChanged()
{
qDebug()<<"inside onProgressChanged";
}

QImage and Threads

I am having problems with QImages and Qthreads.
I am trying to load big images in a Thread and then display them as QPixmap on a QLabel.
My problem is that as long as I don't use a different thread to load the QImages, everything is perfect but as soon as I use a different thread, nothing is renderder.
Though I still have a valid size for my QImage.
The thing that puzzles me is that, if I just comment the 22nd line in the cpp that moves the loader to the other thread, the label displays nicely.
Does anyone have an idea?
Here is my very simplified code:
Header :
class Loader : public QObject
{
Q_OBJECT
public:
explicit Loader(QObject *parent = 0);
signals:
void imageLoaded(QString, const QImage &);
public slots:
void loadImage(const QString& fichier);
};
namespace Ui {
class MainWindow;
}
class LoaderImages;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void loadImage(const QString& dossier);
private slots:
void imageAvailable(const QString& dossier, const QImage& img);
private:
Ui::MainWindow *ui;
//QString mDossier;
Loader* mLoader;
//QMap<QString, QImage*> mMapDesImages;
int mWidth;
};
cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFile>
#include <QPixmap>
#include <QImage>
#include <QDir>
#include <QThread>
#include <QDebug>
#include <QLabel>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
mLoader(new Loader(NULL)),
mWidth(0)
{
ui->setupUi(this);
QThread* thread = new QThread(this);
mLoader->moveToThread(thread);
thread->start();
connect(this, SIGNAL(loadImage(QString)), mLoader, SLOT(loadImage(QString)));
connect(mLoader, SIGNAL(imageLoaded(QString,QImage)), this, SLOT(imageAvailable(QString,QImage)));
emit loadImage("C:/img.jpg");
}
void MainWindow::imageAvailable(const QString &dossier, const QImage& img)
{
mWidth += (img.width() + 20);
ui->mScrollContent->setMinimumSize(mWidth,img.height());
QLabel* lab = new QLabel(ui->mScrollContent);
lab->setFixedSize(img.width(), img.height());
lab->setGeometry(mWidth - img.width() + 20, 0, img.width(), img.height());
lab->setPixmap(QPixmap::fromImage(img));
}
MainWindow::~MainWindow()
{
delete mLoader;
delete ui;
}
Loader::Loader(QObject *parent) :
QObject(parent)
{
}
void Loader::loadImage(const QString& fichier)
{
QImage* image = new QImage(fichier);
emit imageLoaded(fichier, *image);
}
Thx!
There are several mistakes:
You're not showing the label. When the image loader is in the GUI thread, the image is loaded and the label added to the contents pane before the main window is shown. Since the parent is shown, the children become visible.
When the loading is done in another thread, you'll be adding image labels to a widget that's already shown. Such child widgets are not visible unless you explicitly show() them.
You're leaking the image in loadImage. There's no reason to put that QImage on the heap.
You're allowing a running QThread to be destructed. That's a common error since QThread is essentially broken by design. Sane C++ classes should be always destructible. QThread isn't. Thus you need a workaround.
You're not setting the minimum height of the contents widget as well.
You might wish to consider the use QtConcurrent::run instead of a dedicated thread. This is especially worthwhile when the operation you're undertaking is a one liner, more or less. I've shown both, the implementations are alternated between at runtime. Note that you need to add the concurrent module and CONFIG += c++11 to the project file.
Style bugs:
There's no reason to pass NULL for default-valued parameters that are already zero.
There's no reason to keep QObject members that have the lifetime of the parent object on the heap, if such members are constructed along with the parent object.
Just because Qt Creator comes with silly template files doesn't mean that you shouldn't be using a std::unique_ptr or QScopedPointer to hold the ui member. Naked pointers should almost never be members unless they're pointers to QObjects with parents.
As quite a bit of the code is missing, I can't really tell what else might be wrong. Below is a complete example.
// https://github.com/KubaO/stackoverflown/tree/master/questions/image-loader-24853687
#include <QtWidgets>
#include <QtConcurrent>
class Thread final : public QThread {
public:
~Thread() { quit(); wait(); }
};
class Loader : public QObject
{
Q_OBJECT
public:
explicit Loader(QObject *parent = nullptr) : QObject(parent) {}
Q_SIGNAL void imageLoaded(const QString &, const QImage &);
Q_SLOT void loadImage(const QString& fichier) {
QImage img(fichier);
if (! img.isNull()) emit imageLoaded(fichier, img);
}
};
class MainWindow : public QWidget
{
Q_OBJECT
Loader m_loader;
Thread m_loaderThread;
QGridLayout m_layout{this};
QPushButton m_open{"Open"};
QScrollArea m_view;
QWidget m_content;
int m_width{};
bool m_threadImpl = true;
Q_SIGNAL void loadImage(const QString &);
Q_SIGNAL void imageLoaded(const QString &, const QImage & img);
Q_SLOT void imageAvailable(const QString &, const QImage & img) {
int spacing = 20;
if (m_width) m_width += spacing;
auto lab = new QLabel(&m_content);
lab->setFixedSize(img.width(), img.height());
lab->setGeometry(m_width, 0, img.width(), img.height());
lab->setPixmap(QPixmap::fromImage(img));
lab->show();
m_width += img.width();
m_content.setMinimumWidth(m_width);
m_content.setMinimumHeight(qMax(m_content.minimumHeight(), img.height()));
}
Q_SLOT void open() {
auto dialog = new QFileDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
if (m_threadImpl)
connect(dialog, &QFileDialog::fileSelected, this, &MainWindow::loadImage);
else
connect(dialog, &QFileDialog::fileSelected, [this](const QString & fichier){
QtConcurrent::run([this, fichier]{
QImage img(fichier);
if (! img.isNull()) emit this->imageLoaded(fichier, img);
});
});
m_threadImpl = !m_threadImpl;
}
public:
explicit MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
m_layout.addWidget(&m_open);
m_layout.addWidget(&m_view);
m_view.setWidget(&m_content);
m_loader.moveToThread(&m_loaderThread);
m_loaderThread.start();
connect(&m_open, &QPushButton::clicked, this, &MainWindow::open);
connect(this, &MainWindow::loadImage, &m_loader, &Loader::loadImage);
connect(this, &MainWindow::imageLoaded, this, &MainWindow::imageAvailable);
connect(&m_loader, &Loader::imageLoaded, this, &MainWindow::imageAvailable);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#include "main.moc"

change text of QLabel in another class with button

I have ever post a question at there: change text in another class by button But it justs working when second class is create by first class. Now I have 2 classes, they're created sametime, so how to I connect with together? below is all my code, first class has a button, and second class has a label, I want to when user click on the button in first class, label in second class will be changed. They're put in stackWidget:
// file.h
#include <QWidget>
#include <QtGui>
class widgetA;
class widgetB;
class A : public QWidget
{
Q_OBJECT
public:
explicit A(QWidget *parent = 0);
private:
QComboBox* comboBox;
QStackedWidget* stackWidget;
widgetA *wa;
widgetB *wb;
};
class widgetA : public QWidget
{
Q_OBJECT
public:
widgetA(QWidget *parent = 0);
public slots:
void buttonClicked();
private:
QPushButton* button;
};
class widgetB : public QWidget
{
Q_OBJECT
public slots:
void labelChangeText(const QString);
public:
widgetB(QWidget *parent = 0);
QLabel* label;
};
And this's cpp file:
//file.cpp
#include "a.h"
A::A(QWidget *parent) :
QWidget(parent)
{
comboBox = new QComboBox(this);
comboBox->addItem(tr("Widget A"));
comboBox->addItem(tr("Widget B"));
wa = new widgetA(this);
wb = new widgetB(this);
stackWidget = new QStackedWidget(this);
stackWidget->addWidget(wa);
stackWidget->addWidget(wb);
stackWidget->setCurrentIndex(0);
connect(comboBox, SIGNAL(currentIndexChanged(int)), stackWidget, SLOT(setCurrentIndex(int)));
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(comboBox);
layout->addWidget(stackWidget);
setLayout(layout);
}
widgetA::widgetA(QWidget *parent):
QWidget(parent)
{
button = new QPushButton(tr("Click"));
connect(button, SIGNAL(clicked()), this, SLOT(buttonClicked()));
QHBoxLayout* lay = new QHBoxLayout;
lay->addWidget(button);
setLayout(lay);
}
void widgetA::buttonClicked()
{
// what I have to do at there for call the function at widgetB class?
}
widgetB::widgetB(QWidget *parent):
QWidget(parent)
{
label = new QLabel("....");
QHBoxLayout* lay = new QHBoxLayout;
lay->addWidget(label);
setLayout(lay);
}
void widgetB::labelChangeText(const QString text)
{
label->setText(text);
}
P/S: sorry my english
You can either pass a pointer of widgetB object to widgetA, or you can connect a signal from widgetA to a slot in widgetB.
Here's an example of the signal/slot option:
// widgeta.h
signals:
void changeText(QString text);
-
// widgeta.cpp
void widgetA::buttonClicked()
{
emit changeText("button clicked");
}
-
//widgetb.h
public slots:
void labelChangeText(const QString & text);
-
// a.cpp
connect(wa, SIGNAL(changeText(QString)), wb, SLOT(labelChangeText(QString)));
What hinders you to call wb->labelChangeText("some string") in widgetA::buttonClicked()? You have a pointer to widgetB in class A. And labelChangeText is public in class B. Just that it is a slot does not mean that you cannot call it as a normal method.

Resources