【问题标题】:Find column based on column header根据列标题查找列
【发布时间】:2019-02-13 23:13:41
【问题描述】:

我有此代码,它仅在我要查找的标题位于 B 列或“更高”列时才有效。
假设我有这张表,需要找到“名称”和“分数”在哪一列。

Name    score
John       1
Joe        5

如果“姓名”在 B1 中,“分数”在 C1 中,则以下代码将起作用:

NameColumn = Split(Cells(1, Cells(1, 1).EntireRow.Find(What:="Name", LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByColumns, searchdirection:=xlNext, MatchCase:=True).Column).Address(True, False), "$")(0)
ScoreColumn = Split(Cells(1, Cells(1, 1).EntireRow.Find(What:="score", LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByColumns, searchdirection:=xlNext, MatchCase:=True).Column).Address(True, False), "$")(0)
                                                                ^^ <- search value

上面的代码会在这种情况下返回

NameColumn = "B"
ScoreColumn = "C"

但假设列是 A 和 B,那么它将找不到“名称”,因为它开始在单元格 1,1 (A1) 之后搜索,这是标题所在的位置。

我可以进行哪些更改以使其正常工作,或者在上面的示例中返回“A”和“B”有哪些替代方法?

【问题讨论】:

  • 改用worksheetfunction.match,这将为您提供范围内的列,然后您可以在以后需要时从中获得offset
  • 搜索你的问题标题turnsupseveralways来做到这一点。
  • @Comintern 这些答案使用 find 返回找到的值。我知道我可以使用 debug.print f.column 但这会返回一个整数偏移量,这不是我想要的。
  • 另外请告诉我,这些变量的使用已经结束,我希望你不要在 Range() 或 Cells() 中使用这些字母。如果是这样,为什么不只返回列号并跳过拆分/转换
  • 此代码按原样“工作”(前提是第 1 行中每个标题名称只有一个实例)。虽然 Find 将从 B1 开始搜索是正确的,但它会在单元格 A1 中换行并找到 Name。也就是说,我和 Scott 在一起,这可能是 X-Y 问题。很少真正需要找到列字母而不是列索引。

标签: excel vba


【解决方案1】:

这是我过去使用过的快速 UDF 函数。

这可能不是最好的方法,但这是我多年来一直使用的方法。

Function ColumnHeaderLocation(entry As String, Optional ColumnNumber As Boolean)
Dim x, y As Long

y = ActiveSheet.Columns.Count
x = 1

Do Until x > y
    If ActiveSheet.Cells(1, x).Value = entry Then
        ColumnHeaderLocation = Split(ActiveSheet.Cells(1, x).Address(True, False), "$")(0)
        If ColumnNumber = True Then ColumnHeaderLocation = x
        Exit Function
    Else
        x = x + 1
    End If
Loop

End Function

只需使用列标题的名称(见示例)...

NameColumn = ColumnHeaderLocation("Name") ' returns Column Location as a letter

或者

NameColumn = ColumnHeaderLocation("Name", True) ' Returns column Location as a Number

【讨论】:

  • 如果我只是将它改造成正常功能,这可能会起作用。明天我会调查一下。现在需要回家了。
【解决方案2】:

标题列字母计算

您必须将After 参数添加到指向最后一个单元格.Cells(.Cells.Count)Find 方法,以从第一个单元格.Cells(1) 开始搜索。但正如 cmets 中的 chris neilsen 所指出的,这不是您的代码失败的原因,因为它会在搜索结束时找到 Name
由于您尚未定义“不工作”的实际含义,并且您在 A1 中拼写错误 Name 的可能性很小,因此我假设 NameColumn 返回一个不希望的结果 (&lt;&gt;A),这意味着您在第一行的其他地方使用了Name,您确实需要从第一个单元格.Cells(1) 开始搜索。

短版

Sub FindAfterShort()

    Dim NameColumn As String   ' Name Column Letter
    Dim ScoreColumn As String  ' Score Column Letter

    With Rows(1)
        NameColumn = Split(.Find("Name", .Cells(.Cells.Count), xlValues, _
                xlWhole).Address, "$")(1)
        ScoreColumn = Split(.Find("Score", .Cells(.Cells.Count), xlValues, _
                xlWhole).Address, "$")(1)
    End With

    Debug.Print "Column Letters '" & NameColumn & "' and '" & ScoreColumn & "'."

End Sub

首选版本

Sub FindAfterPref()

    Const cSheet As String = "Sheet1"   ' Worksheet Name

    Dim strName As String   ' Name Column Letter
    Dim strScore As String  ' Score Column Letter

    With ThisWorkbook.Worksheets(cSheet).Rows(1)
        strName = Split(.Find("Name", .Cells(.Cells.Count), xlValues, _
                xlWhole).Address, "$")(1)
        strScore = Split(.Find("Score", .Cells(.Cells.Count), xlValues, _
                xlWhole).Address, "$")(1)
    End With

    Debug.Print "Column Letters '" & strName & "' and '" & strScore & "'."

End Sub

小研究

Sub FindAfter()

    Const cSheet As String = "Sheet1"   ' Worksheet Name
    Const cFR As Long = 2               ' First Row
    ' The column where the Last Row Number will be calculated.
    Const cLRColumn As Variant = "A"    ' Last-Row Column Letter/Number

    Dim rngName As Range    ' Name Column Range, Name Range
    Dim rngScore As Range   ' Score Column Range, Score Range
    Dim lngName As Long     ' Name Column Number
    Dim lngScore As Long    ' Score Column Number
    Dim strName As String   ' Name Column Letter
    Dim strScore As String  ' Score Column Letter
    Dim lngLR As Long       ' Last Row Number (Calculated in Last-Row Column)

    With ThisWorkbook.Worksheets(cSheet).Rows(1)

        ' Find Method Arguments
        '   2. After: If you want to start the search from the first cell, you
        '             have to set the After parameter to the last cell. If you
        '             have the matching data in the first cell and you set the
        '             parameter to the first cell (default), it will still be
        '             found, but a little later (not mili, but micro seconds
        '             later) so it could be omitted.
        '   5. SearchOrder: Whenever a range is a one-row or a one-column range,
        '                   this argument can be omitted. Since you're searching
        '                   in a one-row range, "xlByRows" would have been the
        '                   more 'correct' way in this case.
        '   6. SearchDirection: This argument's parameter is by default "xlNext"
        '                       and can therefore be omitted
        '   7. MatchCase: This argument's parameter is by default "False". Since
        '                 I don't see the reason why you would have headers with
        '                 the same name, especially the ones you don't need
        '                 before the ones you need, it is omitted. If you really
        '                 need it, use "... xlWhole, , , True".
        Set rngName = .Find("Name", .Cells(.Cells.Count), xlValues, xlWhole)
        Set rngScore = .Find("Score", .Cells(.Cells.Count), xlValues, xlWhole)

        ' Address Arguments
        ' If the Address arguments are omitted, Range.Address returns the
        ' address as an absolute reference e.g. $A$1. When you split
        ' $A$1 you will get the following
        ' INDEX STRING
        '   0           - Empty string ("").
        '   1      A    - Use this i.e. index 1 for the split array index.
        '   2      1
        If Not rngName Is Nothing Then ' When "Name" was found.
            ' Calculate Name Column Number.
            lngName = rngName.Column
            ' Calculate Name Column Letter.
            strName = Split(rngName.Address, "$")(1)
        End If
        If Not rngScore Is Nothing Then ' When "Score" was found.
            ' Calculate Score Column Number.
            lngScore = rngScore.Column
            ' Calculate Score Column Letter.
            strScore = Split(rngScore.Address, "$")(1)
        End If

        Debug.Print "Column Numbers '" & lngName & "' and '" & lngScore & "'."
        Debug.Print "Column Letters '" & strName & "' and '" & strScore & "'."
        Debug.Print "Name Column Header Address '" & rngName.Address & "'."
        Debug.Print "Score Column Header Address '" & rngScore.Address & "'."

        With .Parent ' instead of "ThisWorkbook.Worksheets(cSheet)".

            '*******************************************************************
            ' This should demonstrate a case where you don't need the column
            ' letter (mostly you don't). You should use ".Cells", ".Range" is
            ' not an option.
            '*******************************************************************
            ' Column Number (lngName)
            ' Last Row Number calculated using Cells and lngName.
            If lngName <> 0 Then
                ' Calculate last row in Name Column.
                lngLR = .Cells(.Rows.Count, lngName).End(xlUp).Row
                ' Create a reference to the range from First Row to Last Row in
                ' Name Column.
                Set rngName = .Range(.Cells(cFR, lngName), _
                        .Cells(lngLR, lngName))
            End If

            '*******************************************************************
            ' This is the same as the previous and should demonstrate that
            ' when you already know the column letter, you have two choices:
            ' you can use ".Cells" or ".Range".
            '*******************************************************************
            ' Column Letter (strName)
            ' Last Row Number calculated using Cells and strName.
            If strName <> "" Then
                ' Calculate last row in Name Column.
                lngLR = .Cells(.Rows.Count, strName).End(xlUp).Row
                ' Create a reference to the range First Row to Last Row in
                ' Name Column.
                Set rngName = .Range(.Cells(cFR, strName), _
                        .Cells(lngLR, strName))
            End If
            ' Last Row Number calculated using Range and strName.
            If strName <> "" Then
                ' Calculate last row in Name Column.
                lngLR = .Range(strName & .Rows.Count).End(xlUp).Row
                ' Create a reference to the range from First Row to Last Row in
                ' Name Column.
                Set rngName = .Range(strName & cFR & ":" & strName & lngLR)
            End If
            '*******************************************************************

            ' Since the column letter is more user-friendly, the only use
            ' I can imagine where you might need it, is to inform the user e.g.
            MsgBox "Column '" & strName & "' contains the names and column '" _
                    & strScore & "' contains the scores.", vbInformation, _
                    "User Information"

        End With

        Debug.Print "Last (used) Row in Name Column '" & lngLR & "'."
        Debug.Print "Name Range Address '" & rngName.Address & "'."
        Debug.Print "Column '" & strName & "' contains the Names and column'" _
                & strScore & "' contains the scores."

    End With

End Sub

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-19
    • 2019-11-19
    • 1970-01-01
    • 1970-01-01
    • 2012-11-08
    • 2022-01-18
    • 2020-12-23
    相关资源
    最近更新 更多