Link to home
Start Free TrialLog in
Avatar of VikingCoder
VikingCoder

asked on

Simple Drag Question

Hello,

In MSVC++ 6.0, I'm designing an application that I want to be able to perform a drag operation on.
I'd like to add code to the OnLButtonDown call which sets up my drag.  I'd like to be able to drag
from my application into another application (Paint Shop Pro) and pass a file name, so that PSP will open
the corresponding file name.  (Which happens to be a bitmap.)

I've done the same thing under Delphi using a great component someone wrote which implements the DoDragDrop
call, and the descendants of IDataObject, IEnumFormatEtc, and IDropSource to properly handle the CF_HDROP.
I believe that this is the "clean" way to do it.  And I could easily translate that code over
(it'd just take a while.)

Before I get too far down this path, I thought I'd ask if someone else has already implemented the same thing,
if there's a simple way I can use the Ole stuff to do the same thing, or if you have a simple API call which
does the same thing.

o In OnLButtonDown
o Create data structures
o Insert a file name
o Execute DoDragDrop

I'm looking for sample source, or a library which takes care of the dirty work for me.

Many thanks!
Avatar of abancroft
abancroft

MFC provides a framework for OLE drag'n'drop - it requires the source/target to be derived from CView.

Look up the help for: OnBeginDrag(), OnDragEnter(), OnDragLeave(), OnDragOver(), OnDrop().

MFC also provides classes to encapsulate the data. See the help for COleDataObject & COleDataSource.
Try with that:
char sFiles[] = "C:\\Temp\\file1.txt\0""C:\\Temp\\file2.txt";
DROPFILES dobj = { 20, { 0, 0 }, 0, 1 };int nLen = sizeof(sFiles);
int nGblLen = sizeof(dobj) + nLen*2 + 5;//lots of nulls and multibyte_char
HGLOBAL hGbl = GlobalAlloc(GMEM_ZEROINIT|GMEM_MOVEABLE|GMEM_DDESHARE, nGblLen);
char* sData = (char*)::GlobalLock(hGbl);memcpy( sData, &dobj, 20 );
char* sWStr = sData+20;for( int i = 0; i < nLen*2; i += 2 ){
sWStr[i] = sFiles[i/2];}::GlobalUnlock(hGbl);
if( OpenClipboard() ){
EmptyClipboard();
SetClipboardData( CF_HDROP, hGbl );
CloseClipboard();
}
And after that use DoDragDrop and OLE will take care of other jobs.

Avatar of VikingCoder

ASKER

Thanks, Pig.  What do you mean by "use DoDragDrop"?  What do I call it with?  What call do I make?
Thanks, Pig.  What do you mean by "use DoDragDrop"?  What do I call it with?  What call do I make?
I know I'm being stupid, but I can't get PIG's code to work.  It seems as though you think there's one last simple thing I have to add to the code - well, I can't figure it out!

void DNDCleverCall(CString FileName)
{
      // Does everything to drag
      // the File to another app
}

void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{
      // TODO: Add your message handler code here and/or call default

      CString FileName = "d:/temp.txt";
      DNDCleverCall(FileName);
}

Thanks!  Sorry, but I really did try digging through the help, and monkeying with the code you gave me, PIG.
Have you tried the samples at codeguru.developer.com? May be you will find the exact sample you are looking for.

1.Copying File Names from Explorer
http://209.66.99.126/clipboard/PasteFNames.shtml

Good Luck!
Thanks, Naveenkohli,

I've been sifting through CodeGuru, and found this exact article.  It talks about dragging from explorer TO your application.  One of the articles talks about a MS KB article that does (supposedly) what I want, but you have to include all of this crap to do it, and the article is using such things as WndMain and WndProc - obviously a little involved and too low-level.  I was just hoping that I could have something fewer than a thousand lines of code to do what I want - either that or well defined / well documented.
I have a CTreeView derived class that implements OLE drag'n'drop & supports the CF_HDROP format.

Since you are using MFC, you should derive a class from COleDataSource. In the constructor, I call DelayRenderData(CF_HDROP). I then override OnRenderGlobalData() to provide the HDROP data. Here's the code:

BOOL COlePathDataSrc::RenderPathHDROPData(LPFORMATETC lpFormatEtc, HGLOBAL* phGlobal)
{
      VERIFY(lpFormatEtc->cfFormat==CF_HDROP);

      // Allocate space for DROPFILE structure plus the file path and one extra
      // byte for final NULL terminator
      *phGlobal = GlobalAlloc(GHND|GMEM_SHARE,(DWORD)(sizeof(DROPFILES)+_MAX_PATH+1));

      if (*phGlobal)
      {
            // Get temp path
            TCHAR szTmpPath[_MAX_PATH];
            GetTempPath(sizeof(szTmpPath)-1, szTmpPath);

            // Make temp file name
            CString csFile;
            csFile.Format("%sfoo.bar", szTmpPath);

            LPDROPFILES pDropFiles;
            pDropFiles = (LPDROPFILES)GlobalLock(*phGlobal);

            // Set the offset where the starting point of the file start
            pDropFiles->pFiles = sizeof(DROPFILES);
            // File doesn't contain wide characters
            pDropFiles->fWide=FALSE;
            // Copy file name.
            lstrcpy(((LPSTR)pDropFiles)+sizeof(DROPFILES), (LPCSTR)csFile);

            // Final null terminator as per CF_HDROP Format specs.
            ((LPSTR)pDropFiles)[sizeof(DROPFILES)+csFile.GetLength()+1]=0;

            GlobalUnlock(*phGlobal);

            // Now create the actual disk file.
            CFile cFile;
            if (cFile.Open(csFile, CFile::modeCreate | CFile::modeWrite))
            {
                  // Write to the file here...
                  cFile.Close();
            }
            else      // Uh oh!
            {
                  GlobalFree(*phGlobal);
                  *phGlobal = NULL;
            }
      }

      return *phGlobal!=NULL;
}

In the function that starts the drag:
CMyView::BeginDrag()
{
  // Allocate our data object
 COleDataSource *pDataSource = new CMyDataSource();
  pDataSource->DoDragDrop(DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK);
  delete pDataSource;
}

(My view is derived from CTreeView so I catch TVN_BEGINDRAG notifications & begin the drag there).
Thanks abancroft,

But that doesn't work for me, either.  It literally doesn't do anything.  I mean, I replaced your stuff from creating a file to just listing a file name that's pre-created.  I also am just intercepting the OnLButtonDown message - I can't find a "BeginDrag" notification to intercept - my ChildView doesn't have a handler like that.  Gah!

The call to DoDragDrop returns immediately with a value of zero.

My mouse never switches, and the drag-target application never sees anything.  Notepad, Paint Shop Pro, and Explorer.

I believe this is how I created the project :

File | New
Projects
MFC AppWizard (exe)
Single Document
Turn OFF Document / View Architecture support (is this the problem?)
Database support - none
(which means that I can't turn on Automation support)
Turn off ActiveX support
Leave everything else the same...

So, what the heck am I missing?  I've tried about 15 different ways to try to get this to work, and none of them has done anything.
BeginDrag() was just a placeholder - OnLButtonDown() is fine.

You'll need to post the code for your COleDataSource derived class & OnLButtonDown().
Thanks for paying attention, abancroft!

I've tried to do exactly what you told me to do.  Am I missing some reserved word, or something?  (Forgive my ignorance!)

The code compiles, links, and runs just fine - it just doesn't do anything!

#include <afxole.h>

class CMyOleDataSource : public COleDataSource
{
public:
      CMyOleDataSource();
      BOOL OnRenderGlobalData(LPFORMATETC lpFormatEtc, HGLOBAL *phGlobal);
};

CMyOleDataSource::CMyOleDataSource()
{
      DelayRenderData(CF_HDROP);
}

BOOL CMyOleDataSource::OnRenderGlobalData(LPFORMATETC lpFormatEtc, HGLOBAL *phGlobal)
{
      VERIFY(lpFormatEtc->cfFormat==CF_HDROP);
      
      // Allocate space for DROPFILE structure plus the file path and one extra
      // byte for final NULL terminator
      *phGlobal = GlobalAlloc(GHND|GMEM_SHARE,(DWORD)(sizeof(DROPFILES)+_MAX_PATH+1));
      
      if (*phGlobal)
      {
            // Make temp file name
            CString csFile;
            csFile.Format("d:/temp.txt");
            
            LPDROPFILES pDropFiles;
            pDropFiles = (LPDROPFILES)GlobalLock(*phGlobal);
            
            // Set the offset where the starting point of the file start
            pDropFiles->pFiles = sizeof(DROPFILES);
            // File doesn't contain wide characters
            pDropFiles->fWide=FALSE;
            // Copy file name.
            lstrcpy(((LPSTR)pDropFiles)+sizeof(DROPFILES), (LPCSTR)csFile);
            
            // Final null terminator as per CF_HDROP Format specs.
            ((LPSTR)pDropFiles)[sizeof(DROPFILES)+csFile.GetLength()+1]=0;
            
            GlobalUnlock(*phGlobal);
      }
      return *phGlobal!=NULL;
}

void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{

      // Allocate our data object
      COleDataSource *pDataSource = new CMyOleDataSource();
      pDataSource->DoDragDrop(DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK);
      delete pDataSource;
}
Looks OK.

Are you calling AfxOleInit() somewhere?

Try setting the filename to "D:\\temp.txt" (and make sure it exists).

If that doesn't work, set the clip format to CF_TEXT and copy a null terminated text string to the global memory block. Then try to drag to notepad.
So, abancroft wins the cigar!

Thank you, thank you, thank you.  It was the AfxOleInit();  Why is this not documented?

abancroft, if you just post something real quick as an answer, I'll give you all the points!
ASKER CERTIFIED SOLUTION
Avatar of abancroft
abancroft

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial