由于项目需要,某些输入框需要能限制只输入数字或小数,而且需要限制输入的数字范围。

开源的Duilib只有 CEditUI可用,它的属性中与之相关的有一个numberonly属性,它并不满足需求。设置了numberonly之后,它只能输入数字,不能输入小数点或者正负号。

整理下当前的需求,大概是这样:

  • 能输入小数点,能输入正负号
  • 不能输入除了数字,小数点,正负号之外的其他字符
  • 当输入的键不满足要求时,停留在上一次的输入
  • 可限制输入的范围,例如 1 ~ 50, -1.0 ~ 1.0等

最终,基于CEditUI实现了一个可用的NumberEdit。

1
class NumberEditUI : public CEditUI

它接受min 和 max两个属性,用于限制输入的范围。

重新实现SetAttribute函数,增加min和max。

1
2
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消息

1
2
3
4
NumberEditUI() : CEditUI() 
{
this->OnNotify += DuiLib::MakeDelegate(this, &NumberEditUI::OnTextChanged);
}

OnTextChanged中,对输入的字符串进行正则匹配,若满足,再判断范围。

1
2
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;
}

完整的代码在这里