-2

I have an application in which I want to prompt a dialog window for further (advanced) editing of apps content. It has to be a modal window, so that's why it can't be a QMainWindow. I just can't seem to make connect() macro work in QDialogwindow.

Previously, I got around this by using the response of the QDialog and a getter function in my dialog class:

A screenshot of a simple query

Dialog_CreateNew mDialog(this);
mDialog.setModal(true);
if (mDialog.exec() == QDialog::Accepted)
{
    QString username, password;
    mDialog.getData(&username, &password);
}

I used built-in SLOTs of QDialog library accept() and reject():

connect(_cancel, SIGNAL(clicked()), this, SLOT(reject()));
connect(_createNew, SIGNAL(clicked()), this, SLOT(accept()));

But now when I have to create my own slots in the dialog window, idk how to make it work. It will be a complex window, and just accept() and reject() won't do it.

One more thing: when I add Q_OBJECT macro in the dialog class, I get an error:

error: undefined reference to `vtable for Dialog_CreateNew'

These built-in SLOTs accept() and reject() work without Q_OBJECT.

I've seen this work in the Qt Designer, therefore it is possible. I don't want to use the Designer, everything ought to be done in coding.

That's my research and the things I tried.

My question is: How to make a signal-slot mechanism work in a modal child dialog window?

"dialog_createNew.h":

#ifndef DIALOG_CREATENEW_H
#define DIALOG_CREATENEW_H

#include <QtCore>
#include <QDialog>
#include <QIcon>
#include <QWidget>
#include <QLabel>
#include <QLineEdit>
#include <QGridLayout>
#include <QPushButton>
#include <QMessageBox>
#include <QStatusBar>

class Dialog_CreateNew : public QDialog

{
    Q_OBJECT
public:
    Dialog_CreateNew(QWidget* parent);
    void getData(QString* username, QString* password);
    void setErrorTip(QString errorTip);
    virtual ~Dialog_CreateNew();
private:
    QWidget* _parent;
    QLabel* _lInstruction;
    QLabel* _lUsername;
    QLabel* _lPassword;
    QLineEdit* _edUsername;
    QLineEdit* _edPassword;
    QPushButton* _createNew;
    QPushButton* _cancel;

    QLabel* _lErrorTip;
    QString* _errorTip;

    QGridLayout* _layout;

    void createConnects();

private slots:
    void onTextChanged_connect(QString);

};

#endif // DIALOG_CREATENEW_H

"dialog_createNew.cpp":

#include "dialog_createnew.h"

Dialog_CreateNew::Dialog_CreateNew(QWidget* parent)
{
    _parent = parent;
    this->setWindowTitle("Create New Bot");
    this->setWindowIcon(QIcon(":/icons/createNew.svg"));
    int nHeight = 150;
    int nWidth = 360;
    this->setGeometry(parent->x() + parent->width()/2 - nWidth/2,
                    parent->y() + parent->height()/2 - nHeight/2,
                    nWidth, nHeight);
    this->setFixedSize(QSize(nWidth, nHeight));

    _lInstruction = new QLabel(this);
    _lInstruction->setText("Enter Your Instagram credentials:");

    _lUsername = new QLabel(this);
    _lUsername->setText("Username:");

    _lPassword = new QLabel(this);
    _lPassword->setText("Password:");

    _edUsername = new QLineEdit(this);
    _edUsername->setPlaceholderText("classybalkan");

    _edPassword = new QLineEdit(this);
    _edPassword->setEchoMode(QLineEdit::Password);
    _edPassword->setPlaceholderText("•••••••••••");

    _createNew = new QPushButton(this);
    _createNew->setText("Create New Bot");

    _cancel = new QPushButton(this);
    _cancel->setText("Cancel");

    _errorTip = new QString("");
    _lErrorTip = new QLabel(this);


    _layout = new QGridLayout(this);
    _layout->addWidget(_lInstruction, 0, 0, 1, 2);
    _layout->addWidget(_lUsername, 1, 0);
    _layout->addWidget(_edUsername, 1, 1);
    _layout->addWidget(_lPassword, 2, 0);
    _layout->addWidget(_edPassword, 2, 1);
    _layout->addWidget(_lErrorTip, 3, 1);
    _layout->addWidget(_cancel, 4, 0);
    _layout->addWidget(_createNew, 4, 1);

    this->setLayout(_layout);

    createConnects();
}

void Dialog_CreateNew::createConnects()
{
    connect(_cancel,
            SIGNAL(clicked()),
            this,
            SLOT(reject()));
    connect(_edPassword,
             &QLineEdit::textChanged,
             this,
             &Dialog_CreateNew::onTextChanged_connect);
}

void Dialog_CreateNew::getData(QString* username, QString* password)
{
    *username = _edUsername->text();
    *password = _edPassword->text();
}

void Dialog_CreateNew::setErrorTip(QString errorTip)
{
    *_errorTip = errorTip;
    _lErrorTip->setText("<center><font color=""red"">"
                       + *_errorTip +
                       "</font><center>");
}

void Dialog_CreateNew::onTextChanged_connect()
{
    QString text = _edPassword->text();
    if (text == "")
    {
        disconnect(_createNew,
             &QPushButton::clicked,
             this,
             &QDialog::accept);
        _lErrorTip->setText("Invalid Password");
    }
    else
    {
        connect(_createNew,
             &QPushButton::clicked,
             this,
             &QDialog::accept);
    }
}

Dialog_CreateNew::~Dialog_CreateNew()
{

}

Solution: Changing old SIGNAL/SLOT notation with the new function binding:

connect(_edPassword, &QLineEdit::textChanged, this, &Dialog_CreateNew::onTextChanged_connect);

This allowed me to use my own slots, and did the fix.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
iamalminko
  • 105
  • 8

1 Answers1

1

Three things you should do to fix this:

  1. Split your work to header (.h file) and source (.cpp file)
  2. Define the destructor of your dialog as virtual, e.g.:

    virtual ~MyDialog()
    {
    }
    
  3. Run qmake again

One or more of these will fix it for you

PS: Please, oh, Please... stop using the outdated SIGNAL() and SLOT(), and start using the new format that uses function binding, like:

connect(_cancel, &QPushButton::clicked, this, &QDialog::reject);

It's faster, more efficient, doesn't use strings, and once compiled, works. Unlike the old format that can fail at runtime.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • Isn't the `~MyDialog()` implicitly `virtual` if `MyDialog` is derived from `QDialog` which has a `virtual` destructor? – Scheff's Cat Oct 26 '18 at 10:12
  • @Scheff Not necessarily. `MyDialog` doesn't have to have a vtable if it just overrides another class. – The Quantum Physicist Oct 26 '18 at 10:14
  • Hmm. I found [SO: Confusion with virtual pointer of derived class](https://stackoverflow.com/a/20985548/7478597). It sounds like what you described with _overrides another class_. But, then I tried this on coliru to see it in action. (I used RTTI because this seems to me the relevant manifestation of that.) How ever I defined the destructor - it makes no difference: [Live demo on coliru](http://coliru.stacked-crooked.com/a/42f095911d2c2b15). So, I'm still not convinced about your 2. point. ;-) – Scheff's Cat Oct 26 '18 at 10:34
  • @Scheff I said "not necessarily", because a compiler can change this. Especially that it's a rule of thumb and a good practice in C++ to make your destructors virtual to avoid memory leaks. If you wish to prove me wrong, please quote the standard. On the other hand, this issue in Qt is quite well-known. Try to google it. – The Quantum Physicist Oct 26 '18 at 10:51
  • Sorry. _Especially that it's a rule of thumb and a good practice in C++ to make your destructors virtual to avoid memory leaks._ That's the point. In daily work, I do this carefully for every class: either a `virtual` destructor or a non-virtual if inheritance is not possible/intended. Therefore, I stumbled about your statement and tried to dig deeper to find out what _might_ happen otherwise. Until now, I thought making the destructor virtual in base class is important, only (and any derived class will inherit this "automatically"). – Scheff's Cat Oct 26 '18 at 10:55
  • Btw. I tried an "authoritative" source - cppreference.com but I couldn't identify the clear sentence which would me prove right or wrong... – Scheff's Cat Oct 26 '18 at 11:00
  • thank You so much for Your response. The function binding notation did the fix – iamalminko Oct 26 '18 at 12:07