第 29 章 拖放(1):檔案拖放


簡介

使用過圖形界面作業系統,如 Mac OS、IBM OS/2、Microsoft Windows,的人,應該曾經操作過以滑鼠拖放 ( drag and drop ) 的功能。例如在微軟的 Windows 作業系統堙A把檔案總管內的檔案拖到資源回收筒。甚至有時候,也能把選定的資料,以拖放的方式,放到本身或另一個視窗。例如在 WORD 堙A就可以把一段文字反白,表示已選定這段文字,然後把滑鼠游標移到這段文字上,按住滑鼠左鍵不放,就能夠拖著這段文字移到另一個地方 ( 可能是其他視窗,也可能是 WORD 本身的視窗 )。使用者操作這兩種拖放功能,不會感覺有何差異,但是在程式的實現上,卻是不同的機制。在這一章堙A小木偶先探討檔案的拖放。


使視窗成為「拖放標的」

原理

拖放檔案的動作過程如下,先在一個視窗內,選定一個或一個以上的檔案,然後把滑鼠游標移到這些檔案圖示上,按住滑鼠左鍵不放,再移動滑鼠游標到另一個視窗,最後放開滑鼠左鍵,就完成拖放動作。選定一個或一個以上的檔案所在的視窗,稱為「拖放物件的供應者」;而滑鼠左鍵釋放時,游標所在的視窗稱為「拖放標的」視窗。

例如底下的圖,小木偶開啟檔案總管兩次,左邊的檔案總管視窗切換到「我的文件」,右邊的檔案總管視窗在「E:\HomePage\SOURCE\Win32\2048」子目錄,如果要「E:\HomePage\SOURCE\Win32\2048」內的 2048.ASM 檔案複製到「我的文件」裡,可以把滑鼠游標移到右邊檔案總管的「2048.ASM」檔案上,壓住滑鼠左鍵不放,然後拖曳滑鼠游標到左邊的檔案總管視窗再釋放滑鼠左鍵,這時 Windows 系統會把「2048.ASM」複製到左邊的檔案總管所顯示的目錄堙A如下圖紅線的拖曳過程。以此例子而言,右邊的檔案總管就是「拖放物件的供應者」,而左邊的檔案總管則是「拖放標的」。在拖曳過程中,滑鼠游標上會顯示淡淡的「2048.ASM」圖示及其資料,而滑鼠游標右下方會有一個號,表示複製檔案;如果滑鼠游標右下方就沒有任何符號,表示把檔案會被移到「拖放標的」內,而原檔案會被刪除,這種情形發生在「拖放標的」與「拖放物件供應者」都在同一個磁碟機內。

當檔案由「拖放物件的供應者」被拖曳到「拖放標的」的視窗後,「拖放標的」的視窗可以對檔案作不同的處理。例如把純文字檔拖到「UltraEdit-32」堙A該純文字檔會自動開啟,等待編輯;把檔案拖曳到「資源回收筒」後,該檔案就被刪除;把 *.EXE 可執行檔案拖曳到 OllyDbg.exe 堙A會開啟該可執行檔,進行除錯過程。

要成為「拖放標的」的視窗,其實很簡單,只要告訴 Windows 系統,我可以接受拖放而來的檔案即可,這只要兩個步驟:

  1. 「拖放標的」的視窗須具有「WS_EX_ACCEPTFILES」延伸風格。
  2. 在「拖放標的」的視窗函式堻B理 WM_DROPFILES 訊息。

WS_EX_ACCEPTFILES 延伸風格與 DragAcceptFiles API

關於第一步,要使視窗具有 WS_EX_ACCEPTFILES 延伸風格,有兩種方法,一是呼叫 CreateWindowEx 建立視窗時,第一個參數就是延伸風格,須包含 WS_EX_ACCEPTFILES。第二種方法是,如果視窗已經建立好了,但要使視窗具有 WS_EX_ACCEPTFILES,可以呼叫 DragAcceptFiles API。DragAcceptFiles 的原型如下:

DragAcceptFiles PROTO   hWnd:HWND,fAccept:BOOL

其中 hWnd 是「拖放標的」的視窗代碼,fAccept 有兩種選擇,TRUE 代表使前述視窗具有 WS_EX_ACCEPTFILES 延伸風格;FALSE 則代表使其不具此延伸風格。事實上,視窗的延伸風格是一個 32 位元的數值,每個位元代表一個延伸風格,WS_EX_ACCEPTFILES 是其中的第三個位元,此位元為 0,表示不具有 WS_EX_ACCEPTFILES;若此位元為 1,表示具有 WS_EX_ACCEPTFILES。當一視窗具有 WS_EX_ACCEPTFILES 延伸風格時,則此視窗就可以接受來自滑鼠拖曳而釋放的檔案。如果使用者將檔案拖到此視窗上面,滑鼠游標如上圖所示,會變成,表示可接受拖過來的檔案;如果使用者將檔案拖到沒有 WS_EX_ACCEPTFILES 延伸風格的視窗上,滑鼠游標會變成,表示不能接受拖過來的檔案。不過,不同的軟體,滑鼠游標圖示可能不同,但聰明的你應可猜得出來。

能成為「拖放標的」的視窗,除了要具有 WS_EX_ACCEPTFILES 之外,還要在其視窗函式內,處理 WM_DROPFILES 訊息。當使用者拖曳檔案到「拖放標的」的視窗,並釋放滑鼠左鍵時,Windows 系統會把 WM_DROPFILES 訊息發送給滑鼠游標底下的「拖放標的」視窗的視窗函式。WM_DROPFILES 訊息中的 wParam 是由「拖放物件的供應者」所提供的記憶體區塊代碼,稱為 hDrop。此記憶體區塊屬於可移動的記憶體區塊 ( 什麼是可移動的記憶體區塊,請參閱第十四章配置記憶體 ),這個可移動的記憶體區塊其實內含一個叫做 DROPFILES 的結構體及被使用者選定的檔案名稱,這些檔案名稱都是完整的檔案名稱,包含磁碟機名及路徑名稱。假如要從 hDrop 取出檔案名稱,較方便的做法是呼叫 DragQueryFile API。

DragQueryFile API

DragQueryFile 是用來從 hDrop 奡ㄗ檔案名稱的 API,其原型是:

DragQueryFile   PROTO   hDrop:HDROP,iFile:DWORD,lpszFile:LPTSTR,cch:DWORD

hDrop 是拖放物件時,「拖放物件的供應者」產生的記憶體區塊代碼。iFile 是第幾個檔案,從零開始。我們拖放檔案時,可能拖放一個檔案,也可以同時拖放好幾個檔案,iFile 就是指要取得哪一個檔案的檔名。lpszFile 是一個位址指標,指向要存放檔名的緩衝區位址,當 DragQueryFile 返回時,會把檔名存到這個位址。cch 是緩衝區的長度,以字元為單位。如果 DragQueryFile 成功的把檔名存入 lpszFile 所指的位址時,DragQueryFile 會傳回檔名的總字元數 ( 不包含 NULL 字元 )。

DragQueryFile 還有另外兩種有趣用法,也非常有用。第一種是,如果 iFile 設為 -1,DragQueryFile 會忽略 lpszFile 與 cch 兩參數,而傳回使用者總共拖曳了多少個檔案。第二種是,如果 lpszFile 設為零,那麼 DragQueryFile 會返回第 iFile 個檔案的檔名所需的字元總數 ( 不包含 NULL 字元 )。

DragFinish API

DragFinish 是用來釋放「拖放物件的供應者」產生的記憶體區塊代碼,一般是在處理 WM_DROPFILES 訊息最後面,如果沒有這樣作,Windows 就會在程式結束後,幫您處理掉記憶體區塊。其原型為:

DragFinish  PROTO   hDrop:HDROP

hDrop 為要釋放的拖放代碼。DragFinish 沒有返回值。

DragQueryPoint API

如果想要取得使用者在拖放過程,釋放滑鼠左鍵時滑鼠游標所在座標,可以呼叫 DragQueryPoint,其原型為:

DragQueryPoint  PROTO   hDrop:HDROP,lppt:DWORD

第一個參數是被拖放而來的記憶體區塊代碼,hDrop;第二個參數則是位址指標,指向 POINT 結構體,當滑鼠左鍵釋放時,滑鼠游標座標將被 DragQueryPoint 存入此 POINT 結構體內。如果返回值為非零,表示釋放滑鼠左鍵時,滑鼠游標在視窗的工作區內;如果返回值為零,表示釋放時發生在工作區外。

DROP.ASM

底下介紹一個範例程式,DROP.ASM。執行後會出現一個對話盒,中間有一個黑色的子視窗,如果把任何一個檔案拖進這個子視窗內,該子視窗會以十六進位的方式顯示被拖入的檔案內容。底下的圖片是在檔案總管下,把「C:\boot.ini」拖進黑色的子視窗埵荍e現的畫面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
                .586
                .MODEL  FLAT,STDCALL
                OPTION  CASEMAP:NONE
 
__UNICODE__     EQU     1
FontHeight      EQU     15      ;邏輯字形高度
FontWidth       EQU     7       ;邏輯字形寬度
 
INCLUDE         WINDOWS.INC
INCLUDE         GDI32.INC
INCLUDE         KERNEL32.INC
INCLUDE         SHELL32.INC
INCLUDE         USER32.INC
INCLUDELIB      GDI32.LIB
INCLUDELIB      KERNEL32.LIB
INCLUDELIB      SHELL32.LIB
INCLUDELIB      USER32.LIB
 
;*************************************************************************************************************
.CONST
szClassName     DW      44h,52h,4fh,50h,5fh,54h,45h,53h,54h,0h                          ;DROP_TEST
szIcon          DW      45h,59h,45h,0h                                                  ;EYE
szAppName       DW      6e2ch,8a66h,62d6h,653eh,7684h,300ch,653eh,300dh,0h              ;測試拖放的「放」
szHelp          DW      4f7fh,7528h,8005h,53efh,4ee5h,7528h,6ed1h,9f20h,62d6h,66f3h     ;使用者可以用滑鼠拖曳
                DW      4efbh,4f55h,4e00h,500bh,6a94h,6848h,5230h,6b64h,8996h,7a97h     ;任何一個檔案到此視窗
                DW      5167h,0ff0ch,8a72h,6a94h,6848h,7684h,5167h,5bb9h,6703h,88abh    ;內,該檔案的內容會被
                DW      986fh,793ah,5728h,8996h,7a97h,88cfh,3002h,0h                    ;顯示在視窗堙C
szFontFamily    DW      43h,6fh,75h,72h,69h,65h,72h,20h,4eh,65h,77h,0h                  ;Courier New
szCrtDateFmt    DW      67h,67h,79h,79h,79h,79h,2fh,4dh,2fh,64h,64h,0h  ;ggyyyy/M/dd(顯示建檔時間)
szCrtTimeFmt    DW      20h,48h,48h,3ah,6dh,6dh,3ah,73h,73h,3000h,0     ; HH:mm:ss
szAddrFmt       DW      25h,30h,38h,58h,28h,25h,30h,39h,64h,29h,0h      ;%08X(%09d)
szFileSizeFmt   DW      25h,64h,4f4dh,5143h,7d44h,3000h,0h      ;%d位元組 
szOffsetAddr    DW      504fh,79fbh,4f4dh,5740h,0h              ;偏移位址
szHex           DW      5341h,516dh,9032h,4f4dh,5167h,5bb9h,0h  ;十六進位內容
szUnicodeTitle  DW      842ch,570bh,78bch,0     ;萬國碼
szASCIITitle    DW      41h,53h,43h,49h,49h,0   ;ASCII
szShortKey      DW      48h,6fh,6dh,65h,0ff1ah,7b2ch,4e00h,9801h,3000h,45h,6eh,64h      ;Home:第一頁 End
                DW      0ff1dh,6700h,672bh,9801h,3000h,50h,67h,55h,70h,0ff1dh,4e0ah     ;=最末頁 PgUp=上
                DW      4e00h,9801h,3000h,45h,6eh,64h,0ff1dh,6700h,672bh,9801h,3000h    ;一頁 End=最末頁 
                DW      45h,73h,63h,0ff1dh,96e2h,958bh,0h                               ;Esc=離開
rcTitleAddr     RECT    <0,FontHeight,133,FontHeight*2>         ;標題欄堛漲鴔}剪裁矩形
rcTitleHex      RECT    <134,FontHeight,484,FontHeight*2>       ;標題欄堛漱Q六進位數值剪裁矩形
rcTitleStr      RECT    <485,FontHeight,608,FontHeight*2>       ;標題欄堛漲r串剪裁矩形
rcTitle         RECT    <0,FontHeight,608,FontHeight*2>         ;標題欄的剪裁矩形
rcShortKey      RECT    <0,FontHeight*18,608,FontHeight*19>     ;按鍵說明的剪裁矩形
rcFileName      RECT    <0,0,608,FontHeight>                    ;顯示檔名的剪裁矩形
rcRedraw        RECT    <0,FontHeight*2,608,FontHeight*18>      ;中間重繪區域的矩形
rcClickReresh   RECT    <485,FontHeight,608,FontHeight*18>      ;使用者以滑鼠點擊標題欄切換萬國碼或ASCII更新畫面
;*************************************************************************************************************
.DATA
hInstance       HINSTANCE       ?       ;模組代碼
hwnd            HWND            ?       ;視窗代碼
hMem            HGLOBAL         ?       ;記憶體區塊代碼
wcex            WNDCLASSEX      <?>
msg             MSG             <?>
;位元0:0-表示尚未有檔案被拖進視窗;1-表示已有檔案被拖進視窗
;位元1:0-表示在視窗右上角為ASCII或BIG5;1-表示萬國碼
;位元2:0-表示未到檔案結尾;1表示已為檔案尾端
flag            DD      0
hBlackBrush     DD      ?       ;黑色畫刷
hFont           DD      ?       ;Courier New字形
dwFileSize      DD      ?       ;拖曳進來的檔案長度
p1stByte        DD      ?       ;每頁第一個位元組指標,在hMem堛澈標,若p1stByte=0,即指到hMem起始位址
pMax1stByte     DD      ?       ;最大頁數
szFileData      DW      MAX_PATH DUP (0)
;*************************************************************************************************************
.CODE
;-------------------------------------------------------------------------------------------------------------
;把檔案名稱、檔案大小、建檔時間填入szFileData堙Apfi_CrtTime指向FILETIME結構體,為檔案建立時間;dwFileSize為
;一雙字組,檔案大小;在szFileData前面已經存有完整的檔案名稱了
set_file_info   PROC    USES edi pfi_CrtTime:DWORD
                LOCAL   ft:FILETIME,syst:SYSTEMTIME
            ;搜尋檔名最後一個位址
                mov     ax,0
                mov     ecx,MAX_PATH
                lea     edi,szFileData
                repne   scasw
                mov     WORD PTR [edi-2],3000h
                INVOKE  wsprintf,edi,OFFSET szFileSizeFmt,dwFileSize
                shl     eax,1
                add     edi,eax
                INVOKE  FileTimeToLocalFileTime,pfi_CrtTime,ADDR ft ;把UTC時間變成當地時間(中華民國標準時間)
                INVOKE  FileTimeToSystemTime,ADDR ft,ADDR syst    ;把FILETIME格式的當地時間變成SYSTEMTIME格式
                INVOKE  GetDateFormat,LOCALE_USER_DEFAULT,0,ADDR syst,OFFSET szCrtDateFmt,edi,SIZEOF szFileData
                dec     eax
                shl     eax,1
                mov     ecx,SIZEOF szFileData
                add     edi,eax
                sub     ecx,eax
                INVOKE  GetTimeFormat,LOCALE_USER_DEFAULT,0,ADDR syst,OFFSET szCrtTimeFmt,edi,ecx
                dec     eax
                shl     eax,1
                add     edi,eax
                ret
set_file_info   ENDP
;-------------------------------------------------------------------------------------------------------------
;把AL的內容變成十六進位萬國碼,存於ECX所指位址,每個AL需要四個位元組空間存放
al_to_hex       PROC
                mov     dl,al
                shr     al,4
                and     dl,0fh
                call    trans_and_save
                mov     al,dl
trans_and_save: add     al,"0"
                cmp     al,"9"
                jbe     ok00
                add     al,7
ok00:           mov     [ecx],ax
                add     ecx,2
                ret
al_to_hex       ENDP
;-------------------------------------------------------------------------------------------------------------
display_page    PROC    USES edi hDC:HDC
                LOCAL   rcDisplay:RECT  ;剪裁矩形
                LOCAL   cRow,cHex:DWORD ;cRow:有16行,cHex:每行顯示16個位元組
                LOCAL   buffer[20h]:WORD
                LOCAL   string[20]:WORD ;用於顯示最右邊的字串
                LOCAL   hbrTitle:HBRUSH
                LOCAL   pByte:LPSTR     ;指向某一頁的每一個位元組的位址,一開始pByte為p1stByte
                INVOKE  SelectObject,hDC,hFont
                INVOKE  DeleteObject,eax
                INVOKE  SetBkColor,hDC,0        ;黑底
                INVOKE  SetTextColor,hDC,0ff00h ;綠字
                test    flag,1
                jnz     dp00
            ;使用者沒有拖曳檔案到此視窗
                INVOKE  GetClientRect,hwnd,ADDR rcDisplay
                INVOKE  DrawText,hDC,OFFSET szHelp,-1,ADDR rcDisplay,DT_LEFT or DT_WORDBREAK
                jmp     dp06
            ;使用者已拖曳檔案到此視窗堙A顯示此檔案內容
dp00:           mov     cRow,10h                ;每頁顯示16行
                mov     edx,p1stByte
                mov     rcDisplay.top,FontHeight*2
                mov     pByte,edx
                mov     rcDisplay.bottom,FontHeight*3
            ;印出最左邊的偏移位址
dp01:           INVOKE  wsprintf,ADDR buffer,OFFSET szAddrFmt,pByte,pByte
                mov     rcDisplay.left,0
                mov     rcDisplay.right,133
                INVOKE  DrawText,hDC,ADDR buffer,-1,ADDR rcDisplay,DT_CENTER
            ;印出中間部份的十六進位內容
                mov     rcDisplay.left,144
                mov     rcDisplay.right,144+FontWidth*2
                lea     edi,string
                mov     cHex,10h        ;每一行有16個位元組的資料
dp02:           mov     edx,pByte
                cmp     edx,dwFileSize
                jne     dp03
                or      flag,4          ;如果已到檔案尾端,印出十六進位內容停止,並設定flag的第
                jmp     dp04            ;3位元為一,以便只印出右邊的字串一次即可
dp03:           add     edx,hMem
                movzx   eax,BYTE PTR [edx]
                lea     ecx,buffer
                stosb
                call    al_to_hex
                INVOKE  DrawText,hDC,ADDR buffer,2,ADDR rcDisplay,DT_LEFT
            ;計算下一個rcDisplay
                mov     ecx,FontWidth
                shl     ecx,1
                add     ecx,FontWidth   ;ECX=FontWidth*3
                add     rcDisplay.left,ecx
                add     rcDisplay.right,ecx
                inc     pByte
                dec     cHex
                jnz     dp02
dp04:       ;印出右邊的萬國碼字串或ASCII字串
                sub     eax,eax
                stosw
                mov     rcDisplay.left,485
                mov     rcDisplay.right,485+FontWidth*16
                test    flag,2
            .IF ZERO?
                INVOKE  DrawTextA,hDC,ADDR string,-1,ADDR rcDisplay,DT_LEFT or DT_SINGLELINE
            .ELSE
                INVOKE  DrawTextW,hDC,ADDR string,-1,ADDR rcDisplay,DT_LEFT or DT_SINGLELINE
            .ENDIF
                test    flag,4
                jnz     dp05
                add     rcDisplay.top,FontHeight
                add     rcDisplay.bottom,FontHeight
                dec     cRow
                jnz     dp01
dp05:           and     flag,0fffffffbh
            ;設定標題欄顏色為黃底藍字
                INVOKE  CreateSolidBrush,0ffffh
                mov     hbrTitle,eax
                INVOKE  FillRect,hDC,OFFSET rcTitle,hbrTitle    ;黃色畫刷塗滿標題欄
                INVOKE  SetBkColor,hDC,0ffffh
                INVOKE  SetTextColor,hDC,0ff0000h
                INVOKE  DrawText,hDC,OFFSET szOffsetAddr,-1,OFFSET rcTitleAddr,DT_CENTER
                INVOKE  DrawText,hDC,OFFSET szHex,-1,OFFSET rcTitleHex,DT_CENTER
                test    flag,2
            .IF ZERO?
                mov     edx,OFFSET szASCIITitle
            .ELSE
                mov     edx,OFFSET szUnicodeTitle
            .ENDIF
                INVOKE  DrawText,hDC,edx,-1,OFFSET rcTitleStr,DT_CENTER
                INVOKE  FillRect,hDC,OFFSET rcShortKey,hbrTitle
                INVOKE  DrawText,hDC,OFFSET szShortKey,-1,OFFSET rcShortKey,DT_LEFT
                INVOKE  FillRect,hDC,OFFSET rcFileName,hbrTitle
                INVOKE  SetTextColor,hDC,0ffh
                INVOKE  DrawText,hDC,OFFSET szFileData,-1,OFFSET rcFileName,DT_LEFT
                INVOKE  DeleteObject,hbrTitle
dp06:           ret
display_page    ENDP
;-------------------------------------------------------------------------------------------------------------
WndProc         PROC    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
                LOCAL   ps:PAINTSTRUCT
                LOCAL   hFile:HFILE
                LOCAL   fi:BY_HANDLE_FILE_INFORMATION
                LOCAL   NumberOfBytesRead:DWORD ;已讀入的檔案大小
.IF uMsg==WM_PAINT
                INVOKE  BeginPaint,hWnd,ADDR ps
                INVOKE  display_page,ps.hdc
                INVOKE  EndPaint,hWnd,ADDR ps
 
.ELSEIF uMsg==WM_KEYDOWN
    .IF wParam==VK_PRIOR                ;按下Page Up鍵
                sub     p1stByte,100h
                jge     key_up_ok
                mov     p1stByte,0
key_up_ok:      INVOKE  InvalidateRect,hWnd,OFFSET rcRedraw,TRUE
    .ELSEIF wParam==VK_NEXT             ;按下Page Down鍵
                add     p1stByte,100h
                mov     eax,pMax1stByte
                cmp     eax,p1stByte
                ja      key_up_ok
                mov     p1stByte,eax
                jmp     key_up_ok
    .ELSEIF wParam==VK_HOME             ;按下Home鍵
                mov     p1stByte,0
                jmp     key_up_ok
    .ELSEIF wParam==VK_END              ;按下End鍵
                mov     eax,pMax1stByte
                mov     p1stByte,eax
                jmp     key_up_ok
    .ELSEIF wParam==VK_ESCAPE           ;按下Esc鍵
                jmp     wm_close
    .ENDIF
 
.ELSEIF uMsg==WM_DROPFILES
                INVOKE  DragQueryFile,wParam,0,OFFSET szFileData,MAX_PATH*2     ;取得在hDrop堬0個檔案的檔名
                INVOKE  CreateFile,OFFSET szFileData,GENERIC_READ,0,0,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
    .IF eax==INVALID_HANDLE_VALUE
                xor     eax,eax
                mov     p1stByte,OFFSET szHelp
                and     flag,0fffffffeh
    .ELSE
                mov     hFile,eax
        .IF hMem!=0
                INVOKE  GlobalFree,hMem
        .ENDIF
                INVOKE  GetFileInformationByHandle,hFile,ADDR fi
                mov     eax,fi.nFileSizeLow
                mov     dwFileSize,eax
                mov     edx,eax
                add     eax,200h
                and     eax,0fffffff0h
                and     edx,0ffffff00h
                mov     pMax1stByte,edx
                INVOKE  GlobalAlloc,GPTR,eax    ;依檔案大小配置記憶體區塊
                mov     hMem,eax
                mov     p1stByte,0
                INVOKE  ReadFile,hFile,hMem,dwFileSize,ADDR NumberOfBytesRead,0
                INVOKE  set_file_info,ADDR fi.ftCreationTime
                INVOKE  CloseHandle,hFile
                or      flag,1
    .ENDIF
                INVOKE  DragFinish,wParam
                INVOKE  InvalidateRect,hWnd,0,1
 
.ELSEIF uMsg==WM_LBUTTONUP
                mov     eax,lParam
                mov     edx,lParam
                and     eax,0ffffh
                shr     edx,10h
    .IF (eax>=rcTitleStr.left)&&(eax<=rcTitleStr.right)&&(edx>=rcTitleStr.top)&&(edx<=rcTitleStr.bottom)
                xor     flag,2
                INVOKE  InvalidateRect,hWnd,OFFSET rcClickReresh,TRUE
    .ENDIF
 
.ELSEIF uMsg==WM_CREATE
                INVOKE  CreateFont,FontHeight,FontWidth,0,0,FW_NORMAL,0,0,0,DEFAULT_CHARSET,0,0,0,\
                        0,OFFSET szFontFamily
                mov     hFont,eax
 
.ELSEIF uMsg==WM_CLOSE
wm_close:       INVOKE  DestroyWindow,hWnd
 
.ELSEIF uMsg==WM_DESTROY
                INVOKE  PostQuitMessage,NULL
 
.ELSE
                INVOKE  DefWindowProc,hWnd,uMsg,wParam,lParam
                ret
.ENDIF
                xor     eax,eax
                ret
WndProc         ENDP
;-------------------------------------------------------------------------------------------------------------
start:          INVOKE  GetModuleHandle,NULL
                mov     hInstance,eax
                mov     wcex.cbSize,SIZEOF WNDCLASSEX
                mov     wcex.style,CS_HREDRAW or CS_VREDRAW
                mov     wcex.lpfnWndProc,OFFSET WndProc
                mov     wcex.cbClsExtra,0
                mov     wcex.cbWndExtra,0
                mov     wcex.hInstance,eax
                INVOKE  LoadIcon,hInstance,OFFSET szIcon;取得圖示代碼
                mov     wcex.hIcon,eax                  ;存入圖示代碼
                mov     wcex.hIconSm,eax                ;存入小圖示代碼
                INVOKE  LoadCursor,NULL,IDC_ARROW       ;取得游標代碼
                mov     wcex.hCursor,eax                ;存入游標代碼
                INVOKE  CreateSolidBrush,0              ;建立邏輯畫刷
                mov     hBlackBrush,eax
                mov     wcex.hbrBackground,eax
                mov     wcex.lpszMenuName,0
                mov     wcex.lpszClassName,OFFSET szClassName
                INVOKE  RegisterClassEx,OFFSET wcex         ;註冊視窗類別
                INVOKE  CreateWindowEx,WS_EX_ACCEPTFILES,OFFSET szClassName,OFFSET szAppName,WS_OVERLAPPED or \
                        WS_SYSMENU or WS_MINIMIZEBOX ,660,562,615,315,0,0,hInstance,0
                mov     hwnd,eax
                INVOKE  ShowWindow,hwnd,SW_SHOWDEFAULT
                INVOKE  UpdateWindow,hwnd
.WHILE TRUE
                INVOKE  GetMessage,OFFSET msg,NULL,0,0
.BREAK .IF !eax
                INVOKE  TranslateMessage,OFFSET msg
                INVOKE  DispatchMessage,OFFSET msg
.ENDW
                mov     eax,msg.wParam          ;程式結束
                INVOKE  ExitProcess,eax
;*************************************************************************************************************
END             start

底下是 DROP.RC 的內容:

1
2
3
4
5
6
#include "c:\masm32\include\resource.h"
#define RT_MANIFEST     24
 
EYE     ICON    viewer02.ico
 
1       RT_MANIFEST MOVEABLE PURE "DROP.EXE.MANIFEST"

底下是 DROP.EXE.MANIFEST 的內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<dependency>
   <dependentAssembly>
      <assemblyIdentity type='win32'
                        name='Microsoft.Windows.Common-Controls'
                        version='6.0.0.0'
                        processorArchitecture='*'
                        publicKeyToken='6595b64144ccf1df'
                        language='*'
      />
   </dependentAssembly>
</dependency>
</assembly>

把 DROP.ASM、DROP.RC、DROP.EXE.MANIFEST 及 viewer02.ico 放在同一子目錄,依下面方式組譯,就可以得到 DROP.EXE:

E:\HomePage\SOURCE\Win32\DragAndDrop>rc drop.rc [Enter]

E:\HomePage\SOURCE\Win32\DragAndDrop>ml drop.asm /link drop.res [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: drop.asm

*************
UNICODE Build
*************

Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS
"drop.obj"
"/OUT:drop.exe"
"drop.res"

E:\HomePage\SOURCE\Win32\DragAndDrop>

執行後,開啟檔案總管,把「F:\大法師.txt」拖拉到 DROP 視窗堳寣A畫面如下。這個程式是利用鍵盤的 PageUp、PageDown、Home、End 鍵來「捲動」畫面,並非用滑鼠拉動捲軸。按下 Esc 鍵可關閉此程式。使用者也可以用滑鼠點擊視窗右上方黃底藍字的「ASCII」或「萬國碼」字串,可以使右邊的字串在這兩種編碼中切換。

解說 DROP.ASM

其實大部分的解釋已在前面解釋過了,此處僅僅點出未解釋的部份。

SHELL32.INC 與 SHELL32.DLL

首先,第 12 行與 16 行,把 SHELL32.INC 與 SHELL32.LIB 兩個檔案包含進來,這是因為 DragQueryFile、DragFinish 都在 SHELL32.DLL 堙C

處理 WM_DROPFILES 訊息

在第 321 行,建立 DROP 視窗時,使視窗具有「WS_OVERLAPPED or WS_SYSMENU or WS_MINIMIZEBOX」風格的原因是禁止使用者調整視窗大小。這是因為 DROP 視窗內的文字行數與列數均為固定的,故沒有必要調整。

底下先看看視窗函式如何處理 WM_DROPFILES 訊息。首先,第 243 行呼叫 DragQueryFile,取得拖曳的第零個檔名,並存於 szFileData 字串堙CDROP 以萬國碼設計,所以最大的檔名長度為 MAX_PATH×2,這是因為一個萬國碼文字佔據兩個位元組,故乘以 2。接下來呼叫 CreateFile 開啟檔案,如果成功,則到第 250 行,把檔案代碼存入 hFile。先說明第 254∼263 行,這幾行程式的目的是依據檔案代碼求得檔案長度,以配置適當的記憶體區塊大小,存放拖曳進來的檔案內容,配置好的記憶體區塊代碼存於 hMem 堙C

第 251∼253 行用來檢查使用者是否第一次拖拉檔案到 DROP 視窗堙A如果不是第一次,那麼 hMem 為已配置過的記憶體區塊,現已用不著了,故應先釋放再配置新的記憶體區塊,但仍稱為 hMem。因為當使用者執行 DROP 程式後拖曳檔案到此視窗堳寣A使用者有可能會再度拖曳其他檔案到此視窗堙A這時就得先釋放原來的記憶體,第 251∼253 行就是為了這件事,否則無用的記憶體區塊太多,會消耗掉太多資源。

剩下來的部份就是讀取檔案到 hMem ( 第 265 行 )、呼叫 set_file_info 設定檔案資料 ( 第 266 行 )、關閉檔案、呼叫 DragFinish 結束拖拉動作釋放 hDrop ( 第 270 行 )、對自己發出 WM_PAINT 訊息以更新畫面 ( 第 271 行 )。

處理 WM_KEYDOWN 訊息

小木偶打算在 DROP 視窗的工作區內,一次顯示 256 個位元組,此 256 個連續的位元組稱為一頁。另外小木偶設計以「PageUp」、「PageDown」、「Home」、「End」四個按鍵顯示上一頁、下一頁、第一頁、最後一頁。最簡便的方法是用 p1stByte 與 pMax1stByte 兩個變數,就可以顯示一頁。p1stByte 變數是位址指標,指向一頁的第一個位元組位址。pMax1stByte 是最後一頁的第一個位元組的位址,也就是 p1stByte 的最大值,其計算方式在程式第 255∼261 行。

因此當使用者按下鍵盤上的按鍵時,有五種結果:

前四種情形,執行完後,要重新繪製工作區,所以都會到第 223 行,呼叫 InvalidateRect,進行重繪。

處理 WM_PAINT 訊息

WM_PAINT 訊息堙A主要呼叫 display_page 副程式更新畫面。進入 display_page 後,先選擇自行設計的字形、字形顏色、背景顏色 ( 第 120∼123 行 ),然後在第 124 行檢查 flag 的第 0 位元,如果為零,表示尚未有檔案被使用者拖入 DROP 視窗內,那麼在印出「使用者可以用滑鼠拖曳任何一個檔案到此視窗…」的訊息 ( 第 128 行 ) 後,就退出 display_page。;若為一,則表示已有檔案被使用者拖入 DROP 視窗內,要在視窗內以十六進位及字元方式印出檔案內容的其中一頁 ( 共 256 個位元組 )。

DROP 以變數 p1stByte 指向 hMem 堛瑰仵蚺漁e,印出這 256 個位元組的十六進位數值與字元,採用類似以前的 PCTOOLS 方式,每行 16 個位元組,共 16 行。cRow、cHex 分別代表共 16 行,每行 16 個位元組。每當繪製十六進位數值時,都是呼叫 DrawText 來完成 ( 見第 156 行 ),而剪裁矩形的位置大小記錄在 rcDisplay 堙C

最左邊的是字元,使用者可以用滑鼠點擊黃底藍字的「ASCII」或「萬國碼」,在這兩者中切換。小木偶以 flag 的第一個位元記錄編碼方式。如果是一,表示萬國碼,呼叫 DrawTextW 繪製字元;如果是 0,表示 ASCII,呼叫DrawTextA 繪製字元。( 第 171∼176 行 )

處理 WM_LBUTTONUP

處理這個訊息只是為了使用者在黃底藍字的「ASCII」或「萬國碼」標題上做切換。處理過程在第 278∼280 行,先檢查使用者點擊時,滑鼠游標是否落在黃底藍字的「ASCII」或「萬國碼」標題上,如果是才切換 flag 的第一個位元。


使視窗成為「拖放物件的供應者」

如果只有「拖放標的」,而沒有「拖放物件的供應者」,那也毫無用處,因此底下的篇幅用來談談如何寫出「拖放物件的供應者」。

原理

先看看拖放檔案的操作過程:當使用者要拖放某個檔案時,先在滑鼠游標移到該檔案的「領空」上,按住滑鼠左鍵不放,接著移動滑鼠游標拖曳檔案到「拖放標的」視窗,才釋放滑鼠左鍵,就完成了整個過程。這過程中可能會通過其他視窗的「領空」,「拖放物件的供應者」應該要獲得這些視窗的代碼,並檢查它們是否具有 WS_EX_ACCEPTFILES 擴充風格,如果具有此擴展風格的話,顯示可以釋放滑鼠左鍵的游標;如果不具 WS_EX_ACCEPTFILES 的話,則顯示禁止游標。換句話說,在拖曳檔案到「拖放標的」視窗的步驟中,即使滑鼠游標不在「拖放物件的供應者」視窗內,也要能把訊息傳回給「拖放物件的供應者」視窗函式,可以呼叫 SetCapture 達到這個目的。

拖拉過程的最後階段,使用者壓著滑鼠左鍵,移動滑鼠游標到「拖放標的」視窗內,只需釋放滑鼠左鍵,就可完成拖放過程。但是對「拖放物件的供應者」視窗而言,並非僅僅呼叫 ReleaseCapture 就可以了。還必須填好一個稱為 DROPFILES 結構體的各欄位。一般而言,這個結構體會被放在一個可移動的記憶體區塊堙A然後對「拖放標的」視窗發送 WM_DROPFILES 訊息,就能將此記憶體區塊傳遞給「拖放標的」視窗了。要配置可移動的記憶體區塊,必須以 GMEM_MOVEABLE 為參數,呼叫 GlobalAlloc。有關可移動的記憶體區塊,可參考第十四章的說明。DROPFILES 結構體的欄位如下:

DROPFILES       STRUCT
pFiles          DD       ?
pt              POINT    <>
fNC             DD       ?
fWide           DD       ?
DROPFILES       ENDS

當使用者釋放滑鼠左鍵時,「拖放物件的供應者」填好 DROPFILES 結構體之後,對「拖放標的」視窗發送 WM_DROPFILES 訊息,發送的方法是呼叫 PostMessage,而不可呼叫 SendMessage。原因是 PostMessage 會對 WM_DROPFILES 訊息所攜帶的記憶體區塊做特別的處理。眾所皆知,在 Win32 系統堙A每個程式是無法讀取,也無法存入資料到其他程式的位址空間。但是 PostMessage 一見到 WM_DROPFILES 訊息,便會在「拖放標的」視窗的位址空間配置一記憶體區塊,然後把 WM_DROPFILES 所攜來的記憶體區塊內容拷貝到新配置記憶體區塊堙A再呼叫 GlobalFree,把 WM_DROPFILES 所攜帶來的記憶體區塊釋放掉,同時把 wParam 參數設定為新的記憶體區塊代碼。當「拖放目標」的視窗函式收到了 WM_DROPFILES 訊息,wParam 參數所指的是自己程式的位址空間中的記憶體區塊,因此 DragQueryPoint、DragQueryFile 和 DragFinish 才能正確無誤的使用這塊記憶體。

由以上過程,可以猜測,「拖放物件的供應者」必須處理 WM_LBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP 三個訊息,在這三個訊息堨眸歲B理以下事情:

WM_LBUTTONDOWN:
當使用者按下滑鼠左鍵時,系統會把 WM_LBUTTONDOWN 訊息傳給「拖放物件的供應者」的視窗函式。視窗函式處理 WM_LBUTTONDOWN 訊息時,要呼叫 SetCapture 把滑鼠訊息傳回給「拖放物件的供應者」,並使 DragMode 為一,表示在拖拉過程狀態中。DragMode 是一個變數,如果其值為一,表示於處於拖拉狀態中,可供處理 WM_MOUSEMOVE、WM_LBUTTONUP 訊息時,判斷是否在拖拉模式狀態下。
WM_MOUSEMOVE:
如果在拖拉狀態,當使用者拖拉檔案,不論是否在原視窗內,因為已經 SetCapture 了,所以系統都會把 WM_MOUSEMOVE 訊息傳給「拖放物件的供應者」的視窗函式。程式可在處理此訊息時,呼叫 GetCursorPos 取得滑鼠游標所在的螢幕座標 ( 以螢幕左上角為原點 ),再以這座標為參數,呼叫 WindowFromPoint 獲得該點落在哪一個視窗,最後呼叫 GetWindowLong 取得該視窗的擴充風格,檢查是否具有 WS_EX_ACCEPTFILES 擴充風格,再依此設定滑鼠游標。
WM_LBUTTONUP:
如果在拖拉狀態,收到 WM_LBUTTONUP 則表示使用者在釋放滑鼠左鍵以結束拖拉狀態。要處理的事情有:
 ①把 DragMode 設為 FALSE,表示處理完此訊息就結束拖拉狀態。
 ②呼叫 ReleaseCapture,釋放滑鼠。
 ③呼叫 GlobalAlloc 配置可移動的記憶體區塊。
 ④鎖住此記憶體區塊,讓程式能夠在此記憶體區塊存放資料。
 ⑤此記憶體區塊即為 DROPFILES 結構體所在,填入適當資料。
 ⑥呼叫 GetCursorPos 取得滑鼠游標所在座標,在以此座標呼叫 WindowFromPoint,取得滑鼠游標所在的視窗代碼。
 ⑦呼叫 PostMessage 把 WM_DROPFILES 訊息傳給前一步驟所的的視窗。
結束拖拉動作,所做的事情雖多,但並不一定要照上面的順序。例如,將 DragMode 設為 FALSE 也可以放在最後一步,或任何一步都可以。

雖然基本上,「拖放物件的供應者」的拖拉動作,處理上面三個訊息就可以了,但是應該還得處理 WM_CAPTURECHANGED 訊息。這是因為拖拉動作很容易被某些按鍵動作取消。例如當使用者進行拖拉動作時,如果又按下了 Ctrl-Esc、Alt-Tab 等組合按鍵,系統會自動跳到特殊程式執行,拖拉動作便被取消了。但是這時候「拖放物件的供應者」仍以為在拖拉狀態,DragMode 仍為 TRUE,這會造成邏輯上的錯誤。當視窗失去捕獲滑鼠訊息時,系統會發出 WM_CAPTURECHANGED 訊息給該視窗的視窗函式。因此應該在處理 WM_CAPTURECHANGED 訊息時,把 DragMode 設為 FALSE。WM_CAPTURECHANGED 訊息中,wParam 不使用,lParam 是當前捕獲滑鼠訊息的視窗之視窗代碼。

DRAG.ASM,拖放物件的供應者

底下小木偶說明如何實作「拖放物件的供應者」,此程式稱為「DRAG」,執行後出現一個包含清單檢視及兩個按鈕的對話盒,使用者可以按下「瀏覽」按鈕,選擇電腦上的任一子目錄,DRAG 會把這個子目錄內的所有檔案顯示在清單檢視控制項內。使用者可以以滑鼠拖拉其內的任何一個檔案,到「拖放目標」的視窗堙C下圖是 DRAG.EXE 執行後,使用者選擇「F:\」的畫面:

您當可見到使用者正在拖拉「F:\大法師.txt」檔案,滑鼠游標移到對話盒上,因為對話盒無 WS_EX_ACCEPTFILES 擴充風格,所以顯示禁止游標的圖案。底下是 DRAG.ASM 檔案的內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
                .586
                .MODEL  FLAT,STDCALL
                OPTION  CASEMAP:NONE
 
IDB_BROWSE      EQU     15000
IDS_PATH        EQU     15001
IDC_LISTVIEW    EQU     15002
IDB_QUIT        EQU     15003
__UNICODE__     EQU     1
 
INCLUDE         WINDOWS.INC
INCLUDE         COMCTL32.INC
INCLUDE         KERNEL32.INC
INCLUDE         SHELL32.INC
INCLUDE         USER32.INC
INCLUDELIB      COMCTL32.LIB
INCLUDELIB      KERNEL32.LIB
INCLUDELIB      SHELL32.LIB
INCLUDELIB      USER32.LIB
 
;*************************************************************************************************************
.CONST
szDlgName       DW      56h,69h,65h,77h,46h,69h,6ch,65h,4ch,69h,73h,74h,0h              ;ViewFileList
szIconName      DW      46h,69h,6ch,65h,4ch,69h,73h,74h,0h                              ;FileList
szBrowseTitle   DW      9078h,64c7h,8cc7h,6599h,593eh,0h                                ;選擇資料夾
szFileTitle     DW      6a94h,540dh,0h                                                  ;檔名
szFileSizeTitle DW      6a94h,6848h,5927h,5c0fh,0h                                      ;檔案大小
szFileCrtTime   DW      5efah,6a94h,6642h,9593h,0h                                      ;建檔時間
szFileSizeFmt   DW      25h,49h,36h,34h,64h,0h                                          ;%I64d
szAllFiles      DW      5ch,2ah,2eh,2ah,0h                                              ;\*.*
szCrtDateFmt    DW      67h,67h,79h,79h,79h,79h,2fh,4dh,2fh,64h,64h,0                   ;ggyyyy/M/dd
szCrtTimeFmt    DW      20h,48h,48h,3ah,6dh,6dh,3ah,73h,73h,0                           ; HH:mm:ss
szFmt           DW      28h,25h,64h,2ch,25h,64h,29h,0h                                  ;(%d,%d)
szDropFile      DW      44h,72h,6fh,70h,46h,69h,6ch,65h,0h                              ;DropFile
szDropStop      DW      44h,72h,6fh,70h,53h,74h,6fh,70h,0h                              ;DropStop
;*************************************************************************************************************
.DATA
hInstance       HINSTANCE       ?       ;模組代碼
hListView       HANDLE          ?       ;清單檢視的子視窗代碼
hCoord          HANDLE          ?       ;靜態控件的子視窗代碼
hCursorStop     HCURSOR         ?       ;拖曳時,在非拖放標的視窗上的滑鼠游標,停止的符號
hCursorDrop     HCURSOR         ?       ;拖曳時,在拖放標的視窗上的滑鼠游標,可以放下的符號
hCursorArrow    HCURSOR         ?       ;正常的滑鼠游標
pOldFnListView  LPSTR           ?       ;原來的清單檢視的視窗函式位址
pFileTitle      LPSTR           ?       ;在szPath中,主檔名的起始位址
DragMode        DD              FALSE   ;拖曳期間為TRUE,否則為FALSE
SelFile         DD              -1      ;使用者選擇的檔案索引,在清單檢視的索引值
cbPath          DD              ?       ;被選擇的檔名(含路徑及一個NULL字元)佔據多少位元組
szPath          DW      MAX_PATH DUP (0)
;*************************************************************************************************************
.CODE
;-------------------------------------------------------------------------------------------------------------
;使用者按下「瀏覽」按鈕,可讓使用者選擇一資料夾,再把此資料夾內的檔案顯示在清單顯示之內
;輸入:EAX-對話盒代碼
browse          PROC    USES esi edi
                LOCAL   bi:BROWSEINFO,lvi:LVITEM,w32fd:WIN32_FIND_DATA
                LOCAL   ft:FILETIME,syst:SYSTEMTIME
                LOCAL   hFind:DWORD
                LOCAL   buffer[20]:WORD
                mov     bi.pidlRoot,0
                mov     bi.hwndOwner,eax
                mov     bi.pszDisplayName,OFFSET szPath
                mov     bi.lpszTitle,OFFSET szBrowseTitle
                mov     bi.ulFlags,BIF_RETURNONLYFSDIRS
                mov     bi.lpfn,0
                mov     bi.iImage,0
                INVOKE  SHBrowseForFolder,ADDR bi       ;讓使用者選擇子目錄
                or      eax,eax                         ;若EAX=0,表示使用者按取消
                jz      quit_browse
                INVOKE  SHGetPathFromIDList,eax,OFFSET szPath   ;取得路徑名稱
                INVOKE  SendMessage,hListView,LVM_DELETEALLITEMS,0,0
                lea     edi,szPath
                mov     ecx,MAX_PATH
                sub     eax,eax
                cld
                repne   scasw
        .IF WORD PTR [edi-4]==5ch       ;檢查路徑名最末字元是否為「\」
                sub     edi,2
                mov     pFileTitle,edi  ;檔名起始位址
                mov     esi,OFFSET szAllFiles+2
                mov     ecx,4
        .ELSE
                mov     pFileTitle,edi  ;檔名起始位址
                sub     edi,2
                mov     esi,OFFSET szAllFiles
                mov     ecx,5
        .ENDIF
                rep     movsw
                INVOKE  FindFirstFile,OFFSET szPath,ADDR w32fd
                mov     hFind,eax
                cmp     eax,INVALID_HANDLE_VALUE
                je      finish
                mov     lvi.imask,LVIF_TEXT
                mov     lvi.iItem,0
    .WHILE eax!=0
                cmp     w32fd.dwFileAttributes,20h
        .IF ZERO?
                lea     edx,w32fd.cFileName
                mov     lvi.pszText,edx
                mov     lvi.iSubItem,0
                INVOKE  SendMessage,hListView,LVM_INSERTITEM,0,ADDR lvi
                INVOKE  wsprintf,ADDR buffer,OFFSET szFileSizeFmt,w32fd.nFileSizeLow,w32fd.nFileSizeHigh
                lea     edx,buffer
                inc     lvi.iSubItem
                mov     lvi.pszText,edx
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
                INVOKE  FileTimeToLocalFileTime,ADDR w32fd.ftCreationTime,ADDR ft ;把UTC時間變成當地時間(中華民國標準時間)
                INVOKE  FileTimeToSystemTime,ADDR ft,ADDR syst                    ;把FILETIME格式的當地時間變成SYSTEMTIME格式
                lea     edi,buffer
                INVOKE  GetDateFormat,LOCALE_USER_DEFAULT,0,ADDR syst,OFFSET szCrtDateFmt,edi,SIZEOF buffer/2
                mov     ecx,SIZEOF buffer/2
                dec     eax
                sub     ecx,eax
                shl     eax,1
                add     edi,eax
                INVOKE  GetTimeFormat,LOCALE_USER_DEFAULT,0,ADDR syst,OFFSET szCrtTimeFmt,edi,ecx
                lea     edx,buffer
                inc     lvi.iSubItem
                mov     lvi.pszText,edx
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
        .ENDIF
                INVOKE  FindNextFile,hFind,ADDR w32fd
    .ENDW
finish:         INVOKE  FindClose,hFind
quit_browse:    ret
browse          ENDP
;-------------------------------------------------------------------------------------------------------------
;設定DROPFILES結構體
;輸入:EAX=記憶體區塊位址,此位址為DROPFILES結構體起始位址,之後為檔名
;   ECX=使用者釋放滑鼠時的座標(螢幕座標)
set_dropfiles   PROC    USES esi edi
                LOCAL   lvi:LVITEM
                mov     edx,ecx
                and     ecx,0ffffh
                shr     edx,10h
                ASSUME  eax:PTR DROPFILES
                mov     [eax].pFiles,SIZEOF DROPFILES   ;檔名在pFiles之後,SIZEOF DROPFILES個位元組開始
                mov     [eax].pt.x,ecx                  ;使用者釋放滑鼠時的 X 座標(螢幕座標)
                mov     [eax].pt.y,edx                  ;使用者釋放滑鼠時的 Y 座標(螢幕座標)
                mov     [eax].fNC,TRUE                  ;TRUE表示螢幕座標
                mov     [eax].fWide,TRUE                ;非零表示檔名為萬國碼
                ASSUME  eax:NOTHING
                mov     ecx,pFileTitle
                add     eax,SIZEOF DROPFILES
                mov     edi,eax                 ;EDI=DROPFILES結構體之後的路徑名起始位址
                mov     esi,OFFSET szPath       ;ESI=使用者選擇的路徑名起始位址
                sub     ecx,esi                 ;ECX=路徑名有多少字元(以位元組為單位)
                cld
                shr     ecx,1                   ;ECX=路徑名有多少字元(以字組為單位)
                rep     movsw
                mov     edx,SelFile             ;被拖拉的檔案在清單檢視內第幾個項目,由0開始
                mov     lvi.imask,LVIF_TEXT     ;取得項目名稱(亦即檔案名稱)
                mov     lvi.pszText,edi         ;設定取得的檔名存於EDI所指位址
                mov     lvi.iItem,edx           ;設定要取得第幾個項目的檔名
                mov     lvi.iSubItem,0          ;子項目為0,才是項目名
                mov     lvi.cchTextMax,MAX_PATH
                INVOKE  SendMessage,hListView,LVM_GETITEM,0,ADDR lvi
                ret
set_dropfiles   ENDP
;-------------------------------------------------------------------------------------------------------------
;新的清單檢視的視窗函式
fnNewListView   PROC    hLV:DWORD,uMsg:DWORD,wParam:WPARAM,lParam:LPARAM
                LOCAL   pos:POINT
                LOCAL   hWndUnderMouse:HWND
                LOCAL   hDrop,pLockMem:HGLOBAL
                LOCAL   lvhti:LVHITTESTINFO
.IF uMsg==WM_MOUSEMOVE
     .IF DragMode==TRUE
                INVOKE  GetCursorPos,ADDR pos
                INVOKE  WindowFromPoint,pos.x,pos.y
                mov     hWndUnderMouse,eax
                INVOKE  GetWindowLong,hWndUnderMouse,GWL_EXSTYLE
                test    eax,WS_EX_ACCEPTFILES
        .IF ZERO?
                mov     edx,hCursorStop
        .ELSE
                mov     edx,hCursorDrop
        .ENDIF
                INVOKE  SetCursor,edx
     .ENDIF
 
.ELSEIF uMsg==WM_LBUTTONDOWN
                INVOKE  CallWindowProc,pOldFnListView,hLV,uMsg,wParam,lParam
                mov     ecx,lParam
                mov     edx,lParam
                and     ecx,0ffffh
                shr     edx,10h
                mov     lvhti.pt.x,ecx
                mov     lvhti.pt.y,edx
                INVOKE  SendMessage,hLV,LVM_HITTEST,0,ADDR lvhti
                mov     SelFile,eax
                cmp     eax,-1
                je      quit_wndproc
                INVOKE  SetCapture,hLV
                mov     DragMode,TRUE
 
.ELSEIF uMsg==WM_LBUTTONUP
        .IF DragMode==TRUE
                INVOKE  SetCursor,hCursorArrow
                INVOKE  GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,SIZEOF DROPFILES+MAX_PATH*2
                mov     hDrop,eax
                INVOKE  GlobalLock,hDrop        ;鎖定記憶體區塊
                mov     pLockMem,eax
                mov     ecx,lParam
                call    set_dropfiles           ;填好DROPFILES結構體
                INVOKE  GlobalUnlock,hDrop
                INVOKE  GetCursorPos,ADDR pos                   ;取得當前滑鼠游標在螢幕座標的位置
                INVOKE  WindowFromPoint,pos.x,pos.y             ;取得當前滑鼠游標下的視窗
                INVOKE  PostMessage,eax,WM_DROPFILES,hDrop,0    ;對上述視窗發出WM_DROPFILES
                call    ReleaseCapture
                mov     DragMode,FALSE
        .ENDIF
 
.ELSEIF uMsg==WM_CAPTURECHANGED
                mov     DragMode,FALSE
 
.ELSE
                INVOKE  CallWindowProc,pOldFnListView,hLV,uMsg,wParam,lParam
                ret
.ENDIF
quit_wndproc:   xor     eax,eax
                ret
fnNewListView   ENDP
;-------------------------------------------------------------------------------------------------------------
DlgProc         PROC    hDlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
                LOCAL   lvc:LVCOLUMN,lvi:LVITEM,ptMousePos:POINT
                LOCAL   buffer[40]:WORD
.IF uMsg==WM_COMMAND
                mov     edx,wParam
                mov     eax,wParam
                shr     edx,10h         ;EDX=通知碼
                and     eax,0ffffh      ;EAX=控制元件識別碼
   .IF dx==BN_CLICKED
        .IF ax==IDB_BROWSE
                mov     eax,hDlg
                call    browse
                or      eax,eax
                jz      quit_dlg_proc
                INVOKE  SetDlgItemText,hDlg,IDS_PATH,OFFSET szPath      ;把IDS_FILE設為空字串
        .ELSEIF ax==IDB_QUIT
                jmp     quit
        .ENDIF
   .ENDIF
 
.ELSEIF uMsg==WM_NOTIFY
                mov     eax,hListView
                push    ebx
                ASSUME  ebx:PTR NM_LISTVIEW
                mov     ebx,lParam
  .IF [ebx].hdr.hwndFrom==eax
     .IF [ebx].hdr.code==NM_CLICK
                mov     eax,[ebx].iItem
                mov     edx,[ebx].ptAction.y
                mov     ecx,[ebx].ptAction.x
                shl     edx,10h
                add     ecx,edx
                mov     SelFile,eax
                mov     DragMode,FALSE
                INVOKE  PostMessage,hListView,WM_LBUTTONUP,0,ecx
     .ENDIF
  .ENDIF
                pop     ebx
                ASSUME  ebx:PTR NOTHING
 
.ELSEIF uMsg==WM_CLOSE
quit:           INVOKE  EndDialog,hDlg,NULL
 
.ELSEIF uMsg==WM_INITDIALOG
              ;載入圖示並以此設定對話盒圖示
                INVOKE  LoadIcon,hInstance,OFFSET szIconName
                INVOKE  SendMessage,hDlg,WM_SETICON,ICON_SMALL,eax
              ;取得清單檢視的代碼及設定欄位
                INVOKE  GetDlgItem,hDlg,IDC_LISTVIEW
                mov     hListView,eax
                mov     lvc.imask,LVCF_FMT or LVCF_WIDTH or LVCF_TEXT or LVCF_SUBITEM
                mov     lvc.fmt,LVCFMT_LEFT
                mov     lvc.lx,290
                mov     lvc.pszText,OFFSET szFileTitle
                mov     lvc.iSubItem,0
                INVOKE  SendMessage,hListView,LVM_INSERTCOLUMN,0,ADDR lvc
                mov     lvc.lx,100
                mov     lvc.pszText,OFFSET szFileSizeTitle
                inc     lvc.iSubItem
                INVOKE  SendMessage,hListView,LVM_INSERTCOLUMN,1,ADDR lvc
                mov     lvc.lx,120
                mov     lvc.pszText,OFFSET szFileCrtTime
                inc     lvc.iSubItem
                INVOKE  SendMessage,hListView,LVM_INSERTCOLUMN,2,ADDR lvc
                INVOKE  SendMessage,hListView,LVM_SETEXTENDEDLISTVIEWSTYLE,LVS_EX_FULLROWSELECT,\
                        LVS_EX_FULLROWSELECT
              ;設定清單檢視的子類化
                INVOKE  SetWindowLong,hListView,GWL_WNDPROC,OFFSET fnNewListView
                mov     pOldFnListView,eax
              ;取得滑鼠游標
                INVOKE  LoadCursor,hInstance,OFFSET szDropStop
                mov     hCursorStop,eax
                INVOKE  LoadCursor,hInstance,OFFSET szDropFile
                mov     hCursorDrop,eax
                INVOKE  LoadCursor,0,IDC_ARROW
                mov     hCursorArrow,eax
 
.ELSE           ;其他未處理的訊息返回 FALSE
                mov     eax,FALSE
                ret
 
.ENDIF          ;已處理的訊息,返回 TRUE
quit_dlg_proc:  mov     eax,TRUE
                ret
DlgProc         ENDP
;-------------------------------------------------------------------------------------------------------------
start:          INVOKE  GetModuleHandle,NULL                    ;取得模組代碼
                mov     hInstance,eax
                INVOKE  DialogBoxParam,hInstance,OFFSET szDlgName,NULL,OFFSET DlgProc,NULL
                INVOKE  ExitProcess,eax
                INVOKE  InitCommonControls
;*************************************************************************************************************
END             start

底下是 DRAG.RC 檔的內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "c:\masm32\include\resource.h"
#define IDB_BROWSE      15000
#define IDS_PATH        15001
#define IDC_LISTVIEW    15002
#define IDB_QUIT        15003
#define RT_MANIFEST     24
#define LVS_EX_FULLROWSELECT 32
 
ViewFileList    DIALOG  200,100,360,200
STYLE           WS_CAPTION|WS_VISIBLE|WS_SYSMENU
FONT            9,"MS Sans Serif"
CAPTION         "檔案列表"
BEGIN
  PUSHBUTTON    "瀏覽",IDB_BROWSE, 5, 5, 50,14
  LTEXT         "",    IDS_PATH,  60, 8,294,14
  CONTROL       "",IDC_LISTVIEW,"SYSListView32",LVS_REPORT|WS_BORDER|LVS_SHOWSELALWAYS|LVS_SINGLESEL, 5,23,350,160
  PUSHBUTTON    "離開",IDB_QUIT, 306,184, 50,14
END
 
FileList        ICON    folder1.ico
DropFile        CURSOR  drop.cur
DropStop        CURSOR  stop.cur
 
1       RT_MANIFEST MOVEABLE PURE "DRAG.EXE.MANIFEST"

底下是 DRAG.EXE.MANIFEST 的內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<dependency>
   <dependentAssembly>
      <assemblyIdentity type='win32'
                        name='Microsoft.Windows.Common-Controls'
                        version='6.0.0.0'
                        processorArchitecture='*'
                        publicKeyToken='6595b64144ccf1df'
                        language='*'
      />
   </dependentAssembly>
</dependency>
</assembly>

把 DRAG.ASM、DRAG.RC、DRAG.EXE.MANIFEST、drop.cur、stop.cur、folder1.ico 六個檔案放在同一子目錄堙A然後依下面方法組譯及連結:

E:\HomePage\SOURCE\Win32\DragAndDrop>rc drag.rc [Enter]

E:\HomePage\SOURCE\Win32\DragAndDrop>ml drag.asm /link drag.res [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: drag.asm

*************
UNICODE Build
*************

Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS
"drag.obj"
"/OUT:drag.exe"
"drag.res"

E:\HomePage\SOURCE\Win32\DragAndDrop>

解說 DRAG.ASM

清單檢視控制項的子類化 ( Subclasse )

程式第 291∼293 行的目的是使清單檢視「子類化」。像編輯框、清單檢視這種已經由系統定義好的控制項,都是視窗的一種,也都有自己的視窗函式,大部分的情形下,應用程式不須擔心也不必撰寫控制項的視窗函式,因為微軟已經撰寫好完善的視窗函式了,並已含在系統內了。但是有時候,我們只想做一些小小的修改,而大部分的功能都與原控制項相同。例如,我們想用編輯框輸入十六進位數值,這時編輯框應該只能輸入 0∼9 與 A∼F 字元,而且有關使用者選擇文字、拷貝等等功能均與原來相同,但是原來的編輯框視窗函式沒有只能輸入 0∼9、A∼F 的功能。這時就得靠「子類化」的方式達成了。

要達成控制項子類化的工作有兩步。第一步是取得控制項的視窗函式位址,並儲存起來。可以呼叫 SetWindowLong API 完成,SetWindowLong 的功能是設定視窗的某些資料,其中有一項就是設定視窗函式,SetWindowLong 的原型是:

SetWindowLong   PROTO   hWnd:HEND,nIndex:DWORD,dwNewLong:DWORD

SetWindowLong 返回時,如果成功,會傳會原值。例如,nIndex 為 GWL_WNDPROC 且 SetWindowLong 成功執行,那麼會返回原來的視窗函式位址。如果 SetWindowLong 執行失敗,返回值為 0。

DRAG 對話盒會把使用者所選擇子目錄內的檔案顯示在清單檢視控制項內。使用者可以用滑鼠拖拉清單檢視內的檔名,到「拖放標的」視窗。因此,DRAG 必須處理系統傳給清單檢視的 WM_LBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP、WM_CAPTURECHANGED 四個訊息,而其他訊息則交由原來的視窗函式處理。所以,小木偶才把清單檢視給子類化了。

控制項子類化的第二步就是撰寫新的控制項視窗函式。在 DRAG 堙A新的清單檢視視窗函式,除了要處理 WM_LBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP、WM_CAPTURECHANGED 訊息之外,其餘的訊息必須呼叫舊的清單檢視視窗函式來加以處理,因此才會有呼叫 CallWindowProc API 這一行程式。CallWindowProc 的原型是:

CallWindowProc  PROTO   lpPrevWndFunc:WNDPROC,hWnd:HWND,Msg:UINT,wParam:WPARAM,lParam:LPARAM

CallWindowProc API 是用來把 Msg 訊息發送給 hWnd 先前的視窗函式,該視窗函示的進入位址在 lpPrevWndFunc 變數堳定。一般而言,lpPrevWndFunc 是由 GWL_WNDPROC 或 DWL_DLGPROC 為參數,呼叫 SetWindowLong 而得到的,尤其是用在子類化控制項的時機。

您可以參考 DRAG.ASM 程式的第 160∼223 行便是新的視窗函式,其內只處理那四個與拖拉有關的訊息,而其餘訊息則交給原來的視窗函式處理 ( 第 218 行 )。而設定新的視窗函式則在程式的第 291∼293 行,呼叫 SetWindowLong 後,把原有的清單檢視視窗函式位址存到 pOldFnListView 變數堙C

在新的視窗函式堙A處理 WM_LBUTTONDOWN 訊息

在第 183∼195 行的程式碼,是用來處理傳給清單檢視的 WM_LBUTTONDOWN 訊息。但是,並不是只有拖拉動作會產生 WM_LBUTTONDOWN 訊息,使用者以滑鼠單擊 ( click ) 清單檢視任何一處,或以滑鼠單擊清單檢視的檔案項目,都會產生 WM_LBUTTONDOWN 訊息。正常情形下,如果以滑鼠單擊項目名稱,表示使用者選擇檔案並使該檔案反白 ( 被選擇的檔案底色會變藍色,稱為反白或高亮度背景 ),這是由清單檢視原來的視窗函式所做的工作。但是當子類化後,除非自行處理這件工作,否則不會發生反白,在視覺效果上會感覺怪怪的。如要自行處理會增加許多麻煩,所以小木偶呼叫原來的視窗函式處理反白過程,在第 183 行,也就是一開始處理 WM_LBUTTONDOWN 訊息就呼叫 CallWindowProc 來處理其他與拖拉無關的過程。

第 183∼190 行,是用來獲得使用者選擇的檔案名稱,小木偶採用對清單檢視發出 LVM_HITTEST 訊息。當使用者以滑鼠點擊清單檢視時,如果所點擊的點落在某個項目上,可由發出 LVM_HITTEST 訊息給清單檢視控制項而檢測出該項目的索引值;如果測試點不在項目上,則返回-1。( 詳見第 22 章有關 LVM_HITTEST 訊息 )。得到使用者所選擇的項目索引後,存於 SelFile 堙A等到使用者放開滑鼠左鍵時,再繼續處理。

第 194 行,則是設定捕獲滑鼠訊息,傳給清單檢視的視窗函式堙A除非放開滑鼠左鍵才不再捕獲滑鼠訊息 ( 第 199 行 )。第 195 行則是設定將進入拖拉狀態,把 DragMode 設為 1;除非放開滑鼠左鍵才設為非拖拉狀態,見第 211 行。

在新的視窗函式堙A處理 WM_MOUSEMOVE 訊息

在第 168∼180 行的程式碼,處理拖拉狀態下,滑鼠移動過程。首先檢查是否處於拖拉狀態下 ( 在拖拉狀態下,DragMode 變數為 TRUE;否則為 0 ),如果不跳出此訊息;否則檢查滑鼠游標下的視窗是否具有 WS_EX_ACCEPTFILES 延伸風格,如果不具此風格,顯示禁止游標;否則顯示可釋放的游標。要獲得滑鼠游標下的視窗之視窗代碼,並且依是否具有 WS_EX_ACCEPTFILES 改變滑鼠游標,大致經過三個步驟:①呼叫 GetCursorPos,②呼叫 WindowFromPoint,③取得擴充風格,改變滑鼠游標。

GetCursorPos API

取得滑鼠游標所在的座標,可呼叫 GetCursorPos,其原型為:

GetCursorPos    PROTO   lpPoint:DWORD

參數 lpPoint 為一位址,指向 POINT 結構體,當呼叫成功後,會返回非零值,而此結構體會被系統存放當前滑鼠游標所在之座標,此座標是以螢幕左上角為原點。若 GetCursorPos 執行失敗,傳回 0。

WindowFromPoint API

WindowFromPoint 可以測試某一點座標在哪個視窗範圍內,其原型是

WindowFromPoint PROTO   x:DWORD,y:DWORD

x、y 兩參數是螢幕座標的 X 座標與 Y 座標,亦即要測試的點之座標。如果成功地執行,返回測試點所在的視窗代碼。如果測試點不在任何視窗內,則返回 0。如果測試點在靜態文字控件上,則傳回該靜態控件的父視窗代碼。

取得游標所在的視窗代碼後,就可以以 GWL_EXSTYLE 為最後一個參數呼叫 GetWindowLong 取得擴充風格,然後判斷是否具有 WS_EX_ACCEPTFILES。這三個步驟在程式第 169∼173 行,接下來的幾行則是設定游標。

雖然 WM_MOUSEMOVE 訊息中,所攜帶的 lParam 參數亦含有滑鼠游標當前所在座標,但是這個座標是以工作區的左上角為原點,如果滑鼠游標在視窗工作區的左方或上方,那麼座標會變成負值,雖然可以呼叫 ClientToScreen 轉換成螢幕座標,但是較為麻煩。

在新的視窗函式堙A處理 WM_LBUTTONUP 訊息

如果是在拖拉模式下,使用者鬆開滑鼠左鍵,系統把 WM_LBUTTONUP 傳給清單檢視的視窗函式,最重要的工作是對當前滑鼠游標下的視窗發出 WM_DROPFILES 訊息、結束拖拉狀態。程式碼在第 198∼212 行,但前面已介紹過了,這奡N不重複了。

正確的把資料填入 DROPFILES 結構體內的工作,由在第 128∼159 行的 set_dropfiles 副程式執行。以上面的例子,使用者拖拉「F:\大法師.txt」檔案,其中「F:\」稱為路徑名,「大法師.txt」稱為檔名。路徑名是在使用者選擇一子目錄時,就已決定;而檔名則是在拖拉時,按下滑鼠左鍵才決定。

如何讓使用者選擇資料夾 ( 子目錄 )

DRAG 能讓使用者選擇電腦中任一子目錄的檔案拖拉,這是如何做到的呢?在於呼叫 SHBrowseForFolder 及 SHGetPathFromIDList 兩個 API。

SHBrowseForFolder API

呼叫 SHBrowseForFolder 後,系統會出現一個對話盒,其內包含一個樹狀檢視控制項、一個靜態控制項及「確定」、「取消」兩個按鈕,可以讓使用者於樹狀檢視控制項選擇一個子目錄,如右圖。如果 SHBrowseForFolder 執行成功,返回使用者所選的 PIDL ( pointer to item identifier list,縮寫為 PIDL );如果使用者按「取消」按鈕,則返回 NULL。SHBrowseForFolder 的原型是:

SHBrowseForFolder   PROTO   lpbi:LPSTR

SHBrowseForFolder 只有一個參數,lpbi,此參數指向一個稱為 BROWSEINFO 結構體所在位址。BROWSEINFO 結構體包含了呼叫 SHBrowseForFolder 所需的參數以及接收資料的欄位,底下是 BROWSEINFO 結構體的欄位:

BROWSEINFO      STRUCT
hwndOwner       DD      ?
pidlRoot        DD      ?
pszDisplayName  DD      ?
lpszTitle       DD      ?
ulFlags         DD      ?
lpfn            DD      ?
lParam          DD      ?
iImage          DD      ?
BROWSEINFO      ENDS
SHGetPathFromIDList

由 item identifier list 取得完整路徑名稱,item identifier list 的來源可以是呼叫 SHBrowseForFolder 成功後的返回值。SHGetPathFromIDList 的原型是:

SHGetPathFromIDList PROTO   pidl:PCIDLIST_ABSOLUTE,pszPath:LPTSTR

pidl 是呼叫 SHBrowseForFolder 成功後的返回值。pszPath 是一位址指標,呼叫成功後,系統填入會把完整的路徑名稱填入此位址內,大小被假定為 MAX_PATH 字元。


到第二十八章回到首頁到第三十章