User Rating: 4 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Inactive
 

Доброго времени суток.

 

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

Вот список того, что тут можно будет почерпнуть:

  1. Отображение табличной части документа в виде дерева и обратное преобразование в табличную часть.
  2. Работа с условным оформлением и его программное использование.
  3. Динамическое изменение состава реквизитов формы
  4. Удобный интерфейс по редактированию дерева

 

  

А теперь вот исходные данные решаемой задачи:

Речь пойдет о сметах на строительство чего-либо. Есть справочник видов работ (далее просто работ) и материалов. Есть норма расхода материала на единицу работы. Нужно разработать документ, в котором пользователь мог бы задавать для каждой работы список материалов в количестве, необходимом для выполнения определенного объема этой работы на каждом этаже здания (собственно иногда это и не этаж вовсе, а помещение, секция,... да что угодна на что можно разбить строящийся объект).

Т.е. у нас вырисовывается следующая структура табличной части документа:

работа - ссылка на справочник работ

материал - ссылка на справочник материалов

этаж - пока не понятно что это такое

объем работ - число 15.3

количество материалов  - число 15.3

Вроде бы просто, но на практике мы получаем много повторяющейся информации. Например у нас две работы, в каждой 10 материалов, и 9 этажей. Это будет 180 строк в документе, в которых колонка "работа" везде почти одинаковая, каждый материал повторяется по 9 раз.

Ниже на рисунке два представления одной и той же информации. 1 - Линейно, 2 - в виде дерева с динамическими колонками количества. Для наглядности разные данные выделил цветами: красный - работа, синий - материал, этаж - сиреневый, зеленый - объем работы, орнажевый - к.расхода, черный - количество материалов.

Предстваление информации в табличном виде и в виде иерархии

Второй вариант явно экономит место на экране. В первом у нас 150 ячеек с информацией, во втором - 52. Причем, чем больше этажей, материалов в работе, тем экономия сильнее.

Вот второй вариант мы с Вами в этом уроке и реализуем.

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

Структура метаданных документа в 1С Конфигураторе

Имеем:

  • Справочник "Работы"
  • Справочник "Материалы" (или номенклатура - не важно)
  • Документ "..." (можете назвать его "Смета" или еще как)
  • Регистр сведений, периодический, в котором измерения: работа, материал; ресурс: к.расхода (можете для него сбацать регистратор, можете оставить непосредственное редактирование)

Первое, это надо определиться со способом преобразования табличной части в дерево. Первый (самый простой) - это запросом сделать итоги по полю работа. Минус тут в том, что мы не можем в таб.часть добавить две одинаковых работы с разным составом материалов, т.к. работа будет ключевым полем. Работа у нас объединится в один узел дерева, а материалы в нем сложатся. Второй способ - добавить ключевое поле, которое будет определять принадлежность строк к одному узлу.

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

 

Тут у нас не хватает третьей аналитики - этаж. В нашем случае это будет целое положительно число. Для удобства, добавим в шапку поле "Количество этажей". Это поле нам сильно упростит жизнь. Случай, когда в таб.части есть этаж №9, а максимум этажей - 8, считаем не допустимым, эти строки будут пропавшими безвести.

Следующим шагом избавимся от поля "ОбъемРабот" и оставим только количество. В строках, где НомерМатериалаВРаботе = 0 будем считать количество - объемом работ.

Со структурой - все, идем в форму. Создадим форму по умолчанию, в которой разместим все, что есть.

Первое - номер и дату сделаем в одну строку, это действие надо делать на автомате всегда, это уже как лесенка в коде.

Далее рисуем реквизит формы "ДеревоРабот" с типом значения "Дерево значений". Добавим в него колонки: Номер, РаботаМатериал, кРасхода, ЭтоРабота. Среди элементов создадим две страницы: служебная, туда отправим реальную таб.часть, и дерево, туда отправим дерево. У дерева отобразим все колонки, кроме флажка "ЭтоРабота".

Теперь лезем в код. Нам понадобится процедура, которая рисует колонки с количеством по количеству этажей. Назовем её "ОбновитьСоставКолонок()". В ней мы будем динамически создавать/удалять реквизиты формы и элементы формы. Эта процедура будет вызываться при создании формы и при изменении количества этажей. Т.е. чать колонок в момент вызова процедуры у нас уже могут быть и нам надо их оставить, чтобы не потерять данные.

Вот её синтаксис:

&НаСервере
Процедура ОбновитьСоставКолонок()
//1. Правим ревизит формы, добавляем в дерево колонки
дерево = РеквизитФормыВЗначение("ДеревоРабот");
мКУдалению = Новый Массив;
МаксНомерКолонки = 0;
Для каждого Колонка Из Дерево.Колонки Цикл
Если Лев(Колонка.Имя, 3) = "Кол" тогда
НомерКоличества = число(сред(Колонка.Имя,4));
Если НомерКоличества>Объект.КоличествоЭтажей Тогда
мКУдалению.Добавить(Колонка);
КонецЕсли;
Если НомерКоличества>МаксНомерКолонки Тогда
МаксНомерКолонки = НомерКоличества;
КонецЕсли;
КонецЕсли;
КонецЦикла;
//2.
РеквизитыКУдалению = Новый Массив;
Для Каждого элементМКУдалению Из мКУдалению Цикл
РеквизитыКУдалению.Добавить("ДеревоРабот." + элементМКУдалению.Имя);
//элемент формы надо грохнуть раньше, чем реквизит
Элементы.Удалить(Элементы["ДеревоРабот" + элементМКУдалению.Имя])
КонецЦикла;
//3.
РеквизитыКДобавлению = Новый Массив;
Для ж=1 По объект.КоличествоЭтажей - МаксНомерКолонки Цикл
НовыйНомер = МаксНомерКолонки + ж;
НовыйРеквизитФормы = Новый РеквизитФормы("Кол"+НовыйНомер, Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15,3)), "ДеревоРабот", "к "+НовыйНомер);
РеквизитыКДобавлению.Добавить(НовыйРеквизитФормы);
КонецЦикла;
//4.
ИзменитьРеквизиты(РеквизитыКДобавлению,РеквизитыКУдалению);
//5. Рисуем новые элементы формы, добавлять их надо после создания ревизитов
Для ж=1 По объект.КоличествоЭтажей - МаксНомерКолонки Цикл
НовыйНомер = МаксНомерКолонки + ж;
НоваяКолонка = Элементы.Добавить("ДеревоРаботКол"+НовыйНомер, Тип("ПолеФормы"), Элементы.ДеревоРабот);
НоваяКолонка.Вид = ВидПоляФормы.ПолеВвода;
НоваяКолонка.ПутьКДанным = "ДеревоРабот.Кол"+НовыйНомер;
НоваяКолонка.Ширина = 7;
НоваяКолонка.УстановитьДействие("ПриИзменении", "КоличествоПриИзменении");
НоваяКолонка.Заголовок = "к " + ж;
КонецЦикла;
КонецПроцедуры

Прокомментируем этапы её работы:

1. Перебираем все колонки дерева, чтобы узнать сколько у нас уже есть. Тут мы по имени колонки определяем её номер и, если он больше нужного количества, добавляем в список к удалению. Так же находим максимальный номер существующей колонки. (это мы работаем с реквизитами формы!!!)

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

3. Создаем список колонок, которые надо создать.

4. Вызываем встроенную процедуру ИзменитьРеквизиты, после этого у нас уже нет лишних колонок и появились недостающие.

5. Далее мы создаем новые элементы формы, связанные с реквизитами формы.

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

-нельзя удалять реквизиты, созданные не программно

-нельзя удалять реквизиты, используемые в элементах формы

Далее вызовем эту процедуру в ПриСозданииНаСервере и КоличествоЭтажейПриИзменении.

Сохранившись и запустив предприятие, мы увидим, что при открытии существующего документа у нас сразу рисуется нужное количество колонок, при изменении кол-ва этажей все сразу динамически перерисовывается.

Теперь напишем процедуру преобразования таблицы в дерево. Для теста заполним в документе табличную часть как на рисунке:

Для этого мы и оставили реальную таб.часть на форме, чтобы можно было отлаживаться и проверять результат.

&НаСервере
Процедура ТабЧастьВДерево()
Дерево = РеквизитФормыВЗначение("ДеревоРабот");
Дерево.Строки.Очистить();
строкаРаботы = "";
НомерРаботы = "";
НомерМатериала = "";
Объект.ТабличнаяЧасть1.Сортировать("НомерРаботы,НомерМатериалаВРаботе");
Для Каждого Выб Из Объект.ТабличнаяЧасть1 Цикл
//Контроль количества этажей
Этаж = Выб.Этаж;
Если Этаж > Объект.КоличествоЭтажей Тогда
Продолжить;
КонецЕсли;
Если Этаж = 0 Тогда
Продолжить;
КонецЕсли;
Если НомерРаботы<>Выб.НомерРаботы Тогда
строкаРаботы = Дерево.Строки.Добавить();
строкаРаботы.Номер = Выб.НомерРаботы;
строкаРаботы.РаботаМатериал = Выб.Работа;
строкаРаботы.ЭтоРабота = Истина;
//началась следующая работа, отсчет материалов начнем с начала
НомерМатериала = "";
НомерРаботы = Выб.НомерРаботы;
КонецЕсли;
Если Выб.НомерМатериалаВРаботе = 0 тогда//это строка работы
строкаРаботы["Кол"+Этаж] = Выб.Количество;
Иначе//это строка с материалом
Если НомерМатериала<>Выб.НомерМатериалаВРаботе тогда
строкаМатериала = строкаРаботы.Строки.Добавить();
строкаМатериала.Номер = Выб.НомерМатериалаВРаботе;
строкаМатериала.РаботаМатериал = Выб.Материал;
строкаМатериала.КРасхода = Выб.КРасхода;
строкаМатериала.ЭтоРабота = Ложь;
НомерМатериала=Выб.НомерМатериалаВРаботе;
КонецЕсли;
строкаМатериала["Кол"+Этаж] = Выб.Количество;
КонецЕсли;
КонецЦикла;
ЗначениеВРеквизитФормы(Дерево,"ДеревоРабот");
КонецПроцедуры

 

Комментировать тут особо нечего. С помощью полей "номерРаботы" и "НомерМатериалаВРаботе" определяю, что перед нами, строка с работой или материалом, соответственно добавляем строку в корень или в работу. Если сменился только этаж, то берем только количество. Лишние этажи обрубаются, пропущенные этажи - не испортят структуру дерева. 

Вызываем эту процедуру при создании на сервере после "ОбновитьСоставКолонок()".

Теперь можно её протестировать запустив предприятие и открыв созданный ранее документ.

Теперь нам надо перед записью документа сделать наоборот, дерево сохранить в табличную часть. Пишем процедуру:

&НаКлиенте
Процедура ПоместитьДеревоВТабЧасть()
 Объект.Материалы.Очистить();
 Для Каждого стрРаботы Из ДеревоРабот.ПолучитьЭлементы() Цикл
  Для ж=1 По Объект.КоличествоЭтажей Цикл
   стр = Объект.Материалы.Добавить();
   стр.НомерРаботы = стрРаботы.Номер;
   стр.Работа = стрРаботы.РаботаМатериал;
   стр.Этаж = ж;
   стр.Количество = стрРаботы["Кол"+ж];
  КонецЦикла;
  Для Каждого стрМатериала Из стрРаботы.ПолучитьЭлементы() Цикл
   Для ж=1 По Объект.КоличествоЭтажей Цикл
    стр = Объект.Материалы.Добавить();
    стр.НомерРаботы = стрРаботы.Номер;
    стр.НомерМатериалаВРаботе = стрМатериала.Номер;
    стр.Работа = стрРаботы.РаботаМатериал;
    стр.Материал = стрМатериала.РаботаМатериал;
    стр.КРасхода = стрМатериала.КРасхода;
    стр.Этаж = ж;
    стр.Количество = стрМатериала["Кол"+ж];
   КонецЦикла;
  КонецЦикла;
 КонецЦикла;
КонецПроцедуры

Комментировать тут тоже нечего. Добавим её вызов процедуру ПередЗаписью.

Следующим шагом нам надо будет накрутить сервис для удобного редактирования дерева. По умолчанию он никуда не годится. Тут мы все разобьём на отдельные фишки:

фишка 1

Нам надо обсчитывать номера строк самостоятельно, после каждого изменения дерева. Иначе дерево не сохраниться корректно. Так же эту колонку сделаем только на просмотр, чтобы шаловливые ручки не правили его. Добавим обработчик события при изменении у дерева:

&НаКлиенте
Процедура ПеренумероватьСтрокиНаКлиенте()
коллекцияРабот = ДеревоРабот.ПолучитьЭлементы();
жР = 1;
Для Каждого стрРаботы Из коллекцияРабот Цикл
стрРаботы.Номер = жР;
коллецияМатериалов = стрРаботы.ПолучитьЭлементы();
жМ = 1;
Для Каждого стрМатериалов Из коллецияМатериалов Цикл
стрМатериалов.Номер = жМ;
жМ = жМ + 1;
КонецЦикла;
жР=жР+1;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ДеревоРаботПриИзменении(Элемент)
ПеренумероватьСтрокиНаКлиенте();
Модифицированность = Истина;
КонецПроцедуры

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

фишка 2

Коэффициент расхода имеет смысл только в строках с материалом. Давайте закроем это поле для строк с работами. Воспользуемся условным оформлением. Оно находится в свойствах формы:

Добавляем строчку. В оформлении ставим галку и "только просмотр" и выставляем его в истину. В условии добавляем отбор на поле "ЭтоРабота" равное истине. В качестве поля выберем колонку коэффициента расхода.

После этого сохранитесь и проверьте. Собственно ради условного оформления мы и добавляли поле "ЭтоРабота" и заполняли его.

Фишка 3

Теперь по поводу количества. Его есть смысл править только у работы. У материала оно должно обсчитываться автоматом как объем работ умноженный на к.расхода. тут отличие от фишки 2 в том, что колонок с количеством у нас нет. Поэтому добавляем условное оформление не выбрав поля.

Теперь лезем в процедуру рисующую колонки "ОбновитьСоставКолонок" и добавляем туда в конец кусочек:

ПоляУсловногоОформления = ЭтаФорма.УсловноеОформление.Элементы[1].Поля.Элементы;
ПоляУсловногоОформления.Очистить();
Для ж=1 По объект.КоличествоЭтажей Цикл
поле = ПоляУсловногоОформления.Добавить();
поле.Поле = Новый ПолеКомпоновкиДанных("ДеревоРаботКол" + ж);
КонецЦикла;

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

Фишка 4

Теперь добавим обсчет таблицы. Для этого мы уже ранее написали вызов обработчика при создании колонок:

НоваяКолонка.УстановитьДействие("ПриИзменении", "КоличествоПриИзменении");

Осталось только его описать в модуле формы. И добавим обработчик при изменении кРасхода:

&НаКлиенте
Процедура КоличествоПриИзменении(Элемент)
текСтрока = ДеревоРабот.НайтиПоИдентификатору(Элементы.ДеревоРабот.ТекущаяСтрока);
Если текСтрока = Неопределено Тогда Возврат; КонецЕсли;
номерКолонки = Число(СтрЗаменить(Элемент.Имя,"ДеревоРаботКол",""));
Если текСтрока.ЭтоРабота Тогда
//это работа, требуется обсчитать материалы
СтрокиМатериалов = текСтрока.ПолучитьЭлементы();
Для Каждого стрМатериала Из СтрокиМатериалов Цикл
стрМатериала["Кол"+номерКолонки] = стрМатериала.кРасхода * текСтрока["Кол"+номерКолонки];
КонецЦикла;
КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ДеревоРаботКоэффициентРасходаПриИзменении(Элемент)
текСтрока = ДеревоРабот.НайтиПоИдентификатору(Элементы.ДеревоРабот.ТекущаяСтрока);
Если текСтрока = Неопределено Тогда Возврат; КонецЕсли;
Если Не текСтрока.ЭтоРабота Тогда
//это работа, требуется обсчитать материалы
СтрокаРаботы = текСтрока.ПолучитьРодителя();
Если СтрокаРаботы = Неопределено Тогда Возврат; КонецЕсли;
Для ж=1 По Объект.КоличествоЭтажей Цикл
текСтрока["Кол"+ж] = текСтрока.КРасхода * СтрокаРаботы["Кол"+ж];
КонецЦикла;
КонецЕсли;
КонецПроцедуры 

Фишка 5

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

Что мы можем сделать:

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

2. можно просто добавить пустую строку кодом куда нужно и активизировать поле ввода

3. можно оставить стандартное добавление, но в обработчике при добавлении проверить, а не в третий ли мы уровень лезем и Отказ = истина, если что не так

Собственно делать мы будем комбинацию всех возможностей. У нас будет 5 кнопок:

1. Стандратная добавить, которую мы переобзовем в "Добавить материал". т.к. больше не для чего она не сгодится.

2. Наша кнопка "Добавить работу", которая добавляет в конец в корень ноаую строку и позиционирует на поле ввода работы

 

//~~ 26.01.2017 ПоместитьДеревоВТабЧасть. Изменил с серверной на клиентскую. Действительно это важно, и плохо, что я сразу так не сделал. Есть такое важное правило в управляемых формах: если это можно сделать на клиенте - делай это на клиенте. Минимум серверных вызовов. И не важно, что код на клиенте не такой понятный или его сложней писать. В моем случае работа с "ДанныеФормыДерево" не столь очевидна, как с "деревом значений", к которому я привык со времен обычных форм.

//~~ 26.01.2017 Добавил два DT файла: 1. заготовка, с которой начинается статья 2. результат окончания этой статьи по фишку №4 включительно

 

 

 

 

Авторизуйтесь пожалуйста

Comments   

0 # Сергей 2017-01-19 08:30
Приветствую, спасибо за обучение )
И вопросик - конфигурацию не дописал ещё? :-)
0 # Антон Филоненко 2017-01-19 17:40
Здравствуйте.
К сожалению нет, вторую часть публикации еще не подготовил. Думаю скоро появится, осталось оформить.
+1 # Антон Филоненко 2017-01-30 13:33
Продолжение написано
http://prosto1s.ru/index.php/67-urok-po-razrabotke-upravlyaemykh-form-na-primere-sozdaniya-smety-chast-2