2

I have a SpinBox with a custom TextInput as contentItem. I want it to display a quantity, so it want to display something like "10 / 20 pc." for example. The minimum value is always 1, the maximum value can be everything (1.000 or 10.000 or more is possible).

When editing the value manually by keyboard I want the " / 20 pc." to be not editable, the user should only be able to edit the desired quantity in front of the "/". I tried using an inputMask with the help of the Qt doc. Unfortunately it is not working as expected as I am not understanding how to use the mask-characters. I tried it with the mask-character '9' (Character of the Number category required, such as 0-9), but thats only for one digit. The number can be multiple digits as mentioned before. A Regular expression would be feasible as well.

SpinBox {
    property int maxValue

    id: spin
    from: 1
    to: maxValue
    editable: true
    value = 1

    // TextInput inputMask approach
    contentItem: TextInput {
        z: 2
        text: spin.value
        inputMask: "9 / %1 pc.".arg(spin.maxValue) 
        horizontalAlignment: Qt.AlignHCenter
        verticalAlignment: Qt.AlignVCenter
        font: spin.font
        inputMethodHints: Qt.ImhDigitsOnly
        validator: spin.validator
    }

    // or RegEx approach
    validator: RegularExpressionValidator {
        regularExpression: ???
    }
}

1 Answer 1

3

There are a few pitfalls to look out for.

Your input Mask
inputMask: "9 / %1 pc.".arg(spin.maxValue)
You are essentially on the right track here, however you have two problems.

  • One being (as you already noticed), that 9 essentially only allows for one character. If you want to allow for more than one digit, you'll need to add 0s, indicating additional, optional digits.
  • The second is, that by using .arg(spin.maxValue), your max value essentially becomes part of the input mask. If your value contains any 0 or 9, those will break your logic, since they will be replaced with the selected value. Therefore, we escape those digits. "%1".arg(spin.to).replace('0', '\\0').replace('9', '\\9').

This will make the mask work, but has the ugly downside of adding blank spaces to the displayed text after the first digit for every digit you allow for. Unfortunately, there does not seem to be a way to tell Qt to shift those digits to the right, if more characters are allowed. So either you live with it, or - what I did in my case - was to explicitly set the blank character to a zero-width space, by appending ;​ at the end of the mask. You could also adapt the mask dynamically to your actual value length.


Retrieving your value
Now, of course you'll want the value you added to be fed back to the SpinBox. In order to do that, the component needs to know how to get from 9 / 10 pc. to just 9 - and vice versa. SpinBox has two properties for exactly this reason: valueFromText and textFromValue. Two simple implementations can look like this:

valueFromText: (text) => {
  return text.split(' / ')[0]
}

textFromValue: (value) => {
  return "%1 / %2 pc.".arg(value).arg(spin.to)
}

Notice, that for TextField.text (As opposed to TextField.displayText), the blank characters will be stripped, so there's no need to deal with those extra spaces here.


Binding loops
This leaves one final issue. You'll want the SpinBoxs value string to be assigned to the TextInputs text property once it changes (i.e. by clicking the up/down buttons). The naive implementation would be to do: text: spin.displayText. This works, but has the ugly side effect of printing Binding loop detected every time the value changes, since text manipulates spin.displayText which manipulates text again. To solve this, instead of binding I use a specific connection and only re-assign text if the value is actually different:

Connections {
  target: spin
  function onDisplayTextChanged() { 
    if(input.text !== spin.displayText) {
      input.text = spin.displayText
    }
  }
}

Summary
This should fix-up your problems. Below you'll find the full solution in context. I am sure, that there will be things to adapt / improve on, but it will get you a decent part of the way there.

SpinBox {
  property int maxValue: 20

  id: spin

  from: 1
  to: maxValue

  editable: true
  value: 1

  contentItem: TextInput {
    id: input
    z: 2

    font: spin.font
    horizontalAlignment: Qt.AlignHCenter
    verticalAlignment: Qt.AlignVCenter

    readOnly: !spin.editable
    validator: spin.validator
    inputMethodHints: spin.inputMethodHints
    inputMask: "009 / %1 pt.;​".arg("%1".arg(spin.to).replace('0', '\\0').replace('9', '\\9'))

    Connections {
      target: spin

      function onDisplayTextChanged() {
        if(input.text !== spin.displayText) {
          input.text = spin.displayText
        }
      }
    }
  }

  inputMethodHints: Qt.ImhDigitsOnly

  valueFromText: (text) => {
    return text.split(' / ')[0]
  }

  textFromValue: (value) => {
    return "%1 / %2 pc.".arg(value).arg(spin.to)
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

This works. I adapted the inputMask a bit tho: because I do not know how the length of my maxValue will be, I do the following to ensure the mask is set up correctly when getting a number of unknown size: inputMask: "%1%2 / %3 pc.;​".arg("0".repeat(Math.max(0, maxValue.toString().length - 1))).arg("9").arg(maxValue.toString().replace(/0/g, '\\0').replace(/9/g, '\\9')). I am using a regex for the replace() function otherwise it would only replace the first match, e.g. 1000 would be '1\000'.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.