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. Constraining user input to a QLineEdit
Forum Updated to NodeBB v4.3 + New Features

Constraining user input to a QLineEdit

Scheduled Pinned Locked Moved Unsolved General and Desktop
17 Posts 6 Posters 469 Views 4 Watching
  • 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.
  • D Dallas Posey

    @Kent-Dorfman I simply want to force the user to enter a string representation of a positive, floating-point value < 99.99 like this: "XX.YY" without forcing them to type the decimal point. Sounds silly but that is what the customer demands (or strongly desires anyway). Why? Because the previous version of the product (designed 15 years ago) does it that way.

    I got pretty close yesterday by using QLineEdit::textEdited() and selectively inserting the decimal based on current and previous cursor positions. However, for some reason that isn't clear to me, QLineEdit::textEdited() is signaled in response to QLineEdit::insert(). That causes all manner of havoc in the case that the user backspaces over the decimal and then inserts a new numeral.

    From the documentation I don't think that should happen right??? I would think QLineEdit::insert() would qualify as a programmatic change to the text and should only result in QLineEdit::textChanged() being signalled. QLineEdit::textEdited() should not be generated in that case correct??

    The framework is 5.15LTS (which I realize went EOL back in the spring) ... could it just be a bug in QLineEdit of that vintage?

    JonBJ Online
    JonBJ Online
    JonB
    wrote last edited by
    #8

    @Dallas-Posey
    I would not have expected textEdited() to be emitted against a programmatic insert().

    If that is really, really happening you might be able to work around it with one of:

    • Where you do an insert() in code, instead get the whole text as a string, insert your character/. into that, and use setText() to put the whole string back.

    • Set a flag in code prior to your insert(), check the flag in your slot on textEdited() and ignore, clear the flag afterwards.

    1 Reply Last reply
    0
    • D Offline
      D Offline
      Dallas Posey
      wrote last edited by
      #9

      @JonB it is definitely happening. I fixed it with a simple in-flight mechanism in the signal handler:

      QLineEdit* editor  = this->findChild<QLineEdit*>("filterEdit");   // Private Member of fltsetter
      connect(editor, &QLineEdit::textEdited, this, &fltsetter::hdlTextEdited);
      
      ...
      
      void fltsetter::hdlTextEdited(const QString &text)
      {
          int currentCursorPos;
          static int inFlight = 0;
      
          qInfo() << "fltsetter::hdlTextEdited(" << text << ")";
          qInfo() << "Current cursor pos: " << (currentCursorPos = editor->cursorPosition());
          qInfo() << "Last cursor pos: " << lastCursorPos;
          qInfo() << "size: " << editor->text().size();
          qInfo() << "InFlight: " << inFlight;
      
          if (inFlight--)
          {
              qInfo() << "Inflight!";
              return;
          }
      
          // 1. Limit the size to 5
          if (editor->text().size() > 5)
          {
              inFlight++;
      
              editor->backspace();
              qInfo() << "Size limited to 5 chars";
              qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
              return;
          }
      
          // 2. If adding char at position 2, insert decimal
          if ((currentCursorPos == 2) && (lastCursorPos == 1))
          {
              inFlight++;
      
              editor->insert(".");
              qInfo() << "Inserting decimal";
              qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
              return;
          }
      
          // 3. If adding third char after decimal erased, insert a decimal in pos 3
          if ((currentCursorPos == 3) && (lastCursorPos == 2) && (text.at(currentCursorPos) != "."))
          {
              inFlight += 3;
      
              QChar lastChar = text.at(currentCursorPos);
              editor->backspace();
              editor->insert(".");
              editor->insert(lastChar);
              qInfo() << "Substituting decimal";
              qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
              return;
          }
      
          inFlight = 0;
      
          qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
      }
      

      The logs (which I can now pare-down some) verified that the signal handler was re-entering as a result of the insert() and backspace() methods being called and the inFlight mechanism fixed it right up. Behaves exactly as I would have expected (without the inflight) if the insert() and backspace() methods didn't generate the signal.

      Anyway it works now. It's a clumsy, non-generic solution, but pays the bills in this case looks like.

      JonBJ 1 Reply Last reply
      1
      • D Dallas Posey

        @Kent-Dorfman I simply want to force the user to enter a string representation of a positive, floating-point value < 99.99 like this: "XX.YY" without forcing them to type the decimal point. Sounds silly but that is what the customer demands (or strongly desires anyway). Why? Because the previous version of the product (designed 15 years ago) does it that way.

        I got pretty close yesterday by using QLineEdit::textEdited() and selectively inserting the decimal based on current and previous cursor positions. However, for some reason that isn't clear to me, QLineEdit::textEdited() is signaled in response to QLineEdit::insert(). That causes all manner of havoc in the case that the user backspaces over the decimal and then inserts a new numeral.

        From the documentation I don't think that should happen right??? I would think QLineEdit::insert() would qualify as a programmatic change to the text and should only result in QLineEdit::textChanged() being signalled. QLineEdit::textEdited() should not be generated in that case correct??

        The framework is 5.15LTS (which I realize went EOL back in the spring) ... could it just be a bug in QLineEdit of that vintage?

        I Offline
        I Offline
        IgKh
        wrote last edited by
        #10

        @Dallas-Posey said in Constraining user input to a QLineEdit:

        I simply want to force the user to enter a string representation of a positive, floating-point value < 99.99 like this: "XX.YY" without forcing them to type the decimal point.

        That's not a super unusual thing to do. Reminds me of when installers had screen to enter serial numbers, and you could type them without the hyphens.

        Two alternative approaches to possibly consider:

        • Use QLineEdit's input mask functionality to make the decimal point a fixture the user can't delete.

        • Actually use two line edits, each with a validator allowing two digits, the first line edit automatically moving focus to the second one once full. The decimal point can then be just a label between the line edits.

        D 1 Reply Last reply
        1
        • D Dallas Posey

          @JonB it is definitely happening. I fixed it with a simple in-flight mechanism in the signal handler:

          QLineEdit* editor  = this->findChild<QLineEdit*>("filterEdit");   // Private Member of fltsetter
          connect(editor, &QLineEdit::textEdited, this, &fltsetter::hdlTextEdited);
          
          ...
          
          void fltsetter::hdlTextEdited(const QString &text)
          {
              int currentCursorPos;
              static int inFlight = 0;
          
              qInfo() << "fltsetter::hdlTextEdited(" << text << ")";
              qInfo() << "Current cursor pos: " << (currentCursorPos = editor->cursorPosition());
              qInfo() << "Last cursor pos: " << lastCursorPos;
              qInfo() << "size: " << editor->text().size();
              qInfo() << "InFlight: " << inFlight;
          
              if (inFlight--)
              {
                  qInfo() << "Inflight!";
                  return;
              }
          
              // 1. Limit the size to 5
              if (editor->text().size() > 5)
              {
                  inFlight++;
          
                  editor->backspace();
                  qInfo() << "Size limited to 5 chars";
                  qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
                  return;
              }
          
              // 2. If adding char at position 2, insert decimal
              if ((currentCursorPos == 2) && (lastCursorPos == 1))
              {
                  inFlight++;
          
                  editor->insert(".");
                  qInfo() << "Inserting decimal";
                  qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
                  return;
              }
          
              // 3. If adding third char after decimal erased, insert a decimal in pos 3
              if ((currentCursorPos == 3) && (lastCursorPos == 2) && (text.at(currentCursorPos) != "."))
              {
                  inFlight += 3;
          
                  QChar lastChar = text.at(currentCursorPos);
                  editor->backspace();
                  editor->insert(".");
                  editor->insert(lastChar);
                  qInfo() << "Substituting decimal";
                  qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
                  return;
              }
          
              inFlight = 0;
          
              qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
          }
          

          The logs (which I can now pare-down some) verified that the signal handler was re-entering as a result of the insert() and backspace() methods being called and the inFlight mechanism fixed it right up. Behaves exactly as I would have expected (without the inflight) if the insert() and backspace() methods didn't generate the signal.

          Anyway it works now. It's a clumsy, non-generic solution, but pays the bills in this case looks like.

          JonBJ Online
          JonBJ Online
          JonB
          wrote last edited by JonB
          #11

          @Dallas-Posey
          If it works, fine! If I remember I will have a look tomorrow to see how textEdited() behaves under Qt6. The docs are pretty vague about what "whenever the text is edited" means. Oh, look at this at the top of the doc page https://doc.qt.io/qt-6/qlineedit.html#editing-text

          When the text changes, the textChanged() signal is emitted. When the text changes in some other way than by calling setText(), the textEdited() signal is emitted.

          So actually there is your answer, though I didn't know it.

          As I said earlier, ISTM you could have written your code to use setText() where you use insert()/backspace(). (And aren't your calls in hdlTextEdited() causing it to be called again from itself?)

          On a separate matter, it's up to you but this kind thing

          QLineEdit* editor = this->findChild<QLineEdit*>("filterEdit"); // Private Member of fltsetter

          is ugly. Did you consider creating a subclass of QLineEdit for your behaviour and using that for the line edit where appropriate?

          D 1 Reply Last reply
          0
          • JonBJ JonB

            @Dallas-Posey
            If it works, fine! If I remember I will have a look tomorrow to see how textEdited() behaves under Qt6. The docs are pretty vague about what "whenever the text is edited" means. Oh, look at this at the top of the doc page https://doc.qt.io/qt-6/qlineedit.html#editing-text

            When the text changes, the textChanged() signal is emitted. When the text changes in some other way than by calling setText(), the textEdited() signal is emitted.

            So actually there is your answer, though I didn't know it.

            As I said earlier, ISTM you could have written your code to use setText() where you use insert()/backspace(). (And aren't your calls in hdlTextEdited() causing it to be called again from itself?)

            On a separate matter, it's up to you but this kind thing

            QLineEdit* editor = this->findChild<QLineEdit*>("filterEdit"); // Private Member of fltsetter

            is ugly. Did you consider creating a subclass of QLineEdit for your behaviour and using that for the line edit where appropriate?

            D Offline
            D Offline
            Dallas Posey
            wrote last edited by
            #12

            @JonB

            Good catch on the documentation thing. From the 5.15 documentation:

            Unlike textChanged(), this signal is not emitted when the text is changed programmatically, for example, by calling setText().

            Yeah I can refactor and use setText(). Seems a better solution.

            The definition of 'editor' I included here was in fact for clarity only. Actually the class is declared with a private member 'QLineEdit* editor' and then when I need the editor, I use findChild. The actual instantiation of the object is done in ui_fltsetter.h which I intuit to be created by some tool in the chain from the xml code generated by QtDesigner?? (from fltsetter.ui???). Honestly, I have only the vaguest understanding of how that process works, but the only way I know to get a handle to the instance is with findChild. Unless of course I created and configured the object problematically I guess, but doesn't that defeat the purpose of using the Designer? Could you suggest a better solution? I'm very interested in learning because I really like Qt. It was recommended to me years ago, but I ignored the advice as in my 40 year career up until now, I never needed a graphical UI. It's all been little machines gathering data and talking to each other. Qt is more than a UI though, I am happy to be discovering and even as my career winds down, I still love discovering new things.

            Anyway, I do take your point though about creating a scion of QLineEdit. My initial foray into subclassing QtObjects was unsuccessful and due to schedule at that moment (running with scissors, dowsed in gasoline, pursued by stackholders carrying torches and pitchforks) I had to move on with best effort. In the original proto, forcing the user to engage their brain to enter a mixed number with the editing capabilities of the stock QLineEdit seemed acceptable.

            JonBJ 1 Reply Last reply
            0
            • I IgKh

              @Dallas-Posey said in Constraining user input to a QLineEdit:

              I simply want to force the user to enter a string representation of a positive, floating-point value < 99.99 like this: "XX.YY" without forcing them to type the decimal point.

              That's not a super unusual thing to do. Reminds me of when installers had screen to enter serial numbers, and you could type them without the hyphens.

              Two alternative approaches to possibly consider:

              • Use QLineEdit's input mask functionality to make the decimal point a fixture the user can't delete.

              • Actually use two line edits, each with a validator allowing two digits, the first line edit automatically moving focus to the second one once full. The decimal point can then be just a label between the line edits.

              D Offline
              D Offline
              Dallas Posey
              wrote last edited by
              #13

              @IgKh

              Yeah I hear ya, it is an old pattern and I did try the input mask but it I couldn't get it to work. I set the mask in QtDesigner like this:

              bfbf3e43-4571-463b-97f4-b3043db1975b-image.png

              But the inputMask is really just another form of validation right? It doesn't modify the behavior of the editor itself. That is, it doesn't force the user to type 4 digits separated by a period, it just invalidates the entry (hasAcceptableInput() returns false).

              @IgKh said in Constraining user input to a QLineEdit:

              Use QLineEdit's input mask functionality to make the decimal point a fixture the user can't delete.

              Best I can tell, it won't prevent the decimal from being backspaced over ... though you can't set the text attribute to anything but a period in Designer. In the running program however, with the editor focused, the user can backspace right over it and type anything they want. Could it be because I also have a validator set?:

              editor->setValidator(new QDoubleValidator(20, 99.99, 2, this ));
              

              @IgKh said in Constraining user input to a QLineEdit:

              Actually use two line edits, each with a validator allowing two digits, the first line edit automatically moving focus to the second one once full. The decimal point can then be just a label between the line edits.

              Yeah this idea certainly occurred to me and seems perfectly valid. Part of this project though, is a learning exercise for me and I wanted to explore QLineEdit as thoroughly as possible. I do appreciate the help and information @IgKh

              1 Reply Last reply
              0
              • D Dallas Posey

                @JonB

                Good catch on the documentation thing. From the 5.15 documentation:

                Unlike textChanged(), this signal is not emitted when the text is changed programmatically, for example, by calling setText().

                Yeah I can refactor and use setText(). Seems a better solution.

                The definition of 'editor' I included here was in fact for clarity only. Actually the class is declared with a private member 'QLineEdit* editor' and then when I need the editor, I use findChild. The actual instantiation of the object is done in ui_fltsetter.h which I intuit to be created by some tool in the chain from the xml code generated by QtDesigner?? (from fltsetter.ui???). Honestly, I have only the vaguest understanding of how that process works, but the only way I know to get a handle to the instance is with findChild. Unless of course I created and configured the object problematically I guess, but doesn't that defeat the purpose of using the Designer? Could you suggest a better solution? I'm very interested in learning because I really like Qt. It was recommended to me years ago, but I ignored the advice as in my 40 year career up until now, I never needed a graphical UI. It's all been little machines gathering data and talking to each other. Qt is more than a UI though, I am happy to be discovering and even as my career winds down, I still love discovering new things.

                Anyway, I do take your point though about creating a scion of QLineEdit. My initial foray into subclassing QtObjects was unsuccessful and due to schedule at that moment (running with scissors, dowsed in gasoline, pursued by stackholders carrying torches and pitchforks) I had to move on with best effort. In the original proto, forcing the user to engage their brain to enter a mixed number with the editing capabilities of the stock QLineEdit seemed acceptable.

                JonBJ Online
                JonBJ Online
                JonB
                wrote last edited by JonB
                #14

                @Dallas-Posey said in Constraining user input to a QLineEdit:

                Good catch on the documentation thing. From the 5.15 documentation:

                Unlike textChanged(), this signal is not emitted when the text is changed programmatically, for example, by calling setText().

                As I wrote, that is against the textEdited() signal entry. Even in 5.15 go to the Description paragraphs and look for

                When the text changes the textChanged() signal is emitted; when the text changes other than by calling setText() the textEdited() signal is emitted;

                It's worth reading the Description section in each page.

                If you want to work from Designer, it will save a .ui file and your build process will run uic on that to generate a ui_....h file containing C++ code implementing what you have designed. And the will be #included into a class_name.cpp file you compile.

                Usually it's better not to findChild<>() from the outside world. The .cpp can either (a) implement the behaviour or (b) export the QLineEdit or similar for access from elsewhere as a dedicated C++ method. If you intend to do any designing yourself you will want to look at this.

                You can subclass widgets you use from Designer if you wish. It does require an extra "step", This is called Promotion.

                1 Reply Last reply
                1
                • D Offline
                  D Offline
                  Dallas Posey
                  wrote last edited by
                  #15

                  Thanks @JonB . I will read the documentation more carefully.

                  @JonB said in Constraining user input to a QLineEdit:

                  If you want to work from Designer, it will save a .ui file and your build process will run uic on that to generate a ui_....h file containing C++ code implementing what you have designed. And the will be #included into a class_name.cpp file you compile.

                  Ok so there is an additional, pre-compiler compiler other than the moc right? So the uic translates the xml from the .ui files into the ui_.h headers, then the moc runs though all the code in the rest of the source and does it's thing, then that all gets fed to the compiler proper. Or some variant thereof? Cool.

                  Usually it's better not to findChild<>() from the outside world.

                  So not kosher to call this from a descendant of QDialog for example? That is what you mean by outside world?

                  Thx again for the help @JonB et. al. The Qt community is very helpful and friendly!

                  BTW: This worked much better after I thought about what you said earlier:

                  void fltsetter::hdlTextEdited(const QString &text)
                  {
                      int currentCursorPos;
                      
                      qInfo() << "fltsetter::hdlTextEdited(" << text << ")";
                      qInfo() << "Current cursor pos: " << (currentCursorPos = editor->cursorPosition());
                      qInfo() << "Last cursor pos: " << lastCursorPos;
                      qInfo() << "size: " << editor->text().size();
                  
                      disconnect(editor, &QLineEdit::textEdited, this, &fltsetter::hdlTextEdited);
                  
                      // 1. If adding char at position 2, insert decimal
                      if ((currentCursorPos == 2) && (lastCursorPos == 1))
                      {
                          qInfo() << "Inserting decimal";
                          editor->insert(".");
                          qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
                      }
                  
                      // 2. If adding third char after decimal erased, insert a decimal in pos 3
                      else if ((currentCursorPos == 3) && (lastCursorPos == 2) && (text.back() != "."))
                      {
                          qInfo() << "Substituting decimal";
                          auto lastChar = text.back();
                          editor->backspace();
                          editor->insert(".");
                          qInfo() << "Last char: " << lastChar;
                          editor->insert(lastChar);
                          qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
                      }
                  
                      connect(editor, &QLineEdit::textEdited, this, &fltsetter::hdlTextEdited);
                  
                      qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
                  }
                  
                  
                  JonBJ 1 Reply Last reply
                  0
                  • D Dallas Posey

                    Thanks @JonB . I will read the documentation more carefully.

                    @JonB said in Constraining user input to a QLineEdit:

                    If you want to work from Designer, it will save a .ui file and your build process will run uic on that to generate a ui_....h file containing C++ code implementing what you have designed. And the will be #included into a class_name.cpp file you compile.

                    Ok so there is an additional, pre-compiler compiler other than the moc right? So the uic translates the xml from the .ui files into the ui_.h headers, then the moc runs though all the code in the rest of the source and does it's thing, then that all gets fed to the compiler proper. Or some variant thereof? Cool.

                    Usually it's better not to findChild<>() from the outside world.

                    So not kosher to call this from a descendant of QDialog for example? That is what you mean by outside world?

                    Thx again for the help @JonB et. al. The Qt community is very helpful and friendly!

                    BTW: This worked much better after I thought about what you said earlier:

                    void fltsetter::hdlTextEdited(const QString &text)
                    {
                        int currentCursorPos;
                        
                        qInfo() << "fltsetter::hdlTextEdited(" << text << ")";
                        qInfo() << "Current cursor pos: " << (currentCursorPos = editor->cursorPosition());
                        qInfo() << "Last cursor pos: " << lastCursorPos;
                        qInfo() << "size: " << editor->text().size();
                    
                        disconnect(editor, &QLineEdit::textEdited, this, &fltsetter::hdlTextEdited);
                    
                        // 1. If adding char at position 2, insert decimal
                        if ((currentCursorPos == 2) && (lastCursorPos == 1))
                        {
                            qInfo() << "Inserting decimal";
                            editor->insert(".");
                            qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
                        }
                    
                        // 2. If adding third char after decimal erased, insert a decimal in pos 3
                        else if ((currentCursorPos == 3) && (lastCursorPos == 2) && (text.back() != "."))
                        {
                            qInfo() << "Substituting decimal";
                            auto lastChar = text.back();
                            editor->backspace();
                            editor->insert(".");
                            qInfo() << "Last char: " << lastChar;
                            editor->insert(lastChar);
                            qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
                        }
                    
                        connect(editor, &QLineEdit::textEdited, this, &fltsetter::hdlTextEdited);
                    
                        qInfo() << "New cursor pos: " << (lastCursorPos = editor->cursorPosition());
                    }
                    
                    
                    JonBJ Online
                    JonBJ Online
                    JonB
                    wrote last edited by JonB
                    #16

                    @Dallas-Posey
                    By "outside world" (for findChild<>()) I mean anywhere other than the .cpp where it is defined. It is preferable if that exports it if intended, otherwise you are delving into somewhere else's internal details. Not a 100% rule, just preferable not to.

                    You choose to disconnect() and then re-connect(). For my own part I would prefer not to make a change like that, similarly for QObject::blockSignals(bool block) which you will see others would use here (actually it's not too bad in this case, the scope is pretty clear and limited). You can if you wish, but (say those didn't exist) why not use a simple flag (static in the method or as a class member variable) which you set on entry and clear on exit, simply return if you re-enter while it is set?

                    Or use setText() only in the first place :)

                    Anyway you are good, there are many ways to skin a cat....

                    1 Reply Last reply
                    0
                    • D Offline
                      D Offline
                      Dallas Posey
                      wrote last edited by Dallas Posey
                      #17

                      More good points @JonB . I did not know about know about QObject::blockSignals(bool block). A flag would certainly work too which is what my original 'inFlight' idea started out as. I'm gonna play with it some more ... needs some additional refactoring anyway.

                      Thx again!

                      Edit: I shied away from setText() because it doesn't re-validate the text. I don't look at acceptableInput until after the user presses ENTER ... not sure if that event re-runs the validation and maybe it's not an issue anyway.

                      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