1、拖拉演示IntroductionIve seen several questions around the CodeProject boards lately asking about doing drag and drop between a program and Explorer windows. Like many things in Windows, it seems easy once you know how its done, but finding the answer can be quite a chore. In this article, I demonstrate
2、how to hook up drag and drop so your program can accept drops from an Explorer window, and be a drag source so your users can drag files into an Explorer window.The sample project is an MFC app, and the article assumes youre familiar with C+, MFC, and using COM objects and interfaces. If you need he
3、lp with COM objects and interfaces, check out my Intro to COM article. The program is MultiFiler, a little utility that acts like a drag and drop staging area. You can drag any number of files into MultiFiler, and it shows them all in a list. You can then drag files back out to Explorer windows, usi
4、ng the Shift or Ctrl keys to tell Explorer to move or copy the original files, respectively.Drag and Drop with ExplorerAs you know, Explorer lets you drag files among Explorer windows and the desktop. When you begin a drag operation, the Explorer window you drag from (the drop source), creates a COM
5、 object implementing the IDataObject interface, and puts some data into the object. The window you drag into (the drop target), then reads that data using IDataObject methods; thats how it knows what files are being dropped.If you check out the data contained in the IDataObject with a viewer like Cl
6、ipSpy, youll see that Explorer puts several data formats in the data object:The important format is CF_HDROP. The other formats are custom formats registered by Explorer for its own use. If we write an app that registers its window as a drop target, and if we know how to read CF_HDROP data, well be
7、able to accept dropped files. Similarly, if we can fill a data object with CF_HDROP data, Explorer will let our app be a drag source. So, whats contained in that CF_HDROP format? Read on.The DROPFILES data structureSo what exactly is the CF_HDROP format? It turns out that its just a DROPFILES struct
8、. Theres also the HDROP type, which is simply a pointer to a DROPFILES struct.DROPFILES isnt a very complex structure. Here is its definition:struct DROPFILES DWORD pFiles; / offset of file list POINT pt; / drop point (client coords) BOOL fNC; / is it on NonClient area and pt is in screen coords BOO
9、L fWide; / wide character flag;The one thing that isnt listed in the struct definition is the list of filenames. The list is formatted as a double-null terminated list of strings. But where is it stored? It is actually stored right after the fWide member, and pFiles holds the offset (relative to the
10、 beginning of the struct) where the list is located in memory. The only other member thats used in drag and drop is fWide, which indicates whether the filenames are in ANSI or Unicode characters.Accepting a drag and drop from ExplorerAccepting a drag and drop is much easier than initiating one, so I
11、ll cover accepting first.There are two ways for your window to accept drag and drop. The first way is a holdover from Windows 3.1 and uses the WM_DROPFILES message. The other way is to register your window as an OLE drop target.The old way - WM_DROPFILESTo use the old method, you first set the accep
12、t files style in your window. For dialogs, this is on the Extended Styles page, as shown here:If you want to set this style at runtime, call the DragAcceptFiles() API, which takes two parameters. The first is your main window handle, and the second is TRUE to indicate you can accept drag and drop. I
13、f your main window is a CView instead of a dialog, youll need to set this style at runtime.No matter which of the two methods you use, your window becomes a drop target. When you drag files or folders from an Explorer window and drop them in your window, the window receives a WM_DROPFILES message. T
14、he WPARAM of a WM_DROPFILES message is an HDROP that lists what files are being dropped. There are three APIs you use to get the file list out of the HDROP: DragQueryFile(), DragQueryPoint(), and DragFinish(). DragQueryFile() does two things: returns the number of files being dragged, and enumerates
15、 through the list of files. DragQueryPoint() returns the pt member of the DROPFILES struct. DragFinish() frees up memory allocated during the drag and drop process. DragQueryFile() takes four parameters: The HDROP, the index of the filename to return, a buffer allocated by the caller to hold the nam
16、e, and the size of the buffer in characters. If you pass -1 as the index, DragQueryFile() returns the number of files in the list. Otherwise, it returns the number of characters in the filename. You can test this return against 0 to tell if the call succeeded. DragQueryPoint() takes two parameters,
17、the HDROP and a pointer to a POINT struct that receives the value in the pt member of the DROPFILES struct. DragFinish() just takes one parameter, the HDROP.A typical WM_DROPFILES handler would look like this:void CMyDlg:OnDropFiles ( HDROP hdrop ) UINT uNumFiles;TCHAR szNextFile MAX_PATH; / Get the
18、 # of files being dropped. uNumFiles = DragQueryFile ( hdrop, -1, NULL, 0 ); for ( UINT uFile = 0; uFile 0 ) / * / Do whatever you want with the filename in szNextFile. / * / Free up memory. DragFinish ( hdrop );DragQueryPoint() isnt necessary if all you want is the list of files. (Actually, Ive nev
19、er had to use it myself.)The new way - using an OLE drop targetThe other method of accepting drag and drop is to register your window as an OLE drop target. Normally, doing so would require that you write a C+ class that implements the IDropTarget interface. However, MFC has a COleDropTarget class t
20、hat takes care of that for us. The process is a bit different depending on whether your main window is a dialog or a CView, so Ill cover both below.Making a CView a drop targetCView already has some drag and drop support built-in, however its not normally activated. To activate it, you add a COleDro
21、pTarget member variable to the view, and then call its Register() function in your views OnInitialUpdate() to make the view a drop target, as shown below:void CMyView:OnInitialUpdate() CView:OnInitialUpdate(); / Register our view as a drop target. / m_droptarget is a COleDropTarget member of CMyView
22、. m_droptarget.Register ( this );Once thats done, you then override four virtual functions that are called when the user drags over your view: OnDragEnter(): Called when the cursor enters your window. OnDragOver(): Called when the cursor moves inside your window. OnDragLeave(): Called when the curso
23、r leaves your window. OnDrop(): Called when the user drops in your window. OnDragEnter()OnDragEnter() is the first function called. Its prototype is:DROPEFFECT CView:OnDragEnter( COleDataObject* pDataObject, DWORD dwKeyState, CPoint point );The parameters are: pDataObject: Pointer to a COleDataObjec
24、t that contains the data being dragged. dwKeyState: A set of flags indicating which mouse button is clicked and which shift keys (if any) are pressed. The flags are MK_CONTROL, MK_SHIFT, MK_ALT, MK_LBUTTON, MK_MBUTTON, and MK_RBUTTON. point: The cursor position, expressed in the views client coordin
25、ates. OnDragEnter() returns a DROPEFFECT value, which tells OLE whether the drop will be accepted, and if so, what cursor should be displayed. The values and their meanings are: DROPEFFECT_NONE: The drop will not be accepted. The cursor changes to: . DROPEFFECT_MOVE: The data will be moved by the dr
26、op target. The cursor changes to: . DROPEFFECT_COPY: The data will be copied by the drop target. The cursor changes to: DROPEFFECT_LINK: The data will be linked to by the drop target. The cursor changes to: . Normally, in OnDragEnter() you examine the data being dragged and see if it meets your crit
27、eria. If not, you return DROPEFFECT_NONE to reject the drag and drop. Otherwise, you can return one of the other values depending on what you intend to do with the data.OnDragOver()If you return a value other than DROPEFFECT_NONE from OnDragEnter(), OnDragOver() is called whenever the mouse cursor m
28、oves within your window. The prototype of OnDragOver() is:DROPEFFECT CView:OnDragOver ( COleDataObject* pDataObject, DWORD dwKeyState, CPoint point );The parameters and return value are identical to OnDragEnter(). OnDragOver() lets you return different DROPEFFECT values depending on the cursor posit
29、ion and the shift key state. For instance, if your main view window has several areas, displaying different lists of information, and you want to allow drops only in one part, youd check the cursor position in the point parameter, and return DROPEFFECT_NONE if the cursor is not in that area.As for t
30、he shift keys, you normally react to them as described below: SHIFT pressed (MK_SHIFT in dwKeyState): Return DROPEFFECT_MOVE. CONTROL pressed (MK_CONTROL): Return DROPEFFECT_COPY. Both pressed (MK_SHIFT | MK_CONTROL): Return DROPEFFECT_LINK. These are only guidelines, although its best to adhere to
31、them, since they are what Explorer uses. But if some of the actions (copy, move, or link) doesnt make sense for your app, you dont have to return the corresponding DROPEFFECT. For instance, in MultiFiler (Ill get to it, I promise!) OnDragOver() always returns DROPEFFECT_COPY. Just be sure to return the right value, so that the cursor accurately indicates to the user, what will happen if he drops in your window.OnDragLeave()OnDragLeave() is called if the user drags out of your window without dropping. The prototype is:void CView:OnDragLeave();It has no parameters or