В системе 1С 7.7 при интерактивных действиях пользователя, таких как открытие формы документа или справочника, запись документа или ввод нового элемента справочника или документа, возникают одноименные системные события. Штатные средства предоставляют разработчику возможность программной обработки этих событий. Для этого в языке 1С 7.7 зарезервированы названия и синтаксис для системных предопределенных процедур. Вызов таких процедур (если они определены в конфигурации) производится неявно самой системой 1С перед обработкой того или иного интерактивного действия пользователя. Исключение составляют только предопределенные процедуры модуля документа, такие как «ОбработкаПроведения» или «ОбработкаУдаленияПроведения». Их отличие только в том, что вызываются они системой, как при интерактивных действиях, так и при программной инициации этих событий. Тело таких предопределенных процедур разработчик должен написать сам в соответствующих программных модулях конфигурации. Система же сама передает в предопределенную процедуру фактические значения принимаемых процедурой параметров, если они есть, которые можно использовать в теле этих процедур для программной обработки события, например, для выдачи предупреждающих сообщений или отмены выполнения действия того системного события (с помощью системной функции «СтатусВозврата»), которое вызвало данную предопределенную процедуру.
Поясним сказанное на простом примере. Допустим, мы хотим, чтобы введенные документы в базу могли интерактивно изменять только авторы этих документов. Для этого в модуле формы каждого вида документа нам нужно определить (если это еще не было сделано) такую предопределенную процедуру (пока пустую):
Процедура ПриЗаписи()
КонецПроцедуры
Затем в теле этой процедуры необходимо написать примерно такой код, который будет проверять, совпадает ли автор документа с текущим пользователем системы, и если нет, то отменит выполнение действия этого системного события:
Если ((ПустаяСтрока(СокрЛП(Врег(ИмяПользователя())))=0) И (СокрЛП(Врег(ТекущийДокумент().Автор))<>Врег("<>")) И (ПустаяСтрока(СокрЛП(Врег(ТекущийДокумент().Автор)))=0)) И (СокрЛП(Врег(ТекущийДокумент().Автор))<>СокрЛП(Врег(ИмяПользователя()))) Тогда
Предупреждение("Изменение этого документа Вам запрещено, так как Вы не являетесь его автором",5);
СтатусВозврата(0);
Возврат;
КонецЕсли;
Соответственно, когда пользователь откроет форму существующего документа другого автора и попытается его изменить, то при попытке записать этот измененный документ в базу, ему будет выведено сообщение о невозможности выполнить это действие. И изменения этого документа не будут записаны в базу.
Так это работает, но имеет ряд недостатков. Ведь в случае необходимости выполнять какие-либо свои действия при возникновении тех или иных событий в базе данных для тех или иных объектов метаданных, разработчик, использующий только штатные возможности 1С, вынужден вставлять свой код (вызов тех или иных функций глобального модуля, например, написанных разработчиком для этих целей) во все предопределенные процедуры всех необходимых объектов метаданных, коих может быть довольно много. Что довольно не удобно и нудно, если не сказать больше;-)
Но и это еще не беда. А теперь представьте, что после установки типового обновления конфигурации, разработчику каждый раз приходится заново, помимо того, что вставлять в глобальный модуль дописанные им функции, так еще и опять во все необходимые модули объектов метаданных во все нужные предопределенные процедуры опять вставлять свой код. Т.е. при типовом обновлении конфигурации (а, например, с бухгалтерией это происходит довольно часто), разработчику каждый раз приходится проделывать одну и ту же неблагодарную работу.
А при разработке новых объектов метаданных (тех же документов, справочников и т.д.) разработчику также нельзя забывать о вставке своего кода в предопределенные процедуры в модулях этих объектов.
Все это довольно неудобно для разработчика и, соответственно, чревато ошибками и пустой тратой своего времени. Да и для заказчика это все тоже чревато. Ведь подобные масштабные изменения типовой, когда программисту после каждого обновления нужно выполнять кучу действий, сулят зависимость от разработчика, если он фикси, или дополнительные расходы, если это фри или фра, при обновлениях типовых конфигурации.
Внешняя компонента «1С++» (http://www.1cpp.ru) включает в себя класс «Перехватчик», при грамотном использовании возможностей которого, разработчик может избежать всех этих проблем. Более того, построить такое решение, программно обрабатывающее системные события, например, для расширения штатных возможностей контроля прав пользователей, которое можно будет повторно устанавливать (и довольно просто) в разные конфигурации разных заказчиков, а при обновлении типовых тех же заказчиков, также просто и быстро подключать его заново.
Не говоря уже о том, что простота установки и универсальность подобных разработок позволят поставить на поток продажу этих решений, а внедрение может быть произведено и «опытными» пользователями из штата заказчика, без необходимости участия разработчика. А это значит и удешевление стоимости решения для заказчика, и увеличение прибыли для разработчика, за счет продажи не только своих услуг, но и своих готовых «коробочных» универсальных решений большому числу потенциальных покупателей вне зависимости от удаленности и местонахождения покупателя и разработчика.
Итак, покажем, как реализуется, с помощью класса «Перехватчик» внешней компоненты «1С++», тот функционал, который мы реализовали штатными возможностями в начале статьи.
Для начала нам необходимо скачать эту внешнюю компоненту с сайта http://www.1cpp.ru. Последнюю стабильную сборку можно взять тут: http://www.1cpp.ru/images/3/32/Icpp-latest.rar. Из скачанного архива нужно распаковать файл «1cpp.dll» в папку конфигурации или в папку «Bin» платформы 1С 7.7.
Затем в глобальном модуле в предопределенной процедуре «ПриНачалеРаботыСистемы()» прописываем загрузку этой внешней компоненты:
ЗагрузитьВнешнююКомпоненту("1cpp.dll");
После чего создаем обработку с именем «defcls», в теле которой пишем такой код:
//# класс КлассПерехватСобытий = КлассПерехватСобытий@MD
//# {};
Это объявление пользовательского класса 1С++, код которого будет обрабатывать системные события 1С, а экземпляры этого класса будут создаваться системной функцией «СоздатьОбъект» с параметром "КлассПерехватСобытий", а также указание того факта, что код этого класса будет находиться в обработке с именем «КлассПерехватСобытий».
Теперь собственно создаем обработку с именем «КлассПерехватСобытий», в которой и будем реализовывать весь функционал, о котором мы говорим.
Но прежде чем мы начнем, нужно еще в глобальном модуле в предопределенной процедуре «ПриНачалеРаботыСистемы()» после кода загрузки внешней компоненты написать такие строчки:
НастройкиВК = СоздатьОбъект("УправлениеНастройками");
НастройкиВК.Установить("ПерехватитьСобытияГК",1);
ПерехватчикСобытийГМ = СоздатьОбъект("КлассПерехватСобытий");
Перехватчик = СоздатьОбъект("Перехватчик");
Перехватчик.ПерехватитьСобытияГлобальногоМодуля(ПерехватчикСобытийГМ);
Т.е. этим кодом мы включаем перехват событий, создаем экземпляр нашего пользовательского класса «КлассПерехватСобытий» и назначаем его обработчиком системных событий глобального модуля, для того чтобы отлавливать этим экземпляром нашего класса события открытия форм в 1С.
Вот теперь можем заниматься реализацией самого пользовательского класса, который будет обрабатывать необходимые нам события. Т.е. открываем программный модуль обработки «КлассПерехватСобытий» и пишем такой код:
Перем КонтФормы Экспорт, Док Экспорт;
Процедура СобытиеГМ_ПриОткрытии(Конт,ФлагЧтенияНастройки) Экспорт
КонтФормы = Конт;
Попытка
Док = КонтФормы.ТекущийДокумент();
Исключение
Возврат;
КонецПопытки;
ПерехватчикСобытийГК=СоздатьОбъект("КлассПерехватСобытий");
ПерехватчикСобытийГК.КонтФормы=КонтФормы;
ПерехватчикСобытийГК.Док=Док;
Перехватчик = СоздатьОбъект("Перехватчик");
Перехватчик.ПерехватитьСобытияГК(КонтФормы,ПерехватчикСобытийГК);
КонецПроцедуры
Функция Событие_ПриЗаписи() Экспорт
Рез=1;
Если ((ПустаяСтрока(СокрЛП(Врег(ИмяПользователя())))=0) И (СокрЛП(Врег(Док.Автор))<>Врег("<>")) И (ПустаяСтрока(СокрЛП(Врег(Док.Автор)))=0)) И (СокрЛП(Врег(Док.Автор))<>СокрЛП(Врег(ИмяПользователя()))) Тогда
Рез=0;
Предупреждение("Изменение этого документа Вам запрещено, так как Вы не являетесь его автором",5);
Иначе
Перехватчик = СоздатьОбъект("Перехватчик");
Рез = Перехватчик.ВыполнитьОригинальноеСобытиеГК(КонтФормы,"ПриЗаписи");
КонецЕсли;
Возврат Рез;
КонецФункции
В данном случае идея такова, мы будем перехватывать событие открытия любых форм 1С, процедура «СобытиеГМ_ПриОткрытии» (мы помним, что в предопределенной процедуре «ПриНачалеРаботыСистемы()» мы создали экземпляр нашего класса и указали, что он будет перехватывать события глобального модуля, а соответственно и специальное событие «СобытиеГМ_ПриОткрытии»). В этой процедуре мы фактически для каждой открытой формы документа будем создавать новый экземпляр нашего пользовательского класса и назначать его обработчиком системных событий, но уже форм и только для документов. Попутно заполняя глобальные переменные этого класса контекстом формы документа и собственно документа, события которого мы перехватываем, чтобы в классе иметь возможность обращаться к тому документу и его форме. Т.е. для каждой открытой формы документа при ее открытии мы создаем новый экземпляр нашего класса, который будет обрабатывать события именно этой формы именно этого документа. Соответственно при возникновении события «ПриЗаписи», вернее перед ним, будет вызываться обработчик этого события определенный в нашем пользовательском классе, т.е. функция «Событие_ПриЗаписи()». В которой мы уже проверяем является ли текущий пользователь автором документа и в зависимости от этого, или отменяем выполнение действий данного события (возвращая ноль в функции-обработчике события «Событие_ПриЗаписи()», что эквивалентно вызову системной функции «СтатусВозврата» с параметром ноль в штатных предопределенных процедурах) и предупреждаем пользователя об этом или наоборот – разрешаем, возвращая единицу, и выполняем те действия, что определены в штатной предопределенной процедуре формы документа «ПриЗаписи()» , если она, конечно, есть.
Вот собственно и вся реализация. Стоит, конечно, понимать, что приведенные здесь примеры кода носят чисто иллюстративный и обучающий характер, в «боевых» условиях код будет несколько сложнее и лучше организован. Во всяком случае, хотелось бы на это надеяться;-)
Итак, мы имеем две новые обработки и пару строк в глобальном модуле. Т.е. нам не надо во все модули форм всех видов документов в предопределенную процедуру «ПриЗаписи()» вписывать свой код, нам не надо, при обновлении конфигурации опять проделывать эту работу, а надо будет только вставить заново пару строк в глобальный модуль в процедуру «ПриНачалеРаботыСистемы()». Установка же в новую конфигурацию тоже будет проста, закинули файл внешней компоненты (dll’ку) в папку с конфигурацией или с платформой, вставили две обработки «defcls», «КлассПерехватСобытий» и прописали несколько строк в глобальный модуль. Т.е. человек слегка знакомый с конфигуратором вполне может это сделать и без разработчика, особенно, если разработчик написал небольшую инструкцию по установке.
Так что возможность перехватывать события 1С в пользовательских классах, определять свои обработчики этих перехваченных событий, управлять выполнением самих событий и выполнением штатных предопределенных процедур этих событий в объектах метаданных конфигурации с помощью класса «Перехватчик» внешней компоненты «1С++» предоставляет, по истине, революционные возможности для разработчика, которыми грех не воспользоваться.
Подробную документацию по классу «Перехватчик» Вы можете почитать тут:
http://www.1cpp.ru/docum/icpp/html/Hooker.html
Примеры проектов использующих возможности класса «Перехватчик»:
Универсальная подсистема «Дополнительные права для документов»
//infostart.ru/public/22202/
Универсальная подсистема «Сканы документов»
//infostart.ru/public/70831/
Универсальная подсистема «Фабрика событий» + «Доп. права доков» + «Сканы доков»
//infostart.ru/public/71084/
Универсальная подсистема «Подписи/согласования документов»
//infostart.ru/public/73774/
Undo или в помощь операторам
//infostart.ru/public/20038/