Изменяющееся контекстное меню в 1С 8.3

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

И так, я не буду приводить реальную конфигурацию, где возникла эта задача (она достаточно специфична), а создам небольшую учебную конфигурацию, в которой может возникнуть подобная задача. Конфигурация без изысков, учебная, призванная показать метод решения. В этой конфигурации имеется два справочника «Склады» и «Номенклатура», а также документ «Приход» с табличной частью «Список товаров» (с реквизитами «Склад», «Номенклатура» и «Количество» с соответствующими типами).

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

Создадим форму списка документа Приход, а на этой форме у таблицы Список создадим событие ПриАктивизацииЯчейки (в клиентском контексте).

В этом событии мы будем работать с текущей строкой таблицы формы Список, это свойство таблицы будет содержать ссылку на документ из выделенной строки.  Чтобы у нас, не происходило лишний раз перерисовывание контекстного меню,  когда выделана одна и та же строка, но происходит активация разных её ячеек, будем записывать данные текущей строки в глобальную переменную модуля формы,  и в начале процедуры будем проверять, не равна ли эта переменная значению в текущей строке, и если равна, то завершим действие процедуры-обработчика.

Получится следующий код:

&НаКлиенте
Перем ТекущаяСтрока;

&НаКлиенте
Процедура СписокПриАктивизацииЯчейки(Элемент)

Если ТекущаяСтрока = Элементы.Список.ТекущаяСтрока Тогда
Возврат;
КонецЕсли;

ТекущаяСтрока = Элементы.Список.ТекущаяСтрока;

КонецПроцедуры

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

&НаКлиенте
Перем ТекущаяСтрока;

&НаКлиенте
Процедура СписокПриАктивизацииЯчейки(Элемент)

Если ТекущаяСтрока = Элементы.Список.ТекущаяСтрока Тогда
Возврат;
КонецЕсли;
ТекущаяСтрока = Элементы.Список.ТекущаяСтрока;
Если ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
ДобавитьСкладыВМенюСписка(ТекущаяСтрока)

КонецПроцедуры

&НаСервере
Процедура ДобавитьСкладыВМенюСписка(ДокументПриход)

КонецПроцедуры

В этой процедуре создадим запрос, в котором получим сгруппированный список складов  с наименованиями из документа прихода.

&НаСервере
Процедура ДобавитьСкладыВМенюСписка(ДокументПриход)
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
|    ПриходСписокТоваров.Склад КАК Склад,
|    Склады.Наименование КАК НаименованиеСклада,
|    Склады.Код КАК КодСклада
|ИЗ
|    Документ.Приход.СписокТоваров КАК ПриходСписокТоваров
|        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Приход КАК Приход
|        ПО ПриходСписокТоваров.Ссылка = Приход.Ссылка
|        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Склады КАК Склады
|        ПО ПриходСписокТоваров.Склад = Склады.Ссылка
|ГДЕ
|    Приход.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
|    ПриходСписокТоваров.Склад,
|    Склады.Наименование,
|    Склады.Код";

Запрос.УстановитьПараметр("Ссылка",ДокументПриход);
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл

КонецЦикла;
КонецПроцедуры

Внутри цикла выборки создаем имена для команд, которые связаны со складами, команды, а также элементы формы с типом КнопкаФормы, которые разместим в группе Элементы.Список.КонтекстноеМеню

Пока Выборка.Следующий() Цикл
ИмяКоманды = "ОткрытиеСклада_" + Выборка.КодСклада;

Команда = Команды.Добавить(ИмяКоманды);
Команда.Заголовок = "Открыть склад: " + Выборка.НаименованиеСклада;
Команда.Действие  = "ОткрытиеФормыСкладаИзКонтекстногоМеню";

КнопкаКоманды = Элементы.Добавить("КнопкаКоманды" + ИмяКоманды, Тип("КнопкаФормы"),Элементы.Список.КонтекстноеМеню);
КнопкаКоманды.ИмяКоманды = ИмяКоманды;
КонецЦикла;

Заметьте, все команды я привязал к одному действию, это будет процедура, которую мы создадим ниже, и в которой будет код на открытие формы того или иного склада.

Чтобы однозначно определить форму какого склада открывает та или иная команда, мы свяжем имя команды с ссылкой на склад.  Для этого в реквизитах формы создадим таблицу значений, которую назовем СвязьКомандыИСклада, у этой таблицы будут две колонки: ИмяКоманды и Склад.

Будем заполнять эту таблицу в конце цикла.

НовСтрока = СвязьКомандыИСклада.Добавить();
НовСтрока.ИмяКоманды = ИмяКоманды;
НовСтрока.Склад = Выборка.Склад;

Создадим обработчик события, который будет выполняться во время отработки команды из контекстного меню.  Мы уже определили его имя, привязав к действию команды ранее. В этом обработчике нам надо получить ссылку на нужный склад, который связан с обрабатываемой командой.  Для этого, используя параметр процедуры Элемент, получим имя отрабатываемой команды, по имени команды найдем из таблицы значений ссылку на нужный склад и откроем форму этого склада.

&НаКлиенте
Процедура ОткрытиеФормыСкладаИзКонтекстногоМеню(Элемент)
ИмяКоманды = Элемент.Имя;
Отбор = Новый Структура("ИмяКоманды",ИмяКоманды);
МассивСкладов = СвязьКомандыИСклада.НайтиСтроки(Отбор);

Если МассивСкладов.Количество() = 0 Тогда
Возврат;
КонецЕсли;

СсылкаНаСклад = МассивСкладов[0].Склад;

ПараметрыОткрытия = Новый Структура("Ключ",СсылкаНаСклад);

ОткрытьФорму("Справочник.Склады.ФормаОбъекта",
ПараметрыОткрытия,,,,,,
РежимОткрытияОкнаФормы.БлокироватьОкноВладельца);
КонецПроцедуры

Нам осталось в начале процедуры ДобавитьСкладыВМенюСписка очищать команды и элементы формы, которые мы создали ранее.

Для Каждого стрКоманда из СвязьКомандыИСклада Цикл
ИмяКоманды = стрКоманда.ИмяКоманды;
ИмяКнопки  = "КнопкаКоманды" + ИмяКоманды;
Команды.Удалить(Команды[ИмяКоманды]);
Элементы.Удалить(Элементы[ИмяКнопки]);
КонецЦикла;
СвязьКомандыИСклада.Очистить();

Все теперь наше контекстное меню будет динамически изменяться в зависимости от того, какие склады имеются в табличной части документа.

Пример выполнен на платформе 8.3.13.1513

14 Comments

  1. VmvLer

    в событии ПриАктивизацииЯчейки ячейки динамического списка выполнять запрос на приходы каждый раз?

    ну-ну, если приходов в день тысячи, а месяц десятки тысяч, то этот костыль потеряет все свое очарование.

    Откровенно говоря меня смущает, что разработчики и их адепты делают только виды — на песочной демке сделают

    красиво, а что будет на реальной базе в сотни гиг — па-барабану.

    Пора в 1С вводить новое свойство функционала: ГлубинаТестированияТаблиц со значениями {Фигня какая-то, Проверено да демке, Проверено в реале на 100К записей, Проверено в реале на 100КК записей}.

    Это станет способствовать более высокой степени доверия между бизнесом и разработчиками.

    Кроме того,

    в ПриАктивизацииЯчейки нельзя использовать серверные методы. Да, иногда они работают через пень-колоду,

    но сама передача управления между контекстами в таком обработчике — это уже не двойка, а кол!

    Хоть бы во внеконтекст завернули.

    Reply
  2. signum2009

    (1) Вы внимательно текст смотрели, или просто оставить коммент ради хейтинга?

    1) Пример с запросом приведен для удобства дабы не городить огород, основная тема как работать с контекстным меню, а не как оптимально делать запрос;

    2) Серверные методы работают нормально, если перед выполнением кода в ПриАктивизацииЯчейки проверять на повторение текущей строки.

    Reply
  3. acanta

    Классный пример, спасибо большое.

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

    Правильно ли я понимаю, что платформа перехватывает метод активизации ячейки, а вовсе не правый/левый клик мышки и меню обновляется при перемещении стрелками вверх-вниз-право-лево по списку?

    Reply
  4. signum2009

    (3) оно обновляется, только когда активизируется другая строка (или ячейка другой строки), для этого в самом начале выполняется проверка:

        Если ТекущаяСтрока = Элементы.Список.ТекущаяСтрока Тогда
    Возврат;
    КонецЕсли;
    Reply
  5. VmvLer

    (2) Чистые серверные(иногда можно использовать внеконтекстные) методы в обработчиках активизации — это условное зло с костылями, как вы указали.

    И какой бы няшной ни казалась реализация, увы, на реальных базах — это зло абослютное.

    Это мое объективное мнение.

    Если я захочу ублажить какую-то блонди в ларьке, то я с удовольствием использую вашу идею.

    Все остальное мимо и относительно.

    Reply
  6. json

    (1) там у него в запросе отбор стоит по ссылке. А это кластерный индекс (загугли что это значит), Там хоть 100 млн. записей — будет отрабатывать за доли секунды, пользователь не ощутит задержку.

    Ладно бы не разбирался ты в запросах и учился у других. А вместо этого пытаешься умничать и критиковать.

    Reply
  7. the1

    (2) Товарищ конечно часто хейтит, но тут он прав. Зачем соединение с таблицей документа, если ни одного поля из нее не выбираем? Зачем группировка, если ни одно поле не агрегируем? И да, где &БезКонтекста?

    Событие вешать надо на ПриАктивизацииСтроки с подключением обработки ожидания, чтобы не тормозило.

    Спорным является и решение с сопоставлением команд и складов, но тут надо тестировать.

    Reply
  8. dhurricane

    (2) Все таки я бы поостерегся использовать контекстные серверные методы в обработчике активизации строки, т.к. в синтакс-помощнике они явно запрещены:

    ТаблицаФормы (FormTable)

    ПриАктивизацииСтроки (OnActivateRow)

    Синтаксис:

    ПриАктивизацииСтроки()

    Описание:

    Вызывается при активизации строки таблицы.

    Примечание:

    В обработчике данного события нельзя использовать серверные методы формы с директивой компиляции &НаСервере.

    Подсистема БСП «ПодключаемыеКоманды» также предоставляет возможность динамически обновлять меню команд, правда банальным изменением видимости уже созданных кнопок, поэтому серверные вызовы не используются. Тем не менее для такой операции в обработчике активизации строки используется подключение обработчика ожидания с небольшой паузой в 200 мс.

    Reply
  9. logarifm

    Вообщим скажу только одно.

    Решение нормальное — но не оптимальное. Человек же сходу написал, что решал специфичную задачу вот и просто показал свой вариант решения. Ест-но, что когда у тебя в СПИКЕ 100000 ККК записей то это вообще другой подход ВОВСЕ даже штатные подходы нервно курят в уголке. Так что давайте не сортесь девочки…

    Reply
  10. kuzyara

    Код грамотно разбит по статье, приятно читать. Возьму на заметку подход.

    Reply
  11. PerlAmutor

    А я меню создаю с помощью объектов СКД.

    Reply
  12. triviumfan

    (12) Видимо она никому не интересна, т.к. в ПриАктивацииОбласти(), ПриАктивацииСтроки() ещё 10 лет назад создавали меню.

    Reply
  13. MorningStalker

    основная идея статьи понятна и интересна.

    А то, что код нужно оптимизировать — дело тоже нужное, но все же вторичное.

    Кому идея покажется интересной — при использовании сам до ума доведет.

    Reply
  14. Cyberhawk

    (7)

    Событие вешать надо на ПриАктивизацииСтроки с подключением обработки ожидания, чтобы не тормозило

    Как в этом случае предлагается добиваться отображения пунктов в контекстном меню, уже успевшем отобразиться?

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *