Форма поиска банка Primer. Часть 3. Добавляем диалоговые окна

2022, Feb 19    

Продолжаем формировать в демо-банке “Primer” управляющую форму для отбора кандидатов по заданным критериям.

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

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

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

Сегодня займемся полями txtEducation и txtCareer, в которых должны фиксироваться ограничения на направление образования кандидата и занимаемые им в прошлом должности.

Для заполнения обоих полей будем использовать псевдословари, т.е. выводимые в отдельном окне списки возможных значений: всех имеющихся в банке значений характеристики ОБ4 - “Направление (специальность)” для поля txtEducation и характеристики ТД3 “Должность” для поля txtCareer.

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

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

Приступаем.

Создание диалогового окна

Внешний вид управляющей формы, реализующей диалоговое окно, представлен на рисунке:

словарик

Мы будем использовать элемент с именем listbox1 (класс ListBox) для вывода списка значений, и две кнопки btnOk и btnCancel для фиксации выбора. В качестве дополнительного элемента используем надпись labInfo, в которой будем выводить информацию о количестве выбранных на текущий момент значений списка.

Как и для основной формы зададим для нашего окна тип границы - “Окно диалога” и вид окна - “Всплывающее”. Кнопки размера окна отключим, и сделаем его модальным.

Заполняем список

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

function fill_list(field,selection)

В качестве параметров будем передавать в эту функцию идентификатор поля field в виде строки формата “<мнемокод базы=""><номер поля="">" (например, "ОБ4" или "ТД3") и массив строк `selection` с перечнем значений, которые должны быть отмечены в списке.

function fill_list(field,selection)
	Me.listbox1.ShowCheck = true
	Me.listbox1:Clear()
	local code, fnum = field:match("(%u%u)(%d+)")
	local req = "ОТ "..code.."01 "..fnum.." УЗ"
	local rs = GetBank():StringRequest(req)
	local strings = {}
	if rs and rs.Count > 0 then
		for rec in rs.Records do
			val = rec:GetValue(tonumber(fnum)):trim()
			if val ~= "" then
				table.insert(strings,vals[i])
			end	
		end
		table.sort(strings)
		for i=1,#strings do
			Me.listbox1:Add(strings[i])
			if table.getkey(selection,strings[i]) then
				Me.listbox1:SetItemChecked(Me.listbox1.ItemsCount)
			end
		end
	end
	if Me.listbox1.ItemsCount > 0 then
		Me.listbox1.SelectedIndex = 1
	end	
end

Функция используя значение параметра field формирует и исполняет строчный запрос на отбор записей базы, содержащих уникальные значения в указанном поле. Таким образом мы получаем некое подобие индекса по данному полю: набор записей, в которых присутствуют все возможные значения данного поля, в т.ч. пустые, по одной записи для каждого значения.

Далее перебирая записи мы извлекаем из каждой значение нужного поля, сортируем полученный массив по алфавиту и выводим в список listbox1. У значений, которые присутствуют в массиве selection, устанавливается отметка.

Подготовим также обработчик события CheckStateChanged списка. Будем выводить в надпись labInfo сведения о количестве отмеченных в списке значений.

function listbox1_CheckStateChanged( event )
	Me.labInfo.Text = "Выбрано: "..Me.listbox1.CheckedCount
end

Передаем параметры от главной формы в окно

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

В главной форме объявим таблицу args с полями field, title и selection, в которые перед вызовом диалогового окна будем записывать идентификатор нужного поля, заголовок для окна и массив значений списка, которые следует отметить. Последний элемент будет задействован, если пользователь откроет окно, т.е. выполнит выбор значений поля, несколько раз. В этом случае в массив selection будут записаны значения, выбранные в прошлый раз.

Например, для поля txtEducation вызов диалогового окна будет выглядеть так:

args = {field = "ОБ4", title="Направления подготовки",selection = education}
GetBank():OpenForm("словарик",0,Me,args)

Здесь мы используем для заполнения поля selection массив education, в котором будем хранить выбранные пользователем значения списка в течение всего сеанса работы с формой поиска. Этот массив следует объявить в начале модуля формы.

Аналогичным образом будет выглядеть вызов окна для поля txtCareer:

args = {field = "ТД3", title="Опыт работы",selection = career}
GetBank():OpenForm("словарик",0,Me,args)

Массив career также нужно объявить в начале модуля формы.

В самом диалоговом окне переданные аргументы доступны нам через глобальную переменную Arg. в обработчике события Load формы выполним анализ этих аргументов и используем их для заполнения списка:

function Форма_Load ( form, event )
	
	if Arg and type(Arg.field) == "string" and Arg.field:match("%u%u%d+") then
		if type(Arg.selection) ~= "table" then
			Arg.selection = {}
		end
		fill_list(Arg.field, Arg.selection)
	end
	
	if Arg and Arg.title then
		Me.Text = Arg.title
	end
	
	Me.ApplyControl = Me.btnOk
	Me.CancelControl = Me.btnCancel
end

Здесь мы также назначили кнопки btnOk и btnCancel элементами, которые активируются при нажатии клавиш Enter и Esc.

Возвращаем результаты из окна в главную форму

Кога пользователь нажимает в диалоговом окне кнопки Ok или Отмена мы должны закрыть окно и сообщить главной форме о сделанном выборе. Для этого мы снова используем глобальную переменную Arg окна.

Поскольку таблицы в Lua передаются по ссылке, то локальная переменная args главной формы и глобальная переменная Arg окна ссылаются на один и тот же объект в памяти. А значит, изменения, которые мы внесем в Arg в модуле окна, будут видны в главной форме через args.

Итак, создадим обработчики события Click для кнопок btnOk и btnCancel.

function btnOk_Click( control, event )
	if Arg then
		Arg.ModalResult = 1
		Arg.selection = Me.listbox1.CheckedItems
	end
	Me:CloseForm()
end

function btnCancel_Click( control, event )
	if Arg then
		Arg.ModalResult = 0
	end
	Me:CloseForm()
end

В первом из них мы создаем в таблице Arg новое поле с именем ModalResult, записываем в него единицу и записываем в поле selection таблицы Arg массив отмеченных пользователем значений списка listbox1, во втором - записываем в поле ModalResult ноль.

В главной форме обработаем полученные результаты: если пользователь нажал Ok (поле ModalResult таблицы аргументов содержит единицу), сохраним отмеченные им значения в массиве education (для поля txtEducation) или career (для поля txtCareer) и выведем в соответствующее поле формы список отмеченных значений.

if args.ModalResult == 1 then
	education = args.selection
	Me.txtEducation.Text = table.concat(education,"; ")
end

Приведем полностью листинг обработчиков нажатия кнопок выбора из списков:

function btnEducation_Click( control, event )
	args = {field = "ОБ4", title="Направления подготовки",selection = education}
	GetBank():OpenForm(2,0,Me,args)
	if args.ModalResult == 1 then
		education = args.selection
		Me.txtEducation.Text = table.concat(education,"; ")
	end
end

function btnCareer_Click( control, event )
	args = {field = "ТД3", title="Опыт работы",selection = creer}
	GetBank():OpenForm(2,0,Me,args)
	if args.ModalResult == 1 then
		career = args.selection
		Me.txtCareer.Text = table.concat(career,"; ")
	end
end

Обрабатываем закрытие окна

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

function Форма_UnloadForm( form, mode, event )
	if mode == Form.UECloseScript then
		return true 
	end

	if not Arg or type(Arg.selection) ~= "table" then
		return true 
	end
	
	if #Arg.selection ~= Me.listbox1.CheckedCount or SelectionChanged() then
		local answ = MsgBox("Сохранить изменения?",BtnYesNoCancel+IconQuestion)
		if answ == IdCancel then
			return false
		end	
		if answ == IdYes then
			Arg.ModalResult = 1
			Arg.selection = Me.listbox1.CheckedItems
		elseif answ == IdNo then
			Arg.ModalResult = 0
		end
	end	
	return true
end

Если форма закрывается путем вызова метода CloseForm в скрипте (т.е. была нажата кнопка Ok или Cancel) или окно было отрыто без аргументов (т.е. не вызвано из главной формы, а открыто как самостоятельное), данный обработчик не выполняет никакой работы. В противном случае обработчик сравнивает массив изначально отмеченных элементов, переданных главной формой в Arg.selection, с массивом отмеченных элементов списка listbox1. Если они отличаются, предлагает сохранить изменения.

Функция SelectionChanged вызывается лишь в том случае, если размеры массивов совпадают и возвращает true если различаются их элементы.

function SelectionChanged()
	local items = Me.listbox1.CheckedItems
	local n = Me.listbox1.CheckedCount
	for i=1,n do
		if not table.getkey(Arg.selection,items[i]) then
			return true
		end
	end
	return false
end

Включаем выбор пользователя в запрос

Добавим в функцию MakeRequest код, включающий в строчный запрос условия на образование и опыт работы.

--	образование
	if #education > 0 then
		local tmp = {}
		table.foreach(education, function(k,item) tmp[k] = "4 РВ "..item:quote([["]]) end)
		conditions.edu = "ОБ02 "..table.concat(tmp, " ИЛИ ")
		table.insert(conditions,"201 ОБ02")
	end

Здесь мы создаем вспомогательный массив tmp, в который копируем все элементы из education, заключая каждый из них кавычки и дописывая к нему слева номер поля “Направление (специальность)” базы “Образование” и вид сравнения “Равно”. Иными словами формируем набор условий для записей этой базы. Далее объединяя условия связкой ИЛИ, формируем и сохраняем в поле edu таблицы conditions критерий отбора для связанных записей базы ОБ, а в условия отбора для корневой базы “Лицо” добавляем элемент “201 ОБ02” (лицо связано по ссылке № 201 с записями базы “Образование”).

Таблица conditions принимает примерно такой вид:

conditions = {
	[1] = "9 МР 31.12.ГГГГ",
	[2] = "9 БР 00.00.ГГГГ",
	[3] = 201 ОБ02,
	["edu"] = [[ОБ02 4 РВ "налоги" ИЛИ 4 РВ "экономика"]]
}

Аналогичные действий выполняем для массива career:

--	опыт работы	
	if #career > 0 then
		local tmp = {}
		table.foreach(career, function(k,item) tmp[k] = "3 РВ "..item:quote([["]]) end)
		conditions.career = "ТД03 "..table.concat(tmp, " ИЛИ ")
		table.insert(conditions,"90 ТД03")
	end

И включаем сформированные критерии отбора для связанных баз в запись запроса:

if conditions.edu then
	req = req.." "..conditions.edu
end
if conditions.career then
	req = req.." "..conditions.career
end

Полный текст функции MakeRequest на данный момент:

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 #education > 0 then
		local tmp = {}
		table.foreach(education, function(k,item) tmp[k] = "4 РВ "..item:quote([["]]) end)
		conditions.edu = "ОБ02 "..table.concat(tmp, " ИЛИ ")
		table.insert(conditions,"201 ОБ02")
	end
	
--	опыт работы	
	if #career > 0 then
		local tmp = {}
		table.foreach(career, function(k,item) tmp[k] = "3 РВ "..item:quote([["]]) end)
		conditions.career = "ТД03 "..table.concat(tmp, " ИЛИ ")
		table.insert(conditions,"90 ТД03")
	end

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

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

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