我可以使用 VBA 函数将可接受值的(动态)列表返回到 Excel 的数据验证中吗?

Posted

技术标签:

【中文标题】我可以使用 VBA 函数将可接受值的(动态)列表返回到 Excel 的数据验证中吗?【英文标题】:Can I use VBA function to return a (dynamic) list of acceptable values into Excel's data validation? 【发布时间】:2011-01-24 14:19:52 【问题描述】:

对于给定的单元格,我选择数据/验证并将允许设置为“列表”。我现在希望像这样设置 Source:

=rNames(REGS)

但这不起作用(未找到名称)。所以我通过简单地分配上面的公式(没有单元格范围)来插入/命名/定义并创建“REGNAMES”。然后我返回到数据/验证,当我像这样设置源时:

=注册名称

现在我得到“源当前评估为错误”。不幸的是,即使我忽略它,这个错误也不会消失。我可以像这样在工作表中创建一个范围公式:

=REGNAMES

将它拖到右边穿过几个单元格,rNames 函数会忠实地返回

选项#1 |选项 #2 | ...

也就是说,该函数按预期返回一个范围。

我知道我可以使用宏代码从 VBA 中操作该单元格的列表设置。我不太喜欢这些副作用。我更喜欢建立在函数上的干净的依赖树。任何想法如何让数据/验证接受从 rNames 返回的数组值?

谢谢。

PS:rNames 将结果范围作为 Variant 返回,如果这有任何影响的话。

【问题讨论】:

请发布您的 rNames 代码...并解释 rNames(REGS) 中的 REGS 是什么 【参考方案1】:

我认为问题在于数据验证对话框只接受以下“列表”:

直接输入源字段的实际内容列表

文字范围引用(如 $Q$42:$Q$50)

本身解析为范围引用的命名公式

最后一个是关键 - 没有办法让 VBA 函数只返回一个可用于验证的数组,即使您从命名公式调用它也是如此。

可以编写一个返回范围引用的 VBA 函数,然后从命名公式调用 that。作为以下技术的一部分,这可能很有用,可以近似地做你真正想做的事情。

首先,在某处有一个实际范围,可以调用您的任意数组返回 VBA UDF。假设你有这个功能:

Public Function validationList(someArg, someOtherArg)

    'Pretend this got calculated somehow based on the above args...
    validationList = Array("a", "b", "c")
End Function

您将其从 $Q$42:$Q$50 作为数组公式调用。您将获得三个单元格,其中包含“a”、“b”和“c”,其余单元格将出现 #N/A 错误,因为返回的数组小于调用 UDF 的范围。到目前为止一切顺利。

现在,有另一个 VBA UDF,它只返回范围的“占用”部分,忽略 #N/A 错误单元格:

Public Function extractSeq(rng As Range)

    'On Error GoTo EH stuff omitted...

    'Also omitting validation - is range only one row or column, etc.

    Dim posLast As Long
    For posLast = rng.Count To 1 Step -1
        If Not IsError(rng(posLast)) Then
            Exit For
        End If

        If rng(posLast) <> CVErr(xlErrNA) Then
            Exit For
        End If
    Next posLast

    If posLast < 1 Then
        extractSeq = CVErr(xlErrRef)
    Else
        Set extractSeq = Range(rng(1), rng(posLast))
    End If
End Function

然后您可以从命名公式中调用它,如下所示:

=extractSeq($Q$42:$Q$50)

命名公式将返回一个范围引用,Excel 将接受一个允许的验证列表。笨重,但无副作用!

注意上面代码中关键字“Set”的使用。您的问题尚不清楚,但这可能是整个答案中唯一对您重要的部分。如果在尝试返回范围引用时不使用“Set”,VBA 将改为返回范围的,它不能用作验证列表。

【讨论】:

我还没试过这个。我觉得它可能会起作用,但正如你所说,它很笨重。我不喜欢必须搁置固定范围的电子表格单元格——人们永远不知道最大值应该是多少(并且搁置一整列/行是低效的)。不过,这是迄今为止最好的答案。我会考虑... 有效。我留出整个工作表只是为了将验证列表转换为连续范围(使用您的方法)。不理想,但是……不管是什么?谢谢你的想法。【参考方案2】:

我刚刚对访问 Shapes 下拉控件的内容进行了一些研究,并发现了另一种解决此问题的方法,您可能会觉得有帮助。

可以应用验证规则的任何范围都可以通过编程方式应用该规则。因此,如果您想对单元格 A1 应用规则,您可以这样做:

ActiveSheet.Range("A1").Validation.Add xlValidateList, , , "use, this, list"

上面添加了一个单元内下拉验证,其中包含“使用”、“此”和“列表”项。如果您覆盖Worksheet_SelectionChange() 事件,并检查其中的特定范围,您可以调用任意数量的例程来创建/删除验证规则。这种方法的美妙之处在于引用的列表可以是任何可以在 VBA 中创建的列表。我需要一个动态生成的工作簿中不断变化的工作表子集的列表,然后我将其连接在一起以创建验证列表。

Worksheet_SelectionChange() 事件中,我检查范围,如果匹配,则触发验证规则子,因此:

Private Sub Worksheet_SelectionChange(ByVal Target as Range)

    If Target.Address = "$A$1" Then
        UpdateValidation
    End If

End Sub

UpdateValidation() 中的验证列表生成器代码执行以下操作:

Public Sub UpdateValidation()

    Dim sList as String
    Dim oSheet as Worksheet

    For Each oSheet in Worksheets
        sList = sList & oSheet.Name & ","
    Next

    sList = left(sList, len(sList) -1)  ' Trim off the trailing comma

    ActiveSheet.Range("A1").Validation.Delete
    ActiveSheet.Range("A1").Validation.Add xlValidateList, , , sList

End Sub

现在,当用户单击下拉箭头时,他/她将看到更新的验证列表。

【讨论】:

优秀而简单的解决方案。我很快就会发布一个完整的工作示例 xls。其他问题:列表项的数量是否有限制?以及如何处理items中的“,”字符? 非常好的解决方案。但是缺点是 sList-String 的最大长度为 255 个字符。较长的字符串被截断(在 Excel 2003 中测试,32 位)。【参考方案3】:

听起来您的 rNames 函数可能返回一个一维数组(将被视为一行)。 尝试让您的函数将一列作为基于 1 的二维数组返回(Ansa(1,1) 然后 Ansa(2,1) 等)

【讨论】:

你猜对了,但是改变结果数组的方向对结果没有影响——它不起作用。【参考方案4】:

您不能使用dynamic range names 吗?这很简单,不需要任何 vba。

【讨论】:

我将此问题解释为想要动态更改验证列表的内容,而不仅仅是适应列表大小的更改。 不,这不起作用,因为列表项不在连续范围内。谢谢。【参考方案5】:

为了未来:

随后在命名范围中使用以下内容,并将命名范围设置为“数据验证”“列表”值

Function uniqueList(R_NonUnique As Range) As Variant

    Dim R_TempList As Range
    Dim V_Iterator As Variant
    Dim C_UniqueItems As New Collection

    On Error Resume Next
    For Each V_Iterator In R_NonUnique
        C_UniqueItems.Add "'" & V_Iterator.Parent.Name & "'!" & V_Iterator.Address, CStr(V_Iterator.Value2)
    Next V_Iterator
    On Error GoTo 0

    For Each V_Iterator In C_UniqueItems
        If R_TempList Is Nothing Then
            Set R_TempList = Range(V_Iterator)
        End If
        Set R_TempList = Union(R_TempList, Range(V_Iterator))
    Next V_Iterator

    Set uniqueList = R_TempList

End Function

【讨论】:

【参考方案6】:

@user5149293 我非常感谢您的代码,但我建议在添加重复值时防止集合引发错误。在数据验证列表或 Name-Manager-Formula 中使用自定义公式会阻止代码使用 vbe 调试器,这使得在此处追溯错误非常困难(我自己遇到了这个问题,在使用您的代码时) . 我建议使用单独的函数检查集合中是否存在键:

    Function uniqueList(R_NonUnique As Range) As Variant
    'Returns unique list as Array

        Dim R_TempList As Range
        Dim V_Iterator As Variant
        Dim C_UniqueItems As New Collection

        For Each V_Iterator In R_NonUnique
           'Check if key already exists in the Collection
           If Not HasKey(C_UniqueItems, V_Iterator.Value2) Then
              C_UniqueItems.Add Item:="'" & V_Iterator.Parent.Name & "'!" & V_Iterator.Address, Key:=CStr(V_Iterator.Value2)
           End If
        Next V_Iterator

        For Each V_Iterator In C_UniqueItems
            If R_TempList Is Nothing Then
                Set R_TempList = Range(V_Iterator)
            End If
            Set R_TempList = Union(R_TempList, Range(V_Iterator))
        Next V_Iterator

        Set uniqueList = R_TempList

    End Function


    Function HasKey(coll As Collection, strKey As String) As Boolean
    'https://***.com/questions/38007844/generic-way-to-check-if-a-key-is-in-a-collection-in-excel-vba
        Dim var As Variant
        On Error Resume Next
        var = coll(strKey)
        HasKey = (Err.Number = 0)
        Err.Clear

    End Function

【讨论】:

以上是关于我可以使用 VBA 函数将可接受值的(动态)列表返回到 Excel 的数据验证中吗?的主要内容,如果未能解决你的问题,请参考以下文章

EXCEL VBA - 根据单元格范围和字符串创建动态下拉列表[关闭]

函数使用智能感知接受多个枚举

如何将可重复使用的多行/多列表从一个 Excel 工作表复制到另一个工作表上的数据库

在 VBA 中创建范围

Swig:将成员变量(指向值的指针)转换为 python 列表

将值的动态列表传递到 Table.Combine