May 12th, 2021

Using Explorer’s fancy drag/drop effects in your own programs

The default drag/drop effect is very plain. Let’s try it out. Take the scratch program and make these changes. The COM smart pointer library for today is (rolls dice) C++/WinRT. I will also be using wil for error handling and RAII types.

#include <winrt/base.h>
#include <wil/result_macros.h>
#include <wil/resource.h>

struct SimpleDropTarget :
    winrt::implements<SimpleDropTarget, IDropTarget>
{
    SimpleDropTarget(HWND hwnd) : hwndOwner(hwnd)
    {
    }

    STDMETHODIMP DragEnter(IDataObject* pDataObject,
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        dto.copy_from(pDataObject);
        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        return S_OK;
    }
    STDMETHODIMP DragOver(
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        return S_OK;
    }

    STDMETHODIMP Drop(IDataObject* pDataObject,
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        dto.copy_from(pDataObject);
        auto cleanup = wil::scope_exit([&] { DragLeave(); });

        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        if (*pdwEffect != DROPEFFECT_NONE) {
            // Do something cool.
        }
        return S_OK;
    }

    STDMETHODIMP DragLeave()
    {
        dto = nullptr;
        return S_OK;
    }

private:
    HWND hwndOwner;
    winrt::com_ptr<IDataObject> dto;

    HRESULT CalculateFeedback(
        DWORD grfKeyState,
        DWORD* pdwEffect)
    {
        if (grfKeyState & MK_CONTROL) {
            *pdwEffect = DROPEFFECT_COPY;
        } else {
            *pdwEffect = DROPEFFECT_MOVE;
        }
        return S_OK;
    }
};

This drop target does nothing particularly fancy. The only special thing is that holding the Ctrl key provides copy feedback rather than move feedback.

The Drop method processes the new data object and either performs the drop or defers to the Drag­Leave feedback if it turns out nothing is being dropped after all. Regardless of how the drop plays out, we call our own Drag­Leave() to clean up. (We use the wil::scope_exit function to create a one-off RAII type that performs some action at destruction.)

Let’s hook up this drop target to our window.

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
    RegisterDragDrop(hwnd,
        winrt::make<SimpleDropTarget>(hwnd).get());

    return TRUE;
}

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
    LPSTR lpCmdLine, int nShowCmd)
{
    ...
    if (SUCCEEDED(OleInitialize(NULL))) {
        ...
    }
    ...
}

In the main program, we use OleInitialize instead of CoInitialize because we are using OLE drag/drop, which is an OLE feature.

And when the window is created, we register our drop target.

When you run this program, the drag/drop feedback is just an empty gray box, possibly with a + sign to indicate that the resulting operation is a copy.

Let’s add fancy Explorer-style drag/drop feedback.

struct SimpleDropTarget :
    winrt::implements<SimpleDropTarget, IDropTarget>
{
    SimpleDropTarget(HWND hwnd) : hwndOwner(hwnd),
        helper(winrt::create_instance<IDropTargetHelper>(
            CLSID_DragDropHelper))
    {
    }

    STDMETHODIMP DragEnter(IDataObject* pDataObject,
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        dto.copy_from(pDataObject);
        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        POINT point{ pt.x, pt.y };
        RETURN_IF_FAILED(helper->DragEnter(hwndOwner, dto.get(),
            &point, *pdwEffect));
        return S_OK;
    }

    STDMETHODIMP DragOver(
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        POINT point{ pt.x, pt.y };
        RETURN_IF_FAILED(helper->DragOver(&point, *pdwEffect));
        return S_OK;
    }

    STDMETHODIMP Drop(IDataObject* pDataObject,
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        dto.copy_from(pDataObject);
        auto cleanup = wil::scope_exit([&] { DragLeave(); });

        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        POINT point{ pt.x, pt.y };
        RETURN_IF_FAILED(helper->Drop(dto.get(), &point, *pdwEffect));
        if (*pdwEffect != DROPEFFECT_NONE) {
            // Do something cool.
        }
        return S_OK;
    }

    STDMETHODIMP DragLeave()
    {
        dto = nullptr;
        RETURN_IF_FAILED(helper->DragLeave());
        return S_OK;
    }

private:
    HWND hwndOwner;
    winrt::com_ptr<IDataObject> dto;
    winrt::com_ptr<IDropTargetHelper> helper;

    HRESULT CalculateFeedback(
        DWORD grfKeyState,
        DWORD* pdwEffect)
    {
        if (grfKeyState & MK_CONTROL) {
            *pdwEffect = DROPEFFECT_COPY;
        } else {
            *pdwEffect = DROPEFFECT_MOVE;
        }
        return S_OK;
    }
};

We create a shell drop target helper and forward all of our drop target methods into it. There is a bit of frustration here due to the impedance mismatch between the way IDropTarget and IDropTargetHelper represent the point.

With these changes, the drop feedback is much more Explorer-like: The drag image matches the Explorer drag image, showing a thumbnail of the item being dragged, or a collection of items with a numeric badge showing how many items are being dragged. There’s also an information box below the image that says what the resulting operation will be.

That’s a good start. Next time, we’ll add another feature.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

4 comments

Discussion is closed. Login to edit/delete existing comments.

  • Not Sure

    But there are easier helpers DAD_DragEnterEx2, DAD_DragMove, DAD_DragLeave

  • César Angel Martínez Muro · Edited

    Why does Explorer use the term KB instead of KiB? (June 11th, 2009)
    We don´t use it because anybody use it, what a mediocre excuse.
    The purpose is not feeling superior, is to use the correct term, to teach or educate people.

    I will give you an example of why its a bad ide to ignore wrong usage of information:
    In one job training I had some years ago, the trainer took us a math exam,...

    Read more
    • M. W.

      It's for backwards-compatibility, and because of tradition and convention. Take your pick. Binary units came around much later to differentiate things to stop non-tech people from being confused as to why their storage has less space than advertised or why their network connections were slower than promised. Tech people have been using and understanding the context for decades without problem (incidentally, even a "byte" wasn't eight bits for a while, it has had various values,...

      Read more
  • Paulo Pinto

    I would be curious if the end result can be made to match the productivity that can be achieved using C++/CX or C++ Builder instead, regarding the amount of required boilerplate code to deal with COM.