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