Если потребовалось заиметь в Excel функцию, которой там еще нет, но она очень нужна или её применение значительно упростило бы жизнь при выполнение определенных задач, то самое время посмотреть в сторону функций пользователя.
Такие функции вызываются через Мастер функций -категория Определенные пользователем
Так что же это за функции такие? Функция пользователя это функция, написанная при помощи языка Visual Basic for Application (VBA) и вызываемая как любая другая функция с листа. Но т.к. эти функции пишутся самостоятельно - можно создать любую функцию, которая будет делать то, что ни одна стандартная функция делать не умеет. Естественно, теперь возникает вопрос как написать такую функцию. Для написания UDF понадобятся хотя бы базовые знания языка VBA. Я в статье опишу лишь принципы создания таких функций и после прочтения вы сможете создать простейшую функцию. Но это никак не означает, что я научу создавать функции на все случаи жизни, ибо это сводится к обучению самому языку программирования. В статье же рассмотрим основные принципы создания, некоторые нюансы и как уже написанные функции использовать в своей книге.
- Основные ограничения функций пользователя
- Как создать функцию пользователя
- Аргументы функции пользователя
- Необязательные аргументы функции пользователя
- Динамическое количество аргументов в функции пользователя(ParamArray)
- Создание формулы массива из UDF или ввод формулы сразу в несколько ячеек
- Как добавить уже созданную функцию в свою книгу
- Обновление расчетов функции пользователя UDF(автопересчет)
- UDF
не может изменять значения других ячеек (с небольшими недокументированными исключениями). Т.е. фактическиUDF может возвращать значение только в ту ячейку, в которой записана - это ограничение Excel и все обходные методы использовать не рекомендуются во избежание непредвиденных результатов. - UDF
не может изменять форматы ячеек либо присваивать форматы (с небольшими недокументированными исключениями) - UDF
не может изменять так называемые объекты окружения самого Excel. Например, сменить стиль ссылок или параметры вычислений формул, вид курсора и т.п. - UDF
будет некорректно работать с такими методами как FindNext, SpecialCells, CurrentRegion, CurrentArray, Select, ShowPrecedents и ShowDependents(выделение зависимостей ячеек), Application.GoTo и т.п. Хотя методы вроде Range.End(xlUp), Range.End(xlDown), обычный Find(без FindNext) проблем не вызывают.Подробнее про работу этих методов из UDF можно узнать из статьи: Глюк работы в UDF методов SpecialCells и FindNext - для работы функции пользователя(UDF) обязательно должны быть разрешены макросы
Предполагается, что Вы уже обладаете начальными навыками написания процедур в VBA и умеете создавать эти самые процедуры, хотя бы самые простые.
Т.к. функции пользователя создаются в редакторе VBA, то необходимо сначала перейти в редактор: сочетанием клавиш
Однако прежде чем читать дальше советую ознакомиться так же со статьей: Что такое модуль? Какие бывают модули?
- в отличие от процедуры
( функция всегда начинается именно со словаSub ) , а неFunction ;Sub - в теле функции всегда должно быть присвоение ей значения, иначе функция не вернет необходимый результат;
- функция должна располагаться в стандартном модуле или в модуле книги, если Вы планируете вызывать её непосредственно с листа Excel
- функции пользователя "привязаны" к той книге, в которой созданы и по умолчанию не будут работать в других (для этого надо будет всегда указывать имя книги с функцией). Чтобы созданные функции работали удобно и без проблем в любой книге необходимо книгу с функциями сохранить как надстройку: Как создать свою надстройку?
Пример самой простой функции пользователя:
Function ТекущаяДата() 'присваиваем функции значение, чтобы она вернула его на лист(обязательно!) ТекущаяДата = Date 'ТекущаяДата - имя функции и именно ему необходимо передать результат End Function |
Эта функция делает одно - возвращает в ячейку, в которую записана, текущую дату. В ячейке листа Excel эта функция будет выглядеть так:
Иначе функции вернет 0 или пустую ячейку. Т.е. надо записать имя самой функции(ТекущаяДата) и после знака равно указать то значение, которое необходимо записать в ячейку с функцией в качестве результата.
К записи пользовательских функций в ячейку предъявляются такие же требования, как и к встроенным функциям. Это касается так же и скобок на конце функции, у которой нет аргументов. И так же это означает, что в функцию могут быть переданы наши собственные аргументы
Function MySum(vArg1 As Double, vArg2 As Double) Dim dblSum as Double 'получаем сумму двух аргументов dblSum = vArg1 + vArg2 'присваиваем функции значение, чтобы она вернула его на лист(обязательно!) MySum = dblSum 'MySum имя функции и именно ему необходимо передать результат End Function |
В приведенном выше коде я упростил стандартную функцию
где:
Функция вернет
Однако иногда бывает неизвестно, сколько аргументов будет передано в функцию: 1, 2 или 10. Для этого можно использовать ключевой параметр
Function SumFiveArgs(arg1 As Double, Optional arg2 As Double, Optional arg3 As Double, Optional arg4 As Double, Optional arg5 As Double) Dim dblSum As Double dblSum = arg1 dblSum = dblSum + arg2 dblSum = dblSum + arg3 dblSum = dblSum + arg4 dblSum = dblSum + arg5 SumFiveArgs = dblSum End Function |
Функция будет работать отлично, даже если передать одно или два числа. Но это только в том случае, если для аргументов у нас заданы строгие типы данных - в примере это Double. Если тип не задан - получим ошибку
Function SumFiveArgs(arg1 As Double, Optional arg2, Optional arg3, Optional arg4, Optional arg5) Dim dblSum As Double dblSum = arg1 dblSum = dblSum + arg2 dblSum = dblSum + arg3 dblSum = dblSum + arg4 dblSum = dblSum + arg5 SumFiveArgs = dblSum End Function |
Можно, конечно, всегда задавать тип данных, как в первом примере. Но стоит учитывать, что для числовых типов данных(
'функция деления аргументов между собой Function DivideFiveArgs(arg1 As Double, Optional arg2 As Double, Optional arg3 As Double, Optional arg4 As Double, Optional arg5 As Double) Dim dblSum As Double dblSum = arg1 dblSum = dblSum / arg2 dblSum = dblSum / arg3 'уже здесь получим ошибку "на ноль делить нельзя" dblSum = dblSum / arg4 dblSum = dblSum / arg5 DivideFiveArgs = dblSum End Function |
'функция перемножения аргументов между собой Function MultipleFiveArgs(arg1 As Double, Optional arg2 As Double, Optional arg3 As Double, Optional arg4 As Double, Optional arg5 As Double) Dim dblSum As Double dblSum = arg1 dblSum = dblSum * arg2 dblSum = dblSum * arg3 'здесь arg3 равен нулю, значит далее сумма будет тоже равна нулю dblSum = dblSum * arg4 dblSum = dblSum * arg5 MultipleFiveArgs = dblSum End Function |
Передав меньше аргументов в функцию
А передав меньше аргументов в функцию умножения
Проверять каждый аргумент на равенство нулю(
Тут надо знать, что если тип аргумента не указан и сам аргумент в функцию не был передан - то ему назначается особый тип -
Function MultipleFiveArgs(arg1 As Double, Optional arg2, Optional arg3, Optional arg4, Optional arg5) Dim dblSum As Double dblSum = arg1 'проверяем, что аргумент передан(NOT IsMISSING) If Not IsMissing(arg2) Then dblSum = dblSum * arg2 End If If Not IsMissing(arg3) Then dblSum = dblSum * arg3 End If If Not IsMissing(arg4) Then dblSum = dblSum * arg4 End If If Not IsMissing(arg5) Then dblSum = dblSum * arg5 End If MultipleFiveArgs = dblSum End Function |
Как видно - теперь
И для большего кругозора еще одна простая функция, но которая работает уже с текстом и вернет строку до первого пробела:
Function ТекстДоПервогоПробела(Текст As String) As String Dim i As Long Dim Result As String 'переменная для результата i = InStr(1, Текст, " ", 1) 'ищем позицию первого пробела в переданном тексте 'если пробел есть и он не первый символ в строке If i > 1 Then Result = Mid(Текст, 1, i - 1) 'получаем текст до первого пробела Else 'если пробела нет - возвращаем всю строку Result = Текст End If 'присваиваем результат функции для возврата его на лист ТекстДоПервогоПробела = Result End Function |
Эту функцию можно записать и намного короче:
Function ТекстДоПервогоПробела(Текст As String) As String ТекстДоПервогоПробела = Split(Текст, " ")(0) End Function |
Но в таком виде функция вернет значение ошибки
Обе функции можно дополнить не обязательным аргументом - разделитель слов. И сделать его по умолчанию пробелом. Значение по умолчанию в данном случае задается сразу при объявлении аргумента. Выглядеть это будет так:
Function ТекстДоУказанногоСимвола(Текст As String, Optional Разделитель As String = " ") As String ТекстДоУказанногоСимвола = Split(Текст, Разделитель)(0) End Function |
В данном примере если вызвать функцию так:
то функция будет использовать в качестве разделителя пробел(
Сразу после некоторого использования
Function SumMultiple(ParamArray args()) Dim dblSum As Double, arg On Error Resume Next For Each arg In args dblSum = dblSum + arg Next SumMultiple = dblSum End Function |
Но такая функция может выдать ошибку, если в качестве любого аргумента будет передана не одна единственная ячейка или значение - а диапазон ячеек(
Function SumMultiple_DiffTypes(ParamArray args()) Dim dblSum As Double, arg, rc As Range, x On Error Resume Next For Each arg In args Select Case TypeName(arg) Case "Range" 'это диапазон 'цикл по всем ячейкам For Each rc In arg.Cells 'проверяем, что в ячейке числовой тип данных If IsNumeric(rc.Value) Then dblSum = dblSum + rc.Value End If Next Case "Variant()" 'это произвольный массив({10;20;30}) 'цикл по всем ячейкам For Each x In arg 'проверяем, что это числовой тип данных If IsNumeric(x) Then dblSum = dblSum + x End If Next Case "Double", "Long", "Integer" 'это любой числовой тип 'суммируем dblSum = dblSum + arg 'все остальные типы игнорируем End Select Next SumMultiple_DiffTypes = dblSum End Function |
И в такую функцию может быть передан любой из наиболее распространенных типов данных:
Но и
Так же можно применить
Function ОбъединитьВсеСРазделителем(Разделитель As String, ParamArray Значения()) As String Dim result As String, arg, x, rc As Range For Each arg In Значения Select Case TypeName(arg) Case "Range" 'это диапазон 'цикл по всем ячейкам For Each rc In arg.Cells If result = "" Then result = rc.Value Else result = result & Разделитель & rc.Value End If Next Case "Variant()" 'это произвольный массив({"а";"б";"в"}) 'цикл по всем ячейкам For Each x In arg If result = "" Then result = x Else result = result & Разделитель & x End If Next Case Else 'это любой другой тип 'суммируем If result = "" Then result = arg Else result = result & Разделитель & arg End If End Select Next ОбъединитьВсеСРазделителем = result End Function |
Пример вызова такой функции с листа(первым обязательно передается разделитель, а далее уже что объединять - любой тип данных):
Иногда бывает необходимо делать вычисления таким образом, чтобы они возвращались сразу в несколько ячеек. А порой без этого вообще не обойтись. Например, если расчет значения для следующей ячейки напрямую зависит от полученного на предыдущей итерации и видеть надо одновременно значения всех итераций. Например, вывести в несколько строк и столбцов числа от 6 с шагом 2. Стандартными формулами это довольно непросто сделать - ведь придется как-то определять сколько ячеек в каждом столбце и какое последнее число в каждом из столбцов. Через функцию пользователя, созданную как формула массива(подробнее про формулы массива), это сделать проще.
Ниже представлена функция
'--------------------------------------------------------------------------------------- ' Author : Щербаков Дмитрий(The_Prist) ' Профессиональная разработка приложений для MS Office любой сложности ' Проведение тренингов по MS Excel ' https://www.excel-vba.ru ' info@excel-vba.ru ' Purpose: Функция записывает в ячейки числа от первого заданного(Число) с заданным шагом(Шаг) ' Вводится сразу в несколько ячеек и ввод завершается сочетанием клавиш Ctrl+Shift+Enter '--------------------------------------------------------------------------------------- Function WriteNumbers(Число As Double, Шаг As Double) Dim aNumbers() 'массив для записи результата Dim rResRange As Range Dim lr As Long, lc As Long, dblNum As Double 'задаем начальное значение числа - оно равно Шаг dblNum = Число 'определяем кол-во выделенных ячеек, в которые надо вернуть результат Set rResRange = Application.Caller ReDim aNumbers(1 To rResRange.Rows.Count, 1 To rResRange.Columns.Count) 'создаем массив результирующих чисел для всех выделенных ячеек For lc = 1 To rResRange.Columns.Count For lr = 1 To rResRange.Rows.Count aNumbers(lr, lc) = dblNum dblNum = dblNum + Шаг Next Next 'возвращаем результат WriteNumbers = aNumbers End Function |
Чтобы правильно применить приведенную UDF, необходимо
- выделить несколько ячеек(например,
)A1:F10 - в строку формул ввести нашу UDF:
=WriteNumbers(6;2) - завершить ввод формулы сразу тремя клавишами
Ctrl +Shift +Enter
Главное, на что надо обратить внимание - это тип переменной, которая используется для записи результата:
В примере выше итоговый массив определяется автоматически при помощи
Для начала необходимо создать стандартный модуль(Insert -Module). Затем в этот модуль вставить весь текст функции(код). Все, теперь функция доступна из диспетчера функций в категории Определенные пользователем
Если Вы используете версию Excel 2007 и выше, то книгу необходимо будет сохранить с поддержкой макросов: Меню -Сохранить как -Книга Excel с поддержкой макросов.
По умолчанию функции пользователя не пересчитываются вместе с пересчетом листа или по нажатию
Function ТекущаяДатаВремя() ТекущаяДатаВремя = Now 'Now - возвращает текущие дату и время End Function |
Если записать её в таком виде, то после записи в ячейку:
при первой записи будут показаны текущие дата и время. Чтобы эксперимент был более наглядным, лучше перейти в Формат ячеек и выставить для ячейки с функцией формат "
Function ТекущаяДатаВремя() Application.Volatile True ТекущаяДатаВремя = Now 'Now - возвращает текущие дату и время End Function |
Теперь при каждом пересчете листа и при любом изменении на листе функция будет пересчитываться. В некоторых случаях это делать просто необходимо(например, если применяется функция получения имени листа или книги).
Но стоит всегда учитывать тот факт, что не всегда такой автопересчет полезен. Если функция пользователя использует "тяжелые" расчеты и выполняется долго - добавление автопересчета может значительно затормозить работу с файлом. Поэтому применять параметр следует с осторожностью.
Если надо, чтобы функция пересчитывалась только при изменениях в конкретном диапазоне/ячейках, можно просто сделать необязательные параметры:
Function ТекущаяДатаВремя(Optional ДиапазонОбновления As Range = Nothing) ТекущаяДатаВремя = Now End Function |
тогда при любом изменении в ячейках аргумента
при любом изменении в столбце E функция будет пересчитана.
Некоторые примеры функций пользователя можно увидеть здесь на сайте:
- Как оставить в ячейке только цифры или только текст?
- Как получить текст примечания в ячейку?
- Как скопировать картинку из примечания?
- Как получить адрес гиперссылки из ячейки
- Сцепить много ячеек с указанным разделителем
- Как сцепить несколько значений в одну ячейку по критерию? СцепитьЕсли
- Сравнение текста по части предложения
Чтобы использовать функции пользователя более удобно, их лучше размещать в специальных файлах - надстройках: Как создать свою надстройку?
Большое спасибо! Очень полезная информация!
Здравствуйте. Все сделал как написано выше, функция работает! Но при создании нового документа этой функции в списке НЕТ. Я что-то упустил или так и должно быть?
Так и должно быть, в принципе.
Модули с функциями привязаны к документу, в котором они созданы.
Если хотите использовать функции из документа А в документе Б, то просто скопируйте их в Б.
Так же, может быть, можно просто не закрывать документ А - когда он открыт, его модули доступны.
Дмитрий, добрый день!
Извините, если вопрос не подходит в рубрику, но подскажите, пожалуйста! Есть ли возможность функцией записать "подбор параметра"? Если есть, как это сделать?