Click here to Skip to main content
Click here to Skip to main content
Go to top

Drag and Drop on a Tray Icon

, 6 Aug 2007
Rate this:
Please Sign up or sign in to vote.
How to detect that an item was dropped on a specific tray icon
Screenshot - desktop.jpg

Introduction

If you ever wondered how to catch drag and drop on a tray icon, this article might help you to accomplish this. With the help of other CodeProject contributions, I was able to figure out most of the functionality. One thing I have not been able to get working is to successfully get the HDROP structure that usually ships with the WM_DROPFILES message. If anyone can help me here, it would be very great. Please post it here, thanks!

Background

So, how does one find the right window containing the tray icons? You can use Microsoft Spy++ that ships with Visual Studio. Look for a window called "Shell_TrayWnd" containing another window called "TrayNotifyWnd" which has another child window called "SysPager". The "SysPager" contains the actual window holding all icons called "ToolbarWindow32".

Screenshot - MSSpy__.jpg

HWND FindTrayToolbarWindow()
{
    HWND hWnd = FindWindow("Shell_TrayWnd", NULL);
    if(hWnd)
    {
        hWnd = FindWindowEx(hWnd,NULL,"TrayNotifyWnd", NULL);
        if(hWnd)
        {
            hWnd = FindWindowEx(hWnd,NULL,"SysPager", NULL);
            if(hWnd)
            {                
                hWnd = FindWindowEx(hWnd, NULL,"ToolbarWindow32", NULL);
            }
        }
    }
    return hWnd;
}

After the window is found, you need to figure out the correct icon. This is done by using the POINT send with the WM_DROPFILES and looping through all icons, which are actually buttons. This way, one can figure out on which icon you dropped the files.

Using the Code

The code is split into 2 files. One EXE file that represents the tray icon app and a message hook DLL. The DLL installs a message hook to catch the WM_DROPFILES message and will check if we dropped anything on our tray icon. Then I send the message to the window that belongs to our tray icon app.

To run the code, I recommend using dbgview (in the zip file), before trying the app. I used it to catch the debug messages during the drag and drop operation.

Still as mentioned before, I am missing the part where I should get the a pointer to a HDROP structure from the WM_DROPFILES messages. Somehow the message doesn't contain that even though it should. If one could figure out what's wrong there, I would be quite happy.

References

History

  • 2007-08-07 Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sven So.
Software Developer (Senior) biggest furniture dealer in the world
Germany Germany
No Biography provided

Comments and Discussions

 
Answerworking method of dragdrop onto a systray notification icon (without hooking) Pinmemberarnoudmulder21-Mar-11 13:53 
I made a way to support dropping on systray notification icons without the use of an external DLL. The trick is to create a transparent child window to the taskbar and place it above the trayicon.
 
It must be HWND_TOP while dragging and HWND_BOTTOM during mouse actions because otherwise for instance tooltips don't work. I use a timer to fallback to HWND_TOP after a mouse action.
 
Not all the code is placed here, but the missing parts are evident.
 

 

 
/*********************************************************/
/* overlay window to support drag-drop onto systray icon */
/*********************************************************/
 
BOOL _TrayTools_CreateTrayDragDropWnd(TSysTray *in_TrayInfo, TSysTrayEntry *in_Entry)
{
    /* locals */
    VOID    *lv_CP[2];
    RECT     lv_Rect;
    HWND     lv_hAnchor;
    HWND     lv_hNotiTB;
    WNDCLASS lv_WndClass;
 

  // already created?
  if (in_Entry->DragDrop.hDropWnd && IsWindow(in_Entry->DragDrop.hDropWnd))
    return TRUE;
 
  // get the notification toolbar
  // (Shell_TrayWnd/TrayNotifyWnd/SysPager/TOOLBARCLASSNAME)
  lv_hNotiTB = Shell_GetSysTaskbarNotifyWindowTB();
 
  // get the notification area as parent/anchor of the overlay window
  // (Shell_TrayWnd/TrayNotifyWnd)
  lv_hAnchor = Shell_GetSysTaskbarNotifyWindow();
 
  // no tray/explorer running?
  if (!lv_hNotiTB || !lv_hAnchor)
    return NULL;
 
  // get initial rectangle of icon
  //
  // Note: if the icon is hidden and placed into a popup tray (win7) then
  // this routine returns the rectangle of the arrow button, for ALL the dropable
  // systray icons and dropping the file will send the file to any of the icons
  if (!TrayTools_GetSysTrayIconRect(in_TrayInfo, in_Entry->Def.TrayID, &lv_Rect))
    return FALSE;
 
  // class not yet registered?
  if (!GetClassInfo(gv_AppInfo.hInstance, CLASSNAME_TRAYDRAGDROP, &lv_WndClass))
  {
    // set up and register window class
    memset(&lv_WndClass, 0, sizeof(WNDCLASS));
    lv_WndClass.style         = CS_DBLCLKS;
    lv_WndClass.lpfnWndProc   = _TrayTools_TrayDragDropWndProc;
    lv_WndClass.hInstance     = gv_AppInfo.hInstance;
    lv_WndClass.lpszClassName = CLASSNAME_TRAYDRAGDROP;
    lv_WndClass.cbWndExtra    = TRAYDRAGDROP_GWLP_LAST;
    lv_WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    //lv_WndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    RegisterClass(&lv_WndClass);
  }
 
  // store info of tray,
  // - our transparent window is child of taskbar instead of the notification
  //   window otherwise large delays occur as moves
  //   Update 1: the delays are solved via posting a message to self instead
  //   of creating the overlay right after the Shell_NotifyIcon call
  //   Btw: the old method using taskbar still hang on Win7, it worked on XP
  //   Update 2: use TrayNotifyWnd as parent instead of the toolbar because
  //   we must be able to places us on top of bottom of the toolbar
  // - the received mouse messages must be forwarded to the toolbar
  in_Entry->DragDrop.hAnchor   = lv_hAnchor;
  in_Entry->DragDrop.hNotifyTB = lv_hNotiTB;
 
  // prepare passing the parameters to the wndproc
  lv_CP[0] = in_TrayInfo;
  lv_CP[1] = in_Entry;
 
  // from screen coordinates to clientcoordinates of taskbar
  ScreenToClientRect(in_Entry->DragDrop.hAnchor, &lv_Rect);
 
  // create the window as child of the tray window with tray as parent
  // (windows does not set parent if child style is not specified and will not
  //  destroy our window if not set as parent)
  in_Entry->DragDrop.hDropWnd = CreateWindowEx(
    WS_EX_TRANSPARENT | WS_EX_NOACTIVATE,
    CLASSNAME_TRAYDRAGDROP, NULL,
    WS_VISIBLE | WS_CHILD,  // WS_DISABLED |
    lv_Rect.left, lv_Rect.top,
    lv_Rect.right  - lv_Rect.left,
    lv_Rect.bottom - lv_Rect.top,
    in_Entry->DragDrop.hAnchor, NULL, gv_AppInfo.hInstance, lv_CP);
 
  // check
  if (!in_Entry->DragDrop.hDropWnd)
    return FALSE;
 
  // place transparent window on top of the tasbbar
  SetWindowPos(
    in_Entry->DragDrop.hDropWnd, HWND_TOP, 0,0,0,0,
    SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
 
  // done
  return TRUE;
}
 

 
BOOL _TrayTools_DoneTrayDragDropWnd(TSysTrayEntry *in_Entry)
{
  // if created
  if (in_Entry->DragDrop.hDropWnd)
  {
    // destroy our window
    DestroyWindow(in_Entry->DragDrop.hDropWnd);
 
    // clear
    in_Entry->DragDrop.hDropWnd = NULL;
  }
 
  // done
  return TRUE;
}
 

 

LRESULT CALLBACK _TrayTools_TrayDragDropWndProc
(
  HWND   in_hWnd,
  UINT   in_uMsg,
  WPARAM in_wParam,
  LPARAM in_lParam
)
{
    /* locals */
    UINT_PTR      *lv_CP;
    RECT           lv_Rect1;
    RECT           lv_Rect2;
    TSysTray      *lv_TrayInfo;
    TSysTrayEntry *lv_Entry;
 

  // create message?
  if (in_uMsg == WM_CREATE)
  {
    // get the param and check it
    if ((lv_CP = (UINT_PTR*)((CREATESTRUCT *)in_lParam)->lpCreateParams) == NULL)
      return -1;
 
    // store param
    SetWindowLongAsPtr(in_hWnd, TRAYDRAGDROP_GWLP_TRAYINFO,  lv_CP[0]);
    SetWindowLongAsPtr(in_hWnd, TRAYDRAGDROP_GWLP_TRAYENTRY, lv_CP[1]);
 
    // get the param
    lv_TrayInfo = (TSysTray*)     lv_CP[0];
    lv_Entry    = (TSysTrayEntry*)lv_CP[1];
  }
  else
  {
    // get the param
    lv_TrayInfo = (TSysTray*)     GetWindowLongAsPtr(in_hWnd, TRAYDRAGDROP_GWLP_TRAYINFO);
    lv_Entry    = (TSysTrayEntry*)GetWindowLongAsPtr(in_hWnd, TRAYDRAGDROP_GWLP_TRAYENTRY);
  }
 

 
  // process message not depedent on the entry information
  switch (in_uMsg)
  {
    // create message
    case WM_CREATE:
    {
      // register dragdrop
      ShellDD_RegisterWindow(in_hWnd);
 
      // done
      break;
    }
 

    // destroy: happens when explorer/tray gets destroyed (for instances if explorer crashes)
    case WM_NCDESTROY:
    {
      // unregister dragdrop
      ShellDD_UnregisterWindow(in_hWnd);
 
      // ensure timer is stopped
      if (lv_Entry->DragDrop.TimerID)
        KillTimer(in_hWnd, lv_Entry->DragDrop.TimerID);
 
      // clear
      lv_Entry->DragDrop.TimerID  = 0;
      lv_Entry->DragDrop.hDropWnd = NULL;
 
      // done
      break;
    }
  }
 

 
  // check the presence of the entry information
  if (lv_TrayInfo && lv_Entry)
  {
    // process message depedent on the entry information
    switch (in_uMsg)
    {
      // if mouse message
      case WM_MOUSEMOVE:
      case WM_LBUTTONUP:
      case WM_LBUTTONDOWN:
      case WM_LBUTTONDBLCLK:
      case WM_RBUTTONUP:
      case WM_RBUTTONDOWN:
      case WM_RBUTTONDBLCLK:
      case WM_NCHITTEST:
      {
        // place overlay into the background so from now on mousemessages
        // are immediately delivered to the toolbar, this is done to get the
        // tooltips working and to prevent the contextmenu of the taskbar showing up
        SetWindowPos(in_hWnd, HWND_BOTTOM, 0,0,0,0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
 
        // init time of last mouse message
        lv_Entry->DragDrop.LastMouseTime = GetTickCount();
 
        // start timer to restore overlay back to the top after a delay
        if (!lv_Entry->DragDrop.TimerID)
          lv_Entry->DragDrop.TimerID = SetTimer(in_hWnd, 987, 3000, 0);
 
        // convert coordinates to client coordinates of the toolbar
        ClientToClientPoint16(in_hWnd, lv_Entry->DragDrop.hNotifyTB, (POINT16*)&in_lParam);
 
        // forward the message
        return SendMessage(lv_Entry->DragDrop.hNotifyTB, in_uMsg, in_wParam, in_lParam);
      }
 

      // if timer
      case WM_TIMER:
      {
        // timer to bring overlay back to the top?
        if (lv_Entry->DragDrop.TimerID == in_wParam)
        {
          // check the last activity time
          if (GetTickCount() - lv_Entry->DragDrop.LastMouseTime > 1000)
          {
            // bring back to the top
            SetWindowPos(in_hWnd, HWND_TOP, 0,0,0,0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
 
            // stop the timer
            KillTimer(in_hWnd, lv_Entry->DragDrop.TimerID);
 
            // timer is stopped
            lv_Entry->DragDrop.TimerID = 0;
          }
        }
 
        // done
        break;
      }
 

      // forward other messages without conversion
      case WM_SETCURSOR:
        return SendMessage(lv_Entry->DragDrop.hNotifyTB, in_uMsg, in_wParam, in_lParam);
 

      // detect movement of icon at paint message
      case WM_PAINT:
      {
        // get current rectangle of icon
        if (TrayTools_GetSysTrayIconRect(lv_TrayInfo, lv_Entry->Def.TrayID, &lv_Rect1))
        {
          // get current overlay rectangle
          GetWindowRect(in_hWnd, &lv_Rect2);
 
          // need to move?
          if (lv_Rect1.left   != lv_Rect2.left  ||
              lv_Rect1.top    != lv_Rect2.top   ||
              lv_Rect1.right  != lv_Rect2.right ||
              lv_Rect1.bottom != lv_Rect2.bottom)
          {
            // from screen coordinates to clientcoordinates of anchor
            ScreenToClientRect(lv_Entry->DragDrop.hAnchor, &lv_Rect1);
 
            // move overlay at same location as the toolbar button
            SetWindowPos(
              lv_Entry->DragDrop.hDropWnd, HWND_TOP,
              lv_Rect1.left,
              lv_Rect1.top,
              lv_Rect1.right  - lv_Rect1.left,
              lv_Rect1.bottom - lv_Rect1.top,
              SWP_NOACTIVATE);
          }
        }
 
        // further default processing
        break;
      }
 

      // and then there was a drop
      case WM_DROPFILES:
      {
        // call the callback
        if (lv_Entry->Def.DropFileProc)
          lv_Entry->Def.DropFileProc((HDROP)in_wParam);
 
        // done
        break;
      }
    }
  }
 
  // default handling
  return DefWindowProc(in_hWnd, in_uMsg, in_wParam, in_lParam);
}

Questionquery dropped filenames? PinmemberIBM CH DevGroup10-Oct-08 23:31 
AnswerRe: query dropped filenames? PinmemberSven So.12-Oct-08 19:44 
GeneralRe: query dropped filenames? PinmemberBen Voigt26-Oct-10 16:25 
GeneralC# PinmemberMember 301674124-May-08 12:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 7 Aug 2007
Article Copyright 2007 by Sven So.
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid