Форма поиска банка Primer. Часть 2. Организуем контроль правильности ввода

2022, Feb 19    

Продолжение серии статей, описывающих поцесс разработки управляющей формы в демо-банке Primer.

Предыдущая статья:

Часть 1. Готовим макет формы

Прошлый раз мы создали и настроили заготовку формы для отбора записей, а также подготовили функции для формирования запроса и отображения отобранных записей.

Сегодня займемся полями, предназначенными для ввода ограничений на возраст кандидата (txtAgeFrom и txtAgeTo), и научим форму отбирать записи с учетом этих ограничений.

По нашему замыслу значениями этих полей должны быть положительные числа, на основании которых мы будем формировать поисковые ограничения на характеристику “Дата рождения” базы “Лицо”.

Чтобы обеспечить формирование корректного запроса нам нужно помочь пользователю ввести правильные значения: предупредить об ошибках и, по возможности, откорректировать их.

Контроль на этапе ввода

В первую очередь будем предупреждать пользователя о вводе некорректного значения непосредственно в процессе набора. Для этого используем событие ContentChange, которое происходит при любом изменении свойства Text поля, т.е. не только при наборе с клавиатуры, но и при вставке из буфера или присваивании нового значения скриптом.

В случае ошибки не будем отвлекать пользователями всплывающими сообщениями, а просто подкрасим значение красным.

if control.Text:match("%D") then
	control.ForeColor = Color.Red
else	
	control.ForeColor = Color.DimGray
end

Поскольку абсолютно идентичные по содержанию обработчики нам понадобятся для обоих полей txtAgeFrom и txtAgeTo (а в будущем и для поля txtExperience), оформим их отдельной функцией:

function AgeTypeCheck(event)
--	контроль ввода в полях возраста и стажа
	local control = event.Control
	if control.Text:match("%D") then
		control.ForeColor = Color.Red
	else	
		control.ForeColor = Color.DimGray
	end
end

Теперь в обработчике события Load формы свяжем событие ContentChange трех полей с этой функцией:

Фрагмент функции Форма_Load:

--	Контроль ввода (стаж и возраст)
	local age_ctrl = {Me.txtAgeFrom, Me.txtAgeTo, Me.txtExperience }
	for _, ctrl in pairs(age_ctrl) do
		ctrl.ContentChange = AgeTypeCheck
	end

Будем великодушны

Людям свойственно ошибаться. они неизбежно будут вводить (вставлять из буфера обмена) в поля что-нибудь, похожее на “до 50” или “30 - 60”. Попытаемся предугадать наиболее распространенные варианты опечаток и исправить их.

Будем использовать событие TypeValidationCompleted, которое происходит при попытке выхода фокуса с элемента TextBox.

Первым делом очистим введенное значение от лишних пробелов, а также от символов до первой и после последней цифры (от 30 лет → 30).

local control = event.Control
local val = control.Text:trim()
val = val:gsub("^%D+","")
val = val:gsub("%D+$","")

Если в результате останется только строка из цифр, преобразуем ее в число и запишем как новое значение поля.

if not val:match("%D") then
	control.Text = tonumber(val)
	return
end

Теперь проанализируем строки вида “число-число” и попытаемся разумно их интерпретировать.

Будем исходить предположения, что границы диапазона должны попадать в интервал 14-65 лет и второе должно быть больше первого. При соблюдении этих условий первое число следует поместить в поле txtAgeFrom, а второе - в поле txtAgeTo:

local min, max = 14, 65
val = val:gsub("%s+%-","-")
val = val:gsub("%-%s+","-")
if val:match("%d+%-%d+") then
	local st, ed = val:match("(%d+)%-(%d+)")
	local st_ed = st..ed
	st = tonumber(st)
	ed = tonumber(ed)
	st_ed = tonumber(st_ed)
	if ed > st and st >= min and st <= max and ed <= max then
		Me.txtAgeFrom.Text = st
		Me.txtAgeTo.Text = ed
		return
	end
end

Возможно, дефис или другой нецифровой символ попал в запись случайно: хотели написать “30”, а получилось “3-0” или “3щ0”.

Исключим этот символ и проверим получившееся значение на соответствие интервалу 14-65 лет.

--	проверка на случайно набранный нецифровой символ (2r5)
	if val:match("%d+%D%d+") then
		local st_ed = val:match("%d+%D%d+"):gsub("%D","")
		st_ed = tonumber(st_ed)
		if st_ed >= 14 and st_ed < 65 then
			control.Text = st_ed
			return
		end
	end

И, наконец, если ничто другое не помогло, оставим в качестве значения первую встретившуюся в записи последовательность цифр.

	control.Text = val:match("%d+")

Полный листинг функции проверку и коррекцию значений в полях txtAgeFrom и txtAgeTo:

function AgeValidation( event )
	local min, max = 14, 65
	local control = event.Control
	local val = control.Text:trim()
	
-- 	очистка от нецифровых символов в начале и в конце строки
	val = val:gsub("^%D+","")
	val = val:gsub("%D+$","")
	if not val:match("%D") then
		control.Text = tonumber(val)
		return
	end

--	проверка на диапазон (напр., 20-50 )	
	val = val:gsub("%s+%-","-")
	val = val:gsub("%-%s+","-")
	if val:match("%d+%-%d+") then
		local st, ed = val:match("(%d+)%-(%d+)")
		local st_ed = st..ed
		st = tonumber(st)
		ed = tonumber(ed)
		st_ed = tonumber(st_ed)
		if ed > st and st >= min and st <= max and ed <= max then
			Me.txtAgeFrom.Text = st
			Me.txtAgeTo.Text = ed
			return
		end
	end
	
--	проверка на случайно набранный нецифровой символ (2r5)
	if val:match("%d+%D%d+") then
		local st_ed = val:match("%d+%D%d+"):gsub("%D","")
		st_ed = tonumber(st_ed)
		if st_ed <= max then
			control.Text = st_ed
			return
		end
	end
	
--	последний рубеж
	control.Text = val:match("%d+")
end

В результате выполнения этой функции в соответствующем поле будет записано либо целое число (положительное или равное нулю), либо пустая строка.

Для стажа (поле txtExperience) создадим аналогичный обработчик, но без проверки на диапазон и с другим допустимым интервалом значений (0-20 лет).

function ExperienceValidation( event )
	local min, max = 0,20
--	коррекция значений в полях возраста и стажа
	local control = event.Control
	local val = control.Text:trim()
	
-- 	очистка от нецифровых символов в начале и в конце строки
	val = val:gsub("^%D+","")
	val = val:gsub("%D+$","")
	if not val:match("%D") then
		control.Text = tonumber(val)
		return
	end
	
--	проверка на случайно набранный нецифровой символ (2r5)
	if val:match("%d+%D%d+") then
		local st_ed = val:match("%d+%D%d+"):gsub("%D","")
		st_ed = tonumber(st_ed)
		if st_ed >= min and st_ed <= max then
			control.Text = st_ed
			return
		end
	end

--	последний рубеж	
	control.Text = val:match("%d+")
end

Осталось добавить обработчике события Load формы связь события TypeValidationCompleted
с соответствующими функциями:

Фрагмент функции Форма_Load:

--	Контроль ввода (стаж и возраст)
	local age_ctrl = {Me.txtAgeFrom, Me.txtAgeTo, Me.txtExperience }
	for _, ctrl in pairs(age_ctrl) do
		ctrl.ContentChange = AgeTypeCheck
		if ctrl.Name:match("^txtAge") then
			ctrl.TypeValidationCompleted = AgeValidation
		else
			ctrl.TypeValidationCompleted = ExperienceValidation
		end		
	end

Включаем возраст в условия отбора

Осталось доработать функцию MakeRequest, с тем, чтобы формировала запрос вида ОТ ЛЦ01 9 БР <min_date> И 9 МР <max_date>, где min_date и max_date вычисляются с учетом текущей даты и значений в полях txtAgeFrom и txtAgeTo:

function MakeRequest()
--	Формирование строчной записи запроса
	local req = "ОТ ЛЦ01"
	
	conditions = {}
	
--	возраст	
	if Me.txtAgeFrom.Text ~= "" then
		local cur_year = DateTime.Now.Year
		local val = tonumber(Me.txtAgeFrom.Text)
		table.insert(conditions,"9 МР 31.12."..(cur_year - val))
	end

	if Me.txtAgeTo.Text ~= "" then
		local cur_year = DateTime.Now.Year
		local val = tonumber(Me.txtAgeTo.Text)
		table.insert(conditions,"9 БР 00.00."..(cur_year - val))
	end

--	стаж
	if Me.txtExperience.Text ~= "" then
		conditions.experience = tonumber(Me.txtExperience.Text)
	end
	
	if #conditions > 0 then
		req = req.." "..table.concat(conditions," И ")
	end

	return req
end

Для каждого из полей txtAgeFrom и txtAgeTo мы вычитаем его значение из текущего года и формируем поисковое ограничение для характеристики “Дата рождения”. Для поля txtAgeFrom как максимально возможную в данном голу дату, а для поля txtAgeTo - как минимально возможную.

Все формируемые условия запроса мы накапливаем в таблице conditions, которую следует объявить в начале модуля формы, чтобы она была доступна и из других функций (поверьте, она нам еще понадобится).

Значение поля txtExperience мы сохраняем в отдельном именованном поле таблицы conditions, но при формировании запроса его не используем. Отбор кандидатов по стажу мы оставим напоследок: структура банка не позволяет выполнить его обычным запросом, и для фильтрации записей по этому параметру мы напишем отдельный код после того, как будут отработаны все остальные условия.

Дополнительные материалы

Полный текст модуля формы на текущий момент приведен здесь.

Страница создана на основе шаблона flexible-jekyll