【问题标题】:How to loop through multiple columns. If cells exceed specific height then add a row and merge text in the above cell with empty cell below如何循环遍历多个列。如果单元格超过特定高度,则添加一行并将上面单元格中的文本与下面的空单元格合并
【发布时间】:2016-11-24 20:23:25
【问题描述】:

我找到了一个宏来自动适应合并的单元格。我将它与另一个宏组合以循环遍历电子表格并在单元格下方添加超过 408 单元格高度的行。宏确实会在每一列中找到单元格,如果单元格超过 408 将添加一行。问题是,宏只合并一列中的单元格。

我的问题是。如何让宏合并多列中的单元格,以便单元格 AB16 与新行 17 中的单元格合并,而 G11 将单元格与新行 12 合并。

这里是代码。

Option Explicit
Sub MergedandSplit()
    Dim j As Long
    Dim n As Long
    Dim i As Long
    Dim MW As Double 'merge width
    Dim RH As Double 'row height
    Dim MaxRH As Double
    Dim rngMArea As Range
    Dim rng As Range
    Dim lng As Long, lastrow As Long
    ' ' Final_Test Macro ' Locate 409, Insert Row, Change Height, Merge Cells, Highlight ' ' Keyboard Shortcut: Ctrl+k ' Dim lng As Long lastrow = Cells(Rows.Count, "A").End(xlUp).Row
    lastrow = Cells(Rows.Count, "G").End(xlUp).Row

    Const SpareCol  As Long = 26
    Set rng = Range("G5:O" & _
        Range("G" & Rows.Count).End(xlUp).Row)

    With rng
        For j = 1 To .Rows.Count
            'if the row is not hidden
            If Not .Parent.Rows(.Cells(j, 1).Row).Hidden Then
                'if the cells have data
                If Application.WorksheetFunction.CountA(.Rows(j)) Then
                    MaxRH = 0
                    For n = .Columns.Count To 1 Step -1
                        If Len(.Cells(j, n).Value) Then
                            'mergecells
                            If .Cells(j, n).MergeCells Then
                                Set rngMArea = .Cells(j, n).MergeArea
                                With rngMArea
                                    MW = 0
                                    If .WrapText Then
                                        'get the total width
                                        For i = 1 To .Cells.Count
                                            MW = MW + .Columns(i).ColumnWidth
                                        Next
                                        MW = MW + .Cells.Count * 0.66
                                        'use the spare column
                                        'and put the value,
                                        'make autofit,
                                        'get the row height
                                        With .Parent.Cells(.Row, SpareCol)
                                            .Value = rngMArea.Value
                                            .ColumnWidth = MW
                                            .WrapText = True
                                            .EntireRow.AutoFit
                                            RH = .RowHeight
                                            MaxRH = Application.Max(RH, MaxRH)
                                            .Value = vbNullString
                                            .WrapText = False
                                            .ColumnWidth = 8.43

                                        End With
                                        .RowHeight = MaxRH
                                    End If
                                End With
                            ElseIf .Cells(j, n).WrapText Then
                                RH = .Cells(j, n).RowHeight
                                .Cells(j, n).EntireRow.AutoFit
                                If .Cells(j, n).RowHeight < RH Then .Cells(j, n).RowHeight = RH
                            End If
                        End If
                    Next
                End If
            End If
        Next
        .Parent.Parent.Worksheets(.Parent.Name).UsedRange
    End With

    For lng = lastrow To 1 Step -1
        If Rows(lng).RowHeight > 408 Then
            Rows(lng + 1).Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
            Rows(lng).Resize(2).Select
            Selection.RowHeight = 409
            Cells(lng, "G").Resize(2, 1).Select
            With Selection
                .HorizontalAlignment = xlGeneral
                .VerticalAlignment = xlTop
                .WrapText = True
                .Orientation = 0
                .AddIndent = False
                .IndentLevel = 0
                .ReadingOrder = xlContext
                .MergeCells = True
                .ShrinkToFit = True
            End With
        End If
        If lastrow > 1 Then
            ActiveCell.Offset(-1, 0).Range("G5:AB100").Select
        End If
        lastrow = lastrow - 1

    Next
End Sub

【问题讨论】:

  • 我认为您的尝试不会产生您期望的效果。合并两个单元格将不允许更多文本可见。您可以拆分文本,使前 408 个文本点在一行中,其余的在下一行。但是,我发现结果难以阅读,因为您一次只能看到一行。我更喜欢拆分成更小的单元,这样您就可以一次读取两行并获得更平滑的滚动。
  • 谢谢托尼。宏确实允许我在完成自动调整过程后查看单元格。问题是第二个过程。如何让它合并多列中的单元格。 For lng = lastrow To 1 Step -1 If Rows(lng).RowHeight > 408 Then Rows(lng + 1).Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove Rows(lng).Resize(2).Select Selection.RowHeight = 409 单元格(lng, "G").Resize(2, 1).Select
  • 我不知道您可以通过在其下方设置一个空单元格使其溢出,从而使超大单元格中的文本可见。我相信我会发现这很有用。您的代码看起来很难更新。您使用范围内的行和列,然后使用工作表中的行和列。您对列“G”的使用进行了硬编码。第一部分处理第 5 行到最后一行。第二部分将最后一行处理为 1。您需要所有这些功能吗?你有隐藏的行吗?你有合并单元格吗? ActiveCell.Offset(-1, 0).Range("G5:AB100").Select的目的是什么?
  • 我有您寻求的例程的结构,但我不确定要包含哪些功能。我不再认为我之前关于隐藏行的问题很重要。关于合并单元格的问题很重要,尤其是多行合并单元格。这些单元格是否已格式化(例如:粗体、下划线、字体名称、字体大小),因为格式化会影响文本的高度。
  • 谢谢。我需要知道如何遍历一系列列。也许一次做一列或做整个工作表。单元格中有一些格式。

标签: excel vba


【解决方案1】:

简介

这个答案比我预期的要长得多。部分原因是因为其他承诺,我无法投入必要的时间,但主要是因为我遇到了意想不到的困难。

一般建议

避免在活动工作表上工作。如果宏的一个关键特性是用户可以通过在启动宏之前选择它来选择要处理的工作表,那么在活动工作表上工作可能是合适的。但是,通常宏处理特定的工作表。在宏中指定该工作表;不要依赖用户选择了正确的工作表。

避免使用Select。这个建议的正常原因是在工作表中移动选择单元格可能会很慢。这是真的,但我的原因是它使代码难以理解。当原始程序员选择一个单元格然后选择一个偏移量然后选择另一个偏移量时,您会看到代码。维护程序员应该如何破译正在发生的事情?很少有宏被写入然后被丢弃。六个月或一年后,它们需要更新。可能已经为最初的程序员节省了几秒钟或更糟的是,显示了多么聪明的技术可能需要维护程序员几个小时才能理解。请记住,您可能是那个维护程序员。

With rng ... If Not .Parent.Rows(.Cells(j, 1).Row).Hidden 我可以想象创建一个范围然后在该范围内操作会有所帮助的情况。但是,如果有时您在范围内操作,有时您在工作表内操作,您只会让您的代码混乱。

Application.WorksheetFunction.CountA(.Rows(j)) 使用旧版本的 Excel,我发现两个工作表函数在使用 Application.WorksheetFunction 调用时非常慢。我一直向自己保证,我会妥善调查这个问题。也许这是旧版 Excel 的一个错误。 Application.WorksheetFunction 的开销可能很大。我不知道,但如果有一个简单的 VBA 替代方案,我会使用它来代替工作表函数。

If Len(.Cells(j, n).Value) Then 理解这个语句依赖于读者知道零可以用作布尔真,非零可以用作布尔假。我不喜欢依赖这些知识,因为如果读者缺乏这些知识,他们如何理解这句话?你会用谷歌搜索该声明的什么内容,以了解它的工作原理和原因?

请不要使用 j、n 或 i 等变量。它们只会让您的代码难以阅读。

你的日常生活问题

您使用以下方法来计算合并单元格的宽度:

For i = 1 To .Cells.Count
  MW = MW + .Columns(i).ColumnWidth
Next

您无法以这种方式添加列宽并获得可靠的答案。如果您使用 Excel 查看默认列的宽度,您将看到:“宽度 8.43(64 像素)”。 8.43 是以磅为单位的宽度,它是 VBA 给出和期望的宽度单位。要获得组合宽度,您需要对像素宽度求和并为每个内部边框加 1。我提供了将点转换为像素的例程,反之亦然。

我的方法

我保留了你的名字MergeandSplit 作为主宏,尽管这个名字对我来说没有意义。但是,我已经完全重新编写了您的例程,并且还有许多子例程和函数。

很多年前,我做过类似的事情,但我不知道合并多行以使单元格的整个内容可见。一旦我看到你是如何实现这个效果的,我就毫不费力地找到了展示这个技巧的网页。当我需要时,我没有找到任何这些网页,所以我的日常工作涉及拆分单元格。

当单元格超过 409.5 磅的行限制时,您将使用两个 409 磅的行。如果单元格仅比行限制大一点,这会产生大量空白,并且如果单元格需要超过 818 个点,则这将不起作用。我将一个大单元格分成几部分并计算组合高度。我将插入的行数和这些行的高度基于这个组合高度。我相信结果比你的方法更令人满意,尽管我的方法并不完美。如果拆分单元格,它将丢失所有单元格内格式。在单元格中,我的意思是单元格的某些部分是粗体或斜体,而部分不是。可以确定原始单元格的单元格内格式并将这些格式应用于部件,但此过程非常缓慢。我过去曾破译过格式并对结果感到满意。也许我只在小型蜂窝上使用它,因为我没有意识到这个过程有多慢。我有一种更快的技术,但我无法快速提供。我在确定单元格高度时放弃了格式,但应用了一个乘数。这个乘数适用于我的测试数据,但您可能需要调整它。请参阅我的代码中的第 222 行。

我包含了很多 Debug.Print 语句来帮助我开发和测试宏。我将它们全部留在下面的代码中,以帮助您理解代码。

我尝试使用易于查找的简单语句,尽管我对简单的想法可能与您的想法不符。尝试自己理解我的代码,因为这将有助于您的开发。回来有问题是必要的。

警告:宏适用于我命名为“数据”的目标工作表。用你的名字代替我的名字。在测试我的宏之前,请确保您已保存目标工作表的副本。

【讨论】:

  • 托尼!!非常感谢您的努力和对此的调查。我会尽快审查并回复您。干杯
  • 嗨,托尼,由于某种原因,宏无法正常工作。当我在工作表上运行宏时,它找不到超过 409 的单元格。例如。我的工作表中的第一个单元格是 G5。宏跳过第 5 行,其中包含超过 409 且不自动调整的单元格,然后自动调整第 6 行中的单元格,然后拆分 G8 中超过 409 的单元格。我在 B、G、N、AB 列中有超过限制的单元格。该宏无法找到所有单元格,并且没有共同点会导致它跳过某些行。
  • 你能看看这个模块,让我知道需要做什么来扫描整个工作表而不是特定的列。如果我可以扩展此模块中的范围,宏应该没问题。
  • 我尝试为我的日常工作提供与您相同的功能。例如,它会忽略隐藏的行和没有WrapText = True 的单元格。这些中的任何一个都可能是跳过的行和单元格的原因吗?如果没有,您可以将工作表发布到 DropBox 或类似网站并发布链接吗?或者,如果您查看我的个人资料,您会找到一个电子邮件地址。你能把工作表的副本发邮件给我吗?
  • 我应该说我的例程会查看每一行和每个单元格的值。它可能会被边缘的合并单元格打败,因为Find 会忽略合并的单元格。它还忽略了垂直合并的单元格,因为我可以提出一种我认为可以在垂直合并的单元格中产生良好结果的方法。
【解决方案2】:

有关此代码的解释,请参阅答案的第一部分:https://stackoverflow.com/a/41036413/973283

包含全局例程的模块。也就是我在很多宏中使用的套路

Option Explicit

Dim PixelsForPointsUpTo1() As Double
Dim PixelsForPoints1Up() As Double

Public Sub AddressMergedCell(ByVal Wbk As Workbook, ByVal Wsht As Worksheet, _
                             ByRef RowTop As Long, ByRef ColLeft As Long, _
                             ByRef RowBot As Long, ByRef ColRight As Long)

  ' * If Not Wbk Is Nothing, it identifies the workbook containing the cell to be
  '   analysed.  Otherwise, the active workbook contains the cell to be analysed.
  ' * If Not WSht Is Nothing, it identifies the sheet containing the cell to be
  '   analysed.  Otherwise, the active sheet contains the cell to be analysed.
  ' * On entry RowTop and ColLeft identifies a cell.  If this cell is not part of
  '   a merged area then, on exit,  RowBot will have been set to RowTop and ColRight
  '   will have been set to ColLeft.  If this cell is part of a merged area then, on
  '   exit, RowTop, RowBottom, ColLeft and ColRight will identify the boundaries of
  '   the merged area.

  ' 21Apr06  Coded.  I wrote a similar routine some years ago but I cannot find it.
  ' 24Apr06  Changed RowTop and RowBottom to Long.
  ' 11Jun16  Changed ColLeft and ColBottom to Long.
  '          Tidied up old fashioned names.
  '  2Dec16  Replace workbook and worksheet names with workbook and worksheet references

  Dim Address As String
  Dim AddressParts() As String

  Select Case True
    Case Wbk Is Nothing And Wsht Is Nothing
      'The cell is within the active sheet
      Address = ActiveSheet.Cells(RowTop, ColLeft).MergeArea.Address
    Case Wbk Is Nothing And Not Wsht Is Nothing
      ' The cell is within referenced worksheet of the active workbook
      Address = Wsht.Cells(RowTop, ColLeft).MergeArea.Address
    Case Not Wbk Is Nothing And Not Wsht Is Nothing
      ' The cell is within the referenced worksheet of the referenced workbook
      ' A worksheet reference defines it parent.  Check Wbk and Wsht match
      Debug.Assert Wsht.Parent.Name = Wbk.Name
      Address = Wsht.Cells(RowTop, ColLeft).MergeArea.Address
    Case Not Wbk Is Nothing And Wsht Is Nothing
      ' The cell is within the active sheet of a named workbook
      Address = Wbk.ActiveSheet.Cells(RowTop, ColLeft).MergeArea.Address
  End Select

  ' Address will be "$" ColumnId "$" RowNum [ ":$" ColumnId "$" RowNum ]

  If InStr(1, Address, ":") = 0 Then
    ' The cell is not part of an merged area
    RowBot = RowTop
    ColRight = ColLeft
    Exit Sub
  End If

  AddressParts = Split(Address, ":")
  RowTop = Range(AddressParts(0)).Row
  ColLeft = Range(AddressParts(0)).Column
  RowBot = Range(AddressParts(1)).Row
  ColRight = Range(AddressParts(1)).Column

End Sub
Function ColCode(ByVal ColNum As Long) As String

  Dim PartNum As Long

  '  3Feb12  Adapted to handle three character codes.
  ' 28Oct16  Renamed ColCode to match ColNum.

  If ColNum = 0 Then
    Debug.Assert False
    ColCode = "0"
  Else
    ColCode = ""
    Do While ColNum > 0
      PartNum = (ColNum - 1) Mod 26
      ColCode = Chr(65 + PartNum) & ColCode
      ColNum = (ColNum - PartNum - 1) \ 26
    Loop
  End If

End Function
Public Function PadL(ByVal Str As String, ByVal PadLen As Long, _
                     Optional ByVal PadChr As String = " ") As String

  ' Pad Str with leading PadChr to give a total length of PadLen
  ' If the length of Str exceeds PadLen, Str will not be truncated

  '   Sep15 Coded
  ' 20Dec15 Added code so overlength strings are not truncated
  ' 10Jun16 Added PadChr so could pad with characters other than space

  If Len(Str) >= PadLen Then
    ' Do not truncate over length strings
    PadL = Str
  Else
    PadL = Right$(String(PadLen, PadChr) & Str, PadLen)
  End If

End Function
Function WidthPixelsFromPoints(ByVal Points As Double) As Long

  ' Convert a column width in points to pixels
  ' See WidthPrepPixelsPoints for more information

  ' This routine will return an appropriate value for Pixels even if the
  ' Points value does not exactly match one of Excel's value.
  ' For example, Excel has 12 Pixels = 1.00 Points and 13 Pixels = 1.14 Points.
  ' This routine will round Points 1.01 to 1.13 to Pixels 12 or 13

  '   4Dec16  Recoded.  Earlier version lost.

  Dim Pixels As Long

  If Points >= 1# Then
    ' Handle Points values over 1.00
    Pixels = UBound(PixelsForPointsUpTo1)
    Points = Points - 1#
    Pixels = Pixels + CLng(Points * CDbl(UBound(PixelsForPoints1Up)))
  Else
    ' Handle Points values up to 1.00
    Pixels = CLng(Points * CDbl(UBound(PixelsForPointsUpTo1)))
  End If

  WidthPixelsFromPoints = Pixels

End Function
Function WidthPointsFromPixels(ByVal Pixels As Long) As Double

  ' Convert a column width in pixels to points
  ' See WidthPrepPixelsPoints for more information

  '   4Dec16  Recoded.  Earlier version lost.

  Dim Points As Double

  If Pixels > UBound(PixelsForPointsUpTo1) Then
    ' Calculate Pixels for Points above 1.00:
    '   1.00 Point = 12 Pixels
    '   Above 1.00 Points, 1.00 Points = 7 Pixels
    Points = 1#
    Pixels = Pixels - UBound(PixelsForPointsUpTo1)
    ' Calculate whole points.
    Points = Points + CDbl(Pixels \ UBound(PixelsForPoints1Up))
    ' Calculate fractional points if any
    Points = Points + PixelsForPoints1Up(Pixels Mod UBound(PixelsForPoints1Up))
  Else
    ' Calxulate Pixels for Points below 1.00
    Points = PixelsForPointsUpTo1(Pixels)
  End If

  WidthPointsFromPixels = Points

End Function
Sub WidthPrepPixelsPoints()

  ' This routine must be run once before either WidthPixelsFromPoints
  ' or WidthPointsFromPixels are run.

  ' Excel accepts and returns widths of columns in Points. However,
  ' sometimes Pixels are required for calculations.

  ' One Point contains 12 Pixels.  Two Points contains 19 Pixels, three Points
  ' contain 26 Pixels and so on with each additional Point containing an extra
  ' 7 pixels.

  ' Note: this information only applies to column widths. For rows:
  '    Pixels = Points * .75.

  '   4Dec16  Recoded.  Earlier version lost.

  Dim Temp() As Variant
  Dim InxT As Long

  ' Accessing a Double array is faster than accessing a Variant array.
  ' I have seen it recommended but have not conducted my own time tests to
  ' determine if Double arrays are sufficiently faster to justify this
  ' two-step load

  Temp = VBA.Array(0#, 0.08, 0.17, 0.25, 0.33, 0.42, 0.5, _
                   0.58, 0.67, 0.75, 0.83, 0.92, 1#)
  ReDim PixelsForPointsUpTo1(0 To UBound(Temp))
  For InxT = 0 To UBound(Temp)
    PixelsForPointsUpTo1(InxT) = Temp(InxT)
  Next

  Temp = VBA.Array(0#, 0.14, 0.29, 0.43, 0.57, 0.71, 0.86, 1#)
  ReDim PixelsForPoints1Up(0 To UBound(Temp))
  For InxT = 0 To UBound(Temp)
    PixelsForPoints1Up(InxT) = Temp(InxT)
  Next

End Sub

【讨论】:

    【解决方案3】:

    有关此代码的解释,请参阅答案的第一部分:https://stackoverflow.com/a/41036413/973283

    包含 MergeandSplit 的模块

    Option Explicit
    
    Const RowHeightMax As Double = 409.5
    Sub CalcRowHeights(ByRef CellHeights() As Double, ByRef RowHeights() As Double)
    
      ' On entry:
      '  * CellHeights has dimensions (1 to N) where N is the last column of a row
      '    containing a value.  It contains the height of cells 1 to N.  Cells
      '    within that range having no value or with WrapText=False are recorded as
      '    having a height of 0. For cells with a value and WrapText=False, the
      '    value recorded is the height of the cell necessary to view all its
      '    content even if that height is above the maximum for a row.
      ' On exit:
      '  * if none of the cells has a height more than the maximum for a row,
      '    RowHeights will have dimensions (1 to 1) and the value of the single
      '    entry will be the largest height of an of the cells.
      '  * If one or more cells has a height more than the maximum for a row,
      '    RowHeights will have dimensions (1 to M) where M is the number of rows
      '    necessary for the content of all cells to be visible. The values of the
      '    M entries will be the heights of the rows necessary to show the cell
      '    contents to best advantage.
    
      Dim CellHeightMinAboveZero As Double
      Dim CellHeightMax As Double
      Dim CellHeightsRemaining() As Double
      Dim InxCCrnt As Long
      Dim InxRCrnt As Long
      Dim RowHeightRemaining As Long
    
      CellHeightMax = 0#
      CellHeightMinAboveZero = 0#
    
      ' Find cell with largest height
      For InxCCrnt = 1 To UBound(CellHeights)
        If CellHeightMax < CellHeights(InxCCrnt) Then
          CellHeightMax = CellHeights(InxCCrnt)
        End If
      Next
    
      ReDim RowHeights(1 To 1)    ' Will always need at least one entry
    
      If CellHeightMax <= RowHeightMax Then
        ' All cell content will be visible within one row
        RowHeights(1) = CellHeightMax
        Exit Sub
      End If
    
      ' Not all cell content can be visible in single row
    
      ' Copy caller's cell heights to working array and
      ' find minimum non-zero height. Already have maximum.
      CellHeightMinAboveZero = 0#
      ReDim CellHeightsRemaining(1 To UBound(CellHeights))
      For InxCCrnt = 1 To UBound(CellHeights)
        CellHeightsRemaining(InxCCrnt) = CellHeights(InxCCrnt)
        If CellHeightsRemaining(InxCCrnt) > 0 Then
          ' This cell height > 0
          If CellHeightMinAboveZero = 0 Or _
             CellHeightMinAboveZero > CellHeightsRemaining(InxCCrnt) Then
            ' This cell height first non-zero height or less than previous minimum
            CellHeightMinAboveZero = CellHeightsRemaining(InxCCrnt)
            'InxCMin = InxCCrnt
          End If
        End If
      Next
    
      InxRCrnt = 0      ' No entries in RowHeights() yet
    
      Do While True
    
        If CellHeightMinAboveZero = CellHeightMax Then
          ' There is one or more cell with the maximum height.
          ' There are no cells with any other height
          Call CalcRowHeightsAllocate(CellHeightMax, InxRCrnt, RowHeights)
          Exit Sub
        End If
    
        ' Allocate a row or rows for the smallest cell or cells or what
        ' remains unallocated of the smaller cell or cells
        Call CalcRowHeightsAllocate(CellHeightMinAboveZero, InxRCrnt, RowHeights)
    
        ' Reduce values in CellHeightsRemaining
        For InxCCrnt = 1 To UBound(CellHeights)
          If CellHeightsRemaining(InxCCrnt) <> 0 Then
            CellHeightsRemaining(InxCCrnt) = CellHeightsRemaining(InxCCrnt) - _
                                             CellHeightMinAboveZero
          End If
        Next
    
        ' Find new maximum and minimum
        CellHeightMax = 0#
        CellHeightMinAboveZero = 0#
        For InxCCrnt = 1 To UBound(CellHeights)
          If CellHeightMax < CellHeightsRemaining(InxCCrnt) Then
            CellHeightMax = CellHeightsRemaining(InxCCrnt)
          End If
          If CellHeightsRemaining(InxCCrnt) > 0 Then
            ' This cell height > 0
          If CellHeightMinAboveZero = 0 Or _
             CellHeightMinAboveZero > CellHeightsRemaining(InxCCrnt) Then
            ' This cell height first non-zero height or less than previous minimum
              CellHeightMinAboveZero = CellHeightsRemaining(InxCCrnt)
            End If
          End If
        Next
    
      Loop
    
    End Sub
    Sub CalcRowHeightsAllocate(ByVal HeightToAllocate As Double, _
                               ByRef InxRCrnt As Long, _
                               ByRef RowHeights() As Double)
    
      ' Add rows to RowHeights to give a total height of HeightToAllocate
    
      ' If InxRCrnt = 0, RowHeights() has already been dimensioned as (1 To 1)
      ' but entry 1 is unused.
      ' If InxRCrnt > 0, RowHeights() has been dimensioned as (1 To InxRCrnt)
      ' with entries 1 To InxRCrnt unused. A new entry must be added.
      ' In either case, InxRCrnt is stepped to reflect new state of entries.
    
      Dim NumRows As Long
    
      InxRCrnt = InxRCrnt + 1
      If InxRCrnt > 1 Then
        ' At least one new entry will be required.
        ReDim Preserve RowHeights(1 To InxRCrnt)
      End If
    
      If HeightToAllocate <= RowHeightMax Then
        ' The height to allocate is less than the row maximum
        RowHeights(InxRCrnt) = HeightToAllocate
        Exit Sub
      End If
    
      NumRows = Fix((HeightToAllocate + RowHeightMax - 1) / RowHeightMax)
    
      ' Make row heights as close to equal as possible
      ' 1 pixel = 0.75 points.  Height must be interger number of pixels
    
      ' Enlarge RowHeights for the extra rows
      ReDim Preserve RowHeights(1 To InxRCrnt + NumRows - 1)
      ' Calculate preferred row height and allocate to first new row
      RowHeights(InxRCrnt) = (HeightToAllocate * 4# / 3# / CDbl(NumRows)) * 0.75
      ' Reduce HeightToAllocate for height allocated
      HeightToAllocate = HeightToAllocate - RowHeights(InxRCrnt)
    
      ' Set remaining new rows, except the last, to the same height as the first mew row
      For InxRCrnt = InxRCrnt + 1 To UBound(RowHeights) - 1
        RowHeights(InxRCrnt) = RowHeights(InxRCrnt - 1)
        HeightToAllocate = HeightToAllocate - RowHeights(1)
      Next
    
      ' Set last row to remaining height to allow for previous rows not
      ' being the exact, preferred fraction of toal height to allocate
      RowHeights(UBound(RowHeights)) = HeightToAllocate
    
    End Sub
    Function CellHeightMerged(ByVal Wsht As Worksheet, ByVal RowTop As Long, _
                              ByVal ColLeft As Long, ByVal RowBot As Long, _
                              ByVal ColRight As Long, ByRef RowTest As Long, _
                              ByRef ColTest As Long) As Double
    
      ' * Wsht.Cells(RowTop, ColLeft) is the top left cell of an merged cell
      '   with RowBot and ColRight specifying the bottom right cell.
      ' * There is no code that would handle RowTop <> RowBot.
      ' * Wsht.Cells(RowTest, ColTest) is a cell below and to the right of any
      '   used cells.
      ' * The routine will use Wsht.Cells(RowTest, ColTest) and, if necessary
      '   cells below it, to determine the full height of the cell
    
      Dim ColCrnt As Long
      Dim WidthCombinedPixels As Long
      Dim WidthCombinedPoints As Single
    
      ' Calculate combined width of columns within merged cell.
      ' Excel VBA sets and returns column widths in points but the calculate must be
      ' in pixels
    
      With Wsht
    
        WidthCombinedPixels = 0#
    
        ' Calculate total width of merged cells in pixels
        For ColCrnt = ColLeft To ColRight
          WidthCombinedPixels = WidthCombinedPixels + _
                                WidthPixelsFromPoints(.Cells(RowTop, ColCrnt).ColumnWidth)
        Next
        WidthCombinedPixels = WidthCombinedPixels + ColRight - ColLeft - 1 ' Allow for interior borders
    
        ' Set width of test column to total width of merged cells
        .Columns(ColTest).ColumnWidth = WidthPointsFromPixels(WidthCombinedPixels)
        ' Copy contents of merged cell to test cell
        .Cells(RowTop, ColLeft).Copy Destination:=.Cells(RowTest, ColTest)
    
        ' Use CellHeightUnmerged to determine height of test cell
        CellHeightMerged = CellHeightUnmerged(Wsht, RowTest, ColTest, RowTest + 1, ColTest)
    
      End With
    
    End Function
    Function CellHeightUnmerged(ByVal Wsht As Worksheet, ByVal RowCrnt As Long, _
                                ByVal ColCrnt As Long, ByRef RowTest As Long, _
                                ByRef ColTest As Long) As Double
    
      ' * Wsht.Cells(RowCrnt, ColCrnt) is an unmerged cell.
      ' * Wsht.Cells(RowTest, ColTest) is a cell below and to the right of any
      '   used cells.
      ' * The routine will use Wsht.Cells(RowTest, ColTest) and, if necessary
      '   cells below it, to determine the full height of the cell
    
      ' If a cell height exceeds the maximum row height, the cell content is split
      ' into parts so their total height can be calculated. For this to give the
      ' exact height, the separate parts must be split on a line boundary and must
      ' be formatted as the original. Unfortunately, when a cell is split, all the
      ' in-cell formatting is lost. It is possible to determine the formatting of
      ' the original cell and to apply that as approprite to the parts but this has
      ' proved to be impossibly slow.  A  multiplier applied to the height of a
      ' cell part is a crude technique but it gives good results with test data. It
      ' may (probably will) be necessary to experiment with the multiplier's value
      ' to get a statisfactory effect.
      Const Multiplier As Double = 1.08
    
      Dim AllRowHeightsBelowMaximum As Boolean
      Dim CellHeightTotal As Double
      Dim InCellFmts As New Collection
      Dim InxIcf As Long
      Dim LenCopy As Long
      Dim PosStart As Long
      Dim RowHeightCrnt As Double
      Dim RowNumCrnt As Long
      Dim RowNumMax As Long
      Dim RowTemp As Long
      Dim Text As String
    
      With Wsht
    
        If .Cells(RowCrnt, ColCrnt).Value = "" Then
          CellHeightUnmerged = 0#
          Exit Function
        End If
    
        .Columns(ColTest).ColumnWidth = .Columns(ColCrnt).ColumnWidth
        .Cells(RowCrnt, ColCrnt).Copy Destination:=.Cells(RowTest, ColTest)
        With .Cells(RowTest, ColTest)
          .WrapText = True
          .EntireRow.AutoFit
          CellHeightTotal = .RowHeight
        End With
    
        If CellHeightTotal = RowHeightMax Then
          ' Text does not fit into single row at current width
          ' Split text of cell into as many parts as necessary for each part to
          ' give a cell height less than the maximum. This is a crude split but it
          ' is not obvious that a better split would give better results.
          RowNumMax = 2
          ' Clear all formatting from test cells then set wrap
          With .Cells(RowTest, ColTest)
            .Clear
            .WrapText = True
          End With
          With .Cells(RowTest + 1, ColTest)
            .Clear
            .WrapText = True
          End With
          PosStart = 1
          Text = .Cells(RowCrnt, ColCrnt).Value
          LenCopy = Len(Text) \ RowNumMax
          Do While True    ' Loop until row heights are all less than maximum
            ' Split text of cell into RowNumMax parts. This is a crude split but it
            ' is not obvious that a better split would give better results.
            RowTemp = RowTest
            For RowNumCrnt = 1 To RowNumMax - 1
              ' Copy early parts of text
              .Cells(RowTemp, ColTest).Value = Mid(Text, PosStart, LenCopy)
              PosStart = PosStart + LenCopy
              RowTemp = RowTemp + 1
            Next
            ' Copy last part of text
            .Cells(RowTemp, ColTest).Value = Mid(Text, PosStart)
            ' Calculate total height of cell is all parts below maximum
            CellHeightTotal = 0#
            AllRowHeightsBelowMaximum = True
            For RowTemp = RowTest To RowTest + RowNumMax - 1
              .Rows(RowTemp).AutoFit
              RowHeightCrnt = .Rows(RowTemp).RowHeight * Multiplier
              If RowHeightCrnt < RowHeightMax Then
                CellHeightTotal = CellHeightTotal + RowHeightCrnt
              Else
                AllRowHeightsBelowMaximum = False
                Exit For
              End If
            Next
            If AllRowHeightsBelowMaximum Then
              Exit Do
            End If
            ' RowNumMax was not enough rows to show entire text from cell
            With .Cells(RowTest + RowNumMax, ColTest)  ' Prepare another test cell
              .Clear
              .WrapText = True
            End With
            RowNumMax = RowNumMax + 1
          Loop
        End If
    
      End With
    
      CellHeightUnmerged = CellHeightTotal
    
    End Function
    Sub MergedandSplit()
    
      ' * This routine looks for cells whose row height is the maximum allowed
      '   indicating that not all the text within the cell will be visible.
      ' * If a row contains such cells, a row is inserted under it and the
      '   partially visible cells merged with the cell below it and the height of the two
      '   rows set so all the text is visible.
      ' * The previous sentence said "a row is inserted" and this is normally
      '   true. However, if a cell contains so much text that two full size rows
      '   are not enough to make it all visible, then as many rows as necessary
      '   are inserted.
    
      Dim CellHeightCrnt As Double
      Dim CellHeights() As Double
      Dim ColCrnt As Long
      Dim ColLastRowCrnt As Long
      Dim ColLastWsht As Long
      Dim ColLeft As Long
      Dim ColRight As Long
      Dim InxM As Long
      Dim InxR As Long
      Dim MultiRowCellWithinRow As Boolean
      Dim OpenMultirowCells As New Collection
      Dim Rng As Range
      Dim RowCrnt As Long
      Dim RowHeightCrnt As Double
      Dim RowHeights() As Double
      Dim RowBot As Long
      Dim RowLast As Long
      Dim RowTemp As Long
      Dim RowTop As Long
      Dim Wsht As Worksheet
    
      Call WidthPrepPixelsPoints  ' Load arrays for pixel to point conversions
    
      Set Wsht = Worksheets("Data")
    
      With Wsht
    
        ' Find last row and column of worksheet
        Set Rng = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByRows, xlPrevious)
        If Rng Is Nothing Then
          ' The worksheet is empty
          Debug.Assert False
          ' Add code as necessary to tell user
          Exit Sub
        End If
        RowLast = Rng.Row
        ' No need to check worksheet is not empty since already know it isn't
        ColLastWsht = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByColumns, xlPrevious).Column
    
        ' Is is not possible to use "For RowCrnt = 1 To RowLast" becuase inserted
        ' rows would increase RowLast and the end value of a For loop cannot change
        ' during the loop.
        ' "For RowCrnt = RowLast To 1 Step -1" would avoid the inserted row problem
        ' but it is necessary to move down the worksheet so as to detect and
        ' process multi-row cells correctly.
    
        RowCrnt = 1
        Do While RowCrnt <= RowLast
    
          MultiRowCellWithinRow = False
    
          If Not .Rows(RowCrnt).Hidden Then
            ColLastRowCrnt = .Cells(RowCrnt, .Columns.Count).End(xlToLeft).Column
            Debug.Print "Row " & RowCrnt & "'s last column " & ColLastRowCrnt
    
            If ColLastRowCrnt <> 1 Or .Cells(RowCrnt, 1).Value <> "" Then
              ' This row contains at least one cell with a value
    
              ' End(xlToLeft).Column will not recognise a cell of a multi-row
              ' merged cell as containing a value unless it is the top left cell.
              ' If a multi-row merged cell is in the middle of the row, this does
              ' not matter because it will be detected as each column is examined.
              ' However, if a multi-row merged cell is to the right of any cells
              ' with values it will missed. If OpenMultirowCells is not empty,
              ' there is a multi-row merged cell that includes this row
              If OpenMultirowCells.Count > 0 Then
                MultiRowCellWithinRow = True
              End If
    
              ReDim CellHeights(1 To ColLastRowCrnt)
    
              ' Merged cells will always columns N, N+1, N+2 and so on making it
              ' easier to manage with increasing ColCrnt. However, stepping over
              ' a merged cell would not be possible with a For Loop so a Do
              ' Loop is necessary.
              ColCrnt = 1
              Do While ColCrnt <= ColLastRowCrnt
                With .Cells(RowCrnt, ColCrnt)
                  If .Value <> "" And .WrapText Then
                    ' Cell has value and row height is to increase, if necessary, so
                    ' the entire the value is visible.
                    If .MergeCells Then
                      ' Cell is part of merged area
                      Debug.Print "Cell " & Replace(.Address, "$", "") & " non empty and wrapped and merged"
                      RowTop = RowCrnt
                      ColLeft = ColCrnt
                      Call AddressMergedCell(Nothing, Wsht, RowTop, ColLeft, RowBot, ColRight)
                      Debug.Print "Cell " & Replace(Wsht.Cells(RowTop, ColLeft).Address, "$", "") & ":" & _
                                           Replace(Wsht.Cells(RowBot, ColRight).Address, "$", "") & " merged"
                      If RowTop <> RowBot Then
                        ' Rows with multi-row merged cells are ignored
                        Debug.Print "Multirow cell"
                        MultiRowCellWithinRow = True
                        OpenMultirowCells.Add RowBot  ' Record open multi-row cell
                        ColCrnt = ColRight
                      Else
                        ' Note: RowCrnt = RowTop=RowBot   ColCrnt <> ColLeft
                        CellHeights(ColCrnt) = CellHeightMerged(Wsht, RowTop, ColLeft, _
                                                   RowBot, ColRight, RowLast + 2, _
                                                   ColLastWsht + 2)
                        ' Advance ColCrnt to end of merged cell.
                        ColCrnt = ColRight
                      End If
                    Else
                      Debug.Print "Cell " & Replace(.Address, "$", "") & _
                                  " non empty, wrapped and not merged"
                      CellHeights(ColCrnt) = CellHeightUnmerged(Wsht, RowCrnt, _
                                              ColCrnt, RowLast + 2, ColLastWsht + 2)
                    End If
                  Else
                   'Debug.Assert False
                   Debug.Print "Cell " & Replace(.Address, "$", "") & _
                               " empty or non wrapped"
                  End If
                End With
                ' For merged cells, ColCrnt has already been stepped
                ' for the extra columns within the cell
                ColCrnt = ColCrnt + 1
              Loop  ' While ColCrnt <= ColLastRowCrnt
              Debug.Print "Cell heights:";
              For ColCrnt = 1 To ColLastRowCrnt
                Debug.Print " (" & ColCode(ColCrnt) & ")=" & CellHeights(ColCrnt);
              Next
              Debug.Print
              If MultiRowCellWithinRow Then
                Debug.Print "Row cannot be processed because it contains a multi-row cell"
              Else
                ' CellHeight contains the height of every cell within the row that contains
                ' a value and WrapText=True. Some or all of those CellHeights can be more
                ' than the maximum height of a row.
                Call CalcRowHeights(CellHeights, RowHeights)
                Debug.Print "Row height(s):";
                For InxR = 1 To UBound(RowHeights)
                  Debug.Print " " & RowHeights(InxR);
                Next
                Debug.Print
    
                ' RowHeights identifies how many rows are required to properly
                ' display the current row and the height of those rows
    
                If UBound(RowHeights) = 1 Then
                  ' Only one row required
                  .Rows(RowCrnt).RowHeight = RowHeights(1)
                Else
                  ' Two or more rows required
                  ' Insert extra rows below current row
                  For InxR = 2 To UBound(RowHeights)
                    .Rows(RowCrnt + 1).Insert
                  Next
                  ' Set row heights
                  RowTemp = RowCrnt
                  For InxR = 1 To UBound(RowHeights)
                    .Rows(RowTemp).RowHeight = RowHeights(InxR)
                    RowTemp = RowTemp + 1
                  Next
    
                  ' CellHeights identifies the height of each cell of row with a value
                  For ColCrnt = 1 To UBound(CellHeights)
                    If CellHeights(ColCrnt) <> 0 Then
                      ' This cell has a value. Calculate number of rows necessary
                      ' to for the entire value to be visible
                      CellHeightCrnt = 0#
                      For InxR = 1 To UBound(RowHeights)
                        CellHeightCrnt = CellHeightCrnt + RowHeights(InxR)
                        If CellHeightCrnt >= CellHeights(ColCrnt) Then
                          ' Will need to merge InxR rows to reach required height for cell
                          Exit For
                        End If
                      Next
                      If InxR > 1 Then
                        ' It is necessary to merge InxR rows for the cell's content
                        ' to be fully visible
                        ' Check for cell being multi-column
                        RowTop = RowCrnt
                        ColLeft = ColCrnt
                        Call AddressMergedCell(Nothing, Wsht, RowTop, ColLeft, RowBot, ColRight)
                        .Range(.Cells(RowCrnt, ColLeft), _
                               .Cells(RowCrnt + InxR - 1, ColRight)).Merge
                      End If
                    End If
                  Next
                  ' Allow for inserted rows
                  RowCrnt = RowCrnt + UBound(RowHeights) - 1
                  RowLast = RowLast + UBound(RowHeights) - 1
                End If
              End If
            Else
              ' This row is empty
              Debug.Print "Row " & RowCrnt & " empty"
            End If
          Else
            Debug.Print "Row " & RowCrnt & " hidden"
          End If
    
          ' Clear any open multi-row cells that finished on this row
          For InxM = OpenMultirowCells.Count To 1 Step -1
            If OpenMultirowCells(InxM) = RowCrnt Then
              ' RowBot for this multi-row cell = RowCrnt so delete entry
              OpenMultirowCells.Remove InxM
            End If
          Next
    
          RowCrnt = RowCrnt + 1
    
        Loop
    
        ' Delete column that includes test cells
        .Columns(ColLastWsht + 2).Delete
    
      End With
    
    End Sub
    

    【讨论】:

      猜你喜欢
      • 2018-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-26
      • 1970-01-01
      • 2011-10-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多