Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Drag-Drop Manager for CObjects

0.00/5 (No votes)
16 Jan 2000 1  
A drag-drop/clipboard manager class for MFC objects derived from CObject

Sample Image - ddmgr.gif

Introduction

There are many Windows C++ programming books that contain chapters on how to use the clipboard and drag-and-drop. Some show how to perform these operations with the basic Windows API, some use MFC, and others use COM/OLE to do the job.

However, when I wanted to start doing clipboard operations of my own, I discovered that all of these sources had one limitation in common: They usually only dealt with plain text (CF_TEXT) transfers, but I had to handle my own special objects.

Then, I read an article by Keith Rule, showing the use of CMemFile and CSharedFile objects to transfer text blocks via serialization. I decided that this process could be expanded to work on any class which was derived from CObject, as long as the class in question had its own Serialize() method.

The result is the drag-drop manager described in this article.

Target Audience

This article is intended for other MFC programmers who wish to implement drag-and-drop operations in document-view architecture applications. Basic familiarity with the document-view architecture is assumed. No knowledge of OLE or COM is needed in order to use the class described here.

Acknowledgments

  • The idea for using serialization to transfer the data to and from the buffers came the article Basic Copy/Paste and Drag/Drop Support by Keith Rule
  • The general strategy for event handling with drag and drop came from Chapter 1 of The Essence of OLE with Active X by David S. Platt.

The Drag-Drop Manager Class - CDragDropMgr

Requirements

I was planning to use the class in a standard SDI graphics drawing program. It would be necessary to:

  • Copy graphics objects (shapes and lines, generally) out to the clipboard when cut or copied.
  • Paste these objects back into the view window.
  • Drag a selected object around in the view to a new location.
  • Drag an object into another view window and drop it into place.
  • As well as the special graphics objects used in the application, it would also be necessary to let a user select blocks of text from other applications and either copy/paste them into the graphics program using the clipboard, or drag them directly into the view.

There was no requirement for the user to be able to turn graphics objects into plain text for pasting or dragging into other applications.

Environment

The manager class is designed to be created and used by a view class, usually derived from CView or CScrollView. In an MDI-type application, each view creates its own manager class.

It is the owning view's responsibility to communicate changes back to the CDocument class that controls it, and to any other views that might be affected by the change. For example, if a given view accepts the drop of a "square" object, it must tell its owning document to add the new shape, and then tell the other views to update themselves to display the new object.

The manager class uses OLE and MFC. It was compiled and tested using Visual Studio 5.0, on both Windows NT 4.0 and Windows 95.

Inheritance

The manager class is not derived from any other classes.

Static or Global Variables in ddmgr.h

static BOOL g_bOleInitCalled = FALSE;

This variable is used to indicate whether this manager or some other manager in the same application has initialized OLE already.

Project Notes

No special project settings are needed for compilation or link editing. It was compiled successfully under warning level 3.

Public Methods

Constructor

CDragDropMgr(BOOL bInitOle = TRUE);

The constructor sets up the OLE environment by calling InitOle(), if the caller indicates that this needs to be done. It is not necessary for the parent application to initialize OLE itself. If it has done so, then it should either call the constructor with the flag set FALSE, or set the g_bOleInitCalled flag TRUE before creating any manager objects.

There is an annoying debug assertion failure if you try to initialize OLE when it has already been initialized. This function is an attempt to minimize its effects.

MakeDropTarget

void MakeDropTarget(CView* pView);

The caller uses this function to register itself as a valid OLE drop target.

AddFormat

BOOL AddFormat(CString csFormat);

The caller uses this function to create the list of object formats which the manager object should recognize.

If csFormat is set to "CF_TEXT" (note that this is a string, not the UINT CF_TEXT format value), then CF_TEXT type data can be accepted (but cannot be copied out).

OkToPaste

BOOL OkToPaste();

This method returns TRUE if a recognized data type is on the clipboard and available for pasting. It is not used for drag-drop operations. It is really just a shorthand version of the AvailableDataType() method described below.

AvailableDataType

CString AvailableDataType(COleDataObject* pDataObject);

This method returns the string version of the data type contained in the OLE data object referenced by the caller. If pDataObject is NULL, then the method assumes that the caller wants to check the clipboard buffer.

The method returns either a string with the data type, or an empty string if the data is not recognized by the manager.

PrepareDrop

BOOL PrepareDrop(BOOL        bToClipboard,
                 LPCTSTR     lpstrFormat,
                 CObject*    pObj,
                 DROPEFFECT* pDropEffect);

This function is used to copy a CObject-derived object out to the clipboard or to an OLE data object for dragging.

The caller passes in:

  • bToClipboard: A flag stating whether the target object should be sent to the clipboard or readied for drag-drop
  • lpstrFormat: The format type to use (a pointer to a character string)
  • pObj: A pointer to the target object, which must have its own Serialize() method.
  • pDropEffect: A pointer to the DROPEFFECT enumeration which OLE uses. The caller can set this NULL if copying to the clipboard, but it may not be NULL if preparing a drag-drop.

You usually call this function from:

  • Your view class's OnLButtonDown() event handler when starting a drag-drop.
  • Your view (or document) class's OnEditCopy() and/or OnEditCut() event handler when copying data to the clipboard or deleting an object.

DoDrop

BOOL DoDrop(CObject*        pO,
            COleDataObject* pDataObject,
            LPCTSTR         lpstrFormat);

This method is the inverse of PrepareDrop(). It copies data from the clipboard or drag-drop buffer into a target object for use by the caller.

It is the callers' responsibility to create the target object and make sure it has its own version of the Serialize() method.

The caller passes in:

  • pO: A pointer to an object to be filled with data,
  • pDataObject: A pointer to the OLE data object containing the data. If this pointer is set NULL, it means that the data should come from the clipboard.
  • lpstrFormat: A pointer to the data format name.

You usually call this function from:

  • Your view class's OnDrop() event handler when a drag-drop operation ends, to load the data for use.
  • Your view (or document) class's OnEditPaste() method when doing a paste from the clipboard.

OnDragOver

DROPEFFECT OnDragOver(COleDataObject* pDataObject,
                      DWORD           dwKeyState,
                      CPoint          point);

This method is called from the caller's OnDragOver() event handler.

It returns the type of drag which is happening:

If the user is just dragging a valid object, it returns DROPEFFECT_MOVE.

If the user is dragging a valid object with the control key depressed, it returns DROPEFFECT_COPY. If desired, the caller can then create a new copy of the object at the drop location, rather than moving the object.

The caller just passes in the parameters handed to it in its own OnDragOver() event handler.

OnDragEnter

BOOL OnDragEnter(COleDataObject* pDataObject);

Call this method from your view class's OnDragEnter() event handler.

It returns TRUE if the passed-in OLE data object contains data the manager recognizes, otherwise FALSE.

GetCFText

BOOL GetCFText(CStringArray*   pcsText,
               COleDataObject* pDataObject);

This is a specialized method used in place of DoDrop() to handle incoming CF_TEXT data.

The method fills the passed-in CStringArray with the lines of text contained in the data buffer. If pDataObject is NULL, this means the data is to be gotten from the clipboard buffer.

Protected Methods

InitOle()

BOOL InitOle();

This method sets up the OLE environment for your application. It is called from the manager's constructor.

Public Properties

None.

Protected Properties

BOOL m_bOkToDrop;

Used to indicate that there is a "legal" object available to be dropped.

COleDropTarget m_DropTarget;

Used to register the "owning" view:

CStringArray m_csFormats;

Used to store the text string names of registered clipboard data formats that the owning view is able to handle.

CArray<UINT,UINT> m_nFormats;

Parallel array of UINT values associated with registered format strings.

Using the Manager in a Sample Application - "TextDemo"

The description above is probably not enough to let you actually use the class. For that purpose, an example application is probably the best illustration.

The TextDemo App

The textdemo application shows how to use the drag-drop manager class. It also shows the use of a basic text-block object entry class. This class will be the subject of another article, so it is not explained here in detail.

The textdemo application is intended merely as a framework for demonstrating the drag-drop manager and basic text block objects. Therefore it is pretty simple, and the user interface is rather crude.

Textdemo also does a lot of housekeeping that has nothing at all to do with dragging and dropping objects: It must keep track of the objects, re-draw them when appropriate, handle selection of objects, etc. This article does not cover any of these subjects. It is likely that you will not care for the way I have implemented the logic for these tasks; hopefully, you will recognize that you can design your own applications as desired and still use the drag-drop manager class.

Textdemo lets you create text blocks by typing on the screen (draw|text blocks menu option), create gray squares by clicking on the screen (draw|squares menu option), or select objects by entering "select mode" (draw|select objects menu option).

Textdemo uses two different object types in order to show how to use the drag-drop manager to handle more than one object type.

When you have selected an object, you can copy it to the clipboard, paste it into the view (it appears in the upper-left corner), or delete it (it gets cut to the clipboard).

If you drag a selected object, you can move it to a new location in the view, or drag it across the desktop to another instance of textdemo.

You can also select a block of plain text from another application, copy it to the clipboard, and then paste it into textdemo (it gets turned into a text block object), or drag the selected text directly into textdemo and drop it where desired.

If you hold down the control key while dragging, a copy of the selected object is created.

The sections below detail what view class methods to create in order to use the drag-drop manager class. I gathered these functions into the view class merely for convenience. There is no reason you could not handle some of them in the document class if that seems more appropriate to you.

Textdemo uses a few defines, in globals.h:

#define MY_TXTBLK "CTxtBlk"
#define MY_SQUARE "CSquare"
#define PLAIN_TXT "CF_TEXT" //Special for ddmgr class

Preparing and Freeing the Manager

In the constructor for the view class, create the manager object for this view to use, and give it the list of legal object types:

CTextDemoView::CTextDemoView()
{
    . . .

    m_pDDMgr =  

    new CDragDropMgr; m_pDDMgr->AddFormat(MY_TXTBLK);
    //Seeglobals.h
    m_pDDMgr->AddFormat(MY_SQUARE);m_pDDMgr->AddFormat(PLAIN_TXT);
    
    . . .
}

In the destructor, free the manager object:

CTextDemoView::~CTextDemoView()
{
    if (m_pDDMgr != NULL)
        delete m_pDDMgr;
}

Message (Event) Handlers

OnCreate

In the OnCreate() event handler for the view class, register this view as a drop target. This must be done here instead of in the constructor because the view is not valid itself until this point:

int CTextDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    . . .
    
    if (m_pDDMgr == 
        NULL) return
        
    -1;m_pDDMgr->MakeDropTarget((CView*)this);
    
    . . .
}

OnLButtonDown

If the curent action is "select objects", see if an object has been selected. If an object has been selected, always copy its data out to the drag-drop buffer via PrepareDrop(). If the manager indicates that the selected object is being moved, delete that object from the app (its data is safe in the OLE buffer):

void CTextDemoView::OnLButtonDown(UINT nFlags, CPoint point)
{ 
    . . . 
    //Start a select action. 
    if(pDoc->CurrentAction() ==  ACTION_SELECT)
    {
        m_pTextList->HideCaret(this);
        StartSelectAction(point);
    }
    . . .
}

void CTextDemoView::StartSelectAction(CPoint point)
{
    CTextDemoDoc* pDoc = GetDocument();

    FindSelection(point);

    if (pDoc->AnObjectIsSelected())
    {
        DROPEFFECT de = DROPEFFECT_NONE;

        if (m_pDDMgr->PrepareDrop(DO_DRAGDROP, //globals.h
                                  DDFormat(),
                                  pDoc->SelObjectPtr(),
                                  &de))
        {
            pDoc->CheckAndSetCursor();

            SetMoving(de == DROPEFFECT_MOVE);

            if (MovingAnObject())
                pDoc->DeleteSelectedObject();
        }
    }
}

OnDragOver

This event fires every time the mouse moves during a drag operation. Simply call the manager object's OnDragOver() method to let it handle things. The method returns the type of drag (move or copy) taking place:

DROPEFFECT CTextDemoView::OnDragOver(
           COleDataObject* pDataObject,
           DWORD           dwKeyState,
           CPoint          point) 
{
    DROPEFFECT de = m_pDDMgr->OnDragOver(pDataObject,
                         dwKeyState,
                         point);

    SetMoving(de == DROPEFFECT_MOVE);

    return de;
}

OnDrop

This event fires when the user drops data into the view.

The event code simply determines what type of data is out there, and if legal, performs logic to:

  • Create a new instance of the object
  • Load it with the data from the buffer via DoDrop()
  • Place the new object into its proper spot in the view:
BOOL CTextDemoView::OnDrop(COleDataObject* pDataObject,
                           DROPEFFECT      dropEffect,
                           CPoint point) 
{
    CString csF = m_pDDMgr->AvailableDataType(pDataObject);

    if (csF == MY_TXTBLK)
        return DropTextBlock(pDataObject,dropEffect,point);

    else if (csF == MY_SQUARE)
        return DropSquare(pDataObject,dropEffect,point);

    else if (csF == PLAIN_TXT)
        return DropCFText(pDataObject,dropEffect,point);

    return FALSE;
}

Only the code for DropSquare is shown here; the other two functions use essentially the same logic:

BOOL CTextDemoView::DropSquare(
                    COleDataObject* pDataObject,
                    DROPEFFECT      dropEffect,
                    CPoint point) 
{
    CClientDC dc(this);
    CTextDemoDoc* pDoc = GetDocument();
    OnPrepareDC(&dc);
    dc.DPtoLP(&point);

    //Create a new CTxtBlk object
    CSquare* pS = new CSquare(CPoint(0,0));

    if (pS == NULL)
        return FALSE;

    pDoc->SetModifiedFlag(TRUE);

    //If created OK, load the drop buffer contents into
    //the new text block, and place it
    //at the point of the drop.
    if (!m_pDDMgr->DoDrop(pS,
                          pDataObject,
                          MY_SQUARE))
    {
        AfxMessageBox("Drop failed");

        //Delete the newly-created pS above:
        //It will be on the tail of the list
        pDoc->DeleteLastSquare();

        return FALSE;
    }

    //Set new object to drop location
    pS->SetLocationTo(point);

    pDoc->m_SquList.AddTail(pS);

    if (dropEffect == DROPEFFECT_MOVE)
        SetMoving(FALSE);

    RedrawArea(pS->BoundingRect());

    return TRUE;
}

OnDragEnter

This event fires when the user first drags an object into the view.

Simply call the manager's OnDragEnter() method to let it set things up:

DROPEFFECT CTextDemoView::OnDragEnter(
           COleDataObject* pDataObject,
           DWORD           dwKeyState,
           CPoint          point) 
{
    m_pDDMgr->OnDragEnter(pDataObject);

    return this->OnDragOver(pDataObject,dwKeyState,point);
}

OnDragLeave

No action is needed for this handler.

OnEditCopy, OnEditCut

These events fire when the user copies or deletes an object (Your own event handlers for these actions may be different). In both cases, copy the selected object to the clipboard:

void CTextDemoView::OnEditCopy()
{
   CopySelectedObjectToClipboard();
}

void CTextDemoView::OnEditCut()
{
    CopySelectedObjectToClipboard();GetDocument()->DeleteSelectedObject();
}

void CTextDemoView::CopySelectedObjectToClipboard() 
{
    CTextDemoDoc* pDoc = 

    GetDocument(); if(!pDoc->AnObjectIsSelected())
        return;

    DROPEFFECT drop = 

     DROPEFFECT_NONE;
     m_pDDMgr->PrepareDrop(DO_CLIPBOARD,
                 DDFormat(), pDoc->SelObjectPtr(),
                 &drop);
}

OnUpdateEditPaste

This event enables the "paste" edit menu option if the manager indicates that it has valid data for pasting:

void CTextDemoView::OnUpdateEditPaste(CCmdUI*pCmdUI)
{
	pCmdUI->Enable(m_pDDMgr->OkToPaste()
	                  &&!GetDocument()->UserIsDrawingText());
}

OnEditPaste

This event fires when the user pastes data into the view from the clipboard.

void CTextDemoView::OnEditPaste() 
{
    CTextDemoDoc* pDoc = GetDocument();

    CString csF = m_pDDMgr->AvailableDataType(NULL);

    if (csF == MY_TXTBLK)
        DropTextBlock(NULL,DROPEFFECT_NONE,CPoint(0,0));

    else if (csF == MY_SQUARE)
    {
        //Cheating just a little here. . .
        CPoint pt(SQ_HALFSIDE,SQ_HALFSIDE);
        GetDC()->LPtoDP(&pt);
        DropSquare(NULL,DROPEFFECT_NONE,pt);
    }

    else if (csF == PLAIN_TXT)
        DropCFText(NULL,DROPEFFECT_NONE,CPoint(0,0));

    DeselectCurrentObject();
}

Limitations

  • When serializing from the buffer, the clipboard and OLE data objects do not seem to recognize versionable schemas. If you look at the code in the CSquare class, for example, you will see that I've gotten around it by using my own rather crude schema method.
  • The manager does not allow more than 1000 characters of CF_TEXT data to be imported. You can change this if desired, of course.
  • The manager has no methods to let you create CF_TEXT data. It should not be difficult to extend the manager to handle this case, however: You just need to write a "PrepareDrop" method to put the data out in the CF_TEXT format. How the data comes into the class is the real issue to consider. This will depend on what your parent app is doing.

Conclusion

It is my hope that you have found this class of interest, and that you will be able to use it (or change it around to suit your own needs) in any drawing programs you may develop.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here