Лекция 7. Процедуры и функции в VBA 1. Процедуры, функции и макросы Использование языка VBA отличается от использования других языков, в частности, языка Паскаль. На языке Паскаль обычно пишутся программы, которые преобразуются в исполняемый файл и запускаются по требованию пользователя. Программа на языке Паскаль соответствует приложению Microsoft Excel в целом. Язык VBA (Visual Basic for Application) предназначен для написания кода, работающего внутри приложения Microsoft Excel. Поэтому код на языке VBA оформляется не в виде самодостаточной программы, а в виде подпрограмм. Подпрограмма – это записанный отдельно и поименованный алгоритм, решающий определённую задачу, которому можно передавать данные для обработки. Можно также сказать, что подпрограмма – это логически объединённый набор действий, оформленный по правилам языка программирования. Подпрограммы делятся на процедуры и функции. Процедура – это подпрограмма, которая выполняет некоторые действия, но не возвращает никакого значения. Функция – это подпрограмма, которая возвращает значение. В офисных пакета используется также термин «макрос». Макрос – это процедура, записанная с помощью специального средства, встроенного в офисное приложение. 2. Вставка процедур и функций Процедуры, функции и макросы хранятся в модулях. Модуль – это специальная часть рабочей книги, предназначенная для хранения процедур и функций. До написания процедур и функций в рабочую книгу необходимо вставить модуль с помощью пункта меню VBE Insert Module. Затем можно вставить процедуру или функцию с помощью пункта меню VBE Insert Procedure…. В появившемся диалоговом окне необходимо указать имя подпрограммы и тип подпрограммы – процедура (Sub) или функция (Function). Остальные параметры можно оставить без изменения. Однако список параметров и тип результата функции придётся вписывать вручную. В принципе, использовать пункт меню вставки подпрограммы и соответствующий диалог совсем не обязательно. Можно просто набрать в тексте модуля строку Public Sub … или Public Function …, и после нажатия клавиши Ввод редактор VBA вставит завершающую строку End Sub или End Function и отделит новую подпрограмму чертой. 3. Процедуры 3.1. Определение процедуры Как было сказано, процедура – это подпрограмма, которая выполняет некоторые действия, но не возвращает никакого значения. Процедура имеет следующий синтаксис: Sub <имя> (<список параметров>) <инструкции> [Exit Sub] <инструкции> End Sub Первая строка, содержащая имя процедуры и список параметров, называется заголовком процедуры. Имя процедуры должно быть идентификатором. Принято давать процедурам значимые имена – в идеальном случае имя процедуры должно описывать, что делает эта процедура. Список параметров определяет параметры, которые надо передать в процедуру. Эти параметры будут управлять поведением процедуры. Список параметров может быть пустым. Public Sub CountOfSheets() MsgBox "Всего листов - " & Sheets.Count & vbNewLine & _ "Рабочих листов - " & Worksheets.Count & vbNewLine & _ "Листов диаграмм - " & Charts.Count End Sub Public Sub ChangeNegatives() Dim cell As Range, n As Integer n = 0 For Each cell In Selection If cell.Value < 0 Then cell.Value = -cell.Value n = n + 1 End If Next cell MsgBox "Обработано ячеек - " & Selection.Count & vbNewLine & "Изменено ячеек - " & n End Sub 3.2. Вызов процедуры Однако одного определения процедуры недостаточно. Следующее важное действие – вызов процедуры. Без вызова никакая процедура работать не будет, а будет лишь лежать мёртвым грузом в модуле рабочей книги. Проведём такую аналогию. Офисные приложения знают, как сделать шрифт текста жирным. Кто-то когда-то написал процедуру, которая делает буквы толще, и эта процедура лежит в недрах офисных приложений. Однако буквы сами по себе не становятся жирными. Для этого надо выделить часть текста и нажать кнопку на панели инструментов. Нажатие кнопки соответствует вызову процедуры. Для вызова процедуры, написанной на языке VBA, в приложении Microsoft Excel существуют следующие возможности. Команда Run Run Sub/UserForm в VBE. Этот способ используется преимущественно для тестирования процедуры в процессе её разработки. Диалоговое окно Макрос. Комбинация клавиш. Перед записью макроса выводится диалоговое окно, в котором макросу можно назначить некоторую комбинацию клавиш. Процедуре (а также макросу) можно назначить комбинацию клавиш после разработки (записи) с помощью команды Разработчик Код Макросы Параметры. Элементы управления (будут рассмотрены позже). Вызов процедуры из другой процедуры или функции. Пользовательский элемент управления, добавленный на ленту (сложно). Пользовательский пункт контекстного меню (будет рассмотрено позже). Связь процедуры с определённым событием. 3.3. Обработчики событий Событие – это изменение в состоянии объекта. Процедура обработки события – это специальная процедура, которая запускается приложением Microsoft Office при наступлении определённого события. Такие процедуры должны иметь определённое имя, состоящее из имени объекта и имени события, и определённый набор параметров. Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Source As Range) MsgBox "The range " & Source.Address(False, False) & _ " on the worksheet " & Sh.Name & " has been changed" End Sub Кроме того, эти процедуры должны размещаться не в модуле общего назначения, а в модуле, соответствующем конкретному объекту – рабочей книге или рабочему листу. Рассмотрим некоторые события основных объектов приложения Microsoft Excel – рабочей книги и рабочего листа. События объекта Workbook Событие Activate BeforeClose BeforePrint BeforeSave Deactivate Событие происходит При активации рабочей книги Перед закрытием рабочей книги (если книга была изменена, событие происходит перед запросом на сохранение) Перед печатью рабочей книги или любой её части Перед сохранением рабочей книги При деактивации рабочей книги Событие NewSheet Open SheetCalculate SheetChange SheetSelectionChange Событие происходит При добавлении нового листа в рабочую книгу При открытии рабочей книги При пересчёте формул или изменении диаграммы При изменении ячейки любого рабочего листа При изменении выделенного диапазона любого рабочего листа События объекта Worksheet Событие Activate Calculate Change Deactivate SelectionChange Событие происходит При активации рабочего листа При пересчёте формул рабочего листа При изменении любой ячейки рабочего листа При деактивации рабочего листа При изменении выделенного диапазона рабочего листа 'Активация первого рабочего листа при открытии книги Private Sub Workbook_Open() Worksheets(1).Activate End Sub 'Вводим в ячейку А1 дату и время создания листа, запрашиваем имя рабочего листа Private Sub Workbook_NewSheet(ByVal sh As Object) Dim s As String If TypeName(sh) = "Worksheet" Then sh.Range("A1") = "Лист добавлен " & Now() s = InputBox("Введите имя нового рабочего листа") If s <> "" Then sh.Name = s End If End Sub 'Скрытие столбцов B:D перед печатью Private Sub Workbook_BeforePrint(Cancel As Boolean) Dim sheet As Worksheet For Each sheet In Worksheets sheet.Columns("B:D").Hidden = True Next sheet End Sub 'Отображение столбцов B:D перед закрытием книги Private Sub Workbook_BeforeClose(Cancel As Boolean) Dim sheet As Worksheet For Each sheet In Worksheets sheet.Columns("B:D").Hidden = False Next sheet End Sub 'Выделение жирным шрифтом ячеек с формулами на конкретном рабочем листе Private Sub Worksheet_Change(ByVal target As Range) Dim cell As Range Set target = Intersect(target, target.Parent.UsedRange) If target Is Nothing Then Exit Sub For Each cell In target cell.Font.Bold = cell.HasFormula Next cell End Sub 'Выделение строки и столбца, на пересечении которых находится активная ячейка Private Sub Worksheet_SelectionChange(ByVal target As Range) Cells.Interior.ColorIndex = xlColorIndexNone With ActiveCell .EntireRow.Interior.Color = RGB(219, 229, 241) .EntireColumn.Interior.Color = RGB(219, 229, 241) End With End Sub 4. Функции 4.1. Определение функции Как было сказано, функция – это подпрограмма, которая возвращает значение. Функция имеет следующий синтаксис: Function <имя> (<список параметров>) As <тип> <инструкции> <имя> = <выражение> [Exit Function] <инструкции> <имя> = <выражение> End Function Первая строка, содержащая имя функции, список параметров и тип результата, называется заголовком функции. Имя и параметры функции создаются и анализируются компилятором по тем же правилам, что и имя и параметры процедуры. Тип, указываемый после списка параметров, задаёт тип результата функции. Поскольку функция должна возвращать некоторый результат, необходимо указать, какое именно значение будет результатом функции. Для этого используется инструкция <имя> = <выражение>. Рассмотрим для примера разработку функции, вычисляющей в массиве среднее арифметическое значений, больших заданного числа. Public Function Average(mas As Range, h As Double) As Variant Dim s As Double, n As Integer Dim cell As Range s = 0 n = 0 For Each cell In mas If cell.Value > h Then s = s + cell.Value n = n + 1 End If Next cell If n = 0 Then Average = CVErr(xlErrDiv0) Else Average = s / n End If End Function Обратите внимание на то, что тип параметров указан как Range и Double, т.е. использованы конкретные типы данных. Параметры этой функции должны иметь именно такие типы – иначе не логично. При передаче в функцию параметров, типы которых не соответствуют указанным, а также если диапазон содержит не числа, функция будет возвращать признак ошибки #ЗНАЧ!. Проверка соответствия типов производится приложением Microsoft Excel. Если бы тип параметров был указан как Variant, разработчику функции пришлось бы добавлять инструкции для проверки типов параметров. Тип результата функции указан как Variant, поскольку функция может вернуть как число, так и признак ошибки. Объект Range обычно представляет собой двумерный массив. В данной функции, однако, используется только один цикл For Each, который позволяет перебрать все ячейки массива, в том числе и для двумерного массива. Другие языки программирования, в частности, Паскаль и С++ не имеют подобный циклов и для обработки двумерных массивов необходимо использовать два вложенных параметрических цикла. Однако, обратите внимание, что в данной функции расположение элементов по строкам и столбцам не принципиально и на результат не влияет. Поэтому можно использовать один цикл For Each. Но в других случаях, возможно, также придётся использовать два параметрических цикла. Рассмотрим разработку функции, проверяющей в диапазоне наличие пустых ячеек. Public Function Check(mas As Range) As Boolean Dim cell As Range For Each cell In mas If cell.Value = "" Then Check = True Exit Function End If Next cell Check = False End Function 4.2. Вызов функции Что функция заработала, её, также как и процедуру, необходимо вызвать. Для вызова функции существуют две возможности: функция может быть использована как формула (или часть формулы) ячейки рабочего листа; функция может быть вызвана из другой процедуры или функции. Например, можно записать в ячейку следующие формулы: =Average(A1:D5;10) =Average(B2:F7;E14) Можно вызвать функцию Check из функции Average. Public Function Average(mas As Range, h As Double) As Variant Dim s As Double, n As Integer Dim cell As Range If Check(mas) Then Average = CVErr(xlErrNull) Exit Function End If s = 0 n = 0 For Each cell In mas If cell.Value > h Then s = s + cell.Value n = n + 1 End If Next cell If n = 0 Then Average = CVErr(xlErrDiv0) Else Average = s / n End If End Function 4.3. Функции, возвращающие массивы В качестве результата функция VBA может возвращать массив значений. Такая функция вставляется не в одну ячейку, а в диапазон, и вставка завершается нажатием клавиш Ctrl + Shift + Ввод. Public Function GreaterThanAverage(m As Range) As Variant Dim r() As Integer Dim n As Integer, i As Integer, j As Integer Dim av As Double ReDim r(1 To m.Rows.Count, 1 To 1) av = 0 For i = 1 To m.Rows.Count For j = 1 To m.Columns.Count av = av + m.Cells(i, j) Next j Next i av = av / m.Rows.Count / m.Columns.Count For i = 1 To m.Rows.Count n = 0 For j = 1 To m.Columns.Count If m.Cells(i, j) > av Then n = n + 1 Next j r(i, 1) = n Next i GreaterThanAverage = r End Function Обратите внимание, что одномерный массив соответствует строке, поэтому в данном случае не может быть использован. Одномерный массив, который надо расположить в виде столбца, должен быть объявлен как двумерный массив с одним столбцом. 5. Параметры Существуют две точки, где используется список параметров подпрограммы – заголовок процедуры или функции и вызов процедуры или функции. Параметры, записанные в заголовке, называются формальными, а параметры, записанные в вызове, – фактическими. Между этими двумя списками существует разница, во-первых, в синтаксисе, а во-вторых, что более важно, в семантике. Список формальных параметров – это список неких условных переменных. Он описывает данные, которые должны быть переданы в подпрограмму, в общем виде. Например, в функцию Average необходимо передавать диапазон и число. Список фактических параметров – это список вполне конкретных значений, которые реально передаются в подпрограмму и которые она обрабатывает. Мы рассматривали примеры вызова функции Average, в которых в функцию передавались конкретные диапазоны (A1:D5, B2:F7) и конкретные числа (10, число из ячейки E14). Формальные параметры – это, в общем-то, абстракция. Фактические параметры должны реально существовать, т.е. это должны быть конкретные диапазоны, константы, числа, содержащиеся в конкретной ячейке. Можно провести аналогию с математическим выражением, записанным в общем виде с использованием переменных, например, x2 + y2. Можно построить график функции, исследовать свойства этого выражения, оперировать с ним в общем виде, но нельзя вычислить значение этого выражения, пока мы не подставим конкретные числа вместо переменных x и y. Формальные параметры соответствуют переменным математического выражения, а фактические параметры – конкретным числам. Список формальных параметров определяет количество, порядок и типы параметров, которые должны быть переданы в подпрограмму при вызове. Список фактических параметров представляет собой список выражений, разделённых запятыми. Значения этих выражений подставляются вместо формальных параметров последовательно, т.е. значение первого фактического параметра – вместо первого формального параметра, значение второго фактического параметра – вместо второго формального параметра и т.д. Список фактических параметров должен соответствовать списку формальных параметров по следующим критериям. 1. По количеству. 2. По типу. 3. По порядку следования. 5.1. Необязательные параметры Язык VBA позволяет объявлять параметр как необязательный, а также задавать так называемые значения по умолчанию. Для указания того, что параметр является необязательным, используется ключевое слово Optional, которое ставится перед именем параметра. Для задания значения по умолчанию после описания формального параметра ставится знак равенства и значение, которое подставляется в подпрограмму, в случае отсутствия в вызове соответствующего фактического параметра. Рассмотрим функцию, которая может возвращать целый диапазон, одну строку из диапазона, один столбец из диапазона или одну ячейку диапазона. Public Function RangePart(r As Range, Optional row As Integer = 0, Optional column As Integer = 0) As Variant If row = 0 And column = 0 Then RangePart = r ElseIf row = 0 Then RangePart = r.Columns(column) ElseIf column = 0 Then RangePart = r.Rows(row) Else RangePart = r.Cells(row, column) End If End Function 'Функция возвращает весь диапазон, переданный в качестве первого параметра r = RangePart(Worksheets(5).Range("A1:C4")) 'Функция возвращает одну ячейку, находящуюся в 1 строке 3 столбце r = RangePart(Worksheets(5).Range("A1:C4"), 1, 3) 'Функция возвращает одну строку r = RangePart(Worksheets(5).Range("A1:C4"), 5) 'Функция возвращает один столбец r = RangePart(Worksheets(5).Range("A1:C4"), , 4) r = RangePart(Worksheets(5).Range("A1:C4"), column:=4) Параметры с типом Variant можно просто опускать, без задания значения по умолчанию, т.к. тип Variant позволяет в самом параметре указать факт отсутствия параметра. Для проверки, был ли параметр задан, используется функция IsMissing. Рассмотрим для примера процедуру, которая в заданном диапазоне заменяет отрицательные числа. Кроме исходного диапазона можно также задать диапазон, откуда берутся числа для замены (в случае отсутствия этого параметра отрицательные числа заменяются модулями), и диапазон, куда копируется исходный диапазон с изменёнными значениями. Public Sub Change(source As Range, Optional replace, Optional dest) Dim i As Integer, j As Integer If IsMissing(dest) Then Set dest = source Else If TypeName(dest) <> "Range" Then Exit Sub End If If Not IsMissing(replace) And TypeName(replace) <> "Range" Then Exit Sub For i = 1 To source.Rows.Count For j = 1 To source.Columns.Count If source.Cells(i, j) < 0 Then If IsMissing(replace) Then dest.Cells(i, j) = -source.Cells(i, j) Else dest.Cells(i, j) = replace.Cells(i, j) End If Else dest.Cells(i, j) = source.Cells(i, j) End If Next j Next i End Sub 'Замена отрицательных чисел диапазона A1:C2 на их модули 'Результат записывается в исходный диапазон A1:C2 Change Worksheets(4).Range("A1:C2") 'Замена отрицательных чисел диапазона A1:C2 на числа из диапазона E1:G2 'Результат записывается в исходный диапазон A1:C2 Change Worksheets(4).Range("A1:C2"), Worksheets(4).Range("E1:G2") 'Замена отрицательных чисел диапазона A1:C2 на числа из диапазона E1:G2 'Результат записывается в диапазон I1:K2 Change Worksheets(4).Range("A1:C2"), Worksheets(4).Range("E1:G2"), _ Worksheets(4).Range("I1:K2") 'Замена отрицательных чисел диапазона A1:C2 на их модули 'Результат записывается в диапазон I1:K2 Change Worksheets(4).Range("A1:C2"), , Worksheets(4).Range("I1:K2") 'Явное указание имён параметров – данный вызов процедуры аналогичен предыдущему Change dest:=Worksheets(4).Range("I1:K2"), source:=Worksheets(4).Range("A1:C2") 5.2. Передача параметров по значению и по ссылке В списке формальных параметров перед описанием параметра может быть указано одно из ключевых слов ByVal или ByRef. Эти ключевые слова задают способ передачи параметра – по значению или по ссылке. Передача параметра по значению означает передачу подпрограмме копии фактического параметра. Таким образом, переменная, являющаяся фактическим параметром, не изменяется, даже если подпрограмма производит какие-либо действия с данным параметром. Передача параметра по ссылке означает, что переменная, являющаяся фактическим параметром, может быть изменена инструкциями подпрограммы. Public Sub ParameterByValue(ByVal x As Integer) x = x * 10 End Sub Public Sub ParameterByReference(ByRef x As Integer) x = x * 10 End Sub Dim n As Integer n = 8 ParameterByValue n ParameterByReference n 'n = 8 'n = 80 В языке VBA по умолчанию используется способ передачи «по ссылке», что, вообще-то, опасно, т.к. может привести к нежелательному изменению переменных.