Почему колонка табличного поля с типом СписокЗначений не заполняется вручную и 'залипает' во всех строках?

Программист 1С v8.3 (Обычные формы)
← На главную

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

Выясняем причину проблемы: почему СписокЗначений 'залипает' и исчезает

Первоначально, когда мы добавляем колонку в табличное поле на обычную форму и указываем ей тип СписокЗначений, но при этом не привязываем ее к реквизиту табличной части документа (то есть, свойство "Данные" колонки остается пустым), платформа 1С интерпретирует это определенным образом. Давайте разберем, что происходит:

  1. Общий объект СписокЗначений для всех строк: В отсутствие явной привязки к конкретному реквизиту табличной части для каждой строки, система может использовать один и тот же программный объект СписокЗначений для отображения во всех строках табличного поля. Это означает, что когда мы вводим или выбираем контрагентов в первой строке, мы фактически изменяем этот единственный объект. Поскольку он используется для отображения во всех остальных строках, эти же изменения мгновенно отражаются везде. Это и есть причина эффекта "залипания", когда значения, введенные в одной ячейке, появляются во всех остальных.
  2. Невидимость данных после редактирования: После того как мы выбрали значения и перешли к другой ячейке или строке, данные в колонке становятся невидимыми или исчезают. Это происходит потому, что выбранные значения были помещены во временный программный объект (тот самый общий СписокЗначений), но не были сохранены в постоянное хранилище данных, связанное с документом (например, в реквизит табличной части). Платформа не знает, как отобразить эти данные вне режима редактирования, так как они не ассоциированы с конкретной строкой документа на уровне данных.

Критически важно: Для корректной работы колонки табличного поля с любым типом данных, а особенно со сложными объектами вроде СписокЗначений, необходимо, чтобы эта колонка была привязана к соответствующему реквизиту табличной части документа. Этот реквизит будет служить постоянным хранилищем данных для каждой строки.

Решение для хранения нескольких контрагентов: ХранилищеЗначения в основной табличной части

Если нам нужно хранить произвольный набор значений (например, несколько контрагентов) в одной "ячейке" табличной части документа и при этом не требуется часто обращаться к этим данным в запросах или отчетах напрямую (достаточно программной обработки), то оптимальным выбором будет использование реквизита типа ХранилищеЗначения в основной табличной части документа.

Разберем по шагам, как это реализовать:

  1. Добавление реквизита в табличную часть документа:

    Прежде всего, добавим в табличную часть нашего документа новый реквизит. Назовем его, например, КонтрагентыДоп. Очень важно установить его тип как ХранилищеЗначения. Этот реквизит будет предназначен для хранения нашего объекта СписокЗначений со всеми выбранными контрагентами.

  2. Привязка колонки на форме к реквизиту:

    Теперь на форме документа найдем нашу колонку "Контрагенты" (или создадим новую, если ее еще нет). Установим ее свойство "Данные" таким образом, чтобы оно ссылалось на только что созданный реквизит табличной части: ТабличноеПолеДокумента.ТекущиеДанные.КонтрагентыДоп (где ТабличноеПолеДокумента – это имя элемента управления "ТабличноеПоле" на вашей форме).

    Также важно установить свойство "Тип значения" для этой колонки на форме как СписокЗначений. Это позволит платформе корректно работать с данными при отображении и редактировании.

  3. Программное управление ХранилищемЗначения:

    ХранилищеЗначения предназначено для сериализации и десериализации данных. Это означает, что мы должны программно "упаковывать" наш СписокЗначений в ХранилищеЗначения при сохранении и "распаковывать" его при чтении. Давайте посмотрим, как это сделать:

    1. При открытии формы (чтение данных):

      В обработчике события ПриЧтенииНаСервере или ПриОткрытии (для обычных форм, если данные уже загружены) или при активации строки табличного поля (ПриАктивизацииСтроки) мы должны извлечь СписокЗначений из ХранилищаЗначения для текущей строки и поместить его в элемент управления, связанный с колонкой на форме. Примерный код будет выглядеть так:

      
      // Предполагаем, что у вас есть ТабличноеПоле на форме, связанное с ТЧДокумента
      // и колонка "Контрагенты" связана с реквизитом КонтрагентыДоп
      
      &НаКлиенте
      Процедура ТабличноеПолеКонтрагентыПриАктивизацииСтроки(Элемент)
          // Получаем текущую строку табличной части
          ТекущаяСтрока = Элемент.ТекущиеДанные;
          Если ТекущаяСтрока <> Неопределено Тогда
              // Проверяем, есть ли что-то в хранилище значений
              Если ТекущаяСтрока.КонтрагентыДоп.ЗначениеЗаполнено() Тогда
                  // Извлекаем СписокЗначений из ХранилищаЗначения
                  СписокКонтрагентов = ТекущаяСтрока.КонтрагентыДоп.Получить();
                  // Далее этот СписокКонтрагентов можно использовать для отображения
                  // в элементе управления, например, в ПолеВвода, связанном с колонкой
                  // Если колонка связана напрямую, платформа сама справится с отображением,
                  // но для интерактивного редактирования потребуется дополнительный код.
                  // Например, можно временно поместить его в реквизит формы для удобства работы.
                  ЭлементыФормы.ТабличноеПолеКонтрагенты.ТекущиеДанные.Контрагенты = СписокКонтрагентов;
              Иначе
                  // Если хранилище пусто, создаем новый список для текущей строки
                  ЭлементыФормы.ТабличноеПолеКонтрагенты.ТекущиеДанные.Контрагенты = Новый СписокЗначений();
              КонецЕсли;
          КонецЕсли;
      КонецПроцедуры
      

      Примечание: Если колонка на форме напрямую связана с реквизитом КонтрагентыДоп типа ХранилищеЗначения и имеет тип СписокЗначений, то платформа 1С *автоматически* справится с десериализацией для отображения и сериализацией при изменении, но это требует корректной настройки свойств элемента формы. На обычных формах, как правило, требуется больше ручного кода.

    2. При записи документа (сохранение данных):

      Перед записью каждой строки табличной части документа (например, в обработчике события ПередЗаписью или ПриИзмененииСтроки) необходимо "упаковать" текущий СписокЗначений обратно в ХранилищеЗначения.

      
      // Предположим, что в каждой строке ТЧ есть реквизит "Контрагенты" типа СписокЗначений,
      // который мы наполняем интерактивно, а потом сохраняем в ХранилищеЗначения.
      
      &НаСервере
      Процедура ПередЗаписьюНаСервере(Отказ, ПараметрыЗаписи)
          Для Каждого СтрокаТЧ Из Объект.ТабличнаяЧастьДокумента Цикл
              // Создаем новое ХранилищеЗначения из объекта СписокЗначений
              СтрокаТЧ.КонтрагентыДоп = Новый ХранилищеЗначения(СтрокаТЧ.Контрагенты);
          КонецЦикла;
      КонецПроцедуры
      

      Важно: убедитесь, что ваш объект СписокЗначений (СтрокаТЧ.Контрагенты в примере) содержит именно те значения, которые вы хотите сохранить.

Плюсы использования ХранилищаЗначения:

  1. Относительная простота реализации для хранения произвольных объектов.
  2. Данные хранятся в одной табличной части, не требуя создания дополнительных сущностей.
  3. Экономия места в базе данных за счет сжатия данных.

Минусы использования ХранилищаЗначения:

  1. Невозможность прямого поиска и фильтрации запросами: Мы не можем выполнять запросы к полям ХранилищаЗначения, чтобы найти контрагентов или отфильтровать документы по ним. Для отчетности потребуется программное извлечение и обработка данных.
  2. Отсутствие ссылочной целостности для хранимых объектов (в данном случае, для контрагентов).
  3. Требуется дополнительный код для сериализации/десериализации на форме.

Решение для хранения нескольких контрагентов: Отдельная табличная часть

Если нам критически важно выполнять запросы по контрагентам, строить отчеты, осуществлять поиск по ним или поддерживать ссылочную целостность, то более предпочтительным решением будет создание отдельной (подчиненной) табличной части документа. Каждая запись в этой вспомогательной ТЧ будет содержать одного контрагента и ссылку на строку основной ТЧ, к которой он относится.

Разберем это решение по шагам:

  1. Изменение основной табличной части и создание новой подчиненной ТЧ:
    1. Основная ТЧ: В основной табличной части документа нам больше не нужен реквизит Контрагенты или КонтрагентыДоп. Вместо этого, мы добавим новый реквизит, например, ИдентификаторСтроки, с типом УникальныйИдентификатор. Этот идентификатор будет уникальным для каждой строки основной ТЧ.
    2. Новая подчиненная ТЧ: Создадим новую табличную часть в документе, назовем ее, например, КонтрагентыТабличнойЧасти. В этой ТЧ нам потребуются следующие реквизиты:
      • ИдентификаторСтрокиОсновнойТЧ (тип УникальныйИдентификатор): для связи с конкретной строкой основной табличной части.
      • Контрагент (тип СправочникСсылка.Контрагенты): для хранения самого контрагента.
  2. Генерация и установка УникальногоИдентификатора:

    При создании новой строки в основной табличной части документа необходимо присваивать реквизиту ИдентификаторСтроки новое уникальное значение.

    
    &НаКлиенте
    Процедура ТабличноеПолеОсновнаяТЧПриНачалеДобавления(Элемент, Отказ, Копирование)
        Если Отказ = Ложь Тогда
            НоваяСтрока = Элемент.ТекущиеДанные; // Получаем текущую (новую) строку
            Если НоваяСтрока.ИдентификаторСтроки = Неопределено Или НоваяСтрока.ИдентификаторСтроки = УникальныйИдентификатор.ПустаяСсылка() Тогда
                НоваяСтрока.ИдентификаторСтроки = Новый УникальныйИдентификатор();
            КонецЕсли;
        КонецЕсли;
    КонецПроцедуры
    

    Этот подход гарантирует уникальную связь, даже если строки основной ТЧ будут перемещаться или сортироваться.

  3. Отображение и редактирование на форме:

    Теперь на форме документа нам потребуется два табличных поля: одно для основной табличной части, а второе – для нашей новой подчиненной табличной части КонтрагентыТабличнойЧасти. Последнее табличное поле будет отображать контрагентов, связанных с текущей активной строкой основной ТЧ.

    Для этого мы будем использовать событие ПриАктивизацииСтроки основной табличной части:

    
    &НаКлиенте
    Процедура ТабличноеПолеОсновнаяТЧПриАктивизацииСтроки(Элемент)
        ТекущаяСтрокаОсновной = Элемент.ТекущиеДанные;
        Если ТекущаяСтрокаОсновной <> Неопределено Тогда
            // Устанавливаем отбор в табличном поле для подчиненной ТЧ
            ЭлементыФормы.ТабличноеПолеКонтрагентыДоп.Отбор.Элементы.Очистить();
            ЭлементОтбора = ЭлементыФормы.ТабличноеПолеКонтрагентыДоп.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
            ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ИдентификаторСтрокиОсновнойТЧ");
            ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
            ЭлементОтбора.ПравоеЗначение = ТекущаяСтрокаОсновной.ИдентификаторСтроки;
            ЭлементОтбора.Использование = Истина;
        Иначе
            ЭлементыФормы.ТабличноеПолеКонтрагентыДоп.Отбор.Элементы.Очистить();
        КонецЕсли;
    КонецПроцедуры
    

    Таким образом, при переходе по строкам основной ТЧ в табличном поле ТабличноеПолеКонтрагентыДоп будут отображаться только те контрагенты, которые привязаны к текущей строке.

  4. Сохранение данных:

    Запись данных в подчиненную табличную часть происходит стандартным образом при записи документа.

Плюсы использования отдельной табличной части:

  1. Прямая выборка в запросах: Мы можем легко строить отчеты и выполнять запросы, связывая основную ТЧ с подчиненной по УникальномуИдентификатору. Это позволяет эффективно анализировать данные.
  2. Поддержка ссылочной целостности (для реквизита Контрагент).
  3. Более гибкая работа с данными (сортировка, фильтрация внутри подчиненной ТЧ).

Минусы использования отдельной табличной части:

  1. Более сложная реализация на форме (два табличных поля, логика отбора).
  2. Увеличение количества строк в базе данных.

Реализация интерактивного выбора СписокЗначений на обычной форме (при использовании ХранилищаЗначения)

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

  1. Создание вспомогательной формы для выбора контрагентов:

    Создадим отдельную вспомогательную форму (например, ФормаВыбораКонтрагентов). На эту форму поместим табличное поле, отображающее справочник "Контрагенты", и добавим кнопки "Выбрать" и "Отмена". Пользователь сможет отмечать нужных контрагентов галочками (если табличное поле настроено на режим выбора) или выделять их, а затем по кнопке "Выбрать" форма будет возвращать объект СписокЗначений.

    Примерный обработчик кнопки "Выбрать" на вспомогательной форме:

    
    &НаКлиенте
    Процедура КомандаВыбрать(Команда)
        ВыбранныеКонтрагенты = Новый СписокЗначений();
        // Предположим, что ТабличноеПолеСправочникКонтрагенты - это табличное поле со справочником
        Для Каждого Строка Из ЭлементыФормы.ТабличноеПолеСправочникКонтрагенты.ВыделенныеСтроки Цикл
            ВыбранныеКонтрагенты.Добавить(Строка.Значение); // Добавляем ссылку на контрагента
        КонецЦикла;
        // Возвращаем список значений
        Закрыть(ВыбранныеКонтрагенты);
    КонецПроцедуры
    
  2. Вызов формы выбора из колонки табличного поля основной формы:

    На основной форме, в табличном поле документа, мы можем добавить кнопку в колонку "Контрагенты" или использовать событие НачалоВыбора (если это поле ввода). При нажатии на эту кнопку или событии будет открываться наша вспомогательная форма:

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

    В этом примере ВнешняяОбработка.МояОбработка.Форма.ФормаВыбораКонтрагентов — это путь к вашей форме выбора. Если форма принадлежит конфигурации, путь будет другим (например, Документ.МойДокумент.Форма.ФормаВыбораКонтрагентов).

Итак, мы видим, что решение проблемы с "залипанием" и невидимостью СписокЗначений заключается в правильной привязке колонки к реквизиту табличной части. Выбор между ХранилищемЗначения и отдельной табличной частью зависит от ваших требований к отчетности и сложности реализации. В любом случае, оба подхода позволяют эффективно работать с множественными значениями в одной строке документа на обычных формах 1С.

← На главную