前言
剪切板监控改变可以在需要时获取链接等,达到迅雷复制链接提示下载的功能,不需要实时的用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
效果请自测,成功的话应该会收到改变后的剪切板拷贝内容。