При работе с управляемыми формами 1С программисты часто сталкиваются с задачей: нужно вызвать вспомогательное окно для ввода данных (например, многострочного текста или сложного расчета) и поместить результат в ячейку таблицы. Однако стандартный подход через прямое присвоение значения реквизиту ТекущиеДанные.ИмяПоля = Значение имеет существенный недостаток. Как только мы выполняем эту строку кода, данные в источнике (таблице значений или табличной части) меняются окончательно. Если после этого пользователь нажмет клавишу Esc или решит отменить редактирование строки, старое значение уже не восстановится.
В этой статье мы подробно разберем, как реализовать ввод данных так, чтобы значение попадало сначала в «поле ввода» (интерфейсный буфер), а не в сам реквизит, сохраняя тем самым механизм транзакционности редактирования строки. Мы рассмотрим несколько эффективных способов решения этой задачи.
Разберем ситуацию подробнее. В управляемом приложении 1С существует разделение между данными (объектом на сервере или реквизитом формы) и их отображением в элементах формы. Когда пользователь вводит данные руками прямо в ячейку, платформа удерживает эти изменения в специальном состоянии «редактирования». Пока строка не покинута или не нажата кнопка Enter, изменения считаются предварительными. Нажатие Esc просто очищает этот буфер и возвращает отображение старых данных.
Когда же мы пишем в коде ТекущиеДанные.ТекстКомментария = "Новый текст", мы обращаемся напрямую к структуре ДанныеФормыКоллекция. Это действие эквивалентно завершению ввода: платформа считает, что данные изменены программно, и фиксирует их. В этот момент «состояние редактирования» для конкретного поля считается завершенным, и откат к предыдущему значению становится невозможным без специальных ухищрений.
Самый «правильный» с точки зрения архитектуры 1С способ — это имитация стандартного выбора значения. Если мы инициируем процесс через событие НачалоВыбора и правильно указываем владельца, платформа сама позаботится о том, чтобы значение попало в поле ввода «интерактивно».
Проанализируем алгоритм реализации этого метода:
КнопкаВыбора в значение Да.НачалоВыбора для этого поля.Рассмотрим пример кода для этого метода:
&НаКлиенте
Процедура СписокТоваровКомментарийНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь; // Отключаем стандартный выбор справочника
// Параметры для открытия формы редактирования текста
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("Значение", Элементы.СписокТоваров.ТекущиеДанные.Комментарий);
// Важно: Вторым параметром передаем 'Элемент' — это и есть наше поле ввода
// Платформа запомнит его как владельца выбора
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеРедактированияТекста", ЭтотОбъект);
ПоказатьВводСтроки(ОписаниеОповещения, Элементы.СписокТоваров.ТекущиеДанные.Комментарий, "Введите комментарий");
КонецПроцедуры
&НаКлиенте
Процедура ПослеРедактированияТекста(Результат, ДополнительныеПараметры) Экспорт
Если Результат <> Неопределено Тогда
// Вместо прямого присвоения ТекущиеДанные.Комментарий = Результат
// Мы используем механизм ОповеститьОВыборе
// Это заставит платформу поместить значение в ТЕКУЩИЙ активный элемент
Элементы.СписокТоваров.ТекущийЭлемент.ОповеститьОВыборе(Результат);
КонецЕсли;
КонецПроцедуры
При таком подходе значение передается в элемент управления через метод ОповеститьОВыборе. Платформа воспринимает это так, будто пользователь сам выбрал значение из списка или другой формы. Данные будут отображены в ячейке, но строка останется в состоянии редактирования, и Esc отработает корректно.
Если по каким-то причинам метод с ОповеститьОВыборе вам не подходит (например, при сложном программном изменении нескольких полей сразу), можно воспользоваться механизмом ручного отката. Для этого нам понадобится событие таблицы ПриНачалеРедактирования и ПриОкончанииРедактирования.
Рассмотрим этот процесс по шагам:
Пример реализации:
&НаКлиенте
Перем КопияТекущейСтроки; // Переменная модуля формы
&НаКлиенте
Процедура СписокТоваровПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
// Сохраняем текущие значения полей в структуру
ТекущиеДанные = Элементы.СписокТоваров.ТекущиеДанные;
КопияТекущейСтроки = Новый Структура;
Для Каждого Колонка Из Элементы.СписокТоваров.ПодчиненныеЭлементы Цикл
ИмяПоля = Колонка.ПутьКДанным;
// Отсекаем префикс пути к данным, если он есть
ИмяСвойства = СтрЗаменить(ИмяПоля, "Объект.Товары.", "");
КопияТекущейСтроки.Вставить(ИмяСвойства, ТекущиеДанные[ИмяСвойства]);
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура СписокТоваровПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
Если ОтменаРедактирования Тогда
// Если пользователь нажал Esc или отменил ввод другим способом
// Восстанавливаем данные из нашей копии
ТекущиеДанные = Элементы.СписокТоваров.ТекущиеДанные;
Для Каждого КлючИЗначение Из КопияТекущейСтроки Цикл
ТекущиеДанные[КлючИЗначение.Ключ] = КлючИЗначение.Значение;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Этот метод более универсален, так как он позволяет программно менять ТекущиеДанные в любой момент, не заботясь о том, как именно вызвано окно ввода. Мы просто гарантируем, что при отмене редактирования всей строки (событие ПриОкончанииРедактирования с флагом ОтменаРедактирования = Истина) данные вернутся в исходное состояние.
Разберем еще один практический вариант, упомянутый в обсуждениях. Вы можете добавить в таблицу значений или табличную часть дополнительную колонку (реквизит), которую не будете выносить на форму. Назовем ее, например, ЗначениеДоРедактирования.
Логика работы в этом случае следующая:
ПриНачалеРедактирования вы копируете значение из основного поля в скрытое.ПриОкончанииРедактирования, если ОтменаРедактирования равна Истина, переписываете данные обратно из скрытого поля в основное.Это надежное решение, которое не зависит от переменных модуля (которые могут очиститься при некоторых контекстных переходах) и всегда хранит состояние «транзакции» прямо в данных формы.
Выясним некоторые технические детали, которые влияют на поведение поля ввода при программном заполнении:
РежимРедактирования. Если установлено Вход в ячейку, то при программном присвоении фокус может «вылететь» из режима редактирования. Для работы с многострочными текстами часто удобнее использовать режим В отдельном окне.ОбновлениеТекстаРедактирования. Для корректного интерактивного отображения изменений рекомендуется устанавливать его в значение ПриИзменении.ОповеститьОВыборе, то данные сначала попадут в событие колонки ОбработкаВыбора. Если вы опишете этот обработчик и установите в нем СтандартнаяОбработка = Ложь, то автоматической подстановки значения не произойдет, и вам снова придется записывать данные в ТекущиеДанные вручную, что вернет вас к проблеме с отменой. Поэтому в ОбработкаВыбора лучше ничего не писать, если вы хотите стандартного поведения буфера.Проанализировав ситуацию, мы видим, что наиболее элегантным решением является использование штатного механизма ОповеститьОВыборе с предварительной установкой СтандартнаяОбработка = Ложь в событии НачалоВыбора. Это позволяет «обмануть» платформу, заставив её думать, что ввод сделал сам пользователь, и сохранить все преимущества стандартного интерфейса, включая отмену по Esc.
Если же логика ввода слишком сложна для стандартного механизма выбора, использование событий ПриНачалеРедактирования и ПриОкончанииРедактирования для ручного управления кэшем данных является проверенным и надежным профессиональным подходом.