Как реализовать программную отмену изменений (Undo) при замене текста в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке интерфейсов в 1С часто возникает задача автоматизировать редактирование текста. Например, пользователь выделяет слово, нажимает кнопку, и это слово обрамляется какими-либо префиксами или комментариями. Однако стандартная функция отмены Ctrl+Z не всегда корректно отрабатывает программные изменения. В этой статье мы разберем, как реализовать надежный механизм «отката» изменений с использованием стека и уникальных маркеров.

Проблема программной замены выделенного текста

Рассмотрим стандартную ситуацию: у нас есть поле ввода с текстом. Пользователь выделяет фрагмент и нажимает кнопку, которая должна добавить к этому фрагменту определенные символы. Основная сложность заключается в том, что после программного присвоения значения свойству ВыделенныйТекст, выделение в поле ввода сбрасывается. Если мы хотим «вернуть как было» по другой кнопке (или по той же самой), нам нужно точно знать, какой фрагмент был изменен и где он находился.

Проанализируем работу со свойством ВыделенныйТекст. Согласно документации 1С:

  1. При чтении свойство возвращает текст, который выделил пользователь.
  2. При записи новый текст заменяет выделенный фрагмент.
  3. Важно: После записи выделение сбрасывается, а курсор устанавливается в конец вставленного текста.

Вариант 1. Использование системных команд Windows (SendKeys)

Один из самых простых способов, предложенных в обсуждении, — это имитация нажатия комбинации клавиш Ctrl+Z. Для этого используется COM-объект Wscript.shell.


лкСенд = Новый COMОбъект("Wscript.shell");
лкСенд.SendKeys("^z"); // Имитация нажатия Ctrl+Z

Однако этот метод имеет ряд существенных недостатков:

Вариант 2. Реализация собственного стека изменений

Более профессиональный подход заключается в самостоятельном хранении истории изменений. Мы можем создать список значений (стек), где будем хранить состояние строки до её изменения.

Разберем логику: перед тем как заменить текст, мы фиксируем, что было в выделении, сколько символов находилось слева от него и сколько справа. Чтобы корректно найти место вставки в общей строке, можно использовать временную «соль» — уникальную последовательность символов.

Рассмотрим пример кода для выполнения замены:


&НаКлиенте
Перем Соль; // Переменная для хранения уникального маркера

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    Соль = Символ(165); // Используем экзотический символ (например, знак иены)
КонецПроцедуры

&НаКлиенте
Процедура ВставитьТекст(Команда)
    // 1. Получаем исходный выделенный текст
    БылТекст = Элементы.РеквизитТекст.ВыделенныйТекст;
    
    // 2. Формируем временный маркер для определения позиции
    СталТекст = БылТекст + Соль;
    
    // 3. Заменяем выделение на маркер, чтобы вычислить позицию
    Элементы.РеквизитТекст.ВыделенныйТекст = СталТекст;
    
    // 4. Вычисляем координаты (сколько символов слева и справа)
    ПолныйТекст = Объект.РеквизитТекст;
    Слева = СтрНайти(ПолныйТекст, СталТекст) - 1;
    Справа = СтрДлина(ПолныйТекст) - Слева - СтрДлина(СталТекст);
    
    // 5. Сохраняем данные в стек (СписокЗначений)
    // Формат: ИсходныйТекст + Соль + ПозицияСлева + Соль + ПозицияСправа
    ДанныеДляОтмены = БылТекст + Соль + Строка(Слева) + Соль + Строка(Справа);
    СтекИзменений.Добавить(ДанныеДляОтмены);
    
    // 6. Выполняем финальную замену на нужный нам текст (с префиксами)
    ВставкаСлева = "11 ";
    ВставкаСправа = " (работы)";
    Объект.РеквизитТекст = СтрЗаменить(ПолныйТекст, СталТекст, ВставкаСлева + БылТекст + ВставкаСправа);
КонецПроцедуры

Теперь разберем процедуру отмены. Нам нужно извлечь последнюю запись из стека и собрать строку заново, используя функции Лев() и Прав().


&НаКлиенте
Процедура ОтменитьПоследнееДействие(Команда)
    Если СтекИзменений.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;

    // Извлекаем последнюю запись
    ИндексПоследнего = СтекИзменений.Количество() - 1;
    Данные = СтекИзменений[ИндексПоследнего].Значение;
    
    // Разбиваем строку по нашей "соли"
    МассивДанных = СтрРазделить(Данные, Соль, Ложь);
    
    // Если выделение было пустым, массив может иметь меньше элементов, обрабатываем это
    Если МассивДанных.Количество() < 3 Тогда
        ИсходныйФрагмент = "";
        ПозСлева = Число(МассивДанных[0]);
        ПозСправа = Число(МассивДанных[1]);
    Иначе
        ИсходныйФрагмент = МассивДанных[0];
        ПозСлева = Число(МассивДанных[1]);
        ПозСправа = Число(МассивДанных[2]);
    КонецЕсли;

    // Собираем текст обратно в исходное состояние
    ТекстСлева = Лев(Объект.РеквизитТекст, ПозСлева);
    ТекстСправа = Прав(Объект.РеквизитТекст, ПозСправа);
    
    Объект.РеквизитТекст = ТекстСлева + ИсходныйФрагмент + ТекстСправа;

    // Удаляем использованную запись из стека
    СтекИзменений.Удалить(ИндексПоследнего);
КонецПроцедуры

Почему это работает лучше, чем обычный поиск и замена?

Проанализируем ситуацию: в тексте может быть несколько одинаковых слов «деятельность». Если мы просто используем СтрЗаменить(), программа может заменить не то вхождение слова, которое мы редактировали последним. Используя расчет символов Слева и Справа, мы гарантируем, что восстанавливаем текст именно в той позиции, где он был изменен, игнорируя идентичные слова в других частях предложения.

Важные нюансы реализации:

Дополнительные рекомендации

Рассмотрим случай, когда пользователь может вручную редактировать текст между нажатиями кнопок «Применить» и «Отменить». В этом случае жесткая привязка к количеству символов слева и справа может привести к ошибке.

Для более продвинутых решений рекомендуем:

  1. Использовать невидимый символ Unicode Zero Width Space (U+200B) в качестве невидимого маркера прямо в тексте. Это позволит находить место замены даже при изменении остального текста.
  2. При каждой замене сохранять копию всей строки целиком в стек. Это требует больше оперативной памяти, но исключает любые ошибки позиционирования.
  3. Проверять свойство Модифицированность формы, чтобы понимать, были ли внесены изменения пользователем.

Таким образом, мы проанализировали несколько подходов к созданию функции Undo в 1С. Наиболее стабильным является метод с программным стеком и фиксацией длин строк, так как он дает разработчику полный контроль над процессом восстановления данных.

← На главную