При автоматизации формирования документов MS Word из среды 1С программисты часто сталкиваются с ситуацией, когда код, успешно работавший на коротких документах, начинает «падать» с ошибкой при увеличении объема данных. Типичный пример такой ошибки: «Произошла исключительная ситуация (MS): Метод или свойство SelectRow недоступны, потому что некоторые или все объекты не ссылаются на таблицу». Это происходит в тот момент, когда заполняемая таблица переходит на следующую страницу. В этой статье мы подробно разберем, почему это случается и как переписать код так, чтобы он работал стабильно в любых условиях.
Давайте проанализируем ситуацию. В большинстве примеров кода, которые можно найти в сети, используется объект Selection. Этот объект имитирует действия пользователя: он «двигает курсор», «выделяет текст», «копирует и вставляет». Когда мы используем методы MoveDown(), SelectRow() и Paste(), мы жестко привязываемся к визуальному представлению документа.
Как только таблица становится настолько большой, что ее очередная строка не помещается на текущей странице, MS Word выполняет автоматическую разметку. В этот момент «виртуальный курсор» 1С может оказаться в технической области между страницами, на разрыве страницы или в пустом абзаце после таблицы. Поскольку метод SelectRow() может быть вызван только тогда, когда курсор физически находится внутри ячейки таблицы, попытка его вызова вне контекста таблицы приводит к закономерному отказу системы. Программа «теряет» таблицу, и выполнение прерывается.
Самый правильный и профессиональный способ решения проблемы — это отказ от использования «курсора» (Selection) и закладок (Bookmarks) в пользу прямого обращения к объектам структуры документа. Мы будем работать с коллекцией Tables. Это позволит 1С обращаться к конкретной ячейке или строке таблицы по ее индексу, независимо от того, на какой странице она находится и видна ли она сейчас на экране.
Рассмотрим по шагам, как изменить логику заполнения. Вместо того чтобы искать закладку и «прыгать» по строкам, мы получим объект таблицы и будем добавлять в него новые строки программно.
// Получаем доступ к приложению Word и документу
Word = Новый COMОбъект("Word.Application");
Документ = Word.Documents.Open(ПутьКФайлу);
// Вместо поиска закладки, обращаемся к нужной таблице по индексу или через закладку без выделения
// Если в документе несколько таблиц, нужно знать номер нужной (например, вторая по счету)
Если Документ.Tables.Count >= 2 Тогда
Таблица = Документ.Tables.Item(2); // Обращаемся к Таблице №2
Иначе
Возврат;
КонецЕсли;
// Эталонная строка (например, вторая, если первая — заголовок), которую мы будем копировать
ЭталоннаяСтрока = Таблица.Rows.Item(2);
Для Каждого СтрокаДанных Из ДанныеДляЗаполненияТаблицы Цикл
// Добавляем новую строку в конец таблицы
НоваяСтрока = Таблица.Rows.Add();
// Переносим форматирование из эталонной строки в новую
// Это гораздо быстрее и надежнее, чем Paste()
НоваяСтрока.Range.FormattedText = ЭталоннаяСтрока.Range.FormattedText;
// Заполняем ячейки новой строки напрямую
НоваяСтрока.Cells(1).Range.Text = Строка(СтрокаДанных.Номер);
НоваяСтрока.Cells(2).Range.Text = Строка(СтрокаДанных.Номенклатура);
НоваяСтрока.Cells(3).Range.Text = Строка(СтрокаДанных.Цена * СтрокаДанных.Количество);
НоваяСтрока.Cells(4).Range.Text = Строка(СтрокаДанных.СрокОказания);
КонецЦикла;
// После заполнения можно удалить эталонную строку с шаблонами-метками
ЭталоннаяСтрока.Delete();
Преимущества этого метода:
Range.Text работает в несколько раз быстрее, чем поиск и замена (Find.Execute).Иногда нам важно сохранить сложное форматирование внутри ячеек (разные шрифты, цвета, жирность отдельных слов). В этом случае мы можем использовать свойство FormattedText. Проанализируем, как это работает: мы берем «слепок» оформления одной строки и накладываем его на другую.
Рассмотрим пример кода, который автоматизирует этот процесс:
// Допустим, у нас есть шаблонная строка с метками {Наименование}, {Цена}
ШаблоннаяСтрока = Таблица.Rows.Item(2);
Для Сч = 0 По ДанныеДляЗаполненияТаблицы.Количество() - 1 Цикл
ТекущиеДанные = ДанныеДляЗаполненияТаблицы[Сч];
// Создаем новую пустую строку в таблице
НоваяСтрока = Таблица.Rows.Add();
// Копируем оформление вместе с текстом (метками)
НоваяСтрока.Range.FormattedText = ШаблоннаяСтрока.Range.FormattedText;
// Выполняем замену меток только в пределах ОДНОЙ конкретной строки
// Это исключает порчу данных в других частях документа
ОбластьПоиска = НоваяСтрока.Range;
ОбластьПоиска.Find.Execute("{НомП}", , , , , , , , , Строка(Сч + 1), 2);
ОбластьПоиска.Find.Execute("{Наименование}", , , , , , , , , ТекущиеДанные.Номенклатура, 2);
КонецЦикла;
// Удаляем исходную строку-шаблон
ШаблоннаяСтрока.Delete();
Если проект слишком большой и переписывать всё на работу с объектами Table нет времени, можно применить обходной путь. Проблема исходного кода автора темы заключалась в неудачном сочетании MoveUp() и SelectRow() в цикле. Выясним причину: когда мы вставляем строку и перемещаемся вверх, мы рассчитываем оказаться в таблице, но при разрыве страницы курсор может «промахнуться».
Рассмотрим вариант исправления логики внутри цикла, который предложил автор темы в качестве временного решения. Идея в том, чтобы изменить порядок действий: сначала заполнять текущую строку, а потом вставлять новую пустую строку под ней, используя проверку индекса.
Для Сч = 0 по ДанныеДляЗаполненияТаблицы.Количество()-1 Цикл
// Если это не первая итерация, подготавливаем место
Если Сч <> 0 Тогда
Документ.Application.Selection.MoveDown(); // Переход на новую пустую строку
Документ.Application.Selection.Paste(); // Вставка шаблона из буфера
Документ.Application.Selection.MoveUp(); // Возврат в ячейку для заполнения
КонецЕсли;
// Заполнение меток в текущей позиции курсора
Документ.Application.Selection.Find.Execute("{НомП}",,,,,,,,,Строка(Сч+1),2);
Документ.Application.Selection.Find.Execute("{НаименованиеП}",,,,,,,,,Строка(ДанныеДляЗаполненияТаблицы[Сч].Номенклатура),2);
// ... заполнение остальных полей ...
// После заполнения первой строки спускаемся вниз, чтобы быть готовым к следующей итерации
Если Сч = 0 Тогда
Документ.Application.Selection.MoveDown();
КонецЕсли;
КонецЦикла;
Важное замечание: Этот метод все еще использует Selection и остается крайне чувствительным к версии Word и настройкам разметки. Используйте его только в крайнем случае.
Чтобы окончательно победить ошибки взаимодействия с внешними приложениями, рекомендуем придерживаться следующих правил:
Документ.Application.ScreenUpdating = Ложь;. Это не только ускорит работу в 2-3 раза, но и предотвратит множество ошибок, связанных с перерисовкой интерфейса при переходе между страницами. В конце не забудьте вернуть значение Истина.Строка(), прежде чем передавать в Word. COM-объекты чувствительны к типам данных 1С, и передача, например, числа или даты без преобразования может вызвать неявную ошибку.Word.Visible = Истина;, но для работы пользователей лучше оставлять Ложь. Это предотвратит случайное вмешательство пользователя в процесс формирования (например, если он нажмет мышкой в документ во время работы кода 1С).Подводя итог, можно сказать, что ошибка SelectRow — это классический сигнал к тому, что пора переходить от записи макросов (которые генерируют код с Selection) к полноценному объектному программированию через Rows, Cells и Range. Это сделает ваши отчеты надежными и профессиональными.