Excel VBA范围合并单元格和偏移

3

这段代码可以直接复制并粘贴到Excel模块中运行。

问题出在AddCalendarMonthHeader()函数中,月份单元格应该合并、居中、有样式,但实际上并没有。我的想法是Main()函数中的range.offset()语句影响了它,但我不知道为什么或如何修复它。

图片描述

Public Sub Main()

    'Remove existing worksheets
    Call RemoveExistingSheets

    'Add new worksheets with specified names
    Dim arrWsNames() As String
    arrWsNames = Split("BDaily,BSaturday", ",")
    For Each wsName In arrWsNames
        AddSheet (wsName)
    Next wsName

    'Format worksheets columns
    For Each ws In ThisWorkbook.Worksheets
        If ws.name <> "How-To" Then
            Call ColWidth(ws)
        End If
    Next ws

    'Insert worksheet header
    For Each ws In ThisWorkbook.Worksheets
        If ws.name <> "How-To" Then
            Call AddSheetHeaders(ws, 2013)
        End If
    Next ws

    'Insert calendars
    For Each ws In ThisWorkbook.Worksheets
        If ws.name <> "How-To" Then
            Call AddCalendars(ws, 2013)
        End If
    Next ws


End Sub











Public Sub AddCalendars(ByVal ws As Worksheet, year As Integer)
    Dim startCol As Integer, startRow As Integer

    Dim month1 As Integer, month2 As Integer
    month1 = 1
    month2 = 2
        Dim date1 As Date
        Dim range As range
        Dim rowOffset As Integer, colOffset As Integer

        Set range = ws.range("B1:H1")

    'Loop through all months
    For i = 1 To 12 Step 2
        Set range = range.Offset(1, 0)
        date1 = DateSerial(year, i, 1)

        'Add month header
        Call AddCalendarMonthHeader(monthName(i), range)

        'Add weekdays header
        Set range = range.Offset(1, 0)
        Call AddCalendarWeekdaysHeader(ws, range)

        'Loop through all days in the month
        'Add days to calendar '        For j = 1 To DaysInMonth(date1)

        Dim isFirstWeek As Boolean: isFirstWeek = True
        Dim firstWeekOffset As Integer: firstWeekOffset = Weekday(DateSerial(year, i, 1))

        For j = 1 To 6 'Weeks in month
            Set range = range.Offset(1, 0)
            range.Cells(1, 1).Value = "Week " & j
            For k = 1 To 7 'Days in week
                If isFirstWeek Then
                    isFirstWeek = False
                    k = Weekday(DateSerial(year, i, 1))
                End If
            Next k
'Exit For 'k
        Next j
'Exit For 'j
'Exit For 'i
        Set range = range.Offset(1, 0)
    Next i
End Sub
Public Sub AddCalendarMonthHeader(month As String, range As range)
    With range
        .Merge
        .HorizontalAlignment = xlCenter
'       .Interior.ColorIndex = 34
        .Style = "40% - Accent1"
        '.Cells(1, 1).Font = 10
        .Font.Bold = True
        .Value = month
    End With
End Sub
Public Sub AddCalendarWeekdaysHeader(ws As Worksheet, range As range)
    For i = 1 To 7
        Select Case i
            Case 1, 7
                range.Cells(1, i).Value = "S"
            Case 2
                range.Cells(1, i).Value = "M"
            Case 3, 5
                range.Cells(1, i).Value = "T"
            Case 4
                range.Cells(1, i).Value = "W"
            Case 6
                range.Cells(1, i).Value = "F"
        End Select
        range.Cells(1, i).Style = "40% - Accent1"
    Next i
End Sub
Public Function DaysInMonth(date1 As Date) As Integer
    DaysInMonth = CInt(DateSerial(year(date1), month(date1) + 1, 1) - DateSerial(year(date1), month(date1), 1))
End Function








'Remove all sheets but the how-to sheet
Public Sub RemoveExistingSheets()
    Application.DisplayAlerts = False
    On Error GoTo Error:
    For Each ws In ThisWorkbook.Sheets
        If ws.name <> "How-To" Then
            ws.Delete
        End If
    Next ws

Error:
Application.DisplayAlerts = True
End Sub
'Add a new sheet to end with given name
Public Sub AddSheet(name As String)
    ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count)).name = name
End Sub
'Set sheet column widths
Public Sub ColWidth(ByVal ws As Worksheet)
    Application.ScreenUpdating = False
    On Error GoTo Error:
        Dim i As Long
        For i = 1 To 26
           ws.Columns(i).ColumnWidth = 4.43
        Next i
Error:
    Application.ScreenUpdating = True
End Sub
Public Sub AddSheetHeaders(ByVal ws As Worksheet, year As Integer)
    Dim range As range
    Set range = ws.range("B1", "P1")
    With range
        .Merge
        .HorizontalAlignment = xlCenter
        .Font.ColorIndex = 11
        .Font.Bold = True
        .Font.Size = 26

        .Value = year
    End With
End Sub

3
你会因合并单元格而下地狱。认真点,使用“多列居中”格式属性代替将会省去你很多麻烦。 - ApplePie
@AlexandreP.Levasseur,你刚刚救了我于水深火热之中。说真的,我简直不敢相信我一直在浪费时间处理合并单元格! - kaybee99
2个回答

5
您遇到的问题是在合并第一个范围后,范围的长度变成了偏移量上的一列。因此,在此之后,下一个范围就会出现混乱。
    For i = 1 To 12 Step 2
        Set range = range.Offset(1, 0) ' Range is 7 columns wide

        date1 = DateSerial(year, i, 1)

        'Add month header
        Call AddCalendarMonthHeader(MonthName(i), range) ' We merge and range is now 1 column

        'Add weekdays header
        Set range = range.Offset(1, 0) ' Fix here to make it 7 columns
.
.
.

要解决这个问题,你只需要在添加工作日标题之前更改范围的大小即可。
'Add weekdays header
Set range = range.Offset(1, 0).Resize(1, 7)

enter image description here


非常好 - 我很感激它的工作。我在几个地方读到合并也会引起很多问题,但我的似乎与此无关 - 您对使用类似HorizontalAlignment的东西来使文本在行中分散而不是合并有什么想法吗? - Kairan
我个人认为合并单元格并没有人们所说的那么糟糕。只要在合并后小心处理 Range,像偏移这样的操作就不会以不太“自然”的方式进行。问题在于我们假设在执行偏移等操作时,Range 保持其长度不变,但实际上并非如此。 - manimatters

2

哇,我真的很惊讶这个能够正常工作!Range是VBA和Excel中的关键字,所以你能够无问题地使用它作为变量名,这对我来说非常令人惊讶。

你可以通过添加调试语句来更轻松地解决这类问题:

        'Add month header
        Debug.Print "Range Address: " & range.Address & vbTab & "i:" & i
        Call AddCalendarMonthHeader(MonthName(i), range)
        Debug.Print "Range updated00: " & range.Address
        
        'Add weekdays header
        Debug.Print "Range updated0: " & range.Address
        Set range = range.Offset(1, 0) `<---- this is the line where the Offset loses the entire row
        Debug.Print "Range updated1: " & range.Address

这将导致以下结果:
Range Address: $B$2:$H$2    i:1
Range updated00: $B$2:$H$2
Range updated0: $B$2:$H$2
Range updated1: $B$3

第二次偏移后,您的range变量只是一个单元格,这意味着它无法合并。有趣的是,即使您重命名了range变量,情况仍然如此。
现在,只有在调用AddCalendarMonthHeader方法中的.Merge函数时才会出现这种行为(注释掉此功能后,每次迭代都会显示您的范围地址准确)。
看起来这是直接由于使用.Merge引起的 - 我自己进行了一些混乱的尝试,表明即使以下代码仍将具有相同的问题(注意:我将您的range变量重命名为mrange):
        Debug.Print "Range updated First: " & mrange.Address
        Set mrange = mrange.Offset(1, 0)
        date1 = DateSerial(year, i, 1)
        
        'Add month header
        Debug.Print "Range Address: " & mrange.Address & vbTab & "i:" & i
        Dim mStr As String
        mStr = mrange.Address
        AddCalendarMonthHeader MonthName(i), mrange
        Debug.Print "Range updated00: " & mrange.Address
        
        'Add weekdays header
        Debug.Print "Range updated0: " & mrange.Address
        Set mrange = range(mStr)
        Set mrange = mrange.Offset(1, 0)
        Debug.Print "Range updated1: " & mrange.Address

简述

使用.Merge会导致在使用.Offset时VBA功能异常。我建议尝试修改代码,不要使用合并单元格的方式,也许可以像Alexander说的那样或者其他格式化策略。


嗨,enderland - 是的,我很惊讶我可以使用range作为关键字。我也试图debug.print range.Range,感谢你澄清了range.Address。我会考虑manimatters提出的使用.Resize(1,7)进行合并的方法。我可能还会考虑除了合并之外的其他方法。我知道有一个跨选区居中的方法,我在谷歌上搜索了一下,但找不到如何设置range.HorizontalAlignment = xlCenter或xlDistributed - 我无法真正测试,因为我的范围从偏移量中丢失了。 - Kairan
@Kairan 这个问题对我来说实际上非常有趣。昨晚我在试验的时候,我发现即使在完全不同的范围内使用了它,.Merge 随机导致 .Offset 的行为发生变化,但是我却找不到任何解释为什么会这样。 - enderland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接