Dragging a shell object, part 2: Enabling the Move operation


Let's say that we did want to support Move in our drag/drop program, for whatever reason. Let's do it with some scratch file instead of clock.avi, though. Create a file somewhere that you don't mind losing; let's say it's C:\throwaway.txt. Change the function OnLButtonDown as follows:

void OnLButtonDown(HWND hwnd, BOOL fDoubleClick,
                   int x, int y, UINT keyFlags)
{
  IDataObject *pdto;
  if (SUCCEEDED(GetUIObjectOfFile(hwnd,
                    L"C:\\throwaway.txt",
                    IID_IDataObject, (void**)&pdto))) {
    IDropSource *pds = new CDropSource();
    if (pds) {
      DWORD dwEffect;
      if (DoDragDrop(pdto, pds,
                 DROPEFFECT_COPY | DROPEFFECT_LINK | DROPEFFECT_MOVE,
                 &dwEffect) == DRAGDROP_S_DROP) {
        if (dwEffect & DROPEFFECT_MOVE) {
          DeleteFile(TEXT("C:\\throwaway.txt"));
        }
      }
      pds->Release();
    }
    pdto->Release();
  }
}

Oh wait, there are people out there who think I'm advocating hard-coded paths, so let me change the program to operate on a path passed on the command line. This is code that is purely a distraction from the point of this article, which is why I avoided it originally. Personally I dislike it when somebody hands me a sample program that is 90% unrelated to the technology the program is trying to demonstrate. I have to go digging through the code hunting for the 10% of stuff that matters.

#include <shellapi.h>

LPWSTR *g_argv;
LPCWSTR g_pszTarget;

void OnLButtonDown(HWND hwnd, BOOL fDoubleClick,
                   int x, int y, UINT keyFlags)
{
  IDataObject *pdto;
  if (SUCCEEDED(GetUIObjectOfFile(hwnd,
                    g_pszTarget,
                    IID_IDataObject, (void**)&pdto))) {
  ...
          DeleteFileW(g_pszTarget);
  ...
}

BOOL
InitApp(void)
{
  int argc;
  g_argv = CommandLineToArgvW(GetCommandLineW(), &argc);
  if (!g_argv || argc != 2) return FALSE;
  g_pszTarget = g_argv[1];
  if (PathIsRelative(g_pszTarget)) return FALSE;
  ...
}

Woo-hoo, eight distracting lines of code that have nothing to do with the subject of dragging shell objects around. I hope you're happy.

Where was I? Oh right, explaining the first batch of blue code that by now has scrolled off your screen thanks to the intervening meaningless drivel.

Now that we allow move, we need to check whether the resulting effect was DROPEFFECT_MOVE, which tells us, "The drop target wanted to perform a move operation, but it only got as far as copying the object; please finish the move operation by deleting the original."

Notice that DROPEFFECT_MOVE does not mean, "The drop target performed a move." Rather, it tells you that the drop target wants you to delete the original. If the drop target was able to delete the original (or move it directly), then you will not get DROPEFFECT_MOVE back.

(One case where DROPEFFECT_MOVE doesn't even mean that a Move operation occurred at all is if the user dragged the object to an "Incinerator" icon, the purpose of which is to destroy whatever is dropped onto it. In this case the Incinerator would return DROPEFFECT_MOVE without even making a copy. Result: The object is deleted. A better name for DROPEFFECT_MOVE would have been DROPEFFECT_DELETEORIGINAL.)

If the data object represents a file, then the shell is pretty good at figuring out how to move the file to the destination instead of copying it and asking you to delete the original. You will typically get DROPEFFECT_MOVE back only if the data object represents a non-file, since in that case the shell doesn't know how to delete the original.

But what if you want to know whether the operation was a move, regardless of whether the operation was optimized by the drop target? We'll look at that next time.

(By the way, if you execute a Move of the throwaway file, don't forget to move it back so you can run the scratch program again!)

Comments (24)
  1. Anonymous says:

    Oh well, sorry for the bad formatting… I don’t know how to make this appear correctly..

    Regards,

  2. Anonymous,

    Then you wouldn’t be able to compile the example and run it – the point of the series is that you can try these examples yourself because they’re full examples.

  3. mschaef says:

    "Voila, everybody wins !! "

    Raymond is doing the community a service with his posts (which are done on his own time, IIRC). Rather than looking for a solution in which "everybody wins", maybe we should just be thankful that Raymond bothers with this at all.

  4. Eugene says:

    He didn’t have to do it in such a condescending way just to prove a point, IMHO. He preferred to stick the command line work inline, defer explanations of the actual point of interest until he did it, and then complained how irrelevant part obscurs the relevant – when he could have moved the comand line-related code into a function and called it in place of the hard-coded path instead, just to prove a meaningless point.

    Well, the code doesn’t obscur anything in this case – the programmer does.

  5. Raymond Chen says:

    Moving the command line code into a function and calling it when I needed the path would have created a potential security hole. Though not the case for command lines, a general function would permit the opportunity for the function to return a different path each time, resulting in the wrong file being deleted. It also would have opened the issue of "who frees the memory" which I also wanted to avoid.

  6. Don’t worry kids, mommy has your bottle.

    In the meantime, I’m glad I’m finally getting a simple primer on dragging shell objects.

  7. mschaef says:

    "I’m glad I’m finally getting a simple primer on dragging shell objects. "

    Likewise.

  8. Raymond: I’m sorry to say that you sound rather childish when bitching about having to write proper code. I’m rather disappointed in you.

    Next time, just call a non-existent pretend method GetFilenameOrWhatever() {…} and leave its implementation up to the audience. I know, they won’t be able to copy-paste-run the example. Big deal.

  9. Raymond Chen says:

    Copy/paste/run is important because I often will talk about behavior you encounter while running the program, and if we’re not all running the same program, the discussion will be harder to follow.

  10. ac says:

    > Personally I dislike it when somebody hands me a sample program that is 90% unrelated to the technology the program is trying to demonstrate. I have to go digging through the code hunting for the 10% of stuff that matters. <<

    Hey, I think this (almost) everytime I visit CodeProject. Unfortunately, the people who do the right thing by posting a simple console program get criticised for it. Many people must like sifting through hundreds of lines of irrelevant GUI code to find the dozen or so lines that implement the target functionality.

    Please keep your tactic of limiting the irrelevant code to the bare minimum.

  11. Chris Bruyere says:

    I agree w/ac & mschaef:

    Raymond is doing us ALL a favour and personally I am smart enought to understand that C:WindowsClock.avi is and example for "Any darn file you want".

    I would rather Raymond use that example to create a running example than include a whole bunch of meaningless GUI code (like ac said) that might or migth not compile because of some weird library the orignal programmer is using.

    Thank you Raymond for your great articles. I have more respect for Windows and MS developers after reading your articles and I really appreciate your efforts.

  12. Fat Agnus and Denise says:

    Anybody claiming that GetFilenameOrWhatever (with implementation left to the user) is actually better than hardcoded path is out of frikkin mind. Time to deflate your egos.

  13. camillo vezzoli says:

    Rayomond, you’re a lot of fun!

  14. A says:

    OT: Might want to drop a note to the SDK team that the documentation for CommandLineToArgvW is incorrect. It claims GlobalFree should be used to free the returned pointer, when actually LocalFree should be used, as the function allocates the memory with LocalAlloc.

  15. David Heffernan says:

    The point of an example is to demonstrate and illustrate an idea. What you do with an example is examine it until you <em>understand</em> it.

    Bearing this in mind it is clear that the original post with clock.avi and no clutter is the best way to illustrate the idea.

    I think it’s time the whole sorry saga was forgotten and for the people who complain to leave quietly.

  16. Mat Hall says:

    For the last time, I WASN’T COMPLAINING! Jeez, some people are waaaaay too sensitive, or have a humour deficiency… I just thought it was a quick comedy dig that Raymond is always telling us about the developers who have caused Windows to be filled with bizarre workarounds because they’d made assumptions and lo and behold there was just such an assumption.

    I think I’ll just go away until everyone has calmed down…

  17. Eep says:

    Mat, I think it’s reasonably obvious you were tongue in cheek with your posts the other day, but it sparked off some others who seemed to think it was a serious issue that people might take Raymond’s code and plug it straight into their own apps. I think anyone who does something like that deserves what they get – you use examples for learning, not for solving your problem without bothering to learn. I don’t think Raymond needed to sound quite so pissy about it, but it’s his journal, and I think I would be a bit ticked off with people like that as well ;)

  18. David Candy says:

    I start all programs with hard coded paths. Hard coded everything. As the program starts to work I go back and change hard coded things. I know how to find paths et al (and will probably cut and paste previous written code in to do it) but for the part that does work I wnat to make that work first and without other factors needing to be fixed.

    If I’m my own customer then I’ll leave hard coded paths in and edit the source for each execution of the program.

  19. Anonymous says:

    I don’t think you need to be so pedantic to be correct. Use a variable name instead of a hard-coded path, and a small comment to explain why you use it. For example:

    <pre>

    // ‘the_file_path’ points to a wide string holding the path to your file.

    // don’t forget to build it appropriately. hard-coded paths are pure evil !!

    if (SUCCEEDED(GetUIObjectOfFile(hwnd,

    the_file_path,

    IID_IDataObject, (void**)&pdto))) {

    </pre>

    Voila, everybody wins !!

  20. Bill Kidder says:

    Great article, but what if I want to

    move c:windowsclock.avi?

  21. Tim Dawson says:

    This code is top-notch and very informative, as always. The insights and demonstrations given here will always not only be worth a read, but even improve the lives of the developers reading it.

    However, lately your tone has been condescending and verging on elitist, which is a huge shame.

  22. Tom M says:

    Guys, stop looking the gift horse in the mouth, or it might just wander off.

    Seriously, if I was getting this much grief for something I did for free I would start to consider whether it is something still worth doing.

    The only thing I have to say about hard coded paths and example files etc is that when I write proof of concept apps to operate on COM controls I always end up using the polygon ATL example control. It’s my clock.avi :)

    Will other parts of this series be looking at implementing IDropTarget and IDataObject, because implementing IDataObject for some custom COM object has always seemed quite intimidating to me.

  23. Jörgen Sigvardsson says:

    Some of you guys are pretty silly bitching about Raymond’s hardcoded paths. If you can’t see past hardcoded paths in *sample code* demonstrating anything but *file handling*, then perhaps you should get another job. Maybe "Grumpy old [wo]man" pays well. :P

  24. emmenjay@zip.com.au says:

    Hi Raymond

    I understand your point about avoiding clutter in a demonstration program. However I have some sympathy for the person who complained about the hard-coded path. The fact is that so many programmers make that mistake and ship broken software. Some programming concetps are too ugly and dangerous to live: for example you wouldn’t publish an example with "goto" spagetti in it. I agree that the command-line stuff is overkill, but a quick call to GetWindowsDirectory() might be appropriate.

Comments are closed.

Skip to main content