目录
1. 问题的提出
QLineEdit自带的setValidator包含 QIntValidator, QDoubleValidator, QRegularExpressionValidator。但在使用过程中,QIntValidator和QDoubleValidator在限制整数或者小数时无法正确应用。
2. 问题现象
2.1. QDoubleValidator现象
如下代码:
ui.lineEdit->setValidator(new QDoubleValidator(100, 900, 1, this));
其中ui.lineEdit为QLineEdit控件,上述代码的原意是想将编辑框的输入范围控制在[100, 900]的闭区间中的任意double类型且只保留1位小数的数字,但现实中发现:
- 可以输入任意只有1位小数的double数,如:可以输入2211111.1、2.1、120.5、999.6等等。也就是说只要是有1位小数的任何数都能输入,并不是预想的[100, 900]的闭区间中的任意double类型且只有1位小数的数。
- 如果将第3个参数1改为0,即不保留任何小数位,则可以输入任意整数。如:8888866666、8、688,也就是说[100, 900]的闭区间对整数没任何限制。
2.2. QIntValidator现象
如下代码:
ui.lineEdit->setValidator(new QIntValidator(100, 900, this));
其中ui.lineEdit为QLineEdit控件,上述代码的原意是想将编辑框的输入范围控制在[100, 900]的闭区间中的任意整数,但现实中发现:
- 可以输入小于100的任意整数,如:8、6、88 99。
- 可以输入[100, 999]的闭区间中的任意整数,即[900,999]闭区间的任意整数也能输入。
- 不能输入大于999数。
- 不能输入小数。
- 不能输入任何非数字,如:字符。
3. 预备知识及原因分析
3.1. 预备知识
QIntValidator, QDoubleValidator都继承自QValidator类。QValidator类的子类还有 QRegExpValidator、QRegularExpressionValidator类。QValidator类在校验用户输入数据时有三种状态,其是用QValidator::State枚举表示,该枚举有如下三个值:
QValidator::Invalid
QValidator::Intermediate
QValidator::Acceptable
- QValidator::Invalid
该值表示输入值完全是无效的,即清楚无误的、能一眼看出就是无效值。
- QValidator::Intermediate
该值表述输入值暂时还不能确定是有效还是无效,属于中间不确定状态;到底是否有效,取决于输入值的后续动作或行为。
- QValidator::Acceptable
该值表示输入值确定有效的,即清楚无误的、能一眼看出就是有效值。
只有当状态为QValidator::Intermediate、QValidator::Acceptable时,Qt才允许编辑框输入数字;当状态为QValidator::Invalid时,Qt拒绝编辑框输入。
3.2. QIntValidator状态说明
关于上面的三种状态,下面以Qt的整形校验器为例说明:
QString str;
int pos = 0;
QIntValidator v(100, 900, this);
str = "1";
v.validate(str, pos); // returns Intermediate
str = "012";
v.validate(str, pos); // returns Intermediate
str = "123";
v.validate(str, pos); // returns Acceptable
str = "678";
v.validate(str, pos); // returns Acceptable
str = "999";
v.validate(str, pos); // returns Intermediate
str = "1234";
v.validate(str, pos); // returns Invalid
str = "-123";
v.validate(str, pos); // returns Invalid
str = "abc";
v.validate(str, pos); // returns Invalid
str = "12cm";
v.validate(str, pos); // returns Invalid
上面的例子展示了Qt整形检验器类的几个例子。其中最难理解的是str = "012"、str = "999"返回的是Intermediate,而我们直觉觉得应该是返回Invalid才对,因为字符串012、999转为整形时,都不在[100, 900]闭区间。这是我们对Qt检验器类范围的错误理解、望文生义造成的。Qt对范围的官方解释如下:
Notice that the value 999 returns Intermediate. Values consisting of a number of digits equal to or less than the max value are considered intermediate. This is intended because the digit that prevents a number from being in range is not necessarily the last digit typed. This also means that an intermediate number can have leading zeros.
大意是说:
当输入的整形数值不在整形校验器设定的范围且当输入整形的数字个数等于或小于校验器设置的最大值的数字个数时,则状态为Intermediate。这是故意为之的,因为这样可以不用再敲入1个数字就能判断该数值是否在校验器有效区间内。这也意味着一个中间态的数值可能有前导0的数字。
上面的翻译理解很费解,说得直白点就是:
当输入的整形值不在整形校验器的范围(如:上例的012、999,这两个值都不在[100, 900]区间),但组成该整形值的有效数字个数(如:012表示整数12,12是由2个数字而不是3个数字组成、999是由3个9组成)小于或等于校验器范围最大值的有效数字个数(上例中校验器最大值为900,由1个9、2个0,即3个有效数字组成),则认为状态为Intermediate。根据前面的规则,所以在编辑框中输入012、[900, 999]区间中的任意整形值,Qt是可以让编辑框接受输入的,所以我们就看到前文提到的输入超过范围的值了。
总结:
对于整形校验器QIntValidator来说,状态为Intermediate必须同时满足如下条件:
- 输入内容必须为整数。
- 输入的整形值不在校验器设定的区间范围。
- 输入数值的有效数字个数必须小于或等于校验器最大值的有效数字个数。
对于整形校验器QIntValidator来说,满足如下条件中的任意一个就为Invalid状态:
- 输入内容包含字符等非数值。
- 输入数值的有效数字个数大于校验器最大值的有效数字个数。
有了上面的说明,就能很好理解str = "000000"为何能输入了,因为 "000000"其实就是数值0,由1个有效数字0组成,而不是6个0组成,即输入 000000时的状态为Intermediate;而输入1234状态为Invalid。
3.3. QDoubleValidator状态说明
对于双精度浮点型校验器QDoubleValidator来说,满足如下条件中的任意一个,其状态就为Intermediate:
- 输入的内容为双精度浮点值,该值不在校验器设定的区间范围。如:2.1节中代码运行时,输入999.6,超出范围了。
- 输入内容为双精度浮点值,该值在区间范围内但格式不正确,如:通过setNotation设置浮点格式为科学计数法但输入的不是科学计数法,如下:
auto pDoubleValidator = new QDoubleValidator(100, 900, 1, this);
pDoubleValidator->setNotation(QDoubleValidator::ScientificNotation);
ui.lineEdit->setValidator(pDoubleValidator);
上述代码运行时,输入119.6即非标准科学计数法,此时状态就为Intermediate。
如果输入的不是数值类型(如:字符串)或小数个数多于设定的个数(如:设置1位小数,输入的小数多于1位),则返回Invalid状态。
4. 范围限制无效的解决方法
从前文知道,当状态为Intermediate时,依然能输入值,这可不是我们想要的结果,如何能完美控制输入值到Qt校验器设定的范围呢?可以采用如下的任意一种方法:
- 从Qt校验器派生并重写validate函数。
-
正则表达式法。
4.1. 派生子类重写validate函数
下面代码实现只能输入10.0-1000.0范围的且小数点只有一位的浮点数:
class CMyDoubleValidator:public QDoubleValidator
{
public:
CMyDoubleValidator(double bottom, double top, int decimals, QObject *parent = 0):QDoubleValidator(bottom,top,decimals,parent){}
virtual QValidator::State validate(QString &input, int &pos) const
{
const QValidator::State origState = QDoubleValidator::validate( input, pos );
if(input.isEmpty())
{
return QValidator::Intermediate;
}
if(input.length() > 5)
{
return QValidator::Invalid;
}
if((input.length() == 1 && input.at(0) == '.') || input.at(0) == '0')
{
return QValidator::Invalid;
}
if(input.length() == 2 && input.at(1) == '.')
{
return QValidator::Invalid;
}
if( ( origState == QValidator::Intermediate ) && (input.toDouble() > top()) )
{
return QValidator::Invalid;
}
if(( origState == QValidator::Intermediate ) && ( input.toDouble() < bottom()) )
{
return QValidator::Intermediate;
}
else
{
return origState /*QValidator::Acceptable*/;
}
}
};
调用方法如下:
auto pDoubleValidator = new CMyDoubleValidator(10.0, 100.0, 1, this);
ui.lineEdit->setValidator(pDoubleValidator);
上面的方法存在的问题是:当在编辑框输入的值小于要求的最小值,依然能输入,如:上例中只能输入10.0-1000.0范围的且小数点只有一位的浮点数,但当我们输入[0-10)之间的整数值依然能输入,因为下一步用户需要在编辑框中是继续输入还是不再输入,程序是不知道的,此时的状态就是QValidator::Intermediate,比如:当前在编辑框输入的是1,用户可能想继续输入0,以实现在编辑框中输入10或者用户不再输入0也就是编辑框中的值此时是1,故此种方法是无法控制用户不能输入1的,有时需求需要完全严格控制输入值在某个范围,比如不在[10.0, 1000]区间的一切值都不能输入,即1也不能输入,但此种方法办不到,因为如果1都不能输,10就无法输入,毕竟10是1开头的,此时可以响应编辑框的textChanged信号,在该信号的响应槽中检测,将检测结果发送到QLabel或对话框中以提示用户。
4.2. 正则表达式方法
先简要解释下正则表达式的含义:
^: 匹配字符串的开始。
$: 匹配字符串的结束。
\d: 匹配数字。注意:在C++中要对\转义,即\d用代码表示为\\d
. :匹配除换行符以外的任意字符。
{n}:重复n次。
{n, m}:重复至少n次,至多m次。
?: 重复零次或一次。
[0-9]:0到9数字中的任意一个数字。
[A-Z]:A到Z字母中的任意一个字母。
[a-z]:a到z字母中的任意一个字母。
| :或。可以把不同的子表达式合成一个总表达式
以限制输入0-50的正整数为例,具体代码如下:
auto reg = QRegularExpression("^([1-4][0-9]?|50 |[5-9] | 0)$");
auto pValidator = new QRegularExpressionValidator(this);
pValidator->setRegularExpression(reg);
ui.lineEdit->setValidator(pValidator);
[1-4][0-9]?:如果?匹配的是0次。则表示输入的数值为1、2、3、4;如果?匹配的是1次,则输入的数值为[10, 49]闭区间的任意整数值。
[5-9] :表示匹配输入数值为5、6、7、8、9。
50:表示匹配50。
0:表示匹配0。
然后通过|组合,就表示匹配[0, 50]区间的任意整数值。
以限制输入0.1-10的浮点数为例,具体代码如下:
auto reg = QRegularExpression("^(\\d(\\.\\d{1})?|10)$");
auto pValidator = new QRegularExpressionValidator(this);
pValidator->setRegularExpression(reg);
ui.lineEdit->setValidator(pValidator);
首先看^.......$之间的代码段,其中第一个()可以忽略,从\d开始,表示匹配的是数字。再看\d后接的第二个()里的内容:(\.\d{1})
\. 表示匹配小数点这个字符(如果不写\,则会匹配任意字符如abc等)
\d{1} 表示1位数字。可以理解为小数点后面只接一位数字。同理\d{2}便是小数点后两位。
因此\d(\.\d{1})即可表示从0到9.9的小数,再用 | 把10加上就可以生成0到10的一位小数浮点值输入。
? 表示没有小数或最多只有一位小数。
5. 参考链接
【1】:https://blog.csdn.net/qq87125158/article/details/127961528
【2】:关于QDoubleValidator增加上下限无效的解决_qdoublevalidator不起作用-CSDN博客