由于项目需要,某些输入框需要能限制只输入数字或小数,而且需要限制输入的数字范围。
而开源的Duilib只有 CEditUI可用,它的属性中与之相关的有一个numberonly属性,它并不满足需求。设置了numberonly之后,它只能输入数字,不能输入小数点或者正负号。
整理下当前的需求,大概是这样:
- 能输入小数点,能输入正负号
- 不能输入除了数字,小数点,正负号之外的其他字符
- 当输入的键不满足要求时,停留在上一次的输入
- 可限制输入的范围,例如 1 ~ 50, -1.0 ~ 1.0等
最终,基于CEditUI实现了一个可用的NumberEdit。
| 1
 | class NumberEditUI : public CEditUI
 | 
它接受min 和 max两个属性,用于限制输入的范围。
重新实现SetAttribute函数,增加min和max。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue) override{
 if (_tcsicmp(pstrName, _T("min")) == 0) {
 m_min = _tstof(pstrValue);
 m_bValidateMin = true;
 } else if (_tcsicmp(pstrName, _T("max")) == 0) {
 m_max = _tstof(pstrValue);
 m_bValidateMax = true;
 } else {
 CEditUI::SetAttribute(pstrName, pstrValue);
 }
 }
 
 | 
然后,监听CEditUI的DUI_MSGTYPE_TEXTCHANGED消息
| 12
 3
 4
 
 | NumberEditUI() : CEditUI() {
 this->OnNotify += DuiLib::MakeDelegate(this, &NumberEditUI::OnTextChanged);
 }
 
 | 
在OnTextChanged中,对输入的字符串进行正则匹配,若满足,再判断范围。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | bool OnTextChanged(PVOID param){
 TNotifyUI *event = (TNotifyUI *)param;
 if (event->sType != DUI_MSGTYPE_TEXTCHANGED) return true;
 CDuiString text = this->GetText();
 regex rx(R"((?:^|\s)[+-]?(([[:digit:]]+(?:\.[[:digit:]]*)?))?(?=$|\s))");
 
 if (text.IsEmpty()) goto VALID;
 if (!std::regex_match(FrameUtils::wstring2string(text.GetData()), rx)) goto INVALID;
 double current = _tstof(text.GetData());
 if (m_bValidateMin && (current < m_min)) goto INVALID;
 if (m_bValidateMax && (current > m_max)) goto INVALID;
 VALID:
 m_oldValue = text;
 return true;
 INVALID:
 this->SetText(m_oldValue);
 this->SetSel(m_oldValue.GetLength(), m_oldValue.GetLength());
 return true;
 }
 
 | 
完整的代码在这里。