【问题标题】:Can I Get the Source Range Of Excel Clipboard Data?我可以获取 Excel 剪贴板数据的源范围吗?
【发布时间】:2012-08-24 21:42:09
【问题描述】:

如果剪贴板包含 Excel 工作表范围,您可以使用 DataObject 对象访问该范围的数据

您还能找到该数据的实际来源范围(即工作表、行和列)吗?

或者,您能否找到上次复制范围,该范围用虚线边框表示(不是所选范围)?

最好使用 Excel 2003 VBA

【问题讨论】:

    标签: vba clipboard excel-2003 clipboarddata


    【解决方案1】:

    此代码在 Excel 2019 64 位中用于获取剪贴板上的单元格范围,而不是单元格的内容。

    fGetClipRange 返回被剪切或复制到剪贴板(包括书本和工作表)的 Excel 范围的范围对象。它使用“链接”格式直接从剪贴板读取它,并且需要此格式的 ID 号。与注册格式关联的 ID 可以更改,因此 fGetFormatId 从格式名称中查找当前格式 ID。使用 Application.CutCopyMode 确定单元格是否被剪切或复制。

    此站点对于在 VBA 中使用剪贴板很有用:https://social.msdn.microsoft.com/Forums/office/en-US/ee9e0d28-0f1e-467f-8d1d-1a86b2db2878/a-clipboard-object-for-vba-including-microsoft-word?forum=worddev

    Private Declare PtrSafe Function lstrcpy Lib "kernel32" (ByVal lpString1 As Any, ByVal lpString2 As Any) As LongPtr
    Private Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
    Private Declare PtrSafe Function GlobalUnlock Lib "kernel32" (ByVal hMem As LongPtr) As Long
    Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal lngFormat As Long) As LongPtr
    Private Declare PtrSafe Function EnumClipboardFormats Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare PtrSafe Function GetClipboardFormatNameA Lib "user32" (ByVal wFormat As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long
    
    '2020-02-11 get excel copy or cut range from clipboard
    Function fGetClipRange() As Range
    Dim strGetClipRange As String    'return range
    Dim lptClipData As LongPtr  'pointer to clipboard data
    Dim strClipData As String   'clipboard data
    Dim intOffset As Integer    'for parsing clipboard data
    Dim lngRangeLink As Long  'clipboard format
    Const intMaxSize As Integer = 256   'limit for r1c1 to a1 conversion
        lngRangeLink = fGetFormatId("Link") 'we need the id number for link format
        If OpenClipboard(0&) = 0 Then GoTo conDone  'could not open clipboard
        lptClipData = GetClipboardData(lngRangeLink)    'pointer to clipboard data
        If IsNull(lptClipData) Then GoTo conDone    'could not allocate memory
        lptClipData = GlobalLock(lptClipData)   'lock clipboard memory so we can reference
        If IsNull(lptClipData) Then GoTo conDone    'could not lock clipboard memory
        intOffset = 0   'start parsing data
        strClipData = Space$(intMaxSize)    'initialize string
        Call lstrcpy(strClipData, lptClipData + intOffset)  'copy pointer to string
        If strClipData = Space$(intMaxSize) Then GoTo conDone   'not excel range on clipboard
        strClipData = Mid(strClipData, 1, InStr(1, strClipData, Chr$(0), 0) - 1)    'trim null character
        If strClipData <> "Excel" Then GoTo conDone     'not excel range on clipboard
        intOffset = intOffset + 1 + Len(strClipData)    'can't retrieve string past null character
        strClipData = Space$(intMaxSize)    'reset string
        Call lstrcpy(strClipData, lptClipData + intOffset)  'book and sheet next
        strClipData = Mid(strClipData, 1, InStr(1, strClipData, Chr$(0), 0) - 1)
        strGetClipRange = "'" & strClipData & "'!"  'get book and sheet
        intOffset = intOffset + 1 + Len(strClipData)    'next offset
        strClipData = Space$(intMaxSize)    'initialize string
        Call lstrcpy(strClipData, lptClipData + intOffset)  'range next
        strClipData = Mid(strClipData, 1, InStr(1, strClipData, Chr$(0), 0) - 1)
        strGetClipRange = strGetClipRange & strClipData 'add range
        strGetClipRange = Application.ConvertFormula(strGetClipRange, xlR1C1, xlA1)
        Set fGetClipRange = Range(strGetClipRange)  'range needs a1 style
    conDone:
        Call GlobalUnlock(lptClipData)
        Call CloseClipboard
    End Function
    
    '2020-02-11 clipboard format id number changes so get it from format name
    Function fGetFormatId(strFormatName As String) As Long
    Dim lngFormatId As Long
    Dim strFormatRet As String
    Dim intLength As Integer
        If OpenClipboard(0&) = 0 Then Exit Function   'could not open clipboard
        intLength = Len(strFormatName) + 3  'we only need a couple extra to make sure there isn't more
        lngFormatId = 0 'start at zero
        Do
            strFormatRet = Space(intLength) 'initialize string
            GetClipboardFormatNameA lngFormatId, strFormatRet, intLength    'get the name for the id
            strFormatRet = Trim(strFormatRet)   'trim spaces
            If strFormatRet <> "" Then  'if something is left
                strFormatRet = Left(strFormatRet, Len(strFormatRet) - 1)    'get rid of terminal character
                If strFormatRet = strFormatName Then    'if it matches our name
                    fGetFormatId = lngFormatId  'this is the id number
                    Exit Do 'done
                End If
            End If
            lngFormatId = EnumClipboardFormats(lngFormatId) 'get the next used id number
        Loop Until lngFormatId = 0  'back at zero after last id number
        Call CloseClipboard 'close clipboard
    End Function
    

    【讨论】:

    • 另一种基于“链接”格式的方法在这里可用:stackoverflow.com/a/23119068/2981328
    • 哇,这是我在网上看到的最漂亮的代码。除了在第一次尝试时完美运行之外,您为正确处理流程的每个步骤、每个异常、您的变量名是可以理解的和良好的前缀、一流的评论、易于理解和简单的英语,我喜欢您如何保留代码通过将 cmets 放在一边来阻止连续,您将繁重的进程放入其自己的子进程 fGetFormatId,而不是让您的主进程两倍长,难以理解。
    • 这提供了一种比Worksheet_SelectionChange 更不显眼的方法,因为这种方法不需要在用户每次单击工作表时都运行 VBA。这是自找麻烦。
    • 我为您对 GoTo 的无畏使用表示赞赏:D 我认为没有风险,但我还没有研究过。
    • 我理解这么复杂的需求,但我希望有一个更简单的实现。注意你说If OpenClipboard(0&amp;) = 0 两次。这是一个条件调用和 API 调用。你能把If OpenClipboard(0&amp;) = 0 Then GoTo conDone改成If lngRangeLink = 0 Then GoTo conDone
    【解决方案2】:

    不直接,不 - 剪贴板对象似乎只包含单元格的值(尽管 Excel 显然以某种方式记住了边框):

    Sub testClipborard()
    
        Dim test As String
        Dim clipboard As MSForms.DataObject
        Set clipboard = New MSForms.DataObject
    
        clipboard.GetFromClipboard
        test = clipboard.GetText
    
        MsgBox (test)
    
    End Sub
    

    请注意,您需要引用 Microsoft Forms 2.0 库才能运行它(如果单元格中没有值,它也会失败)。


    话虽如此,您可以尝试以下操作 - 将其添加到 VBA 编辑器中的模块中。

    Public NewRange As String 
    Public OldRange As String 
    Public SaveRange As String 
    Public ChangeRange As Boolean 
    

    并在工作表对象中使用以下内容

    Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range) 
    
         'save previous selection
        OldRange = NewRange 
    
         'get current selection
        NewRange = Selection.Address 
    
         'check if copy mode has been turned off
        If Application.CutCopyMode = False Then 
            ChangeRange = False 
        End If 
    
         'if copy mode has been turned on, save Old Range
        If Application.CutCopyMode = 1 And ChangeRange = False Then 
             'boolean to hold "SaveRange" address til next copy/paste operation
            ChangeRange = True 
             'Save last clipboard contents range address
            SaveRange = OldRange 
        End If 
    
    End Sub 
    

    它看似有效,但它也可能相当容易出现不同的错误,因为它试图解决剪贴板的问题。 http://www.ozgrid.com/forum/showthread.php?t=66773

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多