Форма поиска банка Primer. Часть 4. Диалоговое окно с деревом
Это очередная из серии статей, описывающих процесс разработки управляющей формы, предназначенной для отбора кандидатов на работу в демонстрационном банке кадрового агентства Primer.
Предыдущие статьи серии:
В прошлой части мы создали диалоговое окно для выбора из списков ограничений на направление образования кандидата и имеющийся у него опыт работы на конкретных должностях.
Сегодня добавим еще одно окно, предназначенное для выбора ограничений на место проживания кандидата. Задавать ограничение будем в виде списка населенных пунктов (с учетом страны, региона), указанных в записях базы “Адрес”.
В отличие от ситуации, описанной в предыдущей статье, для формирования перечня возможных значений мы будем использовать не одну характеристику базы, а две, одна из которых словарная. При этом нам придется хранить как понятие соответствующего термина словаря - для отображения на форме, так и его код - для использования в запросе.
Создаем диалоговое окно
Создаем управляющую форму следующего вида:
Единственное отличие от формы из предыдущей статьи - вместо элемента “Список” (ListBox) мы используем дерево (TreeControl).
Свойства формы установим такими же, как и для окна списков:
- Тип границы - “Окно диалога”;
- Модальное - Да;
- Всплывающее - Да;
- Кнопки размера - Отсутствуют.
Обмен данными с главной формой
Мы по прежнему используем для обмена данными между окном и главной формой таблицу аргументов Arg, но исключим из нее поля field и title, а в поле selection вместо обычного массива строк будем записывать хеш-таблицу, ключами в которой являются словарные коды стран, а их значениями - массивы названий городов с дополнительным полем Name, в котором хранится название страны - раскодированное понятие словаря:
Пример заполнения таблицы:
selection = {
["801"] = {Name = "Россия", "Москва", "Серпухов", ...},
["286"] = {Name = "Италия"},
}
Будем считать, что если в каком-либо элементе таблицы записано только название страны, а список городов отсутствует, то ограничение наложено только на страну - любой из ее населенных пунктов соответствует запросу.
Заполняем дерево значениями
Начинаем описывать функцию, заполняющую дерево всеми присутствующими в банке названиями стран и городов:
function fill_tree(selection)
Включим в дереве отображение отметок и режим автоматической отметки родительских узлов дерева в зависимости от состояния флажков дочерних узлов:
Me.tree1.ShowCheckBoxes = true
Me.tree1.SmartCheck = true
Отберем из базы “Адрес” записи, содержащие уникальные пары “Страна-город”:
local rs = GetBank():StringRequest("ОТ АД01 1 УЗ 3")
if not rs or rs.Count == 0 then
return
end
Сформируем вспомогательную структуру tree_items
, аналогичную Arg.selections
, но содержащую все наименования городов из банка. Одновременно будем формировать массив index
, содержащий пары {код,понятие}, который в дальнейшем потребуется для того, чтобы вывести названия стран в определенном порядке - по алфавиту:
local tree_items = {}
local index = {}
for rec in rs.Records do
local code = rec:GetValue(1,1,false)
local area = sl(3,code)
if not tree_items[code] then
tree_items[code] = {["Name"] = area}
table.insert(index,{code,area})
end
local val = rec:GetValue(3)
if val ~= "" then
table.insert(tree_items[code],val)
end
end
table.sort(index, function(a,b) return a[2] < b[2] end)
Теперь переберем все элементы tree_items
в порядке, определяемом массивом index
, и создадим в дереве соответствующие узлы. Названия стран будем помещать в дерево корневыми узлами, а названия входящих в них городов - дочерними. Для корневых узлов в свойство ItemData
будем записывать соответствующий код словаря. Дочерние узлы, содержащие названия городов, которые содержатся в selection
, будем отмечать, а их идентификаторы записывать в массив pre_checked
. В дальнейшем этот массив будет использоваться для выявления сделанных изменений в выборе пользователя:
for k = 1,#index do
code, cities = index[k][1], tree_items[index[k][1]]
local node = Me.tree1:InsertItem(cities.Name)
node.ItemData = code
for i=1,#cities do
table.sort(cities)
local sub_node = Me.tree1:InsertItem(cities[i], node)
if selection[code] and (#(selection[code]) == 0 or
table.getkey(selection[code],cities[i])) then
sub_node.Checked = true
table.insert(pre_checked,sub_node.Id)
end
end
end
И в завершение выделим и развернем первый узел в дереве:
if #Me.tree1.RootItems > 0 then
Me.tree1.RootItems[1].Selected = true
Me.tree1.RootItems[1]:Expand()
end
Функцию будем вызывать из обработчика события Load формы:
function Форма_Load( form, event )
if not Arg then
Arg = {}
end
if type(Arg.selection) ~= "table" then
Arg.selection = {}
end
fill_tree(Arg.selection or {})
Me.ApplyControl = Me.btnOk
Me.CancelControl = Me.btnCancel
end
Фиксируем выбор пользователя
Когда пользователь нажимает кнопку Ok нам следует вновь сформировать структуру Arg.selection
, включив в нее отмеченные узлы дерева:
function SaveSelections()
Arg.selection = {}
local codes = Me.tree1.RootItems
for i=1,#codes do
local node = codes[i]
if node.Checked then
Arg.selection[node.ItemData] = {["Name"] = node.Text}
else
local cities = node.Children
for j=1,#cities do
if cities[j].Checked then
if not Arg.selection[node.ItemData] then
Arg.selection[node.ItemData] = {["Name"] = node.Text}
end
table.insert(Arg.selection[node.ItemData],cities[j].Text)
end
end
end
end
end
В основном цикле (for i=1,#codes
) do мы перебираем корневые узлы дерева и, если узел отмечен - а это означает, что выбраны все относящиеся к данной стране города, - включаем в Arg.selection
соответствующий элемент с единственным полем Name
. Если же свойство Checked
корневого узла содержит false
, т.е. отмечены лишь некоторые дочерние по отношению к нему узлы или не отмечен ни один, то перебирая дочерние узлы мы ищем среди них отмеченные и, обнаружив такой, создаем в Arg.selection
элемент по коду страны (значение свойства ItemData
родительского элемента) и добавляем название города в массив значений этого элемента.
Обработчик события Click
кнопки Ok теперь выглядит так:
function btnOk_Click( control, event )
if Arg then
Arg.ModalResult = 1
SaveSelections()
end
Me:CloseForm()
end
Проверяем наличие изменений
при попытке закрыть окно кнопкой в заголовке формы нам следует сопоставить текущий набор отмеченных узлов дерева с данными, которые были переданы из главной формы (они сохранены в массиве pre_checked
) и если они отличаются, предложить сохранить изменения.
Функция сопоставления приведена ниже:
function SelectionChanged()
local t = Me.tree1:GetAllItems()
for i=1,#t do
local item = t[i].Text
if t[i].ParentItem then
if t[i].Checked and not table.getkey(pre_checked,t[i].Id) or
not t[i].Checked and table.getkey(pre_checked,t[i].Id) then
return true
end
end
end
return false
end
Данные различаются, если хотя бы один из отмеченных дочерних узлов отсутствует в pre_checked
или, наоборот, хотя бы один не отмеченный там содержится.
Вызываем диалоговое окно из главной формы и получаем результат
Обработчик нажатия кнопки выбора для места проживания (btnAddress
) опишем аналогично обработчикам кнопок btnEducation
и btnCareer
из предыдущей статьи.
function btnAddress_Click( control, event )
args = {selection = address}
GetBank():OpenForm("Города",0,Me,args)
if args.ModalResult == 1 then
address = args.selection
local tmpAreas = {}
for area, cities in pairs(address) do
if #cities > 0 then
table.insert(tmpAreas,cities.Name..": "..table.concat(cities,","))
else
table.insert(tmpAreas,cities.Name)
end
end
Me.txtAddress.Text = table.concat(tmpAreas,"; ")
end
end
Полученный от диалогового окна перечень выбранных городов сохраняется в массиве address
(как и массивы education
и career
, он должен быть объявлен в начале модуля главной формы). Далее на его основе формируется текстовая строка для отображения на форме:
Включаем адреса в запрос
Теперь добавим в функцию MakeRequest код, включающий в строчный запрос критерий отбора для адреса места жительства кандидата:
-- место проживания
if table.count(address) > 0 then
local tmpAreas = {}
for area,cities in pairs(address) do
local s = "1 РВ "..area
if #cities > 0 then
local tmp = {}
table.foreach(cities, function(k,item) tmp[k] = "3 РВ "..item:quote([["]]) end)
s = s.." И "..table.concat(tmp," ИЛИ ")
end
table.insert(tmpAreas,"("..s..")")
end
conditions.address = "АД04 "..table.concat(tmpAreas, " ИЛИ ")
table.insert(conditions,"80 АД04")
end
Перебирая элементы таблицы address
мы постепенно формируем условия для характеристик “Страна” и “Город”, а затем объединяем их связкой ИЛИ. Сформированный критерий выглядит так:
АД04 (1 РВ код1) ИЛИ (1 РВ код2 И 3 РВ "Город1" ИЛИ 3 РВ "Город2" ...) ИЛИ ...
Он записывается в поле address
таблицы conditions
, а к условиям отбора корневой базы добавляется “80 АД04” (лицо связано ссылкой № 80 с записями базы “Адрес”).
На этом этапе функция 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
-- стаж
AgeValidation({Control = Me.txtExperience})
if Me.txtExperience.Text ~= "" then
conditions.experience = tonumber(Me.txtExperience.Text)
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
-- место проживания
if table.count(address) > 0 then
local tmpAreas = {}
for area,cities in pairs(address) do
local s = "1 РВ "..area
if #cities > 0 then
local tmp = {}
table.foreach(cities, function(k,item) tmp[k] = "3 РВ "..item:quote([["]]) end)
s = s.." И "..table.concat(tmp," ИЛИ ")
end
table.insert(tmpAreas,"("..s..")")
end
conditions.address = "АД04 "..table.concat(tmpAreas, " ИЛИ ")
table.insert(conditions,"80 АД04")
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
if conditions.address then
req = req.." "..conditions.address
end
end
return req
end
Дополнительные материалы
- Форма Города и текст ее модуля;
- Текст модуля формы поиска на данный момент
Страница создана на основе шаблона flexible-jekyll