MyTetra Share
Делитесь знаниями!
Кто вызвал функцию или процедуру?
16.03.2019
23:43
Текстовые метки: Caller
Раздел: !Закладки - VBA - VBA управление кодами

Кто вызвал функцию или процедуру?

Предположим вы написали два макроса - один скрывает строки, другой отображает.

'скрываем строки Sub HideRows() Range("A3:A14").EntireRow.Hidden = True End Sub 'показываем строки Sub UnhideRows() Range("A3:A14").EntireRow.Hidden = False End Sub


1

2

3

4

5

6

7

8

'скрываем строки

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


1

2

3

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


1

2

3

4

5

6

7

8

9

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

Но если в книге несколько листов и на каждом по несколько кнопок, то не очень удобно будет в коде макроса указывать нужную кнопку. Да, можно указать по имени фигуры: ActiveSheet.Shapes("Скругленный прямоугольник 1"). Но опять же - если кнопок много придется давать той единственной свое уникальное имя, совпадающее на всех листах. Но можно сделать проще - использовать свойство Caller:

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


1

2

3

4

5

6

7

8

9

10

11

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



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

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


1

2

3

4

5

6

7

8

9

10

11

12

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


Но это очень неправильно. Во-первых, цикл идет по листам активной книги. А это значит, что если с этой книги перейти в другую - то функция будет вычислять сумму на листах именно этой книги, а не той, в которой записана функция. Во-вторых, строка If Not ws Is ActiveSheet Then исключает из суммирования лист активной книги, а не той книги, в которой записана функция. Это может привести к ошибочным расчетам, что весьма критично, если на расчеты функции опираются функции других книг и листов. Поэтому надо определять не активную книгу, а именно ту, в которой функция. Здесь опять поможет свойство Caller:

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


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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 - свойство объекта Application, которое возвращает информацию о том, как(чем) был вызван код. Есть несколько вариантов вызова и в зависимости от них значение, возвращаемое Caller меняется:

  • Если вызов был из функции пользователя - Caller вернет объект Range, представляющий ссылку на ячейку, в которой записана функция пользователя. Если это функция введена как формула массива - то Caller вернет ссылку на все ячейки, в которые записана функция
  • Если вызов был кнопкой на листе - Caller вернет текст, содержащий локальное имя объекта Shape, к которому привязан вызов процедуры
  • Если вызов был из событийной процедуры(Workbook_Open и им подобные), либо процедура была вызвана через Alt+F11 - Caller вернет ошибку REF
  • Если процедура вызвана с панели(Ribbon или настраиваемая панель) - Caller будет иметь тип Variant(), но не сможет определить как именно был вызван код и при попытке обращения к нему получим так же ошибку REF
  • Если вызов был через процедуры автоматизации (Auto_Open, Auto_Close, Auto_Activate, Auto_Deactivate) - Caller вернет тип String и содержит имя книги и активного листаэто устаревшие процедуры, которые сейчас заменены событийными в классах книг и листов, но тем не менее их можно встретить в некоторых кодах

Хотел бы так же отметить, что для определения ячейки с функцией можно использовать объект Application.ThisCell, который возвращает ссылку на ячейку, из которой была вызвана функция. Этот объект внутри функций пользователя можно применять точно так же, как и Caller. Но он не может быть применен для определения других методов вызова функций и процедур, как Caller.

Так же в этом разделе:
 
MyTetra Share v.0.52
Яндекс индекс цитирования