如何使用VBA构建动态单元格范围输出函数

时间:2015-07-22 16:16:20

标签: excel vba excel-vba

我正在构建一个函数,当您选择一个单元格时,输出是从选择到同一列上最后一个填充单元格行的单元格范围。

这是完美的代码。

+??P?b??0
?rGNh?o(5*?<????x????b??7;1?ƻ?H?L1?>?O~&?O??E??Ӎ?5c?62?? ~?????ܙKv=?E???U?d?

问题: 我想更新此代码,以便在用于日期时,用户可以使用三个选项进行过滤:YTD(年初至今),ALL(所有时间 - 即获取所有数据),一年(即2015 / 2014/2013等) 。)

我的最终目标是让用户选择日期范围列中的单元格并输入 YTD ALL 或指定年份(即 2014 < / em>)并使用他的过滤器获得范围。

示例:用户写''Get the cell range from selection to last cell Function CellRange(CellA As Range) CellRange = CellA.Address + ":" + CellA.End(xlDown).Address End Function =cellrange(A2,2014)会产生$A$2:$A$23,如果用户更改为=cellrange(A2,2014),则会产生$A$24:$A$40在图像上。

enter image description here

我尝试了各种循环或计数,但我觉得很失落,因为我的尝试显然没有任何意义。

我正在寻求一些帮助:指导或问题的解决方案最好,因为我想在解决这个问题之后建立它(因此我在VBA上这样做)。

4 个答案:

答案 0 :(得分:1)

我已经编写了一些代码,我认为这些代码可以捕捉到您尝试做的事情。我将以几点为前言。 (1)如果#Value不是CellA值,则代码会抛出Date错误(我认为这是出于不言自明的原因)。 (2)如果公式中的年份条目与CellA中的年份不匹配,则它也会抛出#Value。我不确定你是否想要退回这种类型的治疗方法,但我个人认为,如果用户指向CellA,2014年为一年,那么对于用户而言,这将是非常令人困惑的。重新寻找2013年的日期。如果你想要改变,请告诉我。

看看代码,给它一些测试用例,并告诉我是否需要修改其他任何内容。

基于新信息编辑: 我没有像往常那样花时间测试这段代码,但看看它是否适合你。

Function cellrange(cellA As Range, vFilter As Variant) As String
    Dim rStart As Range
    Dim rEnd As Range
    Dim bFinished As Boolean
    Dim dToday As Date
    Dim nOffset As Integer

    'Throw an error if cell is not a date cell
    If Not IsDate(cellA) Then
        cellrange = CVErr(xlErrValue)
    End If

    If IsNumeric(vFilter) Then
        If vFilter = Year(cellA) Then
            'Below code if there is a year entered as vFilter
            Set rStart = cellA
            bFinished = False

            'Loop to find start of year range
            Do
                If IsDate(rStart.Offset(-1)) Then
                    If Year(rStart.Offset(-1)) = vFilter Then
                        Set rStart = rStart.Offset(-1)
                    Else
                        bFinished = True
                    End If
                Else
                    bFinished = True
                End If
            Loop While bFinished = False

            'Loop to find end of year range
            Set rEnd = cellA
            bFinished = False
            Do
                If IsDate(rEnd.Offset(1)) Then
                    If Year(rEnd.Offset(1)) = vFilter Then
                        Set rEnd = rEnd.Offset(1)
                    Else
                        bFinished = True
                    End If
                Else
                    bFinished = True
                End If
            Loop While bFinished = False

            cellrange = rStart.Address & ":" & rEnd.Address
        Else
            If Year(cellA) > vFilter Then
                nOffset = -1
            Else
                nOffset = 1
            End If

            Set rEnd = cellA
            bFinished = False

            Do
                If IsDate(rEnd.Offset(nOffset)) Then
                    If Year(rEnd.Offset(nOffset)) <> vFilter Then
                        Set rEnd = rEnd.Offset(nOffset)
                    Else
                        Set rEnd = rEnd.Offset(nOffset)
                        bFinished = True
                    End If
                Else
                    bFinished = True
                End If
            Loop While bFinished = False

            Set rStart = rEnd
            bFinished = False

            Do
                If IsDate(rStart.Offset(nOffset)) Then
                    If Year(rStart.Offset(nOffset)) = Year(rStart) Then
                        Set rStart = rStart.Offset(nOffset)
                    Else
                        bFinished = True
                    End If
                Else
                    bFinished = True
                End If
            Loop While bFinished = False


            If nOffset = -1 Then
                cellrange = rStart.Address & ":" & rEnd.Address
            Else
                cellrange = rEnd.Address & ":" & rStart.Address
            End If
        End If
    Else
        If vFilter = "YTD" Then
            'Below code if there is 'YTD' entered as vFilter
            Set rStart = cellA
            bFinished = False
            dToday = Date

            'Loop to find start of year range
            Do
                If IsDate(rStart.Offset(-1)) Then
                    If Year(rStart.Offset(-1)) = Year(rStart) Then
                        Set rStart = rStart.Offset(-1)
                    Else
                        bFinished = True
                    End If
                Else
                    bFinished = True
                End If
            Loop While bFinished = False

            'Loop to find end of year range
            Set rEnd = cellA
            bFinished = False
            Do
                If rEnd > dToday Then
                    nOffset = -1
                    If IsDate(rEnd.Offset(nOffset)) Then
                        If Year(rEnd.Offset(nOffset)) = Year(rEnd) And rEnd.Offset(nOffset) >= dToday Then
                            Set rEnd = rEnd.Offset(nOffset)
                        Else
                            bFinished = True
                        End If
                    Else
                        bFinished = True
                    End If
                Else
                    nOffset = 1

                    If IsDate(rEnd.Offset(nOffset)) Then
                        If Year(rEnd.Offset(nOffset)) = Year(rEnd) And rEnd.Offset(nOffset) <= dToday Then
                            Set rEnd = rEnd.Offset(nOffset)
                        Else
                            bFinished = True
                        End If
                    Else
                        bFinished = True
                    End If

                End If

            Loop While bFinished = False

            cellrange = rStart.Address & ":" & rEnd.Address
        Else
            'Below returns the 'ALL' case

            Set rStart = cellA
            bFinished = False

            'Loop to find start of year range
            Do
                If IsDate(rStart.Offset(-1)) Then
                    Set rStart = rStart.Offset(-1)
                Else
                    bFinished = True
                End If
            Loop While bFinished = False

            'Loop to find end of year range
            Set rEnd = cellA
            bFinished = False
            Do
                If IsDate(rEnd.Offset(1)) Then
                    Set rEnd = rEnd.Offset(1)
                Else
                    bFinished = True
                End If
            Loop While bFinished = False

            cellrange = rStart.Address & ":" & rEnd.Address
        End If
    End If
End Function

较旧的预编辑代码

Function cellrange(cellA As Range, vFilter As Variant) As String
    Dim rStart As Range
    Dim rEnd As Range
    Dim bFinished As Boolean
    Dim dToday As Date
    Dim nOffset As Integer

    'Throw an error if cell is not a date cell
    If Not IsDate(cellA) Then
        cellrange = CVErr(xlErrValue)
    End If

    'Throw an error if the cell year does not match the value being searched
    If IsNumeric(vFilter) And vFilter <> Year(cellA) Then
        cellrange = CVErr(xlErrValue)
    End If


    If IsNumeric(vFilter) Then
        'Below code if there is a year entered as vFilter
        Set rStart = cellA
        bFinished = False

        'Loop to find start of year range
        Do
            If IsDate(rStart.Offset(-1)) Then
                If Year(rStart.Offset(-1)) = vFilter Then
                    Set rStart = rStart.Offset(-1)
                Else
                    bFinished = True
                End If
            Else
                bFinished = True
            End If
        Loop While bFinished = False

        'Loop to find end of year range
        Set rEnd = cellA
        bFinished = False
        Do
            If IsDate(rEnd.Offset(1)) Then
                If Year(rEnd.Offset(1)) = vFilter Then
                    Set rEnd = rEnd.Offset(1)
                Else
                    bFinished = True
                End If
            Else
                bFinished = True
            End If
        Loop While bFinished = False

        cellrange = rStart.Address & ":" & rEnd.Address
    Else
        If vFilter = "YTD" Then
            'Below code if there is 'YTD' entered as vFilter
            Set rStart = cellA
            bFinished = False
            dToday = Date

            'Loop to find start of year range
            Do
                If IsDate(rStart.Offset(-1)) Then
                    If Year(rStart.Offset(-1)) = Year(rStart) Then
                        Set rStart = rStart.Offset(-1)
                    Else
                        bFinished = True
                    End If
                Else
                    bFinished = True
                End If
            Loop While bFinished = False

            'Loop to find end of year range
            Set rEnd = cellA
            bFinished = False
            Do
                If rEnd > dToday Then
                    nOffset = -1
                    If IsDate(rEnd.Offset(nOffset)) Then
                        If Year(rEnd.Offset(nOffset)) = Year(rEnd) And rEnd.Offset(nOffset) >= dToday Then
                            Set rEnd = rEnd.Offset(nOffset)
                        Else
                            bFinished = True
                        End If
                    Else
                        bFinished = True
                    End If
                Else
                    nOffset = 1

                    If IsDate(rEnd.Offset(nOffset)) Then
                        If Year(rEnd.Offset(nOffset)) = Year(rEnd) And rEnd.Offset(nOffset) <= dToday Then
                            Set rEnd = rEnd.Offset(nOffset)
                        Else
                            bFinished = True
                        End If
                    Else
                        bFinished = True
                    End If

                End If

'                If IsDate(rEnd.Offset(nOffset)) Then
'                    If Year(rEnd.Offset(nOffset)) = Year(rEnd) And rEnd.Offset(nOffset) < dToday Then
'                        Set rEnd = rEnd.Offset(nOffset)
'                    Else
'                        bFinished = True
'                    End If
'                Else
'                    bFinished = True
'                End If
            Loop While bFinished = False

            cellrange = rStart.Address & ":" & rEnd.Address
        Else
            'Below returns the 'ALL' case

            Set rStart = cellA
            bFinished = False

            'Loop to find start of year range
            Do
                If IsDate(rStart.Offset(-1)) Then
                    Set rStart = rStart.Offset(-1)
                Else
                    bFinished = True
                End If
            Loop While bFinished = False

            'Loop to find end of year range
            Set rEnd = cellA
            bFinished = False
            Do
                If IsDate(rEnd.Offset(1)) Then
                    Set rEnd = rEnd.Offset(1)
                Else
                    bFinished = True
                End If
            Loop While bFinished = False

            cellrange = rStart.Address & ":" & rEnd.Address
        End If
    End If
End Function

答案 1 :(得分:1)

稍微紧凑的功能...

  

要在电子表格中使用它,枚举值不会起作用;例如。使用&#39; = CellRange(C3,1)&#39;

Public Enum xlDateAction
    xlYearToDate = 1
    xlCurrentYear = 2
    xlAll = 3
End Enum

Public Function CellRange(SrcCell As Range, DtRange As xlDateAction) As String

    Application.ScreenUpdating = False
    If Not IsDate(SrcCell.Value) Then Exit Function

    Dim CellDate As Date: CellDate = SrcCell.Value

    Dim EndCell As Range
    Set EndCell = Columns(SrcCell.Column).Find(What:="", After:=[SrcCell]).Offset(-1, 0)
    Dim StartCell As Range: Set StartCell = SrcCell

    Do Until StartCell.Row = 1 Or Not IsDate(StartCell.Value)
        Set StartCell = StartCell.Offset(-1, 0)
    Loop
    If Not IsDate(StartCell.Value) Then Set StartCell = StartCell.Offset(1, 0)

    If DtRange <> xlAll Then
        Dim SrcYear As Long: SrcYear = Year(CDate(SrcCell.Value))
        Do Until StartCell.Address = SrcCell.Address Or Year(CDate(StartCell.Value)) = SrcYear
            If Year(CDate(StartCell.Value)) < SrcYear Then Set StartCell = StartCell.Offset(1, 0)
        Loop
        If DtRange = xlCurrentYear Then
            Do Until EndCell.Address = SrcCell.Address Or Year(CDate(EndCell.Value)) = SrcYear
                If Year(CDate(EndCell.Value)) > SrcYear Then Set EndCell = EndCell.Offset(-1, 0)
            Loop
        Else
            Set EndCell = SrcCell
        End If
    End If

    CellRange = Range(StartCell, EndCell).Address
    Application.ScreenUpdating = True

End Function
  

******* 更新 *******

添加了一年覆盖功能,我认为现在应该进行你想要的范围选择...(还调整了枚举,因为这对我来说更有意义)

Public Enum xlDateAction
    xlCurrentYear = 1
    xlYearToDate = 2
    xlAll = 3
End Enum

Public Function CellRange(SrcCell As Range, DtRange As xlDateAction, _
    Optional YearOverride As Long = 0) As String

    Application.ScreenUpdating = False
    If Not IsDate(SrcCell.Value) Then Exit Function

    If YearOverride = Year(CDate(SrcCell.Value)) Then YearOverride = 0
    Dim TargetYear As Long: TargetYear = YearOverride
    Dim StartCell As Range: Set StartCell = SrcCell
    Dim EndCell As Range
    Set EndCell = Columns(SrcCell.Column).Find(What:="", After:=[SrcCell]).Offset(-1, 0)

    Do Until StartCell.Row = 1 Or Not IsDate(StartCell.Value)
        Set StartCell = StartCell.Offset(-1, 0)
    Loop
    If Not IsDate(StartCell.Value) Then Set StartCell = StartCell.Offset(1, 0)

    If TargetYear = 0 Then TargetYear = Year(CDate(SrcCell.Value))

    If DtRange <> xlAll Then
        Do Until StartCell.Address = EndCell.Address Or Year(CDate(StartCell.Value)) >= TargetYear
            If Year(CDate(StartCell.Value)) < TargetYear Then Set StartCell = StartCell.Offset(1, 0)
        Loop
        If DtRange = xlYearToDate And Year(CDate(StartCell.Value)) >= TargetYear And _
            TargetYear > Year(CDate(SrcCell.Value)) Then Set StartCell = StartCell.Offset(-1, 0)

        If DtRange = xlCurrentYear Then
            Do Until EndCell.Address = StartCell.Address Or Year(CDate(EndCell.Value)) <= TargetYear
                If Year(CDate(EndCell.Value)) > TargetYear Then Set EndCell = EndCell.Offset(-1, 0)
            Loop
            ' If target year doesn't exist in dates
            If Year(CDate(EndCell.Value)) <> TargetYear Then Exit Function
        Else
            Set EndCell = SrcCell
        End If
    End If

    CellRange = Range(StartCell, EndCell).Address
    Application.ScreenUpdating = True

End Function

答案 2 :(得分:1)

这是一个更短的解决方案,适用于所有三种情况,并且不需要数据工作表处于活动状态:

Public Function cellrange(rDates As Range, vFilter As Variant) As String
    Dim i As Long, ndx1 As Long, ndx2 As Long, r As Range, vA As Variant, bErr As Boolean, bAll As Boolean    
    bErr = True
    If IsDate(rDates) Then
        With rDates.EntireColumn
            i = rDates.Parent.Evaluate("count(" & .Address & ")")
            Set r = .Cells(1 - i + rDates.Parent.Evaluate("index(" & .Address & ",match(9.9E+307," & .Address & "))").Row).Resize(i, 1)
        End With
        vA = r.Value
        Select Case LCase(vFilter)
            Case "all": bErr = 0: bAll = 1
            Case "ytd"
                For i = 1 To UBound(vA)
                    If ndx1 = 0 And Year(vA(i, 1)) = Year(Date) Then ndx1 = i
                    If vA(i, 1) <= Date Then ndx2 = i
                Next
            Case Else 'year
                vFilter = Val(vFilter)
                If vFilter Then
                    For i = 1 To UBound(vA)
                        If ndx1 = 0 And Year(vA(i, 1)) = vFilter Then ndx1 = i
                        If ndx1 And Year(vA(i, 1)) = vFilter Then ndx2 = i
                    Next
                End If
        End Select
        If Not bAll Then If ndx1 > 0 And ndx2 > 0 Then Set r = r.Range(r.Parent.Cells(ndx1, 1), r.Parent.Cells(ndx2, 1)): bErr = False
        If Not bErr Then cellrange = r.Address Else cellrange = CVErr(xlErrValue)
    End If
End Function

答案 3 :(得分:0)

使用Excel公式可以轻松完成大部分操作。可以使用相同的逻辑来开发VBA函数

enter image description here

我刚刚注意到您的日期不包括本月的第一个到最后一个。它不应该影响原来的YTD / ALL,但如果你需要指定的第一个和最后一个日期,那么这将有效

enter image description here