void CMyDlg::OnDropFiles(HDROP hDropInfo)
{
CDialog::OnDropFiles( hDropInfo );
int nCntFiles= DragQueryFile( hDropInfo, -1, 0,0 );
CEdit* pEd= (CEdit*)GetDlgItem( IDC_EDIT1 );
for (int j=0; j<nCntFiles; j++ ) {
char szBuf[MAX_PATH];
::DragQueryFile( hDropInfo, j, szBuf, sizeof(szBuf) );
int iLen= pEd->GetWindowTextLength();
pEd->SetSel( iLen, iLen ); // position at end of list
pEd->ReplaceSel( szBuf, TRUE ); // append path and filename
pEd->ReplaceSel( "\r\n", TRUE ); // ... and line break
}
}
Note that lines 8-13 could just as easily populate a ListView control or generate lines of HTML for a browser control, or just "silently" fill a CStringArray with filenames. One thing to watch for: The user can drop folder icons as well as files. You can use the PathIsDirectory API function or some other means to differentiate between the two when you process the items.
BOOL CMyDlg::OnInitDialog()
...
::DragAcceptFiles( m_hWnd, true );
return TRUE;
}
Actually, this last step can be accomplished by setting a property of the dialog box (the Accept Files property which sets the WS_EX_ACCEPTFILES style). You can do it either way or both, but I find that using an explicit line of program code is often the best way to document functionality.
class CEditDropNotif : public CEdit
{
virtual BOOL PreTranslateMessage(MSG* pMsg) {
if ( pMsg->message == WM_DROPFILES ) {
GetParent()->SendMessage(WM_DROPFILES, pMsg->wParam, pMsg->lParam);
return TRUE; // eat it
}
return FALSE; // allow default processing
}
};
BOOL CMyDlg::OnInitDialog()
{
...
static CEditDropNotif cEd; // must persist (usually a dlg member)
cEd.SubclassDlgItem( IDC_EDIT1, this );
::DragAcceptFiles( cEd.m_hWnd, true ); // the editbox, not the dialog
...
With that code in place, the drag-and-drop visual feedback works more as expected -- the "Don't Drop" cursor changes into a "Drop OK" cursor only while dragging over the edit box. You might use a similar technique in this common layout, ...where you allow a user to either type in, paste, or browse for a folder... You give him one more option: Drag a folder from any Explorer window.
// file: MyDropTarget.h
//
class CMyDropTarget : public COleDropTarget
{
public:
virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject,
DWORD dwKeyState, CPoint point)
{
CTreeCtrl* pTree= (CTreeCtrl*)pWnd;
UINT nFlags;
HTREEITEM hitem= pTree->HitTest( point, &nFlags );
if ( hitem != NULL) {
DWORD nItemData= pTree->GetItemData( hitem );
if ( (nItemData & 1) == 0 ) { // it's a folder item
pTree->SelectDropTarget( hitem );
pTree->Expand(hitem,TVE_EXPAND );
return( DROPEFFECT_LINK );
}
}
return( DROPEFFECT_NONE ); //else, show the "Don't Drop" pointer
};
virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject,
DROPEFFECT dropEffect, CPoint point) {
if ( dropEffect == DROPEFFECT_LINK ) {
CTreeCtrl* pTree= (CTreeCtrl*)pWnd;
STGMEDIUM rSM;
BOOL fRet= pDataObject->GetData( CF_HDROP, &rSM, 0 );
HDROP hDropInfo= (HDROP)rSM.hGlobal;
HTREEITEM hDropItem= pTree->GetDropHilightItem();
int nCntFiles= DragQueryFile( hDropInfo, -1, 0,0 );
for ( int j=0; j<nCntFiles; j++ ) {
char szBuf[MAX_PATH];
::DragQueryFile( hDropInfo, j, szBuf, sizeof(szBuf) );
HTREEITEM hNew= pTree->InsertItem( szBuf, hDropItem );
pTree->SetItemData( hNew, 1 ); // indicate not a folder
}
pTree->Expand( hDropItem,TVE_EXPAND ); // show the dropped items
}
return( 1 ); // success
};
}; // end of class CMyDropTarget
The OnDragOver() function will be called on mouse movements that are over the registered target -- the tree control. All we need to do is determine whether a drop should or should not be allowed. Line 12 calls the tree control's HitTest() function to learn which, if any, tree node is below the mouse cursor. When a node is there, we make an additional test to see that node is a valid drop target. In this case, I just check the item data of the node. If it has been set to 1, then it is a leaf node (a previously-dropped file) and is not eligible as a drop target. As you drag over these elements, the cursor automatically switches between "Don't Drop" and "Drop OK").
CMyDropTarget gcMyTreeDropTarget; // declare the object
BOOL CMyDlg::OnInitDialog()
{
CDialog::OnInitDialog();
...
HRESULT hr= OleInitialize( 0 ); // <<<---- note: Required!
gcMyTreeDropTarget.Register( &m_ctlTree );
...
Also note the OleInitialize() call. You would typically do that elsewhere, such as in InitInstance(), but I wanted to show it explicitly here. The call is required to initialize the OLE drag-and-drop support.
ShellExecute( NULL, "explore",
"c:\\My Documents\\WonderProg\\Data\\TaskTemplates", // any folder name
NULL,
NULL,
SW_SHOWNORMAL
);
You could use the open verb, but the explore verb shows the folder in two-pane Explorer mode -- with the tree on the left for easy navigation.
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (0)