При работе со справочниками в платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с крайне неприятной проблемой: в базе данных начинают задваиваться элементы с абсолютно одинаковыми кодами и внутренними уникальными идентификаторами (ID или IDD). Это приводит к разрушению целостности базы данных, порче индексов, появлению фантомных записей в файловом режиме (DBF) или к аварийному завершению работы системы с ошибкой уникальности ключа в SQL-версии.
Данная проблема является классическим платформенным багом 1С 7.7, известным в сообществе как «Глюк Юли06». В этой статье мы подробно разберем, почему возникает эта критическая ошибка, и рассмотрим все существующие способы борьбы с ней — от простых интерфейсных решений до сложных программных обходов.
Для начала проанализируем ситуацию и поймем, в какой именно момент платформа дает сбой. Ошибка проявляется при одновременном соблюдении следующих условий:
Enter).Enter или Esc), интерактивно меняет отбор на форме (например, выбирает значение в поле фильтра, где срабатывает код УстановитьОтбор()), а затем кликает мышью на другую строку списка.В этот момент позиционирование курсора базы данных сбивается. Платформа пытается записать измененную строку, но из-за сброшенного контекста позиционирования записывает ее поверх другого элемента или создает дубль с уже существующим в базе данных ID. В результате в базах DBF мы получаем физические дубли идентификаторов, а в базах SQL — критический вылет программы из-за нарушения ограничения уникальности (Duplicate Key).
Давайте подробно рассмотрим, какими путями можно решить эту проблему и полностью обезопасить базу данных от разрушения.
---
Самый простой, надежный и архитектурно правильный способ, к которому рано или поздно приходят все разработчики на платформе 1С 7.7, — это запрет редактирования элементов непосредственно в списке. Перевод редактирования элементов в диалоговое окно полностью исключает саму возможность возникновения этого бага.
Выполним это изменение по шагам:
РедактироватьВДиалоге(1).Если редактирование в списке использовалось для простейших реквизитов (например, быстрое изменение наименования или флага), пользователям придется привыкнуть к открытию карточки элемента. Однако это гарантирует стопроцентную сохранность данных и стабильность работы системы.
---
Если бизнес-требования категорически запрещают отказываться от редактирования в списке, нам необходимо программно заблокировать любые элементы управления отбором на форме в тот момент, когда пользователь начинает редактировать строку, и разблокировать их только после окончательного завершения этого процесса.
Главная сложность заключается в том, что в справочниках 1С 7.7, в отличие от документов, нет предопределенной процедуры ПриОкончанииРедактированияСтроки(). Если пользователь нажмет кнопку Esc для отмены редактирования, стандартная процедура ПриЗаписи() не выполнится. Чтобы обойти это ограничение, мы можем использовать механизм обработки ожидания.
Рассмотрим пример реализации этого подхода:
Перем флРедактируется;
Процедура ПриНачалеРедактированияСтроки()
// Устанавливаем флаг редактирования
флРедактируется = 1;
// Блокируем элементы управления отбором на форме
Форма.ВыборОтбора.Доступность(0);
// Запускаем таймер с интервалом в 1 секунду для отслеживания окончания редактирования
Форма.ОбработкаОжидания("ПроверитьОкончаниеРедактирования", 1);
КонецПроцедуры
Процедура ПроверитьОкончаниеРедактирования()
// Проверяем, активна ли еще строка на редактирование.
// Если фокус ушел из режима редактирования строки:
Если (Форма.Модифицированность() = 0) И (флРедактируется = 1) Тогда
// Сбрасываем флаг и возвращаем доступность элементам отбора
флРедактируется = 0;
Форма.ВыборОтбора.Доступность(1);
// Отключаем таймер ожидания
Форма.ОбработкаОжидания("", 0);
КонецЕсли;
КонецПроцедуры
Процедура ПриЗаписи()
// На случай успешного сохранения строки также сбрасываем флаг и таймер
флРедактируется = 0;
Форма.ВыборОтбора.Доступность(1);
Форма.ОбработкаОжидания("", 0);
КонецПроцедуры
Этот код позволяет динамически отслеживать состояние формы. Как только пользователь сохранит изменения (нажав Enter) или отменит их (нажав Esc), система раз в секунду будет проверять состояние модифицированности и автоматически возвращать доступность элементам отбора.
---
Еще один оригинальный «костыль» из арсенала разработчиков 1С 7.7 — использование динамического вычисления формул текстовых элементов формы. Платформа автоматически пересчитывает формулы всех текстовых полей на форме при любых интерактивных действиях пользователя (клик мышью, перемещение курсора клавиатурой, изменение фокуса).
Рассмотрим, как реализовать этот алгоритм:
КонтрольСостоянияРедактирования().В модуле формы опишем следующую логику:
Функция КонтрольСостоянияРедактирования()
// Эта функция будет выполняться практически непрерывно при действиях пользователя.
// Мы можем проверять, активен ли режим редактирования строки.
Если Форма.Модифицированность() = 1 Тогда
// Если строка изменена, но еще не записана, принудительно отключаем доступность отбора
Если Форма.ВыборОтбора.Доступность() = 1 Тогда
Форма.ВыборОтбора.Доступность(0);
КонецЕсли;
Иначе
// Если изменений нет (редактирование завершено или отменено)
Если Форма.ВыборОтбора.Доступность() = 0 Тогда
Форма.ВыборОтбора.Доступность(1);
КонецЕсли;
КонецЕсли;
// Возвращаем пустую строку, чтобы на форме ничего не отображалось
Возврат "";
КонецФункции
Этот метод работает быстрее и отзывчивее, чем периодический таймер ОбработкаОжидания, так как срабатывает мгновенно при любом движении пользователя по форме списка.
---
Если в вашей конфигурации активно используются возможности внешних библиотек расширения платформы, например FormEx или 1С++, проблему можно решить наиболее изящно и профессионально.
Компонента FormEx позволяет перехватывать низкоуровневые события операционной системы Windows и самой платформы 1С. С ее помощью мы можем напрямую отслеживать нажатия клавиш клавиатуры (включая Esc и Enter в режиме редактирования ячеек таблицы), а также получать полноценные события начала и завершения редактирования.
Для реализации этого метода:
FormEx.dll при начале работы системы.Использование этого метода требует определенных навыков системного программирования в 1С 7.7, однако оно гарантирует полное отсутствие «зависаний» интерфейса и ложных срабатываний, свойственных таймерам.
---
Если мы проанализируем действия пользователей, то поймем, что чаще всего критическая ошибка происходит случайно: пользователь случайно задевает колесико мыши над выпадающим списком отбора или кликает по нему во время ввода текста. Чтобы полностью исключить человеческий фактор, мы можем изменить логику работы интерфейса.
Для этого выполним следующие действия:
УстановитьОтбор() добавим обязательную проверку состояния редактирования строки:
Процедура НажатиеКнопкиПрименитьОтбор()
// Проверяем, не находится ли пользователь в режиме редактирования строки
Если Форма.Модифицированность() = 1 Тогда
Предупреждение("Перед применением отбора необходимо сохранить или отменить изменения в строке!", 5);
Возврат;
КонецЕсли;
// Если всё в порядке, устанавливаем отбор
УстановитьОтбор(ИмяОтбора, ЗначениеОтбора);
КонецПроцедуры
Данный подход полностью защищает базу данных от рассинхронизации курсора позиционирования, так как выполнение потенциально опасного метода УстановитьОтбор() блокируется программно до тех пор, пока пользователь явно не завершит работу с текущей строкой.
---
В качестве альтернативы методу УстановитьОтбор() для фильтрации элементов в списке справочника можно использовать метод ИспользоватьСписокЭлементов(). Этот метод накладывает статический фильтр по заранее подготовленному списку значений и, в отличие от динамического отбора, не вызывает сбоя позиционирования курсора при параллельном редактировании строк.
Рассмотрим пример перевода логики на этот метод:
Процедура УстановитьФильтр()
СписокДляФильтра = СоздатьОбъект("СписокЗначений");
// Заполняем список значений элементами, соответствующими условию отбора
Спр = СоздатьОбъект("Справочник.Номенклатура");
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
Если Спр.ВидНоменклатуры = ВыбранныйВид Тогда
СписокДляФильтра.ДобавитьЗначение(Спр.ТекущийЭлемент());
КонецЕсли;
КонецЦикла;
// Накладываем фильтр на форму списка
ИспользоватьСписокЭлементов(СписокДляФильтра);
КонецПроцедуры
Важное ограничение метода: Использование ИспользоватьСписокЭлементов() может снижать производительность системы на справочниках с огромным количеством элементов (десятки и сотни тысяч записей), так как требует предварительного обхода и формирования списка значений в оперативной памяти. Также этот метод накладывает ограничения на отображение иерархии (дерева групп) справочника.
---
Мы подробно разобрали природу опасного платформенного бага 1С 7.7 и проанализировали различные варианты борьбы с ним. Подводя итог, дадим следующие рекомендации разработчикам: