• The Old New Thing

    Dragging a shell object, part 5: Making somebody else do the heavy lifting

    • 3 Comments

    Creating that drag image was a bit of work. Fortunately, the listview control is willing to do some of the work for you.

    Throw away the OnLButtonDown function (and the HANDLE_MESSAGE that goes with it). Instead, we'll make the listview do all our presentation for us.

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
      g_hwndChild = CreateWindow(WC_LISTVIEW, NULL,
                                 WS_CHILD | WS_VISIBLE | LVS_ICON |
                                 LVS_SHAREIMAGELISTS, // flag added 13 Dec
                                 0, 0, 0, 0,
                                 hwnd, (HMENU)1, g_hinst, 0);
      if (!g_hwndChild) return FALSE;
    
      SHFILEINFOW sfi;
      HIMAGELIST himl = (HIMAGELIST)
        SHGetFileInfoW(g_pszTarget, 0, &sfi, sizeof(sfi),
                       SHGFI_SYSICONINDEX |
                       SHGFI_DISPLAYNAME | SHGFI_LARGEICON);
      if (!himl) return FALSE;
    
      ListView_SetImageList(g_hwndChild, himl, LVSIL_NORMAL);
    
      LVITEM item;
      item.iSubItem = 0;
      item.mask = LVIF_TEXT | LVIF_IMAGE;
      item.pszText = sfi.szDisplayName;
      item.iImage = sfi.iIcon;
      if (ListView_InsertItem(g_hwndChild, &item) < 0)
        return FALSE;
    
      return TRUE;
    }
    

    We now let the listview control worry about the icon and its text and all the other UI that goes along with it. And we can make the listview worry about the drag image, too.

    void OnBeginDrag(HWND hwnd, NMLISTVIEW *plv)
    {
      IDataObject *pdto;
      if (SUCCEEDED(GetUIObjectOfFile(hwnd, g_pszTarget,
                       IID_IDataObject, (void**)&pdto))) {
        IDragSourceHelper *pdsh;
        if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, NULL,
                        CLSCTX_ALL, IID_IDragSourceHelper, (void**)&pdsh))) {
          pdsh->InitializeFromWindow(g_hwndChild, &plv->ptAction, pdto);
          pdsh->Release();
        }
    
        IDropSource *pds = new CDropSource();
        if (pds) {
          DWORD dwEffect;
          if (DoDragDrop(pdto, pds, DROPEFFECT_MOVE |
                         DROPEFFECT_COPY | DROPEFFECT_LINK,
                         &dwEffect) == DRAGDROP_S_DROP &&
              (dwEffect & DROPEFFECT_MOVE)) {
            DeleteFileW(g_pszTarget);
          }
          pds->Release();
        }
        pdto->Release();
      }
    }
    
    LRESULT OnNotify(HWND hwnd, int idCtrl, NMHDR *pnm)
    {
      if (idCtrl == 1) {
        NMLISTVIEW *plv;
        switch (pnm->code) {
        case LVN_BEGINDRAG:
          plv = (NMLISTVIEW*)pnm;
          OnBeginDrag(hwnd, plv);
          break;
        }
      }
      return 0;
    }
    
        HANDLE_MSG(hwnd, WM_NOTIFY, OnNotify);
    

    Instead of detecting the drag operation, we let the listview do it and just wait for the LVN_BEGINDRAG notification, at which point we get the data object for the file we want to drag and ask the listview to create the drag image by passing its window handle to the IDragSourceHelper::InitializeFromWindow method.

    The listview control does the work of generating the drag image and setting it into the data object. In our specific case, it may have been a toss-up which way is easier, but if you enable multiple-selection capability in the listview, using the IDragSourceHelper::InitializeFromWindow method is a major savings because the listview will do the work of generating the radial gradient alpha channel that you see when dragging multiple files in Explorer.

    You may notice some color fringes around the icons generated by the listview. That's because we're using version 5 of the common controls, which doesn't support alpha channels very well. If you switch to version 6, you'll find that the fringes are gone and the icon looks a lot prettier.

    That's all for now on the subject of initiating a drag/drop operation. Back to one-day topics for a while.

  • The Old New Thing

    For all your chicken sequencing needs

    • 11 Comments

    The International Chicken Sequencing Consortium announced that it has completed the sequencing of chicken DNA.

    I repeat: There is an organization called "The International Chicken Sequencing Consortium".

    (For some reason I want to think these people get together and decide, "Okay, Ginger goes first, then Bunty, then Babs...")

  • The Old New Thing

    Dragging a shell object, part 4: Adding a prettier drag icon

    • 0 Comments

    You may have noticed that the drag feedback is rather sad-looking. Just a box, maybe with a plus sign or an arrow; you don't even know what it is you're dragging.

    Let's fix that. We'll drag the icon of the file around. We'll need to add the drag image to the data object.

    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
    {
      IDataObject *pdto;
      if (SUCCEEDED(GetDataObjectOfFileWithCuteIcon(
                    hwnd, g_pszTarget, &pdto))) {
         IDropSource *pds = new CDropSource();
         ...
    

    This new function GetDataObjectOfFileWithCuteIcon creates the data object and then attaches the cute icon to it.

    HRESULT GetDataObjectOfFileWithCuteIcon(HWND hwnd,
     LPCWSTR pszPath, IDataObject **ppdto)
    {
      HRESULT hr = GetUIObjectOfFile(hwnd, pszPath,
                        IID_IDataObject, (void**)ppdto);
      if (SUCCEEDED(hr)) {
        IDragSourceHelper *pdsh;
        if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_ALL,
                                       IID_IDragSourceHelper, (void**)&pdsh))) {
          SHDRAGIMAGE sdi;
          if (CreateDragImage(pszPath, &sdi)) {
            pdsh->InitializeFromBitmap(&sdi, *ppdto);
            DeleteObject(sdi.hbmpDragImage);
          }
          pdsh->Release();
        }
      }
      return hr;
    }
    

    We use the shell drag/drop helper object to attach the bitmap to the data object. The shell drag/drop helper object requires that the data object be able to accept arbitrary blobs, but fortunately, the standard shell data object does this.

    The nasty part is generating the drag image. This is not the fun part, and you're not going to learn anything from this function. It just has to be written.

    BOOL CreateDragImage(LPCWSTR pszPath, SHDRAGIMAGE *psdi)
    {
      psdi->hbmpDragImage = NULL;
      SHFILEINFOW sfi;
      HIMAGELIST himl = (HIMAGELIST)
        SHGetFileInfoW(pszPath, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX);
      if (himl) {
        int cx, cy;
        ImageList_GetIconSize(himl, &cx, &cy);
        psdi->sizeDragImage.cx = cx;
        psdi->sizeDragImage.cy = cy;
        psdi->ptOffset.x = cx;
        psdi->ptOffset.y = cy;
        psdi->crColorKey = CLR_NONE;
        HDC hdc = CreateCompatibleDC(NULL);
        if (hdc) {
          psdi->hbmpDragImage = CreateBitmap(cx, cy, 1, 32, NULL);
          if (psdi->hbmpDragImage) {
            HBITMAP hbmPrev = SelectBitmap(hdc, psdi->hbmpDragImage);
            ImageList_Draw(himl, sfi.iIcon, hdc, 0, 0, ILD_NORMAL);
            SelectBitmap(hdc, hbmPrev);
          }
          DeleteDC(hdc);
        }
      }
      return psdi->hbmpDragImage != NULL;
    }
    

    To create the drag image, we ask the SHGetFileInfo function to give us the imagelist handle and icon index for the icon that represents the file. The icon size in the imagelist goes into the SHDRAGIMAGE structure as the bitmap dimensions and as the cursor point. (We put the cursor at the bottom right corner of the image.) Since we are creating an alpha-blended bitmap, we don't need a color-key. Finally, we create a memory DC to house an ARGB bitmap into which we draw the icon.

    If you run this program, you should see the icon for a text file being dragged around as you drag your throwaway file around the screen.

    Next time, a way to make somebody else do the heavy lifting for you.

  • The Old New Thing

    Time to dust off your conspiracy theories

    • 15 Comments

    When I started studying Swedish, my web searches happened to alight upon Francis Strand's blog thanks to its wonderful title, "How to learn Swedish in 1000 difficult lessons", and I've been following his musings on life on Stockholm ever since.

    Wednesday morning, an apartment just a block away from his own exploded under curious circumstances. According to the initial report,

    A man is missing after the powerful explosion which blew out an entire apartment on Surbrunnsgatan in Stockholm early Wednesday morning. ...

    The apartment's resident was not found among the evacuatees. ... Police bomb-sniffing dogs searched through the apartment without any indication of explosives. The police's main theory, therefore, is that a gas leak caused the explosion.

    Later that day, that theory fell under suspicion, at least by the conspiracy-minded press:

    "Nothing points to gas explosion."

    An expert at the energy company Fortum doubts that the violent explosion was caused by gas.

    "There are things which are inconsistent with a gas explosion. First of all, the explosion should have caused a fire if it had to do with a gas flow. If it is still a question of a gas accident, it is unique in its scope. ... A huge amount of gas is required for so powerful a blast. The residence must have been filled to the gills. We have no documented instance of this type of explosion ever being caused by accident."

    Even more interesting is that the apartment's resident had been released by the police just last Friday:

    The missing man is suspected of book theft.

    The owner of the Surbrunnsgatan apartment which exploded is the man who is suspected of theft in the millions [of Swedish Kronor] from the Royal Library. The man was released from custody last Friday.

    The 48-year-old had been held in custody for approximately one month. On Thursday, police held a so-called conclusion hearing with the man. Prosecutor Stefan Lind determined later that there was no reason to continue holding the 48-year-old who was released on Friday. "But the investigation into him continues," says Stefan Lind.

    The 48-year-old allegedly stole millions of Kronor worth of rarities [rare books, presumably] from the library where he was employed. The man has to some degree admitted to the crimes.

    Francis's current houseguest thinks the man was killed by the people he sold the books to. I myself don't know what to think. It's just plain curious.

    The Swedish word for the day is själ. It means soul. [Updated 9am: Got the word wrong! I had written skäl instead of själ. That'll teach me to flatter by imitation. Only Francis Strand can do it right.]

  • The Old New Thing

    Dragging a shell object, part 3: Detecting an optimized move

    • 4 Comments

    We were considering how to detect that the drag/drop operation resulted in a conceptual Move even if the DROPEFFECT_MOVE was optimized away.

    If the drop target is the shell, you can query the data object for CFSTR_PERFORMEDDROPEFFECT to see what the performed effect was.

    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick,
                       int x, int y, UINT keyFlags)
    {
      ...
            if (dwEffect & DROPEFFECT_MOVE) {
              DeleteFileW(wszPath);
            }
            CheckPerformedEffect(hwnd, pdto);
      ...
    }
    

    Of course, we need that CheckPerformedEffect function too.

    void CheckPerformedEffect(HWND hwnd, IDataObject *pdto)
    {
      FORMATETC fe = {
         (CLIPFORMAT)RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT),
         NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
      STGMEDIUM stgm;
      if (SUCCEEDED(pdto->GetData(&fe, &stgm))) {
        if ((stgm.tymed & TYMED_HGLOBAL) &&
            GlobalSize(stgm.hGlobal) >= sizeof(DWORD)) {
           DWORD *pdw = (DWORD*)GlobalLock(stgm.hGlobal);
           if (pdw) {
             if (*pdw == DROPEFFECT_MOVE) {
                MessageBox(hwnd, TEXT("Moved"), TEXT("Scratch"), MB_OK);
             }
             GlobalUnlock(stgm.hGlobal);
           }
        }
        ReleaseStgMedium(&stgm);
      }
    }
    

    If the item is dropped on a shell window, the drop target will set data into the data object under the clipboard format name CFSTR_PERFORMEDDROPEFFECT. The data takes the form of a DWORD in an HGLOBAL, and the value is the actual drop effect before any optimizations kicked in.

    Here, we check whether it was a DROPEFFECT_MOVE and display a special message if so.

  • The Old New Thing

    It is not illegal to use James Bond style tactics for winning at the roulette wheel

    • 10 Comments

    In an operation straight out of a James Bond movie, a trio of gamblers used a laser range-finder hidden in a mobile phone to predict the likeliest landing spot of a roulette ball, reducing the odds from 37-1 to 6-1.

    Scotland Yard concluded that they did nothing illegal and were allowed to keep their winnings.

    I wonder how long it'll be before this device shows up on Engadget.

    (Once I discovered that I was scooped by Slashdot, I figured there was no hurry and held the story for a quiet day.)

  • The Old New Thing

    Researchers find connection between lack of sleep and weight gain

    • 31 Comments
    Shortage of sleep is linked to obesity, according to research published yesterday.

    Lack of sleep boosts levels of a hormone that triggers appetite and lowers levels of a hormone that tells your body it is full according to the team. The scientists will now study whether obese people should sleep more to lose weight.
  • The Old New Thing

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

    • 24 Comments

    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!)

  • The Old New Thing

    Dragging a shell object, part 1: Getting the IDataObject

    • 20 Comments

    The shell gives you the IDataObject; all you have to do is drag it around. (This is the first of a five-part series.)

    Start with the scratch program, and add the function GetUIObjectOfFile from an earlier article. Also, change the calls to CoInitialize and CoUninitialize to OleInitialize and OleUninitialize, respectively, since we're now going to be using full-on OLE and not just COM.

    In order to initiate a drag/drop operation, we need a drop source:

    class CDropSource : public IDropSource
    {
    public:
      // *** IUnknown ***
      STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
      STDMETHODIMP_(ULONG) AddRef();
      STDMETHODIMP_(ULONG) Release();
    
      // *** IDropSource ***
      STDMETHODIMP QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState);
      STDMETHODIMP GiveFeedback(DWORD dwEffect);
    
      CDropSource() : m_cRef(1) { }
    private:
      ULONG m_cRef;
    };
    
    HRESULT CDropSource::QueryInterface(REFIID riid, void **ppv)
    {
      IUnknown *punk = NULL;
      if (riid == IID_IUnknown) {
        punk = static_cast<IUnknown*>(this);
      } else if (riid == IID_IDropSource) {
        punk = static_cast<IDropSource*>(this);
      }
    
      *ppv = punk;
      if (punk) {
        punk->AddRef();
        return S_OK;
      } else {
        return E_NOINTERFACE;
      }
    }
    
    ULONG CDropSource::AddRef()
    {
      return ++m_cRef;
    }
    
    ULONG CDropSource::Release()
    {
      ULONG cRef = --m_cRef;
      if (cRef == 0) delete this;
      return cRef;
    }
    
    HRESULT CDropSource::QueryContinueDrag(
              BOOL fEscapePressed, DWORD grfKeyState)
    {
      if (fEscapePressed) return DRAGDROP_S_CANCEL;
    
      // [Update: missing paren repaired, 7 Dec]
      if (!(grfKeyState & (MK_LBUTTON | MK_RBUTTON)))
        return DRAGDROP_S_DROP;
    
      return S_OK;
    }
    
    HRESULT CDropSource::GiveFeedback(DWORD dwEffect)
    {
      return DRAGDROP_S_USEDEFAULTCURSORS;
    }
    

    As you can see, this drop source is extraordinarily boring. Even the interesting methods are uninteresting.

    The IDropSource::QueryContinueDrag method is pretty much boilerplate. If the Escape key was pressed, then cancel the drag/drop operation. If the mouse buttons are released, then complete the operation. Otherwise, continue the operation.

    The IDropSource::GiveFeedback method is even less interesting. It merely returns DRAGDROP_S_USEDEFAULTCURSORS to indicate that it wants default drag feedback.

    Believe it or not, we now have everything we need to drag a file.

    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick,
                       int x, int y, UINT keyFlags)
    {
      IDataObject *pdto;
      // In a real program of course
      // you wouldn't use a hard-coded path.
      // [comment added 11am because apparently some
      // people thought this wasn't self-evident.]
      if (SUCCEEDED(GetUIObjectOfFile(hwnd,
                        L"C:\\Windows\\clock.avi",
    		    IID_IDataObject, (void**)&pdto))) {
        IDropSource *pds = new CDropSource();
        if (pds) {
          DWORD dwEffect;
          DoDragDrop(pdto, pds, DROPEFFECT_COPY | DROPEFFECT_LINK,
                     &dwEffect);
          pds->Release();
        }
        pdto->Release();
      }
    }
    
        HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown);
    

    To drag an object, you need two things, a data object and a drop source. We created our drop source above, and the data object comes from the shell. All that's left to do is start the drag/drop operation by calling the DoDragDrop function.

    Notice that we specify that the permitted operations are DROPEFFECT_COPY and DROPEFFECT_LINK. We specifically disallow DROPEFFECT_MOVE because this program doesn't present a folder-like window; the user has no expectation that the drag/drop will result in a Move operation.

    Next time, adding Move support, just to see how it works.

  • The Old New Thing

    Why are documents printed out of order when you multiselect and choose "Print"?

    • 26 Comments

    If you select say five files and then right-click them and choose "Print", they tend to print in a random order. Why is that?

    The shell invokes the Print verb on each file in turn, and depending on how the program responsible for printing the document is registered, one of several things can happen.

    • Most commonly, the program that prints the document registered a simple command line under the shell\print\command registry key. In this case, the program is launched five times, each with a different file. All these print commands are now racing to the printer and it's a question of which copy of the program submits its print job first that determines the order in which they come out of the printer. (You're probably going to see the shortest and simplest documents come out first since they take less time to render.)
    • Occasionally, the program that prints the document registered a DDE verb under the shell\print\ddeexec registry key. In this case, one copy of the program is launched and it is given each filename one at a time. What it does with those filenames is now up to the program. If the program supports background printing, it will probably shunt the printing of the document onto a background thread, and now you're roughly in the same fix as the previous scenario: Five background threads each racing to see who can submit their print job first.
    • Extremely rarely, the program that prints the document registered a drop handler under the shell\print\DropTarget key. In this case, the drop target is instantiated and is given the list of files. It is then up to the drop target to decide what to do with the documents.

    These three ways of registering print actions are described in the MSDN documentation on verbs and file associations.

    [Update: 7:30am, fixed title.]

Page 372 of 431 (4,309 items) «370371372373374»