采用API實現的文件拖放
借助API函數CallWindowProc,DragAcceptFiles,DragQueryFile,DragFinish同樣可以實現文件從資源管理器EXPLORE到應用程序的拖放,另一種方法是采用OLE拖放,可以參照趣味編程欄目的相關內容。下面我們一起來看看程序編寫的過程。
對于每個需要引用的API函數,我們需要先在程序中說明,如果只在某一窗體內聲明并使用它,則把它聲明為Private,而如果在模塊中聲明,且需要在整個工程內使用它,則需要把它聲明為Public。
| Public Declare Function CallWindowProc& Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc&, ByVal hWnd&, ByVal Msg&, ByVal wParam&, ByVal lParam&) Public Declare Sub DragAcceptFiles Lib "shell32.dll" (ByVal hWnd&, ByVal fAccept&) Public Declare Function DragQueryFile& Lib "shell32.dll" Alias "DragQueryFileA" (ByVal hDrop&, ByVal iFile&,ByVal lpszFile$, ByVal cch&) Public Declare Sub DragFinish Lib "shell32.dll" (ByVal hDrop&) Private Declare Function SetWindowLong& Lib "user32" Alias "SetWindowLongA" (ByVal hWnd&,ByVal nIndex&, ByVal dwNewLong&) |
SetWindowLong函數的作用是在窗口結構中為指定的窗口設置屬性,利用它我們來改變窗口的屬性參數,其中各個參數的意義如下表所示:
| 參數 | 意義 |
| hwnd | Long,欲為其取得信息的窗口的句柄 |
| nIndex | Long,這里取GWL_WNDPROC,表示設置該窗口的窗口函數的地址 |
| dwNewLong | Long,由nIndex指定的窗口信息的新值 |
| 返回值 | Long,指定數據的前一個值 |
在默認狀態下WINDOWS操作系統會指定一個窗口函數來接受和處理WINDOWS消息,而通過使用SetWindowLong函數改變窗口函數的地址使它指向我們自己寫的一個函數WindowProc,這樣WINDOWS消息就會轉由WindowProc函數接收和處理。于是我們就能判斷文件拖放的消息并進行處理。改變窗口屬性的語句如下:
| procOld = SetWindowLong(Me.hwnd, GWL_WNDPROC, AddressOf WindowProc) |
DragAcceptFiles、DragQueryFile、DragFinish三個函數的作用分別是:把某個窗口設置為接收拖放的容器、返回拖放的文件的數量和信息、終止拖放。
為了學習方便,以下提供了源代碼:
| '------------------------------------------- ' 采用API實現的文件拖放 '------------------------------------------- '程序說明: '本例是采用子類派生技術實現的文件從EXPLORE到VB程序的拖放 ' 通過三個API函數DragAcceptFiles、DragQueryFiles和 'DragFinish,并通過回調函數WindowProc,窗口屬性函數 'SetWindowLong、CallWindowProc的使用實現,而另一種同樣 '效果的實現方法:OLE拖放,你可以參照文件拖放的另外一個實 '例。 '------------------------------------------- Option Explicit Private Const GWL_WNDPROC As Long = (-4&) ' API call to alter the class data for this window Private Declare Function SetWindowLong& Lib "user32" Alias "SetWindowLongA" (ByVal hWnd&, _ ByVal nIndex&, ByVal dwNewLong&) Private Sub Form_Load() '定義 Picture1作為接收文件拖放的容器 DragAcceptFiles Me.hWnd, 1& '整個procOld變量用來存儲窗口的原始參數,以便恢復 ' 調用了 SetWindowLong 函數,它使用了 GWL_WNDPROC 索引來創建窗口類的子類,通過這樣設置 '操作系統發給窗體的消息將由回調函數 (WindowProc) 來截取, AddressOf是關鍵字取得函數地址 procOld = SetWindowLong(Me.hWnd, GWL_WNDPROC, AddressOf WindowProc) 'AddressOf是一元運算符,它在過程地址傳送到 API 過程之前,先得到該過程的地址 End Sub Private Sub Form_Unload(Cancel As Integer) '此句關鍵,把窗口(不是窗體,而是具有句柄的任一控件,這里指Picture1)的屬性復原 Call SetWindowLong(Me.hWnd, GWL_WNDPROC, procOld) End Sub Public Sub DropFiles(ByVal hDrop&) Dim sFileName$, IReturn& Dim nCount&, I As Integer '為sFileName分配存儲空間 sFileName = String$(MAX_PATH, vbNullChar) '通過文件指針hDrop, DragQueryFile返回是否有文件拖放,nCount返回拖放文件的個數 nCount = DragQueryFile(hDrop, -1, sFileName, MAX_PATH) '循環讀取每一個拖放的文件,把它在列表框中顯示出來 For I = 0 To nCount - 1 sFileName = String$(MAX_PATH, vbNullChar) '如果有文件拖放,接收文件名,并試圖把它在圖片框中打開 'IReturn& IReturn& = DragQueryFile(hDrop, I, sFileName, MAX_PATH) List1.AddItem Left$(sFileName, IReturn) Next I '完成拖放操作 DragFinish hDrop End Sub '以下代碼放在moudle中 Option Explicit Public Const MAX_PATH As Long = 260& '標示我們要截獲的消息 Public Const WM_DROPFILES As Long = &H233& '保存原 窗體屬性的變量,其實是默認的 窗體函數 的地址 Public procOld As Long ' Public Declare Function CallWindowProc& Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc&, _ ByVal hWnd&, ByVal Msg&, ByVal wParam&, ByVal lParam&) '拖放操作相關的API函數 Public Declare Sub DragAcceptFiles Lib "shell32.dll" (ByVal hWnd&, ByVal fAccept&) Public Declare Function DragQueryFile& Lib "shell32.dll" Alias "DragQueryFileA" (ByVal hDrop&, ByVal iFile&, _ ByVal lpszFile$, ByVal cch&) Public Declare Sub DragFinish Lib "shell32.dll" (ByVal hDrop&) 'WARNING!!!!----------------------------------------------------------- '注意這段代碼是不能用DEBUG一步步調試的,否則會造成錯誤(崩潰) '對消息截獲的機制可以按下述理解: ' 這里要仔細理解一下,我們為窗體新指定了窗體函數地址,也就是說操作系統發送給窗體的 '消息將被 WindowProc函數 所截獲(而改變前消息是被默認的 窗體函數 所獲得并作相應處理的) ' 這樣我們在 WindowProc函數 中對所截獲的消息進行判斷,會有三種情況: '<1>如果是需要通過程序來處理的消息就通過 WindowProc函數 中的相應語句處理; '<2>如果是要原來的 窗體函數 來處理則把這個消息傳遞給原窗體函數(其實是指針指向的改變); '<3>如果不是我們需要的消息,也傳遞給原 窗體函數 來處理。 '可以參見 改變系統菜單 中的源碼注釋 'WARNING!!!!----------------------------------------------------------- '回調函數,用來截取消息 Public Function WindowProc(ByVal hWnd As Long, ByVal iMsg As Long, _ ByVal wParam As Long, ByVal lParam As Long) As Long '確定接收到的是什么消息 Select Case iMsg '如果是 通知文件放下 的消息,就攔截消息 Case WM_DROPFILES '通知在FORM模塊中定義的DropFiles函數來接收 指向 放下的文件 的句柄 frmDragDropFiles.DropFiles wParam '返回0并退出這個WindowProc WindowProc = False Exit Function End Select '如果不是我們需要的消息,則傳遞給原來的窗體函數處理 WindowProc = CallWindowProc(procOld, hWnd, iMsg, wParam, lParam) End Function |
相關內容
什么是子類派生技術
WINDOWS運行的基礎是“消息機制”,所謂的“消息”是一個唯一的值,這個值會被一個窗體或操作系統收到,它能告訴什么事件發生了以及需要采用什么樣的動作來響應。這與我們人類的神經系統將感知的信息傳遞給大腦,而大腦發出指令給我們的身體非常相似。于是每一個窗體都具有一個消息句柄,這個機制使得所有發自于WINDOWS操作系統的消息能被接收到需要強調的是每個窗體以及每個控件,包括按鈕、文本框、圖片框等都具有這樣的消息句柄。WINDOWS操作系統會跟蹤這些消息句柄,這稱為類結構中的一個WindowProc,所謂的類結構是于窗體句柄相關聯的。
當我們加入一個新的WindowProc函數而這個WindowProc與原始的窗體函數相符合的話,我們稱這個窗被子類化了。換言之,如果WINDOWS操作系統發給你所在的WindowProc一個消息,而你所在的WindowProc正在響應其它的動作,這時你必須將剩余的消息傳遞給一個默認的WindoProc。
如下所示: 操作系統消息-->你所在WindoProc-->默認的WindoProc而一個窗體是可以被子類化多次的,這樣就產生了如下的情況:
Windows Message Sender --> Your WindowProc --> Another WindowProc _--> Yet Another WindowProc --> Default WindowProc
What is subclassing anyway?
通過窗體子類化,你可以改變響應消息的順序,也就是說,你可以把消息傳遞到默認的WindowProc上而不立即響應。舉個例子:
如果我們要在接收到WM_PAINT 消息后,在窗體上畫出一些東西,可以用下面的語句實現:
| Public Function WindowProc(Byval hWnd, Byval etc....) Select Case iMsg'篩選出WM_PAINT消息 Case SOME_MESSAGE'如果是其他消息 DoSomeStuff Case WM_PAINT'如果是WM_PAINT 消息 '首先把消息傳遞給一個默認的WindowProc WindowProc = CallWindowProc(procOld, hWnd, iMsg, wParam, lParam) DoDrawingStuff'進行畫圖操作 Exit Function '因為我們已經把消息傳遞給默認的WindowProc,我們可以退出這個WindowProc ' End Select ' End Function |