具有参数的Excel自定义函数

时间:2018-08-30 12:21:04

标签: excel vba function

我正尝试在excel模块中创建一个自定义函数,如下所示:

Function STATUS(valuex As String)

    If ActiveCell.Offset(0, 1).Value = valuex Then

    ActiveCell.Value = ActiveCell.Offset(0, -1).Value

    'Remove value from left column
     Activecell.offset(0,1).clearcontents

   End If

End Function

它基本上会这样做:

Number  Result  Status
          11    System
22              Type
          33    System
          44    System
55              Hardware
66              Type
          77    System
          88    System
99              Software
110             Type
         121    System
         132    System
143             Hardware
154             Type
165             Type
         176    System
187             Hardware
198             Type
209             Software

如果右单元格= valuex(例如字符串“ System”)类似于valuex,则 将左单元格的值放在公式/函数单元格中,然后删除左列的值。

但是我编写的所有程序返回的都是零(0)或Name#错误。

请帮助

2 个答案:

答案 0 :(得分:7)

您正在编写用户定义函数(UDF),即公共标准模块中的Public Function,可以从工作表单元格中调用该类型-函数的约束具有一组特定的约束。例如,不允许具有副作用。 UDF接受一些输入,进行处理,然后返回结果。。

因此,UDF的签名应该看起来像这样:

Public Function {name}({args}) As {type}

编写函数时首先要考虑的是返回它需要什么-换句话说,说{{1的单元格是什么}}计算后应包含。例如,如果您编写了一个=MYFUNCTION(A1,A2)函数,将两个Add值相加,那么您将希望它返回一个Double

Double

函数的 body 根据给定的参数 计算结果

Public Function Add(ByVal value1 As Double, ByVal value2 As Double) As Double

在返回/退出之前,您需要分配函数的返回值。这是通过分配给函数的标识符完成的:

    Dim result As Double
    result = value1 + value2

然后,Excel的计算引擎将得出该结果,这就是具有 Add = result 之类的公式的单元格最终得到的值为=Add(2, 2)的原因。


您的4函数依赖于STATUSActiveCell是当前在ActiveSheet上选择的单元格:不是调用该函数的单元格 。如果选择了某些随机单元格,则重新计算工作簿可能会产生错误的结果。

作为UDF,不允许在某个单元格上.ClearContents(或以任何方式影响任何其他单元格)-这就是该函数为执行路径返回#NAME?错误的原因进入条件块,由于没有分配返回值,因此其他执行路径将产生0,这是Empty变体的数字表示,这是函数当前返回的内容。

如果UDF需要了解另一个单元格的值,则最好的办法是将该单元格的值作为参数:这样一来,该函数可以工作,而无需任何关于工作表布局的假设。如果VLOOKUP不使用lookup_value参数,而是从.Offset(0, 1)单元格中获取该值,它将有多大用处?会有骚动!


当您的要求是做某事而不是计算/计算某事时,您需要的不是UDF,而是

宏是公共标准模块(或Public Sub模块)中的无参数 Worksheet过程,可以从“宏”窗口中调用,或者当用户单击Shape,ActiveX CommandButton时执行,或者可以将它们分配给某些自定义菜单项的OnAction属性-无论动摇了什么。

Sub过程要做,它们是动作。他们可以访问和更改全局状态,修改任何单元格,工作表或工作簿;他们甚至可以生成PowerPoint实例,然后将图表作为图片粘贴到新的Slide上-您可能会想到的一切都是天空的极限!

由于这里需要的是做的事情,因此您需要编写的代码必须更像是宏。不要称其为STATUS;使用动词并描述其作用:您正在根据给定的标准将值从一列移动到另一列。在编写Sub过程时,请首先考虑要如何调用

我认为类似这样的东西会很整洁:

MoveValues Sheet1.Range("$B$2:$B$22"), "System"

签名如下所示:

Private Sub MoveValues(ByVal Target As Range, ByVal Criteria As String)

body 现在可以遍历指定的Target范围,评估右边的单元格是否与Criteria相匹配,然后将值左移。或者更好-我们根本不假设工作表布局,而是这样调用它:

With Sheet1
    MoveValues .Range("A2:A22"), .Range("B2:B22"), .Range("C2:C22"), "System"
End With

现在,如果我们需要在A与B之间或B与C之间插入一列,我们只需要更改传递给过程的参数,而不是过程本身!

Private Sub MoveValues(ByVal Source As Range, ByVal Target As Range, ByVal Status As Range, ByVal Criteria As String)

但是首先,我们需要验证我们的假设,并确定在未达到期望的情况下该怎么做-我们需要行数相等的单列范围!

在许多情况下,引发运行时错误是最好的方法。错误5:“无效的过程调用或参数”,似乎很合适:

    If Source.Columns.Count <> 1 Or Target.Columns.Count <> 1 Or  Status.Columns.Count <> 1 Or _
       Source.Rows.Count <> Target.Columns.Count Or _
       Status.Rows.Count <> Target.Columns.Count _
    Then
        Err.Raise 5
    End If

我们甚至可以自定义错误消息,以帮助我们稍后调试调用代码,当我们在此行的6个月内更改参数并忘记了有关该MoveValues过程的假设的所有信息:

    If Source.Columns.Count <> 1 Or Target.Columns.Count <> 1 Or  Status.Columns.Count <> 1 Or _
       Source.Rows.Count <> Target.Columns.Count Or _
       Status.Rows.Count <> Target.Columns.Count _
    Then
        Err.Raise 5, "MoveValues", _
            "Source, Target, and Status ranges must be 1 column and the same number of rows."
    End If

我们还需要验证我们的Criteria不是空的还是空白!

    If Trim$(Criteria) = vbNullString Then
        Err.Raise 5, "MoveValues", "Criteria string cannot be empty or whitespace."
    End If

现在我们已经验证了输入,其余过程可以安全地假设SourceTargetStatus范围都是单一的,列范围,它们都具有相同的大小,并且我们有一个有效的标准可以使用。这样我们就可以迭代细胞并做我们的事情:

    Dim current As Long
    For current = 1 To Target.Rows.Count
        If Status.Cells(current).Value = Criteria Then
            Target.Cells(current).Value = Source.Cells(current).Value
            Source.Cells(current).ClearContents 
        End If
    Next

现在剩下要做的就是编写一个调用它的宏:

Public Sub MoveSystemValues()
    With Sheet1
        MoveValues .Range("A2:A22"), .Range("B2:B22"), .Range("C2:C22"), "System"
    End With
End Sub

现在我们可以从Excel的 Macros 窗口中运行该MoveSystemValues宏,或将MoveSystemValues分配给某些Shape或按钮...然后意识到对于少量的行,它工作得很好,但是在较大的范围内,它的运行速度很慢-但我们现在有足够的机会继续学习。

答案 1 :(得分:2)

您无法使用UDF(用户定义的函数)来完成所需的操作。

您是从VBA开始的,因此您需要了解SUBS和FUNCTIONS之间的主要区别。

Subs在Excel文件上执行操作(诸如选择,清除内容,更改工作表,打开另一个工作簿,进行计算等操作)。

函数返回一个值,但不执行任何操作。将它们视为自己的公式Excel。 Excel中的公式不执行操作,它们只是根据某些参数返回值。

此处有更多信息:http://excelhints.com/2009/02/12/difference-between-sub-and-function/

因此,您需要将代码重写为子过程,然后执行它:)

更新:我在前几行中所说的并非总是100%正确。我发布了这篇文章,因为对于使用VBA开发代码的新手来说,这是一个很好的起点。从我的角度来看,当您是新手时,我认为学习仅使用潜艇进行操作以及仅使用函数来获取自定义计算会更容易。然后,根据经验,进入下一个级别,开始将它们组合起来,并使用它们来做更复杂的事情。执行代码时,可以将Subs和Functions组合在一起。

从子级调用UDF时,它可以执行一些操作(例如删除工作表)。我说了一些动作,因为老实说,我不知道所有动作都可用。

但是,如果您从一个单元格调用UDF,将其作为普通的Excel公式键入,那么它将不会执行任何操作。

一个例子:

&&

此UDF将删除我的工作簿的第三个工作表。

要从子目录中调用它,将是:

Public Function DeleteWorkSheet() As Boolean
Sheets(3).Delete
End Function

是的,它将删除第三个工作表。

但是,如果我从像普通Excel公式这样的单元格中调用此UDF,那么它将无济于事。

希望此说明会有所帮助。