Таблица формы 1С — элемент управляемой формы, который выводит коллекцию: табличную часть объекта, реквизит типа ТаблицаЗначений или ДеревоЗначений, динамический список. С ней работает почти любая доработка формы: пересчёт суммы в строке документа, проверка строки, поиск позиции, обработка отмеченных строк, заполнение из запроса. Ниже — как элемент связан с данными, как в платформе 8.3 добавлять, искать и сортировать строки, какие у таблицы есть события и где чаще всего ошибаются.
На форме всегда две сущности. Реквизит хранит данные, элемент «Таблица» их показывает. Связывает их свойство элемента ПутьКДанным: для табличной части документа это Объект.Товары, для реквизита обработки — просто Товары. Код работает с данными (Объект.Товары), а к элементу (Элементы.Товары) обращается за тем, что видит пользователь: текущей строкой, выделением, активной колонкой.
Данные таблицы формы хранятся не в прикладных типах, а в типах данных формы. Табличная часть и таблица значений становятся ДанныеФормыКоллекция, дерево — ДанныеФормыДерево. Эти типы есть и на клиенте, и на сервере, поэтому обойти строки или поменять значение в ячейке можно без серверного вызова.
| Источник данных | Тип в форме | Что лежит в ТекущаяСтрока |
|---|---|---|
| Табличная часть объекта | ДанныеФормыКоллекция | идентификатор строки (Число) |
| Реквизит ТаблицаЗначений | ДанныеФормыКоллекция | идентификатор строки (Число) |
| Реквизит ДеревоЗначений | ДанныеФормыДерево | идентификатор строки (Число) |
| Динамический список | ДинамическийСписок | ключ строки, при основной таблице — ссылка |
Динамический список устроен по-другому: строки читаются из базы порциями, добавить в него строку кодом нельзя. О нём есть отдельная статья про динамический список, здесь речь о коллекциях.
Колонку сначала добавляют в данные, потом в элемент. Для табличной части это новый реквизит табличной части в конфигураторе, для обработки — новая колонка реквизита формы на вкладке «Реквизиты». Затем колонку перетаскивают из дерева реквизитов в элемент таблицы: появится поле с именем вида ТоварыСкидка и путём к данным Объект.Товары.Скидка.
С колонками табличной части объекта есть тонкость: колонку, которую не вывели в таблицу и не отметили в дереве реквизитов флажком «Использовать всегда», платформа в данные формы не загружает. Служебную колонку, которую пользователь видеть не должен, отмечают этим флажком или выводят полем с Видимость = Ложь. Создать реквизит и таблицу программно, через ИзменитьРеквизиты и Элементы.Добавить, тоже можно — это разобрано в статье про таблицу значений на форме.
У элемента таблицы три свойства, которые нужны постоянно:
ТекущиеДанные — сама строка под курсором или Неопределено, если курсора нет. Через неё поля и читают, и меняют.ТекущаяСтрока — идентификатор этой строки.ВыделенныеСтроки — массив идентификаторов выделенных строк. Чтобы можно было выделить больше одной, у таблицы ставят РежимВыделения = «Множественный».Идентификатор и индекс — разные вещи. Индекс (Объект.Товары.Индекс(Строка)) — позиция с нуля, он меняется после удаления, вставки и сортировки. Идентификатор (Строка.ПолучитьИдентификатор()) закреплён за строкой, пока она существует. По нему строку находят методом НайтиПоИдентификатору, его же передают на сервер вместо самой строки. После Загрузить строки создаются заново, и старые идентификаторы ни на что не указывают.
Пример: команда делит количество текущей строки пополам и вставляет вторую половину сразу под ней.
&НаКлиенте
Процедура РазделитьСтроку(Команда)
СтрокаТаблицы = Элементы.Товары.ТекущиеДанные;
Если СтрокаТаблицы = Неопределено Или СтрокаТаблицы.Количество < 2 Тогда
Возврат;
КонецЕсли;
// Новая строка сразу под текущей
Индекс = Объект.Товары.Индекс(СтрокаТаблицы);
НоваяСтрока = Объект.Товары.Вставить(Индекс + 1);
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТаблицы, "Номенклатура, Цена");
НоваяСтрока.Количество = Цел(СтрокаТаблицы.Количество / 2);
СтрокаТаблицы.Количество = СтрокаТаблицы.Количество - НоваяСтрока.Количество;
НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;
СтрокаТаблицы.Сумма = СтрокаТаблицы.Количество * СтрокаТаблицы.Цена;
// Курсор ставится идентификатором, а не индексом
Элементы.Товары.ТекущаяСтрока = НоваяСтрока.ПолучитьИдентификатор();
Модифицированность = Истина;
КонецПроцедуры
На клиенте у коллекции работают Добавить, Вставить, Удалить, Очистить, Количество, Индекс, НайтиСтроки и НайтиПоИдентификатору. Выгрузить и Загрузить возвращают и принимают ТаблицаЗначений, а на тонком клиенте такого типа нет, поэтому эти методы вызывают на сервере.
Строку добавляют двумя способами. Объект.Товары.Добавить() создаёт её молча: события таблицы не вызываются, редактирование не начинается. Элементы.Товары.ДобавитьСтроку() повторяет кнопку «Добавить»: срабатывают ПередНачаломДобавления и ПриНачалеРедактирования, курсор уходит в новую строку. Первый вариант подходит для заполнения, второй — когда значение должен сразу ввести пользователь.
Заполнить таблицу формы из запроса проще всего на сервере, одной строкой через Загрузить. Пример для обработки:
// Форма обработки. Реквизит Товары: ТаблицаЗначений,
// колонки Выбран (Булево) и Номенклатура (СправочникСсылка.Номенклатура).
&НаКлиенте
Процедура Заполнить(Команда)
ЗаполнитьНаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 100
| Спр.Ссылка КАК Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Спр
|ГДЕ
| НЕ Спр.ПометкаУдаления
| И НЕ Спр.ЭтоГруппа
|
|УПОРЯДОЧИТЬ ПО
| Спр.Наименование";
// Условие ЭтоГруппа нужно, если справочник с иерархией групп и элементов
// Колонки сопоставляются по именам, Выбран получит Ложь
Товары.Загрузить(Запрос.Выполнить().Выгрузить());
КонецПроцедуры
Псевдонимы полей в запросе должны совпадать с именами колонок реквизита. Колонки Выбран в запросе нет, и она получит значение по умолчанию. Если таблицу нужно обработать средствами ТаблицаЗначений (свернуть, найти дубли), используют пару РеквизитФормыВЗначение("Товары") и ЗначениеВРеквизитФормы(Таблица, "Товары"). Для табличной части объекта то же дают Объект.Товары.Выгрузить() и Загрузить.
Поиск по значениям колонок делает НайтиСтроки: на вход структура «колонка — значение», на выходе массив строк. Метод работает на клиенте. Чтобы показать найденную строку, присвойте её идентификатор свойству ТекущаяСтрока и переведите фокус на таблицу — так устроена команда НайтиСтроку в примере ниже.
Сортировать("Сумма Убыв") принимает строку с колонками и направлениями, как у таблицы значений. Метод доступен и на клиенте, но при таком вызове платформа всё равно обращается к серверу. Если на сервер и так нужно идти, сортируйте в той же серверной процедуре: лишнего вызова не будет. Идентификатор текущей строки запоминают до вызова и возвращают после, тогда курсор остаётся на той же позиции товара.
Обработчики событий таблицы выполняются на клиенте и назначаются в палитре свойств элемента. У колонок свои события, чаще других нужны ПриИзменении и НачалоВыбора.
| Событие | Когда вызывается | Зачем обычно нужно |
|---|---|---|
| ПриАктивизацииСтроки | курсор перешёл на другую строку | обновить данные, связанные со строкой |
| Выбор | двойной щелчок или Enter по строке, когда ячейка не редактируется | открыть документ или элемент из строки |
| ПередНачаломДобавления | перед добавлением или копированием строки | запретить добавление через Отказ |
| ПриНачалеРедактирования | строка перешла в режим ввода, у новой НоваяСтрока = Истина | заполнить значения по умолчанию |
| ПередОкончаниемРедактирования | пользователь завершает ввод строки | проверить строку, Отказ оставляет её в редактировании |
| ПриОкончанииРедактирования | ввод строки завершён или отменён | пересчитать итоги |
| ПередУдалением, ПослеУдаления | до и после удаления строк | запретить удаление, пересчитать итоги |
| ПриИзменении | пользователь изменил данные таблицы | общий пересчёт по таблице |
С ПриАктивизацииСтроки осторожнее: событие приходит на каждое нажатие стрелки. Если обработчик ходит на сервер, прокрутка тормозит. Серверную часть откладывают обработчиком ожидания:
// Реквизит формы Подсказка (Строка) выведен надписью под таблицей.
&НаКлиенте
Процедура ТоварыПриАктивизацииСтроки(Элемент)
// Пока пользователь листает стрелками, сервер не вызываем
ОтключитьОбработчикОжидания("ОбработатьАктивизациюСтроки");
ПодключитьОбработчикОжидания("ОбработатьАктивизациюСтроки", 0.2, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьАктивизациюСтроки()
СтрокаТаблицы = Элементы.Товары.ТекущиеДанные;
Если СтрокаТаблицы = Неопределено Тогда
Подсказка = "";
Возврат;
КонецЕсли;
// На сервер уходит значение из строки, а не сама строка
Подсказка = ТекстПодсказки(СтрокаТаблицы.Номенклатура);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ТекстПодсказки(Номенклатура)
// Здесь обычно запрос остатков или цен по позиции
Если ЗначениеЗаполнено(Номенклатура) И Номенклатура.ПометкаУдаления Тогда
Возврат "Позиция помечена на удаление";
КонецЕсли;
Возврат "";
КонецФункции
Модуль формы документа с табличной частью Товары. Код пересчитывает сумму строки, не даёт закончить ввод строки с нулевым количеством, держит итог в реквизите формы, ищет строку по номенклатуре, округляет цены в выделенных строках, сортирует, удаляет пустые строки и сворачивает дубли. Команды создаются на вкладке «Команды» редактора формы, подробнее — в статье о командах формы.
// Модуль формы документа. Управляемая форма, платформа 8.3.
// Табличная часть документа Товары:
// Номенклатура (СправочникСсылка.Номенклатура), Количество (Число 15,3),
// Цена (Число 15,2), Сумма (Число 15,2).
// Реквизиты формы: ИтогСумма (Число 15,2),
// НоменклатураДляПоиска (СправочникСсылка.Номенклатура).
// Элемент Товары: ПутьКДанным = "Объект.Товары", РежимВыделения = Множественный.
// Колонки: ТоварыНоменклатура, ТоварыКоличество, ТоварыЦена, ТоварыСумма.
// Команды: НайтиСтроку, ОкруглитьЦены, СортироватьПоСумме, УдалитьПустые, СвернутьДубли.
// Обработчики событий назначаются в палитре свойств таблицы и колонок.
#Область ОбработчикиСобытийФормы
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ПересчитатьИтог(ЭтотОбъект);
КонецПроцедуры
#КонецОбласти
#Область ОбработчикиСобытийЭлементовТаблицыФормыТовары
&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
ПересчитатьСуммуСтроки(Элементы.Товары.ТекущиеДанные);
КонецПроцедуры
&НаКлиенте
Процедура ТоварыЦенаПриИзменении(Элемент)
ПересчитатьСуммуСтроки(Элементы.Товары.ТекущиеДанные);
КонецПроцедуры
&НаКлиенте
Процедура ТоварыПередОкончаниемРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования, Отказ)
// Esc: пользователь отказался от ввода, проверять нечего
Если ОтменаРедактирования Тогда
Возврат;
КонецЕсли;
СтрокаТаблицы = Элемент.ТекущиеДанные;
Если СтрокаТаблицы = Неопределено Или СтрокаТаблицы.Количество > 0 Тогда
Возврат;
КонецЕсли;
Индекс = Объект.Товары.Индекс(СтрокаТаблицы);
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Количество должно быть больше нуля";
Сообщение.Поле = "Товары[" + Формат(Индекс, "ЧН=0; ЧГ=0") + "].Количество";
Сообщение.ПутьКДанным = "Объект";
Сообщение.Сообщить();
// Строка остаётся в режиме редактирования
Отказ = Истина;
КонецПроцедуры
&НаКлиенте
Процедура ТоварыПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
ПересчитатьИтог(ЭтотОбъект);
КонецПроцедуры
&НаКлиенте
Процедура ТоварыПослеУдаления(Элемент)
ПересчитатьИтог(ЭтотОбъект);
КонецПроцедуры
#КонецОбласти
#Область ОбработчикиКомандФормы
&НаКлиенте
Процедура НайтиСтроку(Команда)
Если Не ЗначениеЗаполнено(НоменклатураДляПоиска) Тогда
ПоказатьПредупреждение(, "Выберите номенклатуру для поиска");
Возврат;
КонецЕсли;
Найденные = Объект.Товары.НайтиСтроки(
Новый Структура("Номенклатура", НоменклатураДляПоиска));
Если Найденные.Количество() = 0 Тогда
ПоказатьПредупреждение(, "В таблице нет такой позиции");
Возврат;
КонецЕсли;
// Где стоит курсор: повторный поиск идёт к следующему совпадению
ТекущийИндекс = -1;
Если Элементы.Товары.ТекущаяСтрока <> Неопределено Тогда
ТекущаяСтрокаТаблицы = Объект.Товары.НайтиПоИдентификатору(Элементы.Товары.ТекущаяСтрока);
Если ТекущаяСтрокаТаблицы <> Неопределено Тогда
ТекущийИндекс = Объект.Товары.Индекс(ТекущаяСтрокаТаблицы);
КонецЕсли;
КонецЕсли;
НужнаяСтрока = Найденные[0]; // после последнего совпадения - снова первое
Для Каждого СтрокаТаблицы Из Найденные Цикл
Если Объект.Товары.Индекс(СтрокаТаблицы) > ТекущийИндекс Тогда
НужнаяСтрока = СтрокаТаблицы;
Прервать;
КонецЕсли;
КонецЦикла;
Элементы.Товары.ТекущаяСтрока = НужнаяСтрока.ПолучитьИдентификатор();
ТекущийЭлемент = Элементы.Товары;
КонецПроцедуры
&НаКлиенте
Процедура ОкруглитьЦены(Команда)
Выделенные = Элементы.Товары.ВыделенныеСтроки;
Если Выделенные.Количество() = 0 Тогда
ПоказатьПредупреждение(, "Выделите строки");
Возврат;
КонецЕсли;
// В массиве идентификаторы, а не сами строки
Для Каждого Идентификатор Из Выделенные Цикл
СтрокаТаблицы = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
СтрокаТаблицы.Цена = Окр(СтрокаТаблицы.Цена, 0);
ПересчитатьСуммуСтроки(СтрокаТаблицы);
КонецЦикла;
ПересчитатьИтог(ЭтотОбъект);
Модифицированность = Истина;
КонецПроцедуры
&НаКлиенте
Процедура СортироватьПоСумме(Команда)
// Идентификатор строки переживает сортировку, индекс - нет
ИдентификаторТекущей = Элементы.Товары.ТекущаяСтрока;
// Метод доступен на клиенте, сервер платформа вызывает сама
Объект.Товары.Сортировать("Сумма Убыв");
Если ИдентификаторТекущей <> Неопределено Тогда
Элементы.Товары.ТекущаяСтрока = ИдентификаторТекущей;
КонецЕсли;
Модифицированность = Истина;
КонецПроцедуры
&НаКлиенте
Процедура УдалитьПустые(Команда)
// Идём с конца: после удаления следующие строки сдвигаются вверх
Индекс = Объект.Товары.Количество() - 1;
Пока Индекс >= 0 Цикл
СтрокаТаблицы = Объект.Товары[Индекс];
Если Не ЗначениеЗаполнено(СтрокаТаблицы.Номенклатура)
Или СтрокаТаблицы.Количество = 0 Тогда
Объект.Товары.Удалить(Индекс);
Модифицированность = Истина;
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
ПересчитатьИтог(ЭтотОбъект);
КонецПроцедуры
&НаКлиенте
Процедура СвернутьДубли(Команда)
СвернутьДублиНаСервере();
Модифицированность = Истина;
КонецПроцедуры
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
&НаСервере
Процедура СвернутьДублиНаСервере()
// Выгрузить и Загрузить работают только на сервере
Таблица = Объект.Товары.Выгрузить();
Таблица.Свернуть("Номенклатура, Цена", "Количество, Сумма");
Объект.Товары.Загрузить(Таблица);
ПересчитатьИтог(ЭтотОбъект);
КонецПроцедуры
&НаКлиенте
Процедура ПересчитатьСуммуСтроки(СтрокаТаблицы)
Если СтрокаТаблицы = Неопределено Тогда
Возврат;
КонецЕсли;
СтрокаТаблицы.Сумма = СтрокаТаблицы.Количество * СтрокаТаблицы.Цена;
КонецПроцедуры
&НаКлиентеНаСервереБезКонтекста
Процедура ПересчитатьИтог(Форма)
Итог = 0;
Для Каждого СтрокаТаблицы Из Форма.Объект.Товары Цикл
Итог = Итог + СтрокаТаблицы.Сумма;
КонецЦикла;
Форма.ИтогСумма = Итог;
КонецПроцедуры
#КонецОбласти
ПересчитатьИтог(ЭтотОбъект) — процедура &НаКлиентеНаСервереБезКонтекста получает форму параметром и вызывается и на сервере, и на клиенте без лишнего серверного вызова.ТоварыПередОкончаниемРедактирования первым делом проверяется ОтменаРедактирования. Пользователь нажал Esc — проверять нечего, а Отказ не дал бы ему выйти из строки.Сообщение.Поле вида Товары[0].Количество вместе с ПутьКДанным = "Объект" привязывает сообщение к ячейке. В скобках индекс с нуля, а не НомерСтроки. Формат(Индекс, "ЧН=0; ЧГ=0"): без ЧН=0 ноль превратится в пустую строку, без ЧГ=0 индекс 1000 получит разделитель групп.СвернутьДублиНаСервере: Выгрузить даёт настоящую таблицу значений с методом Свернуть. В его параметрах перечислите все колонки, которые нужно сохранить, остальные пропадут.Модифицированность = Истина: программное изменение данных форму изменённой не помечает, и без этой строки платформа при закрытии не спросит о записи.Элементы.Товары.ТекущиеДанные.Количество, а текущей строки нет: таблица пустая или курсор не стоит на строке. Сначала проверяйте ТекущиеДанные = Неопределено.ТаблицаЗначений и запросы переносите в процедуру с &НаСервере.ТекущиеДанные.Скидка, а колонки нет в данных формы: её не вывели в таблицу и не отметили флажком «Использовать всегда». Выведите колонку, хотя бы невидимой, или поставьте флажок.&НаСервере и так получает данные формы: передайте ему ТекущаяСтрока и найдите строку через НайтиПоИдентификатору, изменения вернутся в форму. Если нужно только значение из строки, передайте его в метод &НаСервереБезКонтекста.НайтиСтроки.ТекущаяСтрока записан индекс. Ошибки нет, идентификатор ведь тоже число, но курсор встаёт на чужую строку или никуда. Для коллекций нужен ПолучитьИдентификатор(), для динамического списка — ключ строки.Для заполнения — Объект.Товары.Добавить() или Вставить(Индекс). Если строку должен дозаполнить пользователь, вызовите Элементы.Товары.ДобавитьСтроку(): это то же, что кнопка «Добавить».
Колонка — это элемент формы: Элементы.ТоварыЦена.Видимость = Ложь или Элементы.ТоварыЦена.ТолькоПросмотр = Истина. Если закрыть ячейки нужно не во всей колонке, а в отдельных строках, используют условное оформление формы, а не код в ПриАктивизацииСтроки.
Включите у таблицы свойство «Подвал», а у колонки заполните ПутьКДаннымПодвала. Для числовой колонки табличной части подходит путь вида Объект.Товары.ИтогСумма, итог платформа считает сама. Можно указать и обычный реквизит формы, как ИтогСумма в примере, — тогда пересчёт на вашей стороне.
ТаблицаЗначений — объект встроенного языка, на тонком клиенте его нет. Таблица формы — элемент, который показывает коллекцию данных формы. Реквизит типа ТаблицаЗначений хранится в форме как ДанныеФормыКоллекция, полноценную таблицу значений из него получают на сервере через РеквизитФормыВЗначение. Подробнее — в статье «Таблица значений на форме 1С».
Если нужно доработать таблицу в форме типового документа — добавить колонки, пересчёты, проверки строк, подбор — и при этом не снимать конфигурацию с поддержки, такие правки делают расширением. Опишите, что должно происходить в таблице, и разместите задачу — оценка бесплатная, ответ в течение рабочего дня. Все направления работ — на странице услуг по доработке 1С.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.