При разработке интерфейсов в 1С часто возникает задача автоматизировать редактирование текста. Например, пользователь выделяет слово, нажимает кнопку, и это слово обрамляется какими-либо префиксами или комментариями. Однако стандартная функция отмены Ctrl+Z не всегда корректно отрабатывает программные изменения. В этой статье мы разберем, как реализовать надежный механизм «отката» изменений с использованием стека и уникальных маркеров.
Рассмотрим стандартную ситуацию: у нас есть поле ввода с текстом. Пользователь выделяет фрагмент и нажимает кнопку, которая должна добавить к этому фрагменту определенные символы. Основная сложность заключается в том, что после программного присвоения значения свойству ВыделенныйТекст, выделение в поле ввода сбрасывается. Если мы хотим «вернуть как было» по другой кнопке (или по той же самой), нам нужно точно знать, какой фрагмент был изменен и где он находился.
Проанализируем работу со свойством ВыделенныйТекст. Согласно документации 1С:
Один из самых простых способов, предложенных в обсуждении, — это имитация нажатия комбинации клавиш Ctrl+Z. Для этого используется COM-объект Wscript.shell.
лкСенд = Новый COMОбъект("Wscript.shell");
лкСенд.SendKeys("^z"); // Имитация нажатия Ctrl+Z
Однако этот метод имеет ряд существенных недостатков:
Более профессиональный подход заключается в самостоятельном хранении истории изменений. Мы можем создать список значений (стек), где будем хранить состояние строки до её изменения.
Разберем логику: перед тем как заменить текст, мы фиксируем, что было в выделении, сколько символов находилось слева от него и сколько справа. Чтобы корректно найти место вставки в общей строке, можно использовать временную «соль» — уникальную последовательность символов.
Рассмотрим пример кода для выполнения замены:
&НаКлиенте
Перем Соль; // Переменная для хранения уникального маркера
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Соль = Символ(165); // Используем экзотический символ (например, знак иены)
КонецПроцедуры
&НаКлиенте
Процедура ВставитьТекст(Команда)
// 1. Получаем исходный выделенный текст
БылТекст = Элементы.РеквизитТекст.ВыделенныйТекст;
// 2. Формируем временный маркер для определения позиции
СталТекст = БылТекст + Соль;
// 3. Заменяем выделение на маркер, чтобы вычислить позицию
Элементы.РеквизитТекст.ВыделенныйТекст = СталТекст;
// 4. Вычисляем координаты (сколько символов слева и справа)
ПолныйТекст = Объект.РеквизитТекст;
Слева = СтрНайти(ПолныйТекст, СталТекст) - 1;
Справа = СтрДлина(ПолныйТекст) - Слева - СтрДлина(СталТекст);
// 5. Сохраняем данные в стек (СписокЗначений)
// Формат: ИсходныйТекст + Соль + ПозицияСлева + Соль + ПозицияСправа
ДанныеДляОтмены = БылТекст + Соль + Строка(Слева) + Соль + Строка(Справа);
СтекИзменений.Добавить(ДанныеДляОтмены);
// 6. Выполняем финальную замену на нужный нам текст (с префиксами)
ВставкаСлева = "11 ";
ВставкаСправа = " (работы)";
Объект.РеквизитТекст = СтрЗаменить(ПолныйТекст, СталТекст, ВставкаСлева + БылТекст + ВставкаСправа);
КонецПроцедуры
Теперь разберем процедуру отмены. Нам нужно извлечь последнюю запись из стека и собрать строку заново, используя функции Лев() и Прав().
&НаКлиенте
Процедура ОтменитьПоследнееДействие(Команда)
Если СтекИзменений.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Извлекаем последнюю запись
ИндексПоследнего = СтекИзменений.Количество() - 1;
Данные = СтекИзменений[ИндексПоследнего].Значение;
// Разбиваем строку по нашей "соли"
МассивДанных = СтрРазделить(Данные, Соль, Ложь);
// Если выделение было пустым, массив может иметь меньше элементов, обрабатываем это
Если МассивДанных.Количество() < 3 Тогда
ИсходныйФрагмент = "";
ПозСлева = Число(МассивДанных[0]);
ПозСправа = Число(МассивДанных[1]);
Иначе
ИсходныйФрагмент = МассивДанных[0];
ПозСлева = Число(МассивДанных[1]);
ПозСправа = Число(МассивДанных[2]);
КонецЕсли;
// Собираем текст обратно в исходное состояние
ТекстСлева = Лев(Объект.РеквизитТекст, ПозСлева);
ТекстСправа = Прав(Объект.РеквизитТекст, ПозСправа);
Объект.РеквизитТекст = ТекстСлева + ИсходныйФрагмент + ТекстСправа;
// Удаляем использованную запись из стека
СтекИзменений.Удалить(ИндексПоследнего);
КонецПроцедуры
Проанализируем ситуацию: в тексте может быть несколько одинаковых слов «деятельность». Если мы просто используем СтрЗаменить(), программа может заменить не то вхождение слова, которое мы редактировали последним. Используя расчет символов Слева и Справа, мы гарантируем, что восстанавливаем текст именно в той позиции, где он был изменен, игнорируя идентичные слова в других частях предложения.
Важные нюансы реализации:
Строка достаточной длины (или неограниченной).РежимВыделения настроено стандартно.Рассмотрим случай, когда пользователь может вручную редактировать текст между нажатиями кнопок «Применить» и «Отменить». В этом случае жесткая привязка к количеству символов слева и справа может привести к ошибке.
Для более продвинутых решений рекомендуем:
Zero Width Space (U+200B) в качестве невидимого маркера прямо в тексте. Это позволит находить место замены даже при изменении остального текста.Модифицированность формы, чтобы понимать, были ли внесены изменения пользователем.Таким образом, мы проанализировали несколько подходов к созданию функции Undo в 1С. Наиболее стабильным является метод с программным стеком и фиксацией длин строк, так как он дает разработчику полный контроль над процессом восстановления данных.