SubClassing QLineEdit & QDialog



  • I am trying to make a generic name editor/dialog for my application. I want to do this by subclassing QLineEdit and QDialog, where the text entered by the user should satisfy a QRegularExpression and certain existing other names are not allowed. I thought to do this with the following code, but there is not any effect on the Ok button being enabled or not / the color of the text. Any ideas of what is wrong? I would guess that something is not right with the reimplemented methods, but I actually have no clue at all...

    QRegularExpression NameExp("^_?([a-z]|[A-Z])([a-z]|[A-Z]|[0-9]|_|:|.|\\-)*$");
    
    class NameEditor : public QLineEdit {
    
    public:
        NameEditor(const QString &Name, QStringList &Excludes, QWidget *Parent = 0);
        void textChanged(const QString &);
        bool hasAcceptableInput();
    
    private:
        QStringList Excludes;
        bool Valid;
    };
    
    NameEditor::NameEditor(const QString &Name, QStringList &E, QWidget *Parent) : QLineEdit(Name, Parent) {
    
        Excludes = E;
        Valid = true;
        setValidator(new QRegularExpressionValidator(NameExp));
    }
    
    void NameEditor::textChanged(const QString &Text) {
    
        QPalette *Palette = new QPalette;
        Palette->setColor(QPalette::Text, Qt::black);
        setPalette(*Palette);
        Valid = true;
    
        if (QLineEdit::hasAcceptableInput())
            for (int i = 0; Valid && (i != Excludes.size()); i++)
                if (Excludes[i] == Text) {
                    QPalette *Palette = new QPalette;
                    Palette->setColor(QPalette::Text, Qt::red);
                    setPalette(*Palette);
                    Valid = false;
                }
    
        QLineEdit::textChanged(Text);
    }
    
    bool NameEditor::hasAcceptableInput() { return Valid; }
    

  • Moderators

    @ModelTech
    you are calling QLineEdit::hasAcceptableInput() (in the base class).
    You reimplemented this method in your derived class, but this method isn't virtual, so side effects can be expected.
    Rename the method and call it directly in your derived implementation.

    Also it may be confusing to call it with the base class namespace. Call it with this->hasAcceptableInput() instead. So it will refer to the validator you set on the line edit.

    You mixing up 2 different things in your implementation.



  • Thanks for your hint! I will take make the changes you propose.



  • I think I have followed your suggestion (plus storing of the two possible palettes) by changing the code as shown below and using isValid in my application instead of hasAcceptableInput. However, there is no difference in behavior... Other ideas of what might be wrong?

    QRegularExpression NameExp("^_?([a-z]|[A-Z])([a-z]|[A-Z]|[0-9]|_|:|.|\\-)*$");
    
    class NameEditor : public QLineEdit {
    
    public:
        NameEditor(const QString &Name, QStringList &Excludes, QWidget *Parent = 0);
        void textChanged(const QString &);
        bool isValid();
    
    private:
        QStringList Excludes;
        QPalette *Black;
        QPalette *Red;
        bool Valid;
    };
    
    NameEditor::NameEditor(const QString &Name, QStringList &E, QWidget *Parent) : QLineEdit(Name, Parent) {
    
        Excludes = E;
        Black = new QPalette;
        Black->setColor(QPalette::Text, Qt::black);
        Red = new QPalette;
        Red->setColor(QPalette::Text, Qt::black);
        Valid = true;
        setValidator(new QRegularExpressionValidator(NameExp));
    }
    
    void NameEditor::textChanged(const QString &Text) {
    
        setPalette(*Black);
        Valid = true;
    
        if (hasAcceptableInput())
            for (int i = 0; Valid && (i != Excludes.size()); i++)
                if (Excludes[i] == Text) {
                    setPalette(*Red);
                    Valid = false;
                }
    }
    
    bool NameEditor::isValid() { return Valid; }
    

  • Moderators

    @ModelTech
    there is no appearance of a button in the code you've posted?
    How do you try to en-/disable the button.

    Also you should rename your textChanged() to something like onTextChanged() and declare it as slot. Since you overwriting QLineEdit's signal.

    In the constructor then do this:

    connect( this, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString)) );
    

    I assume this is what you want to do.



  • Of course, I want to use my NameEditor as Wiget in a bigger context, where such slots will exist. I want to use my NameEditor in various other widgets, where there may not be a button to take over the input (in such cases, it should be taken over on text change or on focus out) or there may indeed be a button. To exemplify the latter, I also created a NameDialog with Ok & Cancel buttons using the NameEditor. The code for it is shown below, but it does not behave as I hoped...

    The behavior of this NameDialog should be as follows. The NameEditor validator should ensure that the input satisfies the NameExp and change the color of the text to red if a string is entered that is included in the Excludes list. The Ok button of the NameDialog should only be enabled if the NameEditor has valid input (i.e., satisfying the NameExp and not being in the Excludes list).

    class NameDialog : public QDialog {
    
        Q_OBJECT
    
    public:
        NameDialog(const QString &ItemName, const QString &Name, QStringList &Excludes, QWidget *Parent = 0);
        QString name() const;
    
    private slots:
        void textChanged();
    
    private:
        NameEditor *Editor;
        QDialogButtonBox *Buttons;
    };
    
    NameDialog::NameDialog(const QString &ItemName, const QString &Name, QStringList &Excludes, QWidget *Parent) : QDialog(Parent) {
    
        QFormLayout *Top = new QFormLayout;
        Editor = new NameEditor(Name, Excludes, this);
        connect(Editor, SIGNAL(textChanged(QString)), this, SLOT(textChanged()));
        Top->addRow(tr("%1 Name:").arg(ItemName), Editor);
        Buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
        connect(Buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(accept()));
        connect(Buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject()));
        QVBoxLayout *Layout = new QVBoxLayout;
        Layout->addLayout(Top);
        Layout->addWidget(Buttons);
        setLayout(Layout);
        setWindowTitle(tr("Rename %1").arg(ItemName));
    }
    
    QString NameDialog::name() const { return Editor->text(); }
    
    void NameDialog::textChanged() {
    
        Buttons->button(QDialogButtonBox::Ok)->setEnabled(Editor->isValid());
    }
    

  • Moderators

    @ModelTech
    but as i said. Rename it to onTextChanged() since you are overwriting the signal with the same name from the base class.
    Currently you are preventing the class' MetaObject emitting the QLineEdit's textChanged() signal.



  • Oh, of course. Apologies, I overlooked that... So, I made the changes below, but no difference in behavior. I feel quite stupid by now...

    Changes to NameEditor:

    class NameEditor : public QLineEdit {
    
        Q_OBJECT
    
    signals:
        void onTextChanged(const QString &);
    
    public:
        NameEditor(const QString &Name, QStringList &Excludes, QWidget *Parent = 0);
        void textChanged(const QString &);
        bool isValid();
    
    private:
        QStringList Excludes;
        QPalette *Black;
        QPalette *Red;
        bool Valid;
    };
    
    void NameEditor::textChanged(const QString &Text) {
    
        setPalette(*Black);
        Valid = true;
    
        if (hasAcceptableInput())
            for (int i = 0; Valid && (i != Excludes.size()); i++)
                if (Excludes[i] == Text) {
                    setPalette(*Red);
                    Valid = false;
                }
    
        emit onTextChanged(Text);
    }
    

    Changes to NameDialog:

    NameDialog::NameDialog(const QString &ItemName, const QString &Name, QStringList &Excludes, QWidget *Parent) : QDialog(Parent) {
    
        QFormLayout *Top = new QFormLayout;
        Editor = new NameEditor(Name, Excludes, this);
        connect(Editor, SIGNAL(onTextChanged(QString)), this, SLOT(textChanged()));
        Top->addRow(tr("%1 Name:").arg(ItemName), Editor);
        Buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
        connect(Buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(accept()));
        connect(Buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject()));
        QVBoxLayout *Layout = new QVBoxLayout;
        Layout->addLayout(Top);
        Layout->addWidget(Buttons);
        setLayout(Layout);
        setWindowTitle(tr("Rename %1").arg(ItemName));
    }
    

  • Moderators

    @ModelTech
    Still not correct. It seems you have problems understanding the difference between SINGALS and SLOTS, since you mix them up in your code.

    signals:
        void onTextChanged(const QString &);
    
    public:
        NameEditor(const QString &Name, QStringList &Excludes, QWidget *Parent = 0);
        void textChanged(const QString &);
        bool isValid();
    

    i said RENAME your textChanged() to onTextChanged(). And make sure its a SLOT.
    You introduced a new signal onTextChanged(). And still left the old textChanged() ... which still overrides QLineEdit's same named signal.

    Ok i (exceptionally) do it for you (including some small enhancements):

    class NameEditor : public QLineEdit {
    
        Q_OBJECT
    
    public:
        NameEditor(const QString &Name, QStringList &Excludes, QWidget *Parent = 0);
        bool isValid();
    
    signals:
         void validChanged(bool);
    
    protected slots:
        void onTextChanged(const QString &);
    
    private:
        QStringList Excludes;
        QPalette *Black;
        QPalette *Red;
        bool Valid;
    };
    
    void NameEditor::onTextChanged(const QString &Text)
    {
        bool validityCheck = true;
    
        if (hasAcceptableInput())
        {
            for (int i = 0; Valid && (i != Excludes.size()); i++)
                if (Excludes[i] == Text) {
                    validityCheck = false;
                    break;
                }
        }
        else
            validityCheck = false;
    
        if( Valid != validityCheck )
        {
              Valid = validityCheck;
              emit validChanged(Valid);
    
              if( Valid )
                     setPalette(*Black);
              else
                     setPalette(*Red);
        }
    }
    

    Also you should replace the line from

    connect(Editor, SIGNAL(onTextChanged(QString)), this, SLOT(textChanged()));
    

    to

    connect(Editor, SIGNAL(validChanged(bool)), this, SLOT(textChanged()));  // you may want to rename the slot to something like "onValidChanged(bool)"
    

    or even to this (direct approach) and remove your current textChanged() slot in the dialog implementation:

    connect(Editor, SIGNAL(validChanged(bool)), Buttons->button(QDialogButtonBox::Ok), SLOT(setEnabled(bool)));
    


  • That shows what a Qt novice I am... A big thank you for pointing me to my error!! A also appreciate the additional enhancements as they gave me some inspiration for improvements elsewhere in my code.

    Based on your example code, I have been able to fix my situation (in a slightly different way).


  • Moderators

    @ModelTech
    some more hints for the future:

    • it's good practice to name slots onXXX()
    • while naming members make sure they are not already defined in the base class to avoid unexpected behaviors (unless they are virtual and you intent to do so)


  • Thanks, that is a very useful advice. Perhaps for completeness, my solution:

    class NameEditor : public QLineEdit {
    
        Q_OBJECT
    
    signals:
        void validChange(bool);
    
    public:
        NameEditor(const QString &Name, QStringList &Excludes, QWidget *Parent = 0);
    
    private slots:
        void onChange(const QString &);
    
    private:
        QStringList Excludes;
        QPalette *Black;
        QPalette *Red;
        bool Valid;
    };
    
    NameEditor::NameEditor(const QString &Name, QStringList &E, QWidget *Parent) : QLineEdit(Name, Parent) {
    
        Excludes = E;
        Black = new QPalette;
        Black->setColor(QPalette::Text, Qt::black);
        Red = new QPalette;
        Red->setColor(QPalette::Text, Qt::red);
        Valid = true;
        setValidator(new QRegularExpressionValidator(NameExp));
        connect(this, SIGNAL(textChanged(QString)), this, SLOT(onChange(QString)));
    }
    
    void NameEditor::onChange(const QString &Text) {
    
        Valid = hasAcceptableInput();
    
        for (int i = 0; Valid && (i != Excludes.size()); i++)
            Valid = Excludes[i] != Text;
    
        if (Valid)
            setPalette(*Black);
        else
            setPalette(*Red);
    
        emit validChange(Valid);
    }
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.