前言

剪切板监控改变可以在需要时获取链接等,达到迅雷复制链接提示下载的功能,不需要实时的用timer等监控剪切板,比较节省资源和实时。

理解winapi剪切板监控技术

在window里剪切板监控是有一个链表结构的,如同我在前面讲过的键盘鼠标钩子的Hook技术的理解一样,新加入的窗体排在第一个,系统会给第一个clipboardviewer窗体发送两个消息,剪切板内容发生改变是发送WM_DRAWCLIPBOARD,当剪切板监控链表上有新增或移除的访问窗体时,系统会发送该消息WM_CHANGECBCHAIN,所以我们只需要在窗体的wndproc消息循环下捕获该消息,触发剪切板改变事件即可。.net窗体可以通过重写wndproc方法来获取该消息,但是需要继承form或control,但是我不准备这么做,因为每次都继承是很不爽的,因此,我直接简单粗暴点,使用消息钩子,直接接管窗体的消息循环过来,不需要继承。
我已将它封装为类,请看代码

正文代码

需要的winapi

Public Const WM_DRAWCLIPBOARD = &H308 '剪切板改变常数
 Public Const WM_CHANGECBCHAIN= 781 '剪切板链表改变常数
 Public Const WM_CLOSE= 16 '在关闭时需要将窗体从剪切板链表中移除。避免暂用
    ''' <summary>
    ''' 设置剪切板改变是通知的窗口
    ''' </summary>
    ''' <param name="hwnd">设置到剪切板监控链的第一个窗口句柄</param>
    ''' <returns>返回下一个监视的窗口,当监控链中只有一个窗口时,该值等于第一个窗口句柄</returns>
    ''' <remarks></remarks>
    <DllImport("user32.dll")>
    Public Function SetClipboardViewer(hwnd As IntPtr) As IntPtr

    End Function
    <DllImport("User32.dll", CharSet:=CharSet.Auto, EntryPoint:="SetWindowLong")> _
<ResourceExposure(ResourceScope.None)> _
    Public Function SetWindowLong(hWnd As Integer, nIndex As Integer, dwNewLong As Integer) As Integer
    End Function
      <DllImport("user32", entrypoint:="SendMessageA", setlasterror:=True)>
    Function SendMessage(ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, lParam As Integer) As Integer

    End Function
    ''' <summary>
    ''' 移除监视窗口
    ''' </summary>
    ''' <param name="hWndRemove">当前通知的窗口句柄</param>
    ''' <param name="hWndNewNext">移除的监视窗口句柄</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <DllImport("user32.dll")>
    Public Function ChangeClipboardChain(hWndRemove As IntPtr, hWndNewNext As IntPtr) As IntPtr

    End Function

消息钩子

''' <summary>
    ''' 消息钩子
    ''' </summary>
    ''' <remarks></remarks>
    Public Class HookMessage
        Private lpPrevWndProc As Integer
        Public Const GWL_WNDPROC = -4
        <DllImport("user32")>
        Private Shared Function CallWindowProc(ByVal lpPrevWndFunc As Integer, ByVal hWnd As Integer, ByVal Msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer

        End Function
        Public Event 消息事件(ByRef msg As Message)
        Dim msg As New Message
        Private Function WindowProc(ByVal hw As Integer, ByVal uMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
            With msg
                .HWnd = hw
                .Msg = uMsg
                .WParam = wParam
                .LParam = lParam
                .Result = CallWindowProc(controlist(hw), .HWnd, .Msg, .WParam, .LParam) '调用窗体过程,返回响应结果

                RaiseEvent 消息事件(msg) '触发消息事件
                WindowProc = .Result
            End With

        End Function
        Private controlist As New Dictionary(Of IntPtr, Integer) '储存多个控件信息
        Dim pdgate As New ProcDelegate(AddressOf WindowProc) '储存全局委托
        Dim procptr As Integer = Marshal.GetFunctionPointerForDelegate(pdgate)  '储存函数指针
        Public Sub New(ByVal con As Control)

            AddHookCtrl(con)

        End Sub
        Private Delegate Function ProcDelegate(ByVal hw As Integer, ByVal uMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
        Public Sub New(ByVal hwnd As Integer)

            AddHookCtrl(hwnd)
        End Sub
        '添加消息钩子,可监控多个
        Public Sub AddHookCtrl(hwnd As Integer)

            lpPrevWndProc = SetWindowLong(hwnd, GWL_WNDPROC, procptr)
            controlist.Add(hwnd, lpPrevWndProc)
        End Sub
        Public Sub AddHookCtrl(con As Control)

            lpPrevWndProc = SetWindowLong(con.Handle.ToInt32, GWL_WNDPROC, procptr)

            controlist.Add(con.Handle.ToInt32, lpPrevWndProc)
        End Sub
    End Class

剪切板监控

Public Class 剪切板监控
        Public Event 剪切板改变(data As String) '仅监测文本
        Dim WithEvents hm As HookMessage
        Private nextviewhandle As Integer '储存下一个监控窗口句柄
        Public Sub 设置窗口监控剪切板(hwnd As Integer)

            hm = New HookMessage(hwnd) '设置窗体消息钩子,以便获取剪切板改变消息

            nextviewhandle = SetClipboardViewer(hwnd) '设置当前窗口为第一个剪切板监控窗口,并返回其下一个监控窗口句柄

            触发失败事件()
        End Sub
        Public Sub 设置窗口监控剪切板(f As Form)

            hm = New HookMessage(f.Handle)

            nextviewhandle = SetClipboardViewer(f.Handle)
            触发失败事件()
        End Sub
        Private Sub 触发失败事件()
            If nextviewhandle = 0 Then
                RaiseEvent 设置窗口失败(Marshal.GetLastWin32Error)
            End If
        End Sub
        Public Event 设置窗口失败(infonum As Integer)
        Public Event 剪切板监控链改变()
        Private Sub hm_消息事件(ByRef msg As System.Windows.Forms.Message) Handles hm.消息事件
            Select Case msg.Msg
                Case WM_CHANGECBCHAIN
                    RaiseEvent 剪切板监控链改变()
                    If nextviewhandle = msg.HWnd Then '当剪切板监控链中只有自己时,这样nextviewhandle=msg.hwnd,会导致消息不断发送循环
                        Return

                    End If
                    SendMessage(nextviewhandle,msg.msg, msg.wparam, msg.lparam) '通知下一个剪切板监控窗口,维持剪切板监控链的完整
                Case WM_DRAWCLIPBOARD
                    RaiseEvent 剪切板改变(Clipboard.GetText)
                    If nextviewhandle = msg.HWnd Then '当剪切板监控链中只有自己时,这样nextviewhandle=msg.hwnd,会导致消息不断发送循环
                        Return
                           SendMessage(nextviewhandle,msg.msg, msg.wparam, msg.lparam) 
                    End If
                     '通知下一个窗口剪切板改变,维持剪切板监控链的完整
                Case WM_CLOSE
                    ChangeClipboardChain(msg.HWnd, nextviewhandle) '将窗体从链表结构中移除
            End Select
        End Sub

      
    End Class

调用方法

Dim cp As New 剪切板监控 '该处必须为全局变量,避免系统回收委托,导致回调时出错
    Private Sub LoginForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
       cp.设置窗口监控剪切板(Me.Handle) '将当前窗体设置为监控窗体
          AddHandler cp.剪切板改变, Sub(txt)
                                 Debug.WriteLine(txt) '剪切板改变时打印内容
                             End Sub
    End sub

效果请自测,成功的话应该会收到改变后的剪切板拷贝内容。