Results 1 to 7 of 7

Thread: Drag and drop per key press?

  1. #1
    Join Date
    Aug 2020
    Posts
    15
    Thanks
    3
    Qt products
    Qt5
    Platforms
    Windows

    Default Drag and drop per key press?

    I want to achieve a drag and drop from my application to the OS window system (Windows 10), but not by using the mouse. Instead, the content will be selected by a button press and should automatically be drag-dropped to the window under the mouse cursor.

    I wrote a test class, for a regular drag-drop with the mouse, and for the same with a key press:

    Qt Code:
    1. #include <QtCore/QList>
    2. #include <QtCore/QUrl>
    3. #include <QtCore/QMimeData>
    4. #include <QtWidgets/QApplication>
    5. #include <QtWidgets/QMainWindow>
    6. #include <QtGui/QDrag>
    7. #include <QtGui/QKeyEvent>
    8. #include <QtGui/QMouseEvent>
    9. #include <QtGui/QScreen>
    10.  
    11. class DragDropTester: public QMainWindow {
    12. QUrl m_file;
    13.  
    14. void move_file() {
    15. auto* mime_data = new QMimeData;
    16. mime_data->setUrls(QList<QUrl> { } << m_file);
    17.  
    18. auto* screen = QApplication::primaryScreen();
    19. auto mouse_pos = QCursor::pos();
    20. QDropEvent drop_event { mouse_pos, Qt::MoveAction, mime_data, Qt::NoButton, Qt::NoModifier };
    21. QApplication::sendEvent(screen, &drop_event);
    22. }
    23.  
    24. protected:
    25. void keyPressEvent(QKeyEvent* e) override {
    26. switch (e->key()) {
    27. case Qt::Key_M:
    28. move_file();
    29. break;
    30. default:
    31. QMainWindow::keyPressEvent(e);
    32. }
    33. }
    34.  
    35. void mousePressEvent(QMouseEvent*) override {
    36. auto* mime = new QMimeData;
    37. mime->setUrls(QList<QUrl> { } << m_file);
    38.  
    39. auto* drag = new QDrag { this };
    40. drag->setMimeData(mime);
    41.  
    42. drag->exec();
    43. }
    44.  
    45. public:
    46. DragDropTester(const QString& path) : QMainWindow { } {
    47. m_file = QUrl::fromLocalFile(path);
    48. setFixedSize(400, 400);
    49. show();
    50. }
    51.  
    52. };
    To copy to clipboard, switch view to plain text mode 

    The drag & drop with mouse using QDrag works, but the key version does not.
    Unfortunately it looks like QDrag is implemented to always listen for mouse release event to determine when to finalize the drop, instead of providing a ".drop()" function or similar ..

    I looked around in the doc and I think I need to construct and handle my own QDropEvent, which I tried to do in the above code, but it does not work.

    In particular I am unsure about several points:

    • The QDropEvent::QDropEvent(.. doc says "Constructs a drop event ... at the point specified by pos in the destination widget's coordinate system". Is the "destination" widget the "receiver" that I pass to my QDropEvent? I not, what would the correct pos be to provide the global mouse position on the OS desktop?
    • I am not really sure if sendEvent does what I want ?
    • The QCoreApplication::sendEvent(.. doc says "Sends event event directly to receiver receiver, ...". receiver is a QObject* .. I don't know if passing QScreen* to this makes sense? I assume the receiver must implement dropEvent(), but QScreen is not a widget ... This is probably wrong, but I don't know what else to pass as receiver, what would the correct receiver be in my case?


    Or maybe there is completely different way and trying to construct / handle QDropEvent is wrong? Help appreciated!


    Edit: A second idea: I could simulate the mouse events by sending them from the key events, but I am also stuck trying to get this to work:

    Qt Code:
    1. // in keyPressEvent ...
    2. case Qt::Key_M:
    3. if (!e->isAutoRepeat()) {
    4. QMouseEvent mouse_event { QEvent::MouseButtonPress, mapFromGlobal(QCursor::pos()),
    5. Qt::LeftButton, Qt::NoButton, Qt::NoModifier };
    6. QApplication::sendEvent(this, &mouse_event);
    7. }
    8. break;
    To copy to clipboard, switch view to plain text mode 

    This triggers the mouse event, but the QDrag does not show up (i.e. nothing happens and the icon that I get when I instead click the mouse does not show). Maybe because the mouse button needs to be held down, but I don't see any event setting for this
    Last edited by Maryu; 6th September 2020 at 22:01.

  2. #2
    Join Date
    Aug 2020
    Posts
    15
    Thanks
    3
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Drag and drop per key press?

    My attempts did not really work out so far. With QDrag being so easy to use, it seems like an oversight that it provides so little customization.
    It does not allow to set a custom button instead of left click - it does not provide any slots or signals to customize the behavior - it does not allow to manually stop/end the drag ...
    I checked the Qt source but even there everything about how the QDrag is ended is hidden deeper layers and there seemed no way to reasonably reimplement a QDrag for different keys.

    Therefore I decided to go with a platform dependent solution and grab my target directory with Windows functions. This works in my case since I really just want to move files, but I guess it could be expanded to handle different data similar to QMimeData by using the Clipboard. Though that would be more work.

    Anyway, the reason for this post is to provide my solution (even if it has nothing to do with Qt anymore) if anyone happens to need something similar and finds this thread. Mind I have maybe written a total of 300 lines of Windows code in my life. The solution is largely almost 20 year old code taken from this blog, slightly modified.

    Most likely, this can be done better today - I checked some windows documentation and also saw another Stackoverflow post with "better/safer" code but I could not easily get it to compile on my platform so I stuck with this solution for now. Anyway:

    Note: don't put the windows code together with Qt code, including various windows headers gave me many compile time errors.

    Qt Code:
    1. // DragDropTest.cc
    2. #include <string>
    3.  
    4. #include <windows.h>
    5. #include <shlobj.h>
    6. #include <exdisp.h>
    7.  
    8. #include <DragDropTest.hh>
    9.  
    10. HWND toplevel_window_under_mouse() {
    11. const auto mouse_pos = QCursor::pos();
    12. HWND under_mouse_hwnd = WindowFromPoint(POINT { mouse_pos.x(), mouse_pos.y() });
    13. for (auto parent = GetParent(under_mouse_hwnd); parent != nullptr; parent = GetParent(parent))
    14. under_mouse_hwnd = parent;
    15.  
    16. return under_mouse_hwnd;
    17. }
    18.  
    19. std::filesystem::path path_of_window_under_mouse() {
    20. TCHAR explorer_window_directory_path[MAX_PATH];
    21. HWND under_mouse_hwnd = toplevel_window_under_mouse();
    22.  
    23. explorer_window_directory_path[0] = TEXT('\0');
    24. IShellWindows* shell_windows;
    25. if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL, IID_IShellWindows, (void**)&shell_windows))) {
    26. VARIANT v;
    27. V_VT(&v) = VT_I4;
    28. IDispatch* dispatch;
    29. bool fFound = false;
    30. for (V_I4(&v) = 0; !fFound && shell_windows->Item(v, &dispatch) == S_OK; V_I4(&v)++) {
    31. IWebBrowserApp* webbrowser_app;
    32. if (SUCCEEDED(dispatch->QueryInterface(IID_IWebBrowserApp, (void** )&webbrowser_app))) {
    33. HWND webbrowser_hwnd;
    34. if (SUCCEEDED(webbrowser_app->get_HWND((LONG_PTR* )&webbrowser_hwnd))) {
    35. if (webbrowser_hwnd == under_mouse_hwnd) {
    36. fFound = true;
    37. IServiceProvider* service_provider;
    38. if (SUCCEEDED(webbrowser_app->QueryInterface(IID_IServiceProvider, (void** )&service_provider))) {
    39. IShellBrowser* shell_browser;
    40. if (SUCCEEDED(
    41. service_provider->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, (void** )&shell_browser))) {
    42. IShellView* shell_view;
    43. if (SUCCEEDED(shell_browser->QueryActiveShellView(&shell_view))) {
    44. IFolderView* folder_view;
    45. if (SUCCEEDED(shell_view->QueryInterface(IID_IFolderView, (void** )&folder_view))) {
    46. IPersistFolder2* folder;
    47. if (SUCCEEDED(folder_view->GetFolder(IID_IPersistFolder2, (void** )&folder))) {
    48. LPITEMIDLIST folder_item_ids;
    49. if (SUCCEEDED(folder->GetCurFolder(&folder_item_ids))) {
    50. SHGetPathFromIDList(folder_item_ids, explorer_window_directory_path);
    51. CoTaskMemFree(folder_item_ids);
    52. }
    53. folder->Release();
    54. }
    55. folder_view->Release();
    56. }
    57. shell_view->Release();
    58. }
    59. shell_browser->Release();
    60. }
    61. service_provider->Release();
    62. }
    63. }
    64. }
    65. webbrowser_app->Release();
    66. }
    67. dispatch->Release();
    68. }
    69. shell_windows->Release();
    70. }
    71.  
    72. return explorer_window_directory_path;
    73. }
    74.  
    75. void DragDropTester::keyPressEvent(QKeyEvent* e) {
    76. switch (e->key()) {
    77. case Qt::Key_M:
    78. if (!e->isAutoRepeat()) {
    79. const std::filesystem::path to_move { m_file.toStdString() };
    80. const auto destination = path_of_window_under_mouse();
    81. if (std::filesystem::is_regular_file(to_move) && std::filesystem::is_directory(destination))
    82. std::filesystem::rename(to_move, destination / to_move.filename());
    83. }
    84. break;
    85. default:
    86. QMainWindow::keyPressEvent(e);
    87. }
    88. }
    89.  
    90. DragDropTester::DragDropTester(const QString& path) :
    91. QMainWindow { }, m_file { path } {
    92. setFixedSize(400, 400);
    93. show();
    94. }
    To copy to clipboard, switch view to plain text mode 


    Of course the original question still remains. Even if I solved it differently, it would certainly be a welcome alternative .. so if anyone knows how to do this with Qt, please post.
    Last edited by Maryu; 7th September 2020 at 05:39.

  3. #3
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,719
    Thanks
    259
    Thanked 760 Times in 750 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Drag and drop per key press?

    automatically be drag-dropped to the window under the mouse cursor.
    Huh? What if the window under the mouse cursor is the Recycle Bin, the Desktop, or some totally unrelated app which will mangle the files dropped onto it? Once the user selects the files, your proposed method might end up completely trashing them without any safeguards. The widget with keyboard input focus is often not the same window as the mouse location.

    The whole reason for the mouse-based drag and drop system in Qt is to give both users and the app control of where files can be dragged from and dropped to. The method for creating a QDrag in the first place, the various responses to QDragEnterEvent, QDragMoveEvent, and QDragLeaveEvent, and the handling of a mouseReleaseEvent when a drag is in progress are all designed in order to give appropriate feedback to both the UI and the drag/drop system to prevent unexpected things from happening.

    There are plenty of ways to use keypress actions to create something like drag and drop. A few I can think of are using a context menu, using an app level menu, using a toolbutton with a ">>" style icon, and so on to copy / move selected things from one window to another. And none of these require abusing the drag / drop system already in Qt.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  4. #4
    Join Date
    Aug 2020
    Posts
    15
    Thanks
    3
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Drag and drop per key press?

    What if the window under the mouse cursor is the Recycle Bin, the Desktop, or some totally unrelated app which will mangle the files dropped onto it? Once the user selects the files, your proposed method might end up completely trashing them without any safeguards. The widget with keyboard input focus is often not the same window as the mouse location.
    This is my intended functionality. The app is focused on being able to do things fast. I guess with the windows specific solution I have the benefit of being able to check the destination path myself, I didn't check what QDrag does here, but I assume it at least cannot crash, which seems enough to me. Drag-and-drop from some program to some other program, may it be part of Windows explorer, without asking if the user is really sure, seems like a common thing to me.
    I just find clicking, holding, moving, releasing the mouse for every file and/or destination is too much work and takes too long. A simple key press & release is fast - the shortcut for it can be sufficiently cumbersome if needed.

    I appreciate the design of QDrag for almost-always similar use, I'm quite happy with Qt so far as most common things are easy to do. But sometimes common use is not what is wanted, so I just find it unfortunate when something seemingly straightforward to implement like providing a setter for a different key doesn't exist. But maybe I'm wrong and this is hard to implement. Then again there are notes about possible deprecation of internal QDrag related classes, so maybe some extensions are planned already.

    There are plenty of ways to use keypress actions to create something like drag and drop. A few I can think of are using a context menu, using an app level menu, using a toolbutton with a ">>" style icon, and so on to copy / move selected things from one window to another. And none of these require abusing the drag / drop system already in Qt.
    My issue with this is again use-speed, as in the user having to navigate all this. The reason I tried drag & drop was because QDrag was able to determine the window under the mouse even if it was not part of my app, e.g. a Windows explorer window. Sure I could add a menu, add a dialog etc., but how do I get the OS window under the mouse cursor then? Manually selecting a destination is too slow. If the window is already next to my app, I just want to drop it there. The only class I found able to get an OS window under the mouse cursor is QDrag.
    My posted windows-code works fine though.
    Last edited by Maryu; 8th September 2020 at 03:09.

  5. #5
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,719
    Thanks
    259
    Thanked 760 Times in 750 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Drag and drop per key press?

    Drag-and-drop from some program to some other program, may it be part of Windows explorer, without asking if the user is really sure, seems like a common thing to me.
    What your OP proposed is not drag and drop. It is selecting something through key presses and then doing something with that selection outside of the normal drag and drop protocols. In the normal drag and drop interaction, both the drag source and potential drop targets determine what can happen and provide feedback to the user that tells them what is or is not possible. And in many cases, if the drop will be destructive, there will be a confirmation warning first. This is present in Windows Explorer and is an option that can be toggled off.

    I appreciate the design of QDrag for almost-always similar use, I'm quite happy with Qt so far as most common things are easy to do. But sometimes common use is not what is wanted, so I just find it unfortunate when something seemingly straightforward to implement like providing a setter for a different key doesn't exist. But maybe I'm wrong and this is hard to implement. Then again there are notes about possible deprecation of internal QDrag related classes, so maybe some extensions are planned already.
    No, not likely. What you propose is something that goes against accepted conventions for user interface interaction. Drag and drop means exactly what its name implies - you click on something, drag it to where you want it, and drop it if the destination allows.

    My issue with this is again use-speed
    Sorry, but I think most UI designers would tell you that you should focus on usability first, then on performance. If you implement a UI that does things in a way that is completely foreign to what users know and understand from using other, more conventional apps, then most people will not want to use your app because it requires them to remember strange patterns of interaction, or if they don't remember, could cause them to accidentally lose their files or their work because what they did caused something unexpected to happen.

    It really isn't clever or innovative to force users to adapt to your style or think outside the box every time they want to use your app. People won't use your app if they have to do that, or if your app has scared them into not using it because it behaves in strange and unexpected ways. It might be boring to write code that does the usual things, but if users reject your app because it doesn't, then you really haven't succeeded.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  6. #6
    Join Date
    Aug 2020
    Posts
    15
    Thanks
    3
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Drag and drop per key press?

    What your OP proposed is not drag and drop. It is selecting something through key presses and then doing something with that selection outside of the normal drag and drop protocols
    Maybe I should have quoted "drag and drop" but I was not trying to refer to some standard or conventional protocol, but rather to the conceptional, abstract, action of drag & drop. That is, something like
    • Retrieve some object/data calculated from a screen-position
    • Hold on to that object while some condition is true
    • Retrieve some other object/data calculated from some other screen position while that condition is true or it is changed to false
    • Use the first and last selected objects to calculate something once the condition is false


    In my use case: The first selected object is the file displayed by the app. The "condition" is simply always false. The last selected object is the OS window under the mouse cursor. The "calculate something" is moving the file to the directory of the OS window, if it is a Windows explorer window.

    The underlined part is what I found only QDrag be able to do, yet locked away behind the restrictions of QDrag, and seemingly not accessible by other means. Maybe my OP question should have been "How to retrieve path of Windows explorer window under mouse cursor? QDrag can do it, how can I do it without QDrag?"

    No, not likely. What you propose is something that goes against accepted conventions for user interface interaction. Drag and drop means exactly what its name implies - you click on something, drag it to where you want it, and drop it if the destination allows.
    I do not see why the physical process has to have anything to do with the abstract concept. In the points I wrote above, for some reason, the reference positions must be the mouse cursor location. For some reason, the process must be started and ended by pressing and releasing the left mouse button. For some reason, the "condition" is "while left button is down". These are good defaults by convention, but I do not see any reason to intentionally not allow to change these. QDrag implements exactly the drag & drag concept I need, but for some reason does not allow me to change these arbitrary defaults.

    Sorry, but I think most UI designers would tell you that you should focus on usability first, then on performance.
    I am not and do not intend to be a UI designer. I need a specific solution for a specific problem. My apps whole purpose is to be efficient to use: If it can be done by a single button, it should be able to be done by a single button. If I cared about ease-of-use over efficiency-of-use, I would keep using the 3rd party program I have been using for years (minus some missing functionality). But it follows UI conventions, and over the years I am getting increasingly frustrated about unnecessary UI sugar that serves no purpose other than to be easy to use. This is great when starting out, but too cumbersome and slow in the long run. I may make my app available via some means, but this will be a side effect if anything. If someone wants to use it, great, if not, that's fine.


    But as far as I take from the answers, my OP question is not possible with Qt-only. Though I still see it as "open-to-be-implemented" and not "intentionally not implemented".

  7. #7
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,719
    Thanks
    259
    Thanked 760 Times in 750 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Drag and drop per key press?

    I do not see why the physical process has to have anything to do with the abstract concept.
    The physical process is the embodiment of the abstract concept. The abstract concept is not "select something and cause it to be moved somewhere else". There are any number of ways to do that that do not involve the abstract concept of dragging and dropping. The only way you could adequately implement the concept of drag and drop using the keyboard would be to simulate, maybe via arrow keys, picking the selected item(s) up from where they are, providing feedback as you move them to a new location, and feedback that the item is droppable at the new location and has been dropped.

    I need a specific solution for a specific problem.
    So if you are simply dong this for your own use, then have at it. I mistakenly assumed you were developing an app others would use.

    "intentionally not implemented"
    The likely case. The whole UI concept of drag and drop is based on the visual interaction of using the mouse (or your finger, or the claw in an arcade game simulation), picking something up, moving it somewhere else, and dropping it, all the while providing visual feedback about what is happening. QDrag and the logic around it have been designed to implement that concept, and it is a standard part of UI interaction on every graphical UI platform I've come across.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

Similar Threads

  1. Replies: 0
    Last Post: 28th June 2019, 13:36
  2. Replies: 2
    Last Post: 30th January 2014, 07:46
  3. Replies: 0
    Last Post: 7th January 2012, 16:20
  4. Replies: 2
    Last Post: 13th October 2010, 22:51
  5. Replies: 0
    Last Post: 4th May 2010, 11:24

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.