Этот метод появился на свет благодаря публикации //infostart.ru/public/16980/ и внедрения конфигурации «Управление торговлей, редакция 11» в не российской торговой компании, из чего следует, что переписано и изменено было очень многое. С выходом каждого нового обновления, для внесения этих изменений, уходило все больше времени и на пике достигало уже до месяца и выливалось в огромные затраты. Так же при выявлении ошибок их нужно было исправлять оперативно и быстро, что со 100-150 одновременно работающими пользователями составляло огромную проблему.
1. Нужно создать общий модуль МодульПереназначенияОбработчиков, вот такой он у меня:
// Общая процедура перед переназначения обработчиков
//
// Параметры:
// УправляемаяФорма – УправляемаяФорма – Управляемая форма.
//
Процедура ПереназначитьОбработчики(УправляемаяФорма) Экспорт
Если ТипЗнч(УправляемаяФорма) = Тип("УправляемаяФорма") Тогда
ОбщаяВнешняяОбработка = ПолучитьВнешнийОбъект("Библиотека внешних обработок");
ВнешняяОбработкаСерийныеНомера = ПолучитьВнешнийОбъект("Серийные номера");
ИмяВнешнейОбработкиСерийныеНомера = ПолучитьИмяВнешнегоОбъекта("Серийные номера");
ВнешняяОбработкаСерийныеНомера.ПодготовитьСерийныеНомераКРаботе(УправляемаяФорма, ОбщаяВнешняяОбработка, ИмяВнешнейОбработкиСерийныеНомера);
ОбщаяВнешняяОбработка.ПодменитьДействиеУправляемоеПриложение(УправляемаяФорма, "ПриЗакрытии", , "ВыполнитьОткатОбработчиков");
//Пример как можно изменить свойства управляемой формы
УправляемаяФорма.ПоведениеКлавишиEnter = ТипПоведенияКлавишиEnter.ПереходПоЭлементамФормы;
КонецЕсли;
КонецПроцедуры
// Получаем внешнюю обработку или внешний отчет.
//
// Параметры:
// ИмяВнешнегоОбъекта – Строка - имя внешнего объекта;
// ЭтоОтчет – Булево – Ложь (Внешняя обработка), Истина (Внешний отчет).
//
// Возвращаемое значение:
// – ВнешняяОбработка или ВнешнийОтчет
//
Функция ПолучитьВнешнийОбъект(ИмяВнешнегоОбъекта, ЭтоОтчет = Ложь) Экспорт
Ссылка = Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию(ИмяВнешнегоОбъекта);
Если Ссылка = Неопределено Тогда
ВызватьИсключение "Внешний объект с именем не найден: " + ИмяВнешнегоОбъекта;
КонецЕсли;
ДвоичныеДанные = Ссылка.ХранилищеОбработки.Получить();
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
ИмяВнОбработки = ?(ЭтоОтчет, ВнешниеОтчеты, ВнешниеОбработки).Подключить(АдресХранилища, , Ложь);
ОбработкаОбъект = ?(ЭтоОтчет, ВнешниеОтчеты, ВнешниеОбработки).Создать(ИмяВнОбработки);
Возврат ОбработкаОбъект;
КонецФункции
// Получаем имя внешней обработки или внешнего отчета.
//
// Параметры:
// ИмяВнешнегоОбъекта – Строка - имя внешнего объекта;
// ЭтоОтчет – Булево – Ложь (Внешняя обработка), Истина (Внешний отчет).
//
// Возвращаемое значение:
// – Строка - Имя внешней обработки или отчета.
//
Функция ПолучитьИмяВнешнегоОбъекта(ИмяВнешнегоОбъекта, ЭтоОтчет = Ложь) Экспорт
Ссылка = Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию(ИмяВнешнегоОбъекта);
Если Ссылка = Неопределено Тогда
ВызватьИсключение "Внешний объект с именем не найден: " + ИмяВнешнегоОбъекта;
КонецЕсли;
ДвоичныеДанные = Ссылка.ХранилищеОбработки.Получить();
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
Возврат ?(ЭтоОтчет, ВнешниеОтчеты, ВнешниеОбработки).Подключить(АдресХранилища, , Ложь);
КонецФункции
// Получаем ссылку на элемент справочника ДополнительныеОтчетыИОбработки.
//
// Параметры:
// ИмяВнешнегоОбъекта – Строка - имя внешнего объекта;
//
//
// Возвращаемое значение:
// – СправочникСсылка.ДополнительныеОтчетыИОбработки - ссылка на элемент справочника ДополнительныеОтчетыИОбработки.
//
Функция ПолучитьСсылкуНаВнешнийОбъект(ИмяВнешнегоОбъекта) Экспорт
Возврат Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию(ИмяВнешнегоОбъекта);
КонецФункции
2. Далее, нужно загрузить общую внешнюю обработку в режиме предприятия. Код обработки:
// Функция добавляет элемент в коллекцию элементов формы и возращает его.
//
// Параметры:
// ЭлементыФормы – ВсеЭлементыФормы – Содержит коллекцию всех элементов управляемой формы;
// Параметры – Структура – параметры добавляемого элемента;
// Родитель – ГруппаФормы, ТаблицаФормы, УправляемаяФорма – родитель добавляемого элемента.
//
// Возвращаемое значение:
// – ДекорацияФормы, ГруппаФормы, КнопкаФормы, ТаблицаФормы, ПолеФормы – элемент управляемой формы.
//
Функция ДобавитьЭлементВКоллекциюЭлементовФормы(ЭлементыФормы, Параметры, Родитель = Неопределено) Экспорт
Перем ИмяЭлемента, ТипЭлемента;
Если НЕ ТипЗнч(Параметры) = Тип("Структура") Тогда
ВызватьИсключение "Параметры элемента формы не заданы!";
КонецЕсли;
Если НЕ Параметры.Свойство("Имя", ИмяЭлемента) Тогда
ВызватьИсключение "Имя элемента формы не задано!";
КонецЕсли;
Если НЕ Параметры.Свойство("ТипЭлемента", ТипЭлемента) Тогда
ВызватьИсключение "Тип элемента формы не задан!";
КонецЕсли;
Если НЕ Родитель = Неопределено Тогда
Если ТипЗнч(Родитель) <> Тип("ГруппаФормы")
И ТипЗнч(Родитель) <> Тип("ТаблицаФормы")
И ТипЗнч(Родитель) <> Тип("УправляемаяФорма") Тогда
ВызватьИсключение "Родитель должен иметь один из перечисленных типов: ГруппаФормы; ТаблицаФормы; УправляемаяФорма.";
КонецЕсли;
КонецЕсли;
ЭлементФормы = ЭлементыФормы.Добавить(ИмяЭлемента, ТипЭлемента, Родитель);
ЗаполнитьЗначенияСвойств(ЭлементФормы, Параметры);
Возврат ЭлементФормы;
КонецФункции
// Функция добавляет команду в коллекцию команд формы и возращает его.
//
// Параметры:
// Команды – КомандыФормы – Содержит коллекцию всех элементов управляемой формы;
// Параметры – Структура – параметры добавляемой команды.
//
// Возвращаемое значение:
// – КомандаФормы – команда управляемой формы.
//
Функция ДобавитьКомандуВКоллекциюКомандФормы(Команды, Параметры) Экспорт
Перем ИмяЭлемента;
Если НЕ ТипЗнч(Параметры) = Тип("Структура") Тогда
ВызватьИсключение "Параметры команды не заданы!";
КонецЕсли;
Если НЕ Параметры.Свойство("Имя", ИмяЭлемента) Тогда
ВызватьИсключение "Имя команды не задано!";
КонецЕсли;
Команда = Команды.Добавить(ИмяЭлемента);
ЗаполнитьЗначенияСвойств(Команда, Параметры);
Возврат Команда;
КонецФункции
Процедура ПереместитьЭлементВКоллекциюЭлементовФормы(ЭлементыФормы, ИмяЭлемента, ИмяРодителя, МестоРасположения) Экспорт
ЭлементыФормы.Переместить(ЭлементыФормы.Найти(ИмяЭлемента), ЭлементыФормы.Найти(ИмяРодителя), ЭлементыФормы.Найти(МестоРасположения));
КонецПроцедуры
// Переопределяет обработчик события формы.
// Сохраняет штатный обработчик события внутри формы и устанавливает новый.
//
// Параметры:
// Форма – УправляемаяФорма – Управляемая форма;
// ИмяСобытияФормы – Строка – имя события;
// ПолноеИмяЭлементаФормы – Строка – полное имя элемента формы;
// НовоеДействие - Строка - имя процедуры обработчика;
// ОбработкаИсключений – Булево – сообщать о наличии старого обработчика.
//
Процедура ПодменитьДействиеУправляемоеПриложение(Форма, ИмяСобытияФормы = "", ПолноеИмяЭлементаФормы = "", НовоеДействие, ОбработкаИсключений = Ложь) Экспорт
Объект = Форма;
ИмяЭлементаФормы = "";
Если НЕ ПустаяСтрока(ПолноеИмяЭлементаФормы) Тогда
МассивФрагментов = РазобратьСтрокуВМассивПоРазделителю(ПолноеИмяЭлементаФормы);
ИмяЭлементаФормы = МассивФрагментов[0];
Объект = Форма.Элементы.Найти(ИмяЭлементаФормы);
Если Объект = Неопределено Тогда
ВызватьИсключение "Элемент формы: " + ИмяЭлементаФормы + " не найден!";
КонецЕсли;
Если МассивФрагментов.Количество() > 1 Тогда
ИмяЭлементаФормы = ИмяЭлементаФормы + МассивФрагментов[1];
Если ТипЗнч(Объект) = Тип("ТаблицаФормы") Тогда
Объект = Объект.Колонки[МассивФрагментов[1]].ЭлементУправления;
ИначеЕсли ТипЗнч(Объект) = Тип("КоманднаяПанель") Тогда
Объект = Объект.Кнопки[МассивФрагментов[1]];
Иначе
ВызватьИсключение "Ошибка на этапе получения элемента формы. Тип значения(" + ТипЗнч(Объект) + ")";
КонецЕсли;
КонецЕсли;
КонецЕсли;
СтарыеОбработчики = "OldHandlers";
МассивРеквизитов = Форма.ПолучитьРеквизиты();
Для Каждого РеквизитФормы Из МассивРеквизитов Цикл
Если РеквизитФормы.Имя = СтарыеОбработчики Тогда
СтарыеОбработчики = Неопределено;
Прервать;
КонецЕсли;
КонецЦикла;
Если НЕ СтарыеОбработчики = Неопределено Тогда
МассивНовыхРеквизитов = Новый Массив;
МассивНовыхРеквизитов.Добавить(Новый РеквизитФормы(СтарыеОбработчики, Новый ОписаниеТипов("СписокЗначений")));
Форма.ИзменитьРеквизиты(МассивНовыхРеквизитов);
КонецЕсли;
Если ТипЗнч(Объект) = Тип("КнопкаФормы") Тогда
СтароеДействие = Объект.ИмяКоманды;
Иначе
СтароеДействие = Объект.ПолучитьДействие(ИмяСобытияФормы);
КонецЕсли;
Если ОбработкаИсключений И НЕ СтароеДействие = Неопределено Тогда
ВызватьИсключение "Конфликт обработчиков события """ + ИмяСобытияФормы + """ объекта """ + Строка(Объект) + """";
КонецЕсли;
Если ТипЗнч(Объект) = Тип("КнопкаФормы") Тогда
Объект.ИмяКоманды = НовоеДействие;
Иначе
Объект.УстановитьДействие(ИмяСобытияФормы, НовоеДействие);
КонецЕсли;
OldHandlers = Форма.OldHandlers;
Если НЕ СтароеДействие = Неопределено И НЕ ПустаяСтрока(СтароеДействие) Тогда
OldHandlers.Добавить(ИмяЭлементаФормы + "." + ИмяСобытияФормы, СтароеДействие);
КонецЕсли;
КонецПроцедуры
// Возвращает старый обработчик события формы.
//
// Параметры:
// Форма – УправляемаяФорма – Управляемая форма;
// ИмяСобытияФормы – Строка – имя события;
// ПолноеИмяЭлементаФормы – Строка – полное имя элемента формы;
// СтароеДействие - Строка - имя процедуры обработчика.
//
Процедура ОткатитьДействиеУправляемоеПриложение(Форма, ИмяСобытияФормы = "", ПолноеИмяЭлементаФормы = "", СтароеДействие) Экспорт
Объект = Форма;
ИмяЭлементаФормы = "";
Если НЕ ПустаяСтрока(ПолноеИмяЭлементаФормы) Тогда
МассивФрагментов = РазобратьСтрокуВМассивПоРазделителю(ПолноеИмяЭлементаФормы);
ИмяЭлементаФормы = МассивФрагментов[0];
Объект = Форма.Элементы.Найти(ИмяЭлементаФормы);
Если Объект = Неопределено Тогда
ВызватьИсключение "Элемент формы: " + ИмяЭлементаФормы + " не найден!";
КонецЕсли;
Если МассивФрагментов.Количество() > 1 Тогда
ИмяЭлементаФормы = ИмяЭлементаФормы + МассивФрагментов[1];
Если ТипЗнч(Объект) = Тип("ТаблицаФормы") Тогда
Объект = Объект.Колонки[МассивФрагментов[1]].ЭлементУправления;
ИначеЕсли ТипЗнч(Объект) = Тип("КоманднаяПанель") Тогда
Объект = Объект.Кнопки[МассивФрагментов[1]];
Иначе
ВызватьИсключение "Ошибка на этапе получения элемента формы. Тип значения(" + ТипЗнч(Объект) + ")";
КонецЕсли;
КонецЕсли;
КонецЕсли;
Если ТипЗнч(Объект) = Тип("КнопкаФормы") Тогда
Объект.ИмяКоманды = СтароеДействие;
Иначе
Объект.УстановитьДействие(ИмяСобытияФормы, СтароеДействие);
КонецЕсли;
КонецПроцедуры
// Возвращает старые обработчики событий формы.
// Сохраняет штатный обработчик события внутри формы и устанавливает новый.
//
// Параметры:
// Форма – УправляемаяФорма – Управляемая форма.
//
Процедура ВыполнитьОткатОбработчиков(Форма) Экспорт
Для Каждого Обработчик Из Форма.OldHandlers Цикл
Строка = Обработчик.Значение;
ИндексТочки = Найти(Строка, ".");
ОткатитьДействиеУправляемоеПриложение(Форма, Прав(Строка, СтрДлина(Строка)-ИндексТочки), Лев(Строка, ИндексТочки-1), Обработчик.Представление);
КонецЦикла;
КонецПроцедуры
// Функция разбивает строку разделителем.
//
// Параметры:
// Строка - Строка - которую разбиваем;
// Разделитель - Строка - символ-разделитель.
//
// Возвращаемое значение:
// - Массив - содержащий фрагменты, на которые разбивает строку разделитель.
//
Функция РазобратьСтрокуВМассивПоРазделителю(Строка, Разделитель = ".") Экспорт
МассивСтрок = Новый Массив;
СтрокаЗамены = СтрЗаменить(Строка, Разделитель, Символы.ПС);
Для i=1 По СтрЧислоСтрок(СтрокаЗамены) Цикл МассивСтрок.Добавить(СтрПолучитьСтроку(СтрокаЗамены, i)); КонецЦикла;
Возврат МассивСтрок;
КонецФункции
На данном этапе, все уже готово для использования. Вот видео-пример использования механизма.
Статья в нашем блоге: www.avtomat.biz
UPDATE (24.03.2014):
Изменена схема работы метода, проведена оптимизация и исправлены некоторые недочеты. Все это можно посмотреть в новом архиве к статье. Так же сама статья расширена.
ОТВЕТЫ НА ВОПРОСЫ:
- Для каких конфигураций подходит данный модуль? – механизм предназначен для работы с 1С Предприятие 8.2-8.3, основной режим запуска: "Управляемое приложение", так же конфигурация должна использовать "Библиотеку стандартных подсистем" (БСП), в противном случае нужно будет создать справочник "ДополнительныеОтчетыИОбработки" аналогичный как в БСП;
- Какие изменения нужно внести в конфигурацию? – нужно добавить в конфигурацию несколько общих модулей, а так же внести изменения в необходимую управляемую форму (УФ) в конце модуля и для подмены событий этой формы в обработчики ПриСозданииНаСервере() или ПриЧтенииНаСервере(). Реквизиты и табличные части документов можно добавлять, если вам нужно хранить учетные данные;
- Как влияет на "быстродействие" использование внешних обработок? – когда используется одна внешняя обработка ничего не заметно для конечного пользователя, когда парк обработок увеличивается скорость запуска формы увеличивается в среднем на ~0.3с с каждой новой обработкой. Не надо паниковать – проблема уже решена;
- Как можно вызвать старый обработчик события? – вызвать процедуру ВыполнитьДействиеПоУмолчанию() в которую передать имя события и массив параметров;
- Как методика упрощает обновление конфигурации? – после обновления достаточно перенести обработчики переопределения в обновленную конфигурацию и даже если появились новые штатные обработчики событий они будут работать верно в повальном количестве случаев. Управляемые формы не нужно изменять вручную - это все можно сделать с помощью этой методики програмно;
- Методика увеличивает время разработки? – да увеличивает время разработки. Если уже несколько форм переделано этим методом – тогда затраты времени ростут незначительно, а затраты на обновление конфигурации уменьшается очень существенно;
- Есть критическая ошибка, но пользователей нельзя отключать от базы, что делать? - достаточно внести измения во внешную обработку, изменить ее версию и подключить к базе. После этого, пользователи автоматически уже будут использовать исправленную версию обработки. А ночью обновить общий модуль, который связан с внешней обработкой для более комфортного использования конфигурации.
КЛЮЧЕВЫЕ ИЗМЕНЕНИЯ:
- У всех общих модулей снято свойство "Вызов сервера", теперь вызов из модуля формы выполняется вот так: &НаКлиенте -> &НаСервере -> ОбщийМодульСервер;
- Для сокращения запуска и вызова внешних обработчиков событий, код доработок дублируется в общих модулях. При запуске проверяется версия общего модуля с версией внешней обработки из справочника "ДополнительныеОтчетыИОбработки". Если версии совпадают УФ будет работать с контекстом общего модуля, если версии отличаются вызов процедур и функций будет осуществляется из внешнего объекта;
- Переработана работа с контекстом &НаКлиенте – код из форм внешнего объекта дублируется в общем клиентском модуле;
- Сам метод переопределения обработчиков событий оформлен так же как внешняя обработка, что добавляет некоторую дополнительную гибкость;
- Проверка на дополнительный вызов переназначения обработчиков.
ТАК КАК ЖЕ ЭТО РАБОТАЕТ:
- Пользователь открывает документ происходит вызов ПриСозданииНаСервере() или ПриЧтенииНаСервере(). В одну или в обе процедуры мы добавляем наш вызов git_ПереопределениеОбработчиковСервер.ПереопределитьОбработчикиСобытий(ЭтаФорма). Как понять куда добавлять? – нужно посмотреть есть ли переопределение обработчиков в стандартной конфигурации (в УТ 11 оно есть), и добавить перед этим переопределением свой вызов;
- Далее идет переход в общий модуль "git_ПереопределениеОбработчиковСервер" в котором определяется первый ли это вызов (на случай, если добавили обработчики в ПриСозданииНаСервере() или ПриЧтенииНаСервере());
- Идет проверка версии модуля с версией внешней обработки, если версии одинаковые будет использоваться общий модуль git_ПереопределениеОбработчиковСервер, если версии различные будет использоваться внешняя обработка;
- Далее идет такая же проверка для каждой подключаемой внешней обработки (код можно посмотреть в файле .cf) и внутри каждой подключаемой обработки или общего модуля проверяется путь к механизму переопределения для вызова функций и процедур (общий модуль переопределения или модуль внешней обработки, служебные процедуры и функции обозначены комментарием // Только для внутреннего использования);
- Ура! Форма пользовательского документа открылась, теперь так же необходимые функции запускаются из общего модуля (клиент, сервер) или из модуля\формы внешней обработки;
- Вызов обработчиков по умолчанию исполняеться с помощь вызова процедуры ВыполнитьДействиеПоУмолчанию();
- После закрытия формы все обработчики будут восстанавливаться для избежания проблем с кешированием форм.
Статья в нашем блоге (часть 2): www.avtomat.biz