Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Validation behaviour too confusing for end user
QtWS25 Last Chance

Validation behaviour too confusing for end user

Scheduled Pinned Locked Moved Solved General and Desktop
18 Posts 3 Posters 9.5k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • VRoninV VRonin
    1. QRegExp is deprecated, use QRegularExpression
    2. In C++ the solution is trivial, you just need to reimplement the validator to transform Invalid input into intermediate input:
    class LenientRegExpValidator : public QRegularExpressionValidator{
    Q_OBJECT
    Q_DISABLE_COPY(LenientRegExpValidator)
    public:
    LenientRegExpValidator(QObject* parent = Q_NULLPTR) : QRegularExpressionValidator(parent){}
    LenientRegExpValidator(const QRegularExpression &re, QObject *parent = Q_NULLPTR) : QRegularExpressionValidator(re,parent){}
    QValidator::State validate(QString &input, int &pos) const Q_DECL_OVERRIDE{
    const QValidator::State baseValidator = QRegularExpressionValidator::validate(input,pos);
    if(baseValidator ==QValidator::Invalid)
    return QValidator::Intermediate;
    return baseValidator;
    }
    };
    
    JonBJ Offline
    JonBJ Offline
    JonB
    wrote on last edited by JonB
    #7

    @VRonin

    In C++ the solution is trivial, you just need to reimplement the validator to transform Invalid input into intermediate input

    OK, I have managed to implement this in PyQt for my Python (bit of a struggle, but done).

    [As an aside, it's a bit more complicated than that. I presume I need 2 regular expressions in the QValidator, one for the reg exp which is as it is now to return Acceptable, and then a second one which allows no leading digit at all to return Intermediate, and then if it fails both (e.g. user types a letter, or two decimal points) return Invalid. Not sure how to do that as QValidator allows just one expression in setRegularExpression() which is used by the base validate()... Am I supposed to change the reg exp via setRegularExpression() twice and call base validate() twice each time in my validate() override??]

    But ignoring that complication for the moment, what's the "point" of Intermediate? Once I have put in the code, instead of the user not being able to erase the single leading digit because it returns Invalid, he now can, just as if it returns Acceptable, as far as the Qt widget editing is concerned. So he can leave the field reading just .23, which is not acceptable as a "final answer" to me. Where is the difference between Acceptable & Intermediate "expressed"/"acted upon"?

    I can only guess that the widget lets him type intermediate entry, and then it is up to me to go recheck for "truly" Acceptable, either on attempting to OK the Dialog or on attempting to exit editing the field. Is that right? The Qt validator is prepared to prevent me typing a truly illegal sequence into the widget as I go along, but it is not prepared to prevent me finishing editing with a still intermediate sequence? Then my problem is there are hundreds of such validators dotted all over the existing GUI code, with horrendousnesses to track them all down and think about changing behaviour, especially at the Dialog level but in general anyway....

    In short, given my requirement --- a final "decimal" should be 1+ leading digit (optionally followed by 1 or 2 decimal places, but that's not the problem), but while editing the user should be able to delete the single leading digit to replace it with another one --- can you give me an outline (doesn't have to be specific code) as to how you would actually handle this? It seems to me I should not be the only person who requires a monetary input like this, without leaving the user lost as to how to replace the leading digit?

    Thank you, people!

    VRoninV 1 Reply Last reply
    0
    • JonBJ JonB

      @VRonin

      In C++ the solution is trivial, you just need to reimplement the validator to transform Invalid input into intermediate input

      OK, I have managed to implement this in PyQt for my Python (bit of a struggle, but done).

      [As an aside, it's a bit more complicated than that. I presume I need 2 regular expressions in the QValidator, one for the reg exp which is as it is now to return Acceptable, and then a second one which allows no leading digit at all to return Intermediate, and then if it fails both (e.g. user types a letter, or two decimal points) return Invalid. Not sure how to do that as QValidator allows just one expression in setRegularExpression() which is used by the base validate()... Am I supposed to change the reg exp via setRegularExpression() twice and call base validate() twice each time in my validate() override??]

      But ignoring that complication for the moment, what's the "point" of Intermediate? Once I have put in the code, instead of the user not being able to erase the single leading digit because it returns Invalid, he now can, just as if it returns Acceptable, as far as the Qt widget editing is concerned. So he can leave the field reading just .23, which is not acceptable as a "final answer" to me. Where is the difference between Acceptable & Intermediate "expressed"/"acted upon"?

      I can only guess that the widget lets him type intermediate entry, and then it is up to me to go recheck for "truly" Acceptable, either on attempting to OK the Dialog or on attempting to exit editing the field. Is that right? The Qt validator is prepared to prevent me typing a truly illegal sequence into the widget as I go along, but it is not prepared to prevent me finishing editing with a still intermediate sequence? Then my problem is there are hundreds of such validators dotted all over the existing GUI code, with horrendousnesses to track them all down and think about changing behaviour, especially at the Dialog level but in general anyway....

      In short, given my requirement --- a final "decimal" should be 1+ leading digit (optionally followed by 1 or 2 decimal places, but that's not the problem), but while editing the user should be able to delete the single leading digit to replace it with another one --- can you give me an outline (doesn't have to be specific code) as to how you would actually handle this? It seems to me I should not be the only person who requires a monetary input like this, without leaving the user lost as to how to replace the leading digit?

      Thank you, people!

      VRoninV Offline
      VRoninV Offline
      VRonin
      wrote on last edited by VRonin
      #8

      @JNBarchan said in Validation behaviour too confusing for end user:

      I can only guess that the widget lets him type intermediate entry, and then it is up to me to go recheck for "truly" Acceptable

      You can easily do this on editingFinished() signal. This also applies to the default behaviour of QRegularExpressionValidator if you don't type anything after the decimal separator


      let's try this:
      (again, sorry for C++ but I have no idea how you would subclass in Python)

      class LenientRegExpValidator : public QRegularExpressionValidator{
      Q_OBJECT
      Q_DISABLE_COPY(LenientRegExpValidator)
      public:
      LenientRegExpValidator(QObject* parent = Q_NULLPTR) : QRegularExpressionValidator(parent){}
      LenientRegExpValidator(const QRegularExpression &re, QObject *parent = Q_NULLPTR) : QRegularExpressionValidator(re,parent){}
      QValidator::State validate(QString &input, int &pos) const Q_DECL_OVERRIDE{
      const auto regExpr = regularExpression();
      if(regExpr.pattern().isEmpty())
      return Acceptable;
      const auto fullMatch = regExpr.match(input);
      if (fullMatch.hasMatch())
      return QValidator::Acceptable;
      const auto partialMatch = regExpr.globalMatch(input, 0,QRegularExpression::PartialPreferFirstMatch);
      if(partialMatch.hasNext())
      return QValidator::Intermediate;
      return QValidator::Invalid;
      }
      };
      

      "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
      ~Napoleon Bonaparte

      On a crusade to banish setIndexWidget() from the holy land of Qt

      JonBJ 1 Reply Last reply
      1
      • VRoninV VRonin

        @JNBarchan said in Validation behaviour too confusing for end user:

        I can only guess that the widget lets him type intermediate entry, and then it is up to me to go recheck for "truly" Acceptable

        You can easily do this on editingFinished() signal. This also applies to the default behaviour of QRegularExpressionValidator if you don't type anything after the decimal separator


        let's try this:
        (again, sorry for C++ but I have no idea how you would subclass in Python)

        class LenientRegExpValidator : public QRegularExpressionValidator{
        Q_OBJECT
        Q_DISABLE_COPY(LenientRegExpValidator)
        public:
        LenientRegExpValidator(QObject* parent = Q_NULLPTR) : QRegularExpressionValidator(parent){}
        LenientRegExpValidator(const QRegularExpression &re, QObject *parent = Q_NULLPTR) : QRegularExpressionValidator(re,parent){}
        QValidator::State validate(QString &input, int &pos) const Q_DECL_OVERRIDE{
        const auto regExpr = regularExpression();
        if(regExpr.pattern().isEmpty())
        return Acceptable;
        const auto fullMatch = regExpr.match(input);
        if (fullMatch.hasMatch())
        return QValidator::Acceptable;
        const auto partialMatch = regExpr.globalMatch(input, 0,QRegularExpression::PartialPreferFirstMatch);
        if(partialMatch.hasNext())
        return QValidator::Intermediate;
        return QValidator::Invalid;
        }
        };
        
        JonBJ Offline
        JonBJ Offline
        JonB
        wrote on last edited by
        #9

        @VRonin

        You can easily do this on editingFinished() signal.

        But that's the bit I don't get! What do I do in the editingFinished handler? It's not like I can return false and then Qt would refuse leaving editing of the widget (which is exactly what I am used to in systems where a validator is evaluated on attempt to move out of editing instead of each character is typed).

        On top of that, the docs state:

        Note that if there is a validator() or inputMask() set on the line edit and enter/return is pressed, the editingFinished() signal will only be emitted if the input follows the inputMask() and the validator() returns QValidator::Acceptable.

        So if I'm still returning Intermediate for what they've typed, that says I won't get the editingFinished signal anyway. Not that I see that matters, since I don't get what I would do in it even if I did receive it....

        aha_1980A VRoninV 2 Replies Last reply
        0
        • JonBJ JonB

          @VRonin

          You can easily do this on editingFinished() signal.

          But that's the bit I don't get! What do I do in the editingFinished handler? It's not like I can return false and then Qt would refuse leaving editing of the widget (which is exactly what I am used to in systems where a validator is evaluated on attempt to move out of editing instead of each character is typed).

          On top of that, the docs state:

          Note that if there is a validator() or inputMask() set on the line edit and enter/return is pressed, the editingFinished() signal will only be emitted if the input follows the inputMask() and the validator() returns QValidator::Acceptable.

          So if I'm still returning Intermediate for what they've typed, that says I won't get the editingFinished signal anyway. Not that I see that matters, since I don't get what I would do in it even if I did receive it....

          aha_1980A Offline
          aha_1980A Offline
          aha_1980
          Lifetime Qt Champion
          wrote on last edited by
          #10

          @JNBarchan said in Validation behaviour too confusing for end user:

          @VRonin

          You can easily do this on editingFinished() signal.

          But that's the bit I don't get! What do I do in the editingFinished handler? It's not like I can return false and then Qt would refuse leaving editing of the widget (which is exactly what I am used to in systems where a validator is evaluated on attempt to move out of editing instead of each character is typed).

          No, the QValidator works different. it forbids entering chars that lead to non-acceptable cases. for your example, it forbids entering a second . when there's already one. this is very convenient once you get used to.

          On top of that, the docs state:

          Note that if there is a validator() or inputMask() set on the line edit and enter/return is pressed, the editingFinished() signal will only be emitted if the input follows the inputMask() and the validator() returns QValidator::Acceptable.

          So if I'm still returning Intermediate for what they've typed, that says I won't get the editingFinished signal anyway. Not that I see that matters, since I don't get what I would do in it even if I did receive it....

          in editingFinished you could enable an Ok button, for example. I dont know why you would disallow moving out of the edit? you can always get focus out when the user switches to another window.

          Qt has to stay free or it will die.

          1 Reply Last reply
          0
          • JonBJ JonB

            @VRonin

            You can easily do this on editingFinished() signal.

            But that's the bit I don't get! What do I do in the editingFinished handler? It's not like I can return false and then Qt would refuse leaving editing of the widget (which is exactly what I am used to in systems where a validator is evaluated on attempt to move out of editing instead of each character is typed).

            On top of that, the docs state:

            Note that if there is a validator() or inputMask() set on the line edit and enter/return is pressed, the editingFinished() signal will only be emitted if the input follows the inputMask() and the validator() returns QValidator::Acceptable.

            So if I'm still returning Intermediate for what they've typed, that says I won't get the editingFinished signal anyway. Not that I see that matters, since I don't get what I would do in it even if I did receive it....

            VRoninV Offline
            VRoninV Offline
            VRonin
            wrote on last edited by
            #11

            @JNBarchan said in Validation behaviour too confusing for end user:

            But that's the bit I don't get! What do I do in the editingFinished handler?

            What I normally do is set the line edit background to red and maybe show a red QLabel to warn about the invalid output

            that says I won't get the editingFinished signal anyway

            Good spot. You can use textedited then

            "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
            ~Napoleon Bonaparte

            On a crusade to banish setIndexWidget() from the holy land of Qt

            JonBJ 1 Reply Last reply
            0
            • VRoninV VRonin

              @JNBarchan said in Validation behaviour too confusing for end user:

              But that's the bit I don't get! What do I do in the editingFinished handler?

              What I normally do is set the line edit background to red and maybe show a red QLabel to warn about the invalid output

              that says I won't get the editingFinished signal anyway

              Good spot. You can use textedited then

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote on last edited by JonB
              #12

              Thank you all for your input.

              I have sat and thought about this carefully for a while. I am coming round to the conclusion that I cannot meet what I would desire.

              I have no problem with the behaviours of QValidator::Acceptable & QValidator::Invalid. But I do not see how the behaviour of QValidator::Intermediate helps me/meets my requirements. (Please note: I do understand how it works, that's not the issue.)

              Consider my example of a "decimal number": \d+(\.\d{1,2})?

              • If user types a letter, it immediately returns Invalid and prevents the character being placed in the widget. Perfect, since that character can never be acceptable.

              • What should I return if user deletes the single leading digit, in preparation for typing in a new one?

              1. At present it returns Invalid, preventing the user from deleting. I have said I do not like this behaviour. So, I shall be changing over to Intermediate... but how/when?

              2. If I return Intermediate instead of Invalid in all cases, this is simple. However, that allows the useless typing of a letter. It allows everything through, requiring me to check up more on completion.

              3. I start to think of only returning Intermediate instead of Invalid in certain cases, of the kind outlines in @VRonin's last posted code example. That seems possible, till I consider what code I would need. I would require something like a regular expression capturing the difference between "what is potentially heading in the right direction" as opposed to "is simply unacceptable" (e..g typing a letter). @VRonin has suggested QRegularExpression::PartialPreferFirstMatch, but from what I can see in the documentation (not tested) of "partial matches" these only allow for an "incomplete match which could be satisfied by appending further characters". That will not help with the kind of situation I am thinking of, where the user perform edits "in the start/middle of the string" to get to what he wants. I would have to think out a regular expression or code for every situation I can imagine as my definition of "intermediate" to achieve this, a non-trivial task.

              I can see that Qt's Intermediate may work in this sense assuming the user types linearly from left to right --- which is what it seems to be designed for --- but not in my sense. I also realise upon careful reflection how difficult it is to express just what should be Intermediate versus Invalid.

              You people may be familiar & happy with how Qt validators work, but I (and my potential users) may not be attuned to its way of working. At least in my case of entering a "decimal number".

              In light of the above I think I am left with "2.5" choices:

              1. Make all Invalids return Intermediate instead (coding too hard to distinguish specific cases). Then I probably need to do color-marking of "intermediate-value" widgets (how would I even do this from within existing class derived from QValidator, I don't see a method to get at the widget which is being validated?). Then I definitely would need to do "final" validation on, say, Dialog "OK" or whatever, but there are 50 dialogs with say an average of 5 widgets to validate on each one, there's no central place for me to track them all down to alter code? This is why I'm thinking I'm not going to be able to use Intemediate.

              2. a. Put up with exactly the current regular expression & behaviour. The user who attempted to use the widget and needed to delete the first digit to change it to another got completely stuck and had no idea what was wrong/what he needed to do :( But hey ho, you guys seem to like the behaviour :)
                b. Ask my stakeholder if I may change the validator to \d?(\.\d{1,2})? This is my preferred solution. In return for allowing the leading-delete, it will allow through .23, but provided the code can accept this it may be the simplest to resolve just this situation...

              Those are my thoughts! I hope you're all fascinated :)

              1 Reply Last reply
              1
              • VRoninV Offline
                VRoninV Offline
                VRonin
                wrote on last edited by
                #13

                I'm afraid there's not a generic way. In your specific case, probably a QDoubleValidator instead of a regexp one might work better.
                For other cases you'd probably need to return intermediate always and reimplement QValidator::fixup to delete (or do something with it) the line if the input is invalid

                "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
                ~Napoleon Bonaparte

                On a crusade to banish setIndexWidget() from the holy land of Qt

                JonBJ 1 Reply Last reply
                0
                • VRoninV VRonin

                  I'm afraid there's not a generic way. In your specific case, probably a QDoubleValidator instead of a regexp one might work better.
                  For other cases you'd probably need to return intermediate always and reimplement QValidator::fixup to delete (or do something with it) the line if the input is invalid

                  JonBJ Offline
                  JonBJ Offline
                  JonB
                  wrote on last edited by JonB
                  #14

                  @VRonin
                  Yup, thanks for your confirmation & patience.

                  I might have a look at QDoubleValidator, in case it internally handles my case better. Just to complicate things, I know it's not your area, but because I am Python/PyQt what I actually need to validate is that it satisfies an internal PyQt type named Decimal, which is not the same as double, and isn't even documented as to what format it parses...! :(

                  One final (honest!) question, inspired by something I mentioned above and your earlier comment:

                  What I normally do is set the line edit background to red and maybe show a red QLabel to warn about the invalid output

                  How (what code approach do you take) to achieve this in Qt? So far as I can see, one uses QLineEdit::setValidator() to set the validator, so a line edit can see its validator, but QValidator does not have a member to reference the QLineEdit it has been called from? And I suspect there cannot be one, as you could associate the same QValidator object with multiple QLineEdits, if you chose to do so. So when my overridden QValidator::validate() wants to return Intermediate, how can I have code there to know which widget to affect?

                  In the validator model I am accustomed to from another language/library, there is a one-to-one relationship between a control and its validator, so you can access one from the other. But I think that's not the case in Qt, so how do you manage it? I can only see creating a "lookup" table for each dialog so that I know the widget from the validator (assuming I stick to one-to-one), or maybe you sub-class QValidator and add a member for the associated QLineEdit, and that's going to be real messy for me....

                  VRoninV 1 Reply Last reply
                  0
                  • JonBJ JonB

                    @VRonin
                    Yup, thanks for your confirmation & patience.

                    I might have a look at QDoubleValidator, in case it internally handles my case better. Just to complicate things, I know it's not your area, but because I am Python/PyQt what I actually need to validate is that it satisfies an internal PyQt type named Decimal, which is not the same as double, and isn't even documented as to what format it parses...! :(

                    One final (honest!) question, inspired by something I mentioned above and your earlier comment:

                    What I normally do is set the line edit background to red and maybe show a red QLabel to warn about the invalid output

                    How (what code approach do you take) to achieve this in Qt? So far as I can see, one uses QLineEdit::setValidator() to set the validator, so a line edit can see its validator, but QValidator does not have a member to reference the QLineEdit it has been called from? And I suspect there cannot be one, as you could associate the same QValidator object with multiple QLineEdits, if you chose to do so. So when my overridden QValidator::validate() wants to return Intermediate, how can I have code there to know which widget to affect?

                    In the validator model I am accustomed to from another language/library, there is a one-to-one relationship between a control and its validator, so you can access one from the other. But I think that's not the case in Qt, so how do you manage it? I can only see creating a "lookup" table for each dialog so that I know the widget from the validator (assuming I stick to one-to-one), or maybe you sub-class QValidator and add a member for the associated QLineEdit, and that's going to be real messy for me....

                    VRoninV Offline
                    VRoninV Offline
                    VRonin
                    wrote on last edited by
                    #15

                    @JNBarchan said in Validation behaviour too confusing for end user:

                    I might have a look at QDoubleValidator, in case it internally handles my case better.

                    QDoubleValidator basically just uses QLocale::toDouble so should be more lenient.

                    How (what code approach do you take) to achieve this in Qt?

                    I normally change it to red when the user tries to submit a form

                    "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
                    ~Napoleon Bonaparte

                    On a crusade to banish setIndexWidget() from the holy land of Qt

                    JonBJ 2 Replies Last reply
                    0
                    • VRoninV VRonin

                      @JNBarchan said in Validation behaviour too confusing for end user:

                      I might have a look at QDoubleValidator, in case it internally handles my case better.

                      QDoubleValidator basically just uses QLocale::toDouble so should be more lenient.

                      How (what code approach do you take) to achieve this in Qt?

                      I normally change it to red when the user tries to submit a form

                      JonBJ Offline
                      JonBJ Offline
                      JonB
                      wrote on last edited by JonB
                      #16

                      @VRonin said in Validation behaviour too confusing for end user:

                      How (what code approach do you take) to achieve this in Qt?

                      I normally change it to red when the user tries to submit a form

                      Sorry, this is not what I was asking. Ahh! Do you mean, you don't turn it red during QValidator::validate(), so don't access the QLineEdit from there; instead during QDialog:onOK() (or whatever it is, accept()) you enumerate each QLineEdit in the dialog and call its QLineEdit::hasAcceptableInput()? So you never try to map from validator to widget?

                      VRoninV 1 Reply Last reply
                      0
                      • JonBJ JonB

                        @VRonin said in Validation behaviour too confusing for end user:

                        How (what code approach do you take) to achieve this in Qt?

                        I normally change it to red when the user tries to submit a form

                        Sorry, this is not what I was asking. Ahh! Do you mean, you don't turn it red during QValidator::validate(), so don't access the QLineEdit from there; instead during QDialog:onOK() (or whatever it is, accept()) you enumerate each QLineEdit in the dialog and call its QLineEdit::hasAcceptableInput()? So you never try to map from validator to widget?

                        VRoninV Offline
                        VRoninV Offline
                        VRonin
                        wrote on last edited by
                        #17

                        @JNBarchan said in Validation behaviour too confusing for end user:

                        ou enumerate each QLineEdit in the dialog and call its QLineEdit::hasAcceptableInput()? So you never try to map from validator to widget?

                        Correct. The validator's job is not to take care of how an item is displayed. That's either the job of the linedit or its parent (either via connecting the textEdited() signal, using fixup() to clear invalid input and then using editingFinished() or uppon submit of form (e.g. when you press the Ok button at the end of a dialog)

                        "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
                        ~Napoleon Bonaparte

                        On a crusade to banish setIndexWidget() from the holy land of Qt

                        1 Reply Last reply
                        2
                        • VRoninV VRonin

                          @JNBarchan said in Validation behaviour too confusing for end user:

                          I might have a look at QDoubleValidator, in case it internally handles my case better.

                          QDoubleValidator basically just uses QLocale::toDouble so should be more lenient.

                          How (what code approach do you take) to achieve this in Qt?

                          I normally change it to red when the user tries to submit a form

                          JonBJ Offline
                          JonBJ Offline
                          JonB
                          wrote on last edited by
                          #18

                          @VRonin said in Validation behaviour too confusing for end user:

                          @JNBarchan said in Validation behaviour too confusing for end user:

                          I might have a look at QDoubleValidator, in case it internally handles my case better.

                          QDoubleValidator basically just uses QLocale::toDouble so should be more lenient.

                          Changed over to QDoubleValidator. Whether it implements via a different regular expression from the one I inherited or implements validation with dedicated code, either way it gives me/my user a more pleasant editing experience, allowing the original issue of being able to delete a lone leading digit. It also of course "feels" better.

                          So thank you, that will do nicely here after all this discussion (though that was worthwhile so that I now understand how Qt validators work).

                          1 Reply Last reply
                          0

                          • Login

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • Users
                          • Groups
                          • Search
                          • Get Qt Extensions
                          • Unsolved