Книга знаний

1С:Предприятие / v8

v8: Отлов события "Обновление динамического списка". Проблема и решение.

Динамический список на форме может измениться по множеству причин: изменение отбора, добавление или удаление объектов, нажатие кнопки "обновить" в форме. Статья описывает, как отловить момент изменения списка и назначить свой обработчик.Автор статьи: aka clappa | Редакторы: Гений 1С
Последняя редакция №3 от 03.08.06 | История
URL: http://kb.mista.ru/article.php?id=316

Ключевые слова: Динамический список, СправочникСписок, ДокументСписок, Событие, Обновление


Начало здесь:
v8: Как отловить события "Обновление списка", "Изменение данных списка"?

Задача состояла в следующем. В форме динамического списка необходимо было показать итоговую информацию. К примеру, в журнале накладных вывести количество накладных и общую сумму. Получить необходимые данные не составляло труда - формировался запрос с условием, соответствующим текущему отбору списка и запускался на выполнение. Но неожиданно проблемой оказалось обеспечение "синхронности" между данными списка и итоговой информацией.

То есть, данные динамического списка могут изменяться по различным причинам:
1. Изменился отбор списка
2. Пользователь в текущем сеансе изменил или удалил объект из списка
3. Пользователь нажал в форме кнопку "Обновить" (либо настроено автоматическое обновление списка)
Соответственно, хотелось бы после каждого такого события сразу пересчитывать служебную информацию.

Варианты решения были следующие:

1. Использовать событие формы "ОбновлениеОтображения".
Это событие действительно реагирует на любое изменение динамического списка. Но оно также вызывается и во многих других случаях. В частности, при смене страницы формы, при активизации строки в списке (если назначена обработка этих событий) и т.д. Короче, использование этого варианта вызывало приличные тормоза из-за перерасчета итогов после каждого чиха пользователя.

2. Использовать событие табличного поля "ПриПолученииДанных".
Вариант имеет такие же недостатки, как и предыдущий, хотя и в меньшей степени. Т.е. событие (а значит, и перерасчет итогов) выполнялось не только когда данные списка действительно менялись, но и при прокрутке списка, и даже при изменении размеров формы.

3. Добавить в форму кнопочку "Рассчитать", чтобы пользователь жал на неё всякий раз, когда ему потребуются актуальные итоги по списку. Коряво, неудобно, но приходилось поступать именно так, пока не появился ВАРИАНТ 4.

Хотя решение немного через .опу

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

Общий модуль
-------------

// Подключает процедуру-обработчик, вызываемую во всех случаях обновления данных списка
// Процедура будет вызываться при первоначальном получении данных, выполнении команды формы "Обновить",
// изменении отбора или сортировки списка, изменении или удалении элементов списка.
//
// Параметры
//  Форма        – Объект "Форма"
//  Данные       - Строка - Имя реквизита формы, представляющего статический или динамический список
//  ИмяПроцедуры - Имя процедуры, подключаемой в качестве обработчика. Процедура должна располагаться
//                 в модуле формы и иметь два параметра (параметры бесполезны, их наличие необходимо
//                 только для правильной работы механизма)
//
Процедура ПодключитьОбработчикОбновленияСписка(Форма, Данные, ИмяПроцедуры) Экспорт
    Перем ИмяЭлемента, Элемент;
    ИмяЭлемента = "КонтрольСписка_" + СтрЗаменить(СтрЗаменить(Данные + "_" + ИмяПроцедуры, " ", ""), ".", "");
    Элемент = Форма.ЭлементыФормы.Найти(ИмяЭлемента);
    Если Элемент = Неопределено Тогда
        Элемент = Форма.ЭлементыФормы.Добавить(Тип("ТабличноеПоле"), ИмяЭлемента, Ложь);
        Элемент.Данные = Данные;
        Элемент.Ширина = 25;            //Без этого 1С не хочет получать данные в элемент
        Элемент.ПорядокОтображения = 0; //Если не "загнать" элемент на самый низ, другие элементы не смогут получить свои данные из того же источника
        Элемент.Колонки.Добавить();     //Без добавления хотя бы одной колонки, 1С иногда пытается получить данные два раза подряд
    КонецЕсли;
    Элемент.УстановитьДействие("ПриПолученииДанных", Новый Действие(ИмяПроцедуры));
КонецПроцедуры

// Отключает процедуру-обработчик, подключенную раннее методом ПодключитьОбработчикОбновленияСписка(...)
//
// Параметры
//  Форма        – Объект "Форма"
//  Данные       - Строка - Имя реквизита формы, представляющего статический или динамический список
//  ИмяПроцедуры - Имя процедуры-обработчика
//
Процедура ОтключитьОбработчикОбновленияСписка(Форма, Данные, ИмяПроцедуры) Экспорт
    Перем ИмяЭлемента, Элемент;
    ИмяЭлемента = "КонтрольСписка_" + СтрЗаменить(СтрЗаменить(Данные + "_" + ИмяПроцедуры, " ", ""), ".", "");
    Элемент = Форма.ЭлементыФормы.Найти(ИмяЭлемента);
    Если Элемент <> Неопределено Тогда
        //Если попытаться удалить элемент - другие элементы перестанут получать свои данные
        Элемент.УстановитьДействие("ПриПолученииДанных", Неопределено);
    КонецЕсли;
КонецПроцедуры


Модуль формы
-------------

Процедура ПриОткрытии()
    ПодключитьОбработчикОбновленияСписка(ЭтаФорма, "ДокументСписок", "ДокументСписокПриОбновлении")
КонецПроцедуры

Процедура ДокументСписокПриОбновлении(Элемент, ОформленияСтрок)
    <собственно расчет итогов по списку, ради которого всё и затевалось>
КонецПроцедуры




Теперь ложка дёгтя.
Алгоритм не отрабатывает ситуацию, когда список полностью очищается. То есть, в списке были какие-то строки, а потом пользователь все строки удалил, либо задал такой отбор, при котором в список не попадает ни один объект. Как оказалось, 1С просто не вызывает событие "ПриПолученииДанных" для пустого списка.

Чтобы обойти эту проблему, пришлось погрузиться в .опу уже по локоть.
Используя событие "ПриАктивизацииСтроки" нашего вспомогательного табличного поля, можно отловить ситуацию, когда свойство ТекущаяСтрока табличного поля меняет значение на Неопределено, сигнализируя, что в списке больше нет ни одной строки. Но очередной сюрприз от платформы - приняв значение Неопределено, ТекущаяСтрока "фиксируется" на нем, и не меняется, даже когда в списке вновь появляются объекты. То есть, если не поменять значение текущей строки, второй раз событие "ПриАктивизацииСтроки" уже не сработает.
Трагедия в том, что узнать, что список вновь заполнен, можно только в "ПриПолученииДанных", а изменить текущую строку во время обработки данного события невозможно - 1С вылетает с критической ошибкой (желающие могут попробовать). Помогли только пляски с шаманским бубном вокруг компьютера.

В итоге, получилось следующее:

Общий модуль
-------------

// Подключает процедуру-обработчик, вызываемую во всех случаях обновления данных списка
// Процедура будет вызываться при первоначальном получении данных, выполнении команды формы "Обновить",
// изменении отбора или сортировки списка, изменении или удалении элементов списка.
//
// Параметры
//  Форма        – Объект "Форма"
//  Данные       - Строка - Имя реквизита формы, представляющего статический или динамический список
//  ИмяПроцедуры - Имя процедуры, подключаемой в качестве обработчика. Процедура должна располагаться
//                 в модуле формы и иметь два параметра (первый параметр должен иметь имя "Элемент", второй - бесполезен).
//                 ВНИМАНИЕ: Первой строкой процедуры должен идти следующий код:
//
//                   Если Элемент.ТекущаяСтрока = Неопределено Тогда Элемент.ТолькоПросмотр = Элемент.ТолькоПросмотр КонецЕсли;
//
//                 Также в модуле формы должна присутствовать процедура с одним параметром "Элемент" и с именем, состоящим из
//                 ИмяПроцедуры и слова "Вспомогательная", например, "СправочникСписокПриОбновленииВспомогательная(Элемент)",
//                 и имеющая следующий вид:
//
//                   Процедура <ИмяПроцедуры>Вспомогательная(Элемент)
//                       Если Элемент.ТекущаяСтрока = Неопределено Тогда
//                           Выполнить(Строка(Элемент.ПолучитьДействие("ПриПолученииДанных")) + "(Элемент, Неопределено)");
//                       КонецЕсли
//                   КонецПроцедуры
//
Процедура ПодключитьОбработчикОбновленияСписка(Форма, Данные, ИмяПроцедуры) Экспорт
    Перем ИмяЭлемента, Элемент;
    ИмяЭлемента = "КонтрольСписка_" + СтрЗаменить(СтрЗаменить(Данные + "_" + ИмяПроцедуры, " ", ""), ".", "");
    Элемент = Форма.ЭлементыФормы.Найти(ИмяЭлемента);
    Если Элемент = Неопределено Тогда
        Элемент = Форма.ЭлементыФормы.Добавить(Тип("ТабличноеПоле"), ИмяЭлемента, Ложь);
        Элемент.Данные = Данные;
        Элемент.Ширина = 25;            //Без этого 1С не хочет получать данные в элемент
        Элемент.ПорядокОтображения = 0; //Если не "загнать" элемент на самый низ, другие элементы не смогут получить свои данные из того же источника
        Элемент.Колонки.Добавить();     //Без добавления хотя бы одной колонки, 1С иногда пытается получить данные два раза подряд
    КонецЕсли;
    //Подключим обработчик
    Элемент.УстановитьДействие("ПриПолученииДанных", Новый Действие(ИмяПроцедуры));
    Попытка Элемент.УстановитьДействие("ПриАктивизацииСтроки", Новый Действие(ИмяПроцедуры + "Вспомогательная")) Исключение КонецПопытки;
КонецПроцедуры

// Отключает процедуру-обработчик, подключенную раннее методом ПодключитьОбработчикОбновленияСписка(...)
//
// Параметры
//  Форма        – Объект "Форма"
//  Данные       - Строка - Имя реквизита формы, представляющего статический или динамический список
//  ИмяПроцедуры - Имя процедуры-обработчика
//
Процедура ОтключитьОбработчикОбновленияСписка(Форма, Данные, ИмяПроцедуры) Экспорт
    Перем ИмяЭлемента, Элемент;
    ИмяЭлемента = "КонтрольСписка_" + СтрЗаменить(СтрЗаменить(Данные + "_" + ИмяПроцедуры, " ", ""), ".", "");
    Элемент = Форма.ЭлементыФормы.Найти(ИмяЭлемента);
    Если Элемент <> Неопределено Тогда
        //Если попытаться удалить элемент - другие элементы перестанут получать свои данные
        Элемент.УстановитьДействие("ПриПолученииДанных", Неопределено);
        Элемент.УстановитьДействие("ПриАктивизацииСтроки", Неопределено);
    КонецЕсли;
КонецПроцедуры


Модуль формы
-------------

Процедура ПриОткрытии()
    ПодключитьОбработчикОбновленияСписка(ЭтаФорма, "ДокументСписок", "ДокументСписокПриОбновлении")
КонецПроцедуры

Процедура ДокументСписокПриОбновлении(Элемент, ОформленияСтрок)
    Если Элемент.ТекущаяСтрока = Неопределено Тогда Элемент.ТолькоПросмотр = Элемент.ТолькоПросмотр КонецЕсли; //Пляски с бубном
    <собственно расчет итогов по списку, ради которого всё и затевалось>
КонецПроцедуры

Процедура ДокументСписокПриОбновленииВспомогательная(Элемент)
    Если Элемент.ТекущаяСтрока = Неопределено Тогда
        Выполнить(Строка(Элемент.ПолучитьДействие("ПриПолученииДанных")) + "(Элемент, Неопределено)");
    КонецЕсли
КонецПроцедуры


Замечание от Гений1С:
Можно попробовать подключить функцию изменения данных ПодключитьОбработчикИзмененияДанных на данные списка.

Замечание от clappa:
Мне удалось использовать ПодключитьОбработчикИзмененияДанных только для контроля за изменением отбора динамического списка. В других перечисленных случаях этот метод бесполезен. В частности, изменение порядка он "не ловит".


Описание | Рубрикатор | Поиск | ТелепатБот | Захваченные статьи | Установки | Форум
© Станислав Митичкин (Волшебник), 2005-2025 | Mista.ru

Яндекс.Метрика