在 MS Excel 中,GetTextExtentPoint32W 返回的单元格中文本的文本宽度大于实际宽度

问题描述 投票:0回答:2

我正在使用

GetTextExtentPoint32W
获取 MS Excel 2010 中单元格中文本的宽度。单元格宽度是使用
ActiveCell.Width
获取的。然后比较这两个宽度以确定文本是否适合单元格或延伸到单元格之外。 从视觉上看,即使文本完全适合单元格,该方法返回的文本宽度也大于单元格宽度。此外,当我增加字体大小时,实际文本宽度与方法返回的宽度之间的差异也会增加。 以下是用于实现结果的部分源代码。请帮我解决这个错误。

    hDC = ctypes.windll.user32.GetDC(self.windowHandle)
    tempBMP = ctypes.windll.gdi32.CreateCompatibleBitmap(hDC, 1, 1)
    hBMP = ctypes.windll.gdi32.SelectObject(hDC, tempBMP)

    iFontSize = self.excelCellObject.Font.Size
    deviceCaps = ctypes.windll.gdi32.GetDeviceCaps(hDC, 90) 
    iFontSize = int(iFontSize)
    iFontSize = ctypes.c_int(iFontSize)
    iFontSize = ctypes.windll.kernel32.MulDiv(iFontSize, deviceCaps, 72)
    iFontSize = iFontSize * -1
    iFontWeight = 700 if self.excelCellObject.Font.Bold else 400

    sFontName = self.excelCellObject.Font.Name
    sFontItalic = self.excelCellObject.Font.Italic
    sFontUnderline = True if self.excelCellObject.Font.Underline else False
    sFontStrikeThrough = self.excelCellObject.Font.Strikethrough

    #Create a font object with the correct size, weight and style
    hFont = ctypes.windll.gdi32.CreateFontW(iFontSize, 
                                            0, 0, 0, 
                                            iFontWeight, 
                                            sFontItalic, 
                                            sFontUnderline, 
                                            sFontStrikeThrough, 
                                            False, False, False, 
                                            False, False,
                                            sFontName)

    #Load the font into the device context, storing the original font object
    hOldFont = ctypes.windll.gdi32.SelectObject(hDC, hFont)
    sText = self.excelCellObject.Text
    log.io("\nText \t"+sText+"\n")
    textLength = len(sText)

    class structText(ctypes.Structure):
        _fields_ = [("width", ctypes.c_int), 
                    ("height",ctypes.c_int)]

    StructText = structText()
    getTextExtentPoint = ctypes.windll.gdi32.GetTextExtentPoint32W
    getTextExtentPoint.argtypes = [ctypes.c_void_p, 
                                   ctypes.c_char_p, 
                                   ctypes.c_int, 
                                   ctypes.POINTER(structText)]
    getTextExtentPoint.restype = ctypes.c_int

    #Get the text dimensions
    a = ctypes.windll.gdi32.GetTextExtentPoint32W(hDC, 
                                                  sText, 
                                                  textLength,
                                                  ctypes.byref(StructText))

    #Delete the font object we created
    a = ctypes.windll.gdi32.DeleteObject(hFont)
    a = ctypes.windll.gdi32.DeleteObject(tempBMP)

    #Release the device context
    a = ctypes.windll.user32.ReleaseDC(self.windowHandle, hDC)
    textWidth = StructText.width
    cellWidth = self.excelCellObject.Width

谢谢。

python-2.7 excel ctypes vba
2个回答
1
投票

我不使用 Python 或 Excel 2010,因此无法评论您当前的方法。然而,我也曾遇到过类似的问题。希望以下几点对您有所帮助。


背景

如果将鼠标悬停在 Excel 列的右边界上并按住鼠标左键,您将看到以下格式的显示:“宽度:n.nn(毫米像素)”。

ColumnWidth 属性的帮助说:

一个单位的列宽等于表格中一个字符的宽度 正常风格。对于比例字体,字符的宽度为 0 使用(零)。

使用 Width 属性返回列的宽度(以磅为单位)。

据我所知,“正常样式”是指创建工作簿时的标准字体名称和大小。更改现有工作簿的标准字体名称和大小似乎没有任何效果。更改工作表的字体名称和大小没有任何效果。

标准宽度列的两个示例显示是:

For Arial 10         Width: 8.43 (64 pixels)
For Tahoma 10.5      Width: 8.38 (72 pixels)

我创建了一串零,并尝试根据列的宽度来测量有多少个零是可见的。我发现我看到的零的数量与主观测量等显示的值相当匹配。

使用 VBA,列或单元格的 ColumnWidth 属性设置或返回字符宽度。

使用 VBA,列或单元格的只读 Width 属性返回 0.75 * 以像素为单位的宽度。

上述信息的意义在于,从 Excel 获取的宽度值对于所使用的字体不一定是正确的。


我的问题和我发现的解决方案

我遇到的问题是我正在合并单元格并用文本填充它们。尽管 Excel 会调整行高以使未合并单元格中的文本可见,但对于合并单元格则不会这样做。我尝试了许多技术,包括 Microsoft 的 .Net、文本渲染例程,但都没有成功。我尝试过的任何方法都无法可靠地模拟 Excel 的系统来确定文本的宽度。

我最终成功使用的技术包括在所有使用过的细胞的右侧和下方挑选一个细胞以供实验使用。我调整了实验单元格列的宽度以匹配合并单元格的组合宽度,并从我想要的高度的单元格中复制格式化值。由于实验单元格未合并,Excel会适当调整高度。然后我确保源行至少是这个高度。

该技术的关键特征是我并没有尝试模仿 Excel 的系统来确定文本的宽度;我用的是Excel的系统。

您需要为实验单元尝试不同的列宽。我将从源列的当前宽度开始。如果行高与单行的行高匹配,则源列的宽度已经足够。如果行高大于单行的高度,我会增加列宽,直到行高与单行的行高匹配,然后调整源列的宽度以匹配。如果您从最宽的值(以字符或 Python 的文本宽度为单位)开始,您可能会在第一次尝试时获得正确的源列宽度,从而避免以后进行调整。


0
投票

托尼的解决方案对我来说非常有效。除了 WinAPI 函数似乎给出的错误答案以及需要在点和像素之间进行转换之外,我想它们还会受到工作表视图是否缩放的影响。使用 Excel 的本机自动调整方法并放弃 WinAPI 真是天才!

这是我的实现,如果有人感兴趣的话。

Option Explicit

' Change a row's height to hold the wrapped contents of its given cell. This works 
' even on a merged cell; whereas, Excel alone treats such cells' text as unwrapped.
Sub ResizeRow(testCell As Range, Optional padding As Single = 0)
    ' Named range "TempResize" is on a hidden sheet. It is a single, unmerged
    ' cell used to hold the text during this operation.
    With ActiveWorkbook.Names("TempResize").RefersToRange
        ' Make TempResize the same width as testCell. This must be done indirectly
        ' (.Width is readonly) and iteratively (for better accuracy).
        Dim i As Integer
        Dim ratio As Single
        i = 0
        ratio = testCell.MergeArea.Width / .Width
        Do While Abs(ratio - 1) > 0.001 And i < 5
            .ColumnWidth = .ColumnWidth * ratio
            i = i + 1
            ratio = testCell.MergeArea.Width / .Width
        Loop

        ' Make the font match testCell's.
        .Font.Name = testCell.Font.Name
        .Font.Size = testCell.Font.Size
        .Font.Bold = testCell.Font.Bold
        .Font.Italic = testCell.font.Italic

        ' Find the height of one line of text.
        .Value = "0"
        .EntireRow.AutoFit
        Dim heightOfOneLine As Single
        heightOfOneLine = .RowHeight

        ' Let Excel's AutoFit method resize the contents of testCell.
        .Value = testCell.Value
        .EntireRow.AutoFit

        ' Set testCell's height to match TempResize's, plus an optional
        ' amount of padding.
        testCell.RowHeight = .RowHeight + IIf(Trim(.Value) = "", 0, padding) * heightOfOneLine
    End With
End Sub

Public Sub testRowResize()
    ResizeRow ActiveCell, 0
    ResizeRow ActiveCell, 0.5
    ResizeRow ActiveCell, 1
End Sub
© www.soinside.com 2019 - 2024. All rights reserved.