Кто вызвал функцию или процедуру?
Предположим вы написали два макроса - один скрывает строки, другой отображает.
'скрываем строки Sub HideRows() Range("A3:A14").EntireRow.Hidden = True End Sub 'показываем строки Sub UnhideRows() Range("A3:A14").EntireRow.Hidden = False End Sub |
И, конечно - создали кнопки для вызова этих двух кодов (подробнее про создание кнопок - Как создать кнопку для вызова макроса на листе). Но потом захотелось большего - чтобы была всего одна кнопка и первым нажатием строки скрывались, а вторым отображались. Сделать это не проблема, если применить такой финт:
Sub HideUnhideRows() Range("A3:A14").EntireRow.Hidden = Not Range("A3:A14").EntireRow.Hidden End Sub |
Но если код делается для пользователей, то лучше как-то дать понять им, в каком состоянии сейчас строки - скрыты или отображены и к какому действию приведет нажатие на кнопку. И лучше всего это сделать надписью на самой кнопке. Если кнопка одна на одном листе, то проблем быть не должно:
Sub HideUnhideRows() If Range("A3:A14").EntireRow.Hidden Then Range("A3:A14").EntireRow.Hidden = False ActiveSheet.Shapes(1).TextFrame2.TextRange.Text = "Скрыть строки" Else Range("A3:A14").EntireRow.Hidden = True ActiveSheet.Shapes(1).TextFrame2.TextRange.Text = "Показать строки" End If End Sub |
Но если в книге несколько листов и на каждом по несколько кнопок, то не очень удобно будет в коде макроса указывать нужную кнопку. Да, можно указать по имени фигуры:
Sub HideUnhideRows() Dim sShName As String sShName = Application.Caller If ActiveSheet.Shapes(sShName).TextFrame2.TextRange.Text = "Показать строки" Then Range("A3:A14").EntireRow.Hidden = False ActiveSheet.Shapes(sShName).TextFrame2.TextRange.Text = "Скрыть строки" Else Range("A3:A14").EntireRow.Hidden = True ActiveSheet.Shapes(sShName).TextFrame2.TextRange.Text = "Показать строки" End If End Sub |
Автоопределение нажатой кнопки.xls (57,5 KiB, 558 скачиваний)
Теперь рассмотрим другую ситуацию, более распространенную. Вы написали свою пользовательскую функцию, которая должна суммировать данные ячейки со всех листов книги, кроме того, в котором сама функция. Часто это делают так:
Function СуммаЯчеекВсехЛистов(Ячейка As Range) Dim ws As Worksheet 'объявляем переменную для обращения к листам в цикле Dim dblSum As Double 'переменная для хранения суммы 'цикл по листам книги For Each ws In ActiveWorkbook.Worksheets If Not ws Is ActiveSheet Then 'исключаем активный лист из суммирования dblSum = dblSum + ws.Range(Ячейка.Address).Value End If Next ws 'присваиваем значение суммы функции СуммаЯчеекВсехЛистов = dblSum End Function |
Но это очень неправильно. Во-первых, цикл идет по листам активной книги. А это значит, что если с этой книги перейти в другую - то функция будет вычислять сумму на листах именно этой книги, а не той, в которой записана функция. Во-вторых, строка
Function СуммаЯчеекВсехЛистов(Ячейка As Range) Dim ws As Worksheet 'объявляем переменную для обращения к листам в цикле Dim dblSum As Double 'переменная для хранения суммы Dim rFuncCell As Range 'переменная для хранения ссылки на ячейку с функцией Dim wsFunc As Worksheet 'переменная для хранения ссылки на лист с функцией Dim wbFunc As Workbook 'переменная для хранения ссылки на книгу с функцией Set rFuncCell = Application.Caller 'ячейка с функцией Set wsFunc = rFuncCell.Parent 'лист с функцией Set wbFunc = wsFunc.Parent 'книга с функцией 'для листа и книги можно записать одной строкой: 'Set wsFunc = Application.Caller.Parent 'лист с функцией 'Set wbFunc = Application.Caller.Parent.Parent 'книга с функцией 'цикл по листам книги с функцией For Each ws In wbFunc.Worksheets If Not ws Is wsFunc Then 'исключаем лист с функцией из суммирования dblSum = dblSum + ws.Range(Ячейка.Address).Value End If Next ws 'присваиваем значение суммы функции СуммаЯчеекВсехЛистов = dblSum End Function |
А теперь попробуем разобраться, что же за зверь такой, этот Caller.
- Если вызов был из функции пользователя - Caller вернет объект Range, представляющий ссылку на ячейку, в которой записана функция пользователя. Если это функция введена как формула массива - то Caller вернет ссылку на все ячейки, в которые записана функция. Соответственно это дает возможность определить и лист с функцией(Application.Caller.Parent) и книгу(Application.Caller.Parent.Parent)
- Если вызов был кнопкой/фигурой на листе - Caller вернет текст, содержащий локальное имя объекта Shape, к которому привязан вызов процедуры. Что значит локальное имя объекта Shape(это очень важно понимать для правильной работы с Application.Caller): если в русской локализации офиса создать скругленный прямоугольник на листе, то он будет отображаться в окне адреса как "Скругленный прямоугольник 1" - это локальное имя, которое и вернет Application.Caller. Однако внутри кодов VBA коллекция Shapes воспринимает только внутренне имя - "Rounded Rectangle 1". И на попытку обратиться к "Скругленный прямоугольник 1" выдаст сообщение, что компонент не найден. Поэтому, при работе с фигурами и Application.Caller лучше сразу переименовать вручную созданную фигуру на любое имя(лучше только англ.буквами, т.е. латиницей). Сразу после переименования Application.Caller и Shapes будут работать с этим новым именем фигуры.
Отдельно хочу отметить, что это не распространяется на элементы форм(Разработчик -Вставить -Элементы управления формы). Элементы управления формы сразу имеют одинаковое имя и на листе и внутри VBA. - Если вызов был из событийной процедуры (Workbook_Open и им подобные), либо процедура была вызвана через
Alt +F8 , либо через отложенные процедуры приложения(типа Application.OnTime) - Caller вернет ошибкуREF - Если процедура вызвана с панели(Ribbon или настраиваемая панель) - Caller будет иметь тип Variant(), но не сможет определить как именно был вызван код и при попытке обращения к нему получим так же ошибку
REF - Если вызов был через процедуры автоматизации (Auto_Open, Auto_Close, Auto_Activate, Auto_Deactivate) - Caller вернет тип String и содержит имя книги и активного листа
это устаревшие процедуры, которые сейчас заменены событийными в классах книг и листов, но тем не менее их можно встретить в некоторых кодах
Т.е. по факту Caller может пригодится исключительно в случаях, когда код вызывался через нажатие кнопки или необходимо определить адрес ячейки с вычисляемой функцией пользователя(UDF). Во всех остальных случаях Caller по сути бесполезен и определить им любой вызывающий объект/процедуру нельзя. Ниже приведены различные ситуации вызова кодов:
'пробуем вызвать из другой процедуры напрямую - получим ошибку ' т.к. VBA не может определить вызывающий объект Sub TestCaller_CallFromProc() Call IsCaller End Sub 'пробуем вызвать через OnTime - получим ошибку ' т.к. VBA не может определить вызывающий объект Sub TestCaller_CallByAppOnTime() Application.OnTime Now, ThisWorkbook.Name & "!IsCaller" End Sub 'пробуем вызвать через другую процедуру, которая запущена нажатием кнопки 'предварительно надо создать на листе любую фигуру и назначить ей этот макрос: ' правая кнопка на фигуре -Назначить макрос -TestCaller_CallByShape Sub TestCaller_CallByShape() Call IsCaller ' однако, если вызвать из этой процедуры другую через Application.OnTime - получим ошибку Application.OnTime Now, ThisWorkbook.Name & "!IsCaller" End Sub 'если назначить эту процедуру напрямую для вызова нажатием на фигуру - MsgBox покажет имя фигуры Sub IsCaller() Dim v On Error Resume Next v = Application.Caller Select Case True Case IsError(v) MsgBox "Ошибка определения вызова", vbInformation, "www.excel-vba.ru" Case Else MsgBox v, vbInformation, "www.excel-vba.ru" End Select End Sub |
Процедуры
Для примера можно скачать файл по ссылке:
Caller test.xls (56,0 KiB, 603 скачиваний)
Там записаны две функции - одна с использованием ссылки на активную ячейку, другая с применением Caller:
'возвращает адрес активной ячейки активного листа, даже если активная ячейка в другой книге Function GetActCell() Application.Volatile True GetActCell = "'GetActCell' return address: " & ActiveCell.Address(0, 0, xlA1, True) End Function 'возвращает всегда адрес той ячейки, в которой записана Function GetActCell_Caller() Application.Volatile True GetActCell_Caller = "'GetActCell_Caller' return address: " & Application.Caller.Address(0, 0, xlA1, True) End Function |
Чтобы во всей мере понять смысл объекта Caller поизменяйте значения любых ячеек на листе с функциями. Потом перейдите на любой другой лист этой же книги - измените там какую-нибудь ячейку. Вернитесь на лист с функциями и посмотрите, что там отображается. Так же можно перейти в другую книгу и изменить что-то там. Вернуться и посмотреть результат. В ячейке с
Хотел бы так же отметить, что для определения ячейки с функцией можно использовать объект
Статья помогла? Поделись ссылкой с друзьями!

Поиск по меткам
Access apple watch Multex Power Query и Power BI VBA управление кодами Бесплатные надстройки Дата и время Записки ИП Надстройки Печать Политика Конфиденциальности Почта Программы Работа с приложениями Разработка приложений Росстат Тренинги и вебинары Финансовые Форматирование Функции Excel акции MulTEx ссылки статистикаКомментарии, не имеющие отношения к комментируемой статье, могут быть удалены без уведомления и объяснения причин. Если есть вопрос по личной проблеме - добро пожаловать на Форум
Здравствуйте Игорь, очень долго копался везде, никак не могу понять каким образом в VBA можно в коде макроса сослаться на его имя. Например:
на Пользовательской форме есть куча контроллеров (Чекбоксы, текстбоксы и т д). Хочу прописать каждому контроллеру обращение в функцию, в которой одинаковые контроллеры следуют одинаковой логике. В функцию нужно передать имя макроса, который эту функцию вызывает. Спасибо за ответ!
Максим, не знаю про какого Игоря сейчас речь и зачем Вы с ним здороваетесь здесь, но Ваша ситуация очень похожа на необходимость использования классов:Работа с модулями классов
Здравствуйте!
У меня следующие строки:
Dim a As Range
Set a = Application.Caller
выдают ошибку 424 Object required.
Что-то не хватает. Может быть Application.Caller нельзя использовать без объекта?
Андрей, в статье указаны варианты, в которых можно использовать Caller. Если вызываете через Alt+F11 - получите ошибку, т.к. нет вызывающего объекта.
По сути Caller применяется для определения либо кнопки(нажатие которой запускает макрос), либо функции пользователя. В остальных случаях в ней нет необходимости.
Дмитрий, прошу прощения за настойчивость, но в том-то и дело, что редактор (Alt+F11 открывает редактор) закрыт.
дополнил свои строки еще одной - MsgBox a.Address сразу после строки Set a = Application.Caller, и на строке Set a = Application.Caller выходит та же ошибка 424 Object required.
Помогите разобраться в чём проблема.
Свою же задачу решил таким образом:
If TypeName(Application.Caller) = "String" Then vButton = Application.Caller
'Замена слова "Кнопка " на "Button ".
vButton = "Button " & Mid(vButton, InStr(1, vButton, " ") + 1, Len(vButton) - InStr(1, vButton, " "))
'Определение листа на котором находится кнопка.
For Each wsItem In ThisWorkbook.Worksheets 'пробегаем по всем листам
For Each obShape In wsItem.Shapes 'пробегаем по всем фигурам на листе
If obShape.Name = vButton Then
SheetName = wsItem.Name
Exit For
End If
Next obShape
If SheetName = wsItem.Name Then Exit For 'выход из цикла, когда уже определили SheetName
Next wsItem
Андрей, да перепутал - конечно же имелось ввидуAlt +F8 . Спасибо, поправил в статье.
А по конкретно первому(изначальному) вопросу - опять же, в статье указано, что при вызове с кнопки Application.Caller возвращает ИМЯ вызвавшей фигуры/кнопки:
Т.е. это текст. Обратите внимание на коды в начале статьи в процедуре HideUnhideRows, с применением Application.Caller. Там указано как обратиться к фигуре.
SheetName = ActiveSheet.Shapes(Application.Caller).parent.name
SheetName = ActiveSheet.Name
А Вы пытались работать с текстом как с объектом. Плюс локальное имя как правило отличается от внутреннего и для большей стабильности правильнее было бы просто назначить фигуре собственное имя на латинице(например, MyShape). Тогда для определения листа с фигурой хватило бы одной строки:
Другой вопрос еще в том, зачем Вы вообще пытаетесь определить имя листа - ведь Вы не можете нажать кнопку НЕ С АКТИВНОГО ЛИСТА. Следовательно, достаточно в самом начале кода просто запомнить активный лист и все:
Дмитрий, спасибо за пояснения. Разобрался.
На вашу статью вышел из поисковика. У меня одна процедура очищает содержимое ячеек на разных листах имеющих разную структуру. В этой процедуре использую Select Case SheetName и далее Case'ы (так удобно масштабировать, корректировать процедуру), а на листах свои кнопки "Очистить".
Персональные имена кнопок стараюсь не использовать для быстрого переноса процедуры в другие книги, если потребуется.
Еще раз спасибо.