Qt 4.x: how to implement drag-and-drop onto the desktop or into a folder?
Look at QMimeData
and its documentation, it has a virtual function
virtual QVariant retrieveData ( const QString & mimetype, QVariant::Type type ) const
this means to do you drag to the outside you implement this functions accordingly
class DeferredMimeData : public QMimeData
{
DeferredMimeData(QString downloadFilename) : m_filename(downloadFilename)
virtual QVariant retrieveData (const QString & mimetype, QVariant::Type type) const
{
if (mimetype matches expected && type matches expected)
{
perform download with m_filename
}
}
}
The delayed encoding examples shows this principle.
You will probably also have to override hasFormat
and formats
to provide the appropriate types, application/octet-stream
probably being the one that might get you the most play, you will probably have to read up on how windows specifically handles drag and drop using mime types.
I don't know how you will supply the file name under which the file is saved, but you will probably have to get into the windows side of things. Looking at the source of QWindowsMime
might also help. There might me a multiple step process where you will get requests for text/uri-list
data for the filenames and then application/octet-stream
for the data.
Hope this helps
How can I drag to the desktop with Qt?
As far as i know there is no cross-platform solution without using temporary files.
For a Windows-only solution you must set the appropriate MIME data:
if(mimeType == "FileName")
{
QString filename;
// YOUR CODE HERE
mimeData->setData("FileName", filename.toLatin1());
}
if(mimeType == "FileNameW")
{
QString filename;
// YOUR CODE HERE
mimeData->setData("FileNameW", QByteArray((const char*) (filename.utf16()), DDFileName.size() * 2));
}
if(mimeType == "FileContents")
{
QByteArray data;
// YOUR CORE HERE
mimeData->setData("FileContents", data);
}
if(mimeType == "FileGroupDescriptorW")
{
QString filename;
// YOUR CODE HERE
FILEGROUPDESCRIPTOR desc;
desc.cItems = 1;
desc.fgd[0].dwFlags = FD_PROGRESSUI;
wcscpy_s(desc.fgd[0].cFileName, filename.toStdWString().c_str());
// make a deep copy here
mimeData->setData("FileGroupDescriptorW", QByteArray((const char*)&desc,
sizeof(FILEGROUPDESCRIPTOR)));
}
How to implement a drag'n drop from a qt application to a file system folder?
That's one glaring issue in Qt's drag and drop scheme: it doesn't provide the equivalent of OS X's "promised data" drag. And there's really no good way to work around it. The dragging code is not informed of where the drop is, so there's no way to copy the data after the fact. And as you've found out, retrieve data is called right at the moment the drag starts, which doesn't help when it takes a long time to prepare the data.
Right now I'd say hacking it with platform specific code is the only way to do it. Unfortunately, I don't have any experience on that.
How to drag and drop files items inside a folder item using QFileSystemModel and QListView?
You are setting the wrong movement property, since you're using Static
:
The items cannot be moved by the user.
When using the IconMode
, that property is automatically set to Free
, so you can just remove the following line:
lv_file_manager.setMovement(QListView.Static)
The other important implementations are in the model's canDropMimeData()
(which must return True
if the target is a writable directory) and dropMimeData()
(that will actually move the files).
The final step is to override the dragMoveEvent()
to prevent moving icons around the current view.
Note that the following changes have also been made:
flags()
should not returnItemIsDragEnabled
if the target is a file;setAcceptDrops(True)
andsetDragEnabled(True)
are not required as they are automatically set when the movement is notStatic
(which is the case when using theIconMode
as explained above);setDragDropMode()
is also not required;
class ContentFileSystemModel(QFileSystemModel):
# ...
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
defaultFlags = super(ContentFileSystemModel, self).flags(index)
if not index.isValid():
return defaultFlags
fileInfo = self.fileInfo(index)
if fileInfo.isDir():
return Qt.ItemIsDropEnabled | Qt.ItemIsDragEnabled | defaultFlags
elif fileInfo.isFile():
# files should *not* be drop enabled
return Qt.ItemIsDragEnabled | defaultFlags
return defaultFlags
def canDropMimeData(self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex) -> bool:
if row < 0 and column < 0:
target = self.fileInfo(parent)
else:
target = self.fileInfo(self.index(row, column, parent))
return target.isDir() and target.isWritable()
def dropMimeData(self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex) -> bool:
if row < 0 and column < 0:
targetDir = QDir(self.fileInfo(parent).absoluteFilePath())
else:
targetDir = QDir(self.fileInfo(self.index(row, column, parent)).absoluteFilePath())
dataList = []
# first check if the source is writable (so that we can move it)
# and that it doesn't already exist on the target path
for url in data.text().splitlines():
path = QUrl(url).toLocalFile()
fileObject = QFile(path)
if not fileObject.permissions() & QFile.WriteUser:
return False
targetPath = targetDir.absoluteFilePath(QFileInfo(path).fileName())
if targetDir.exists(targetPath):
return False
dataList.append((fileObject, targetPath))
# actually move the objects, you might want to add some feedback
# if movement failed (eg, no space left) and eventually undo the
# whole operation
for fileObject, targetPath in dataList:
if not fileObject.rename(targetPath):
return False
return True
class FileView(QListView):
def dragMoveEvent(self, event):
# accept drag movements only if the target supports drops
if self.model().flags(self.indexAt(event.pos())) & Qt.ItemIsDropEnabled:
super().dragMoveEvent(event)
else:
event.ignore()
def main(argv):
# ...
lv_file_manager = FileView()
Drag anything onto the desktop
You should look at writing Shell Extensions. Here's an article to get you started
http://www.codeproject.com/KB/shell/shellextguide1.aspx
Here's another questions that has some relevant answers
Override Drag and drop for Shell Extension
PyQt5 drag and drop into system file explorer (with delayed encoding)?
Here's what I ended up with for dragging and dropping files from PyQt5 into file explorer, and only having the file write once the mouse is released to finalize the drop.
import time
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
import tempfile
import os
# Use win32api on Windows because the pynput and mouse packages cause lag
# https://github.com/moses-palmer/pynput/issues/390
if os.name == 'nt':
import win32api
def mouse_pressed():
return win32api.GetKeyState(0x01) not in [0, 1]
else:
import mouse
def mouse_pressed():
return mouse.is_pressed()
class DelayedMimeData(QtCore.QMimeData):
def __init__(self):
super().__init__()
self.callbacks = []
def add_callback(self, callback):
self.callbacks.append(callback)
def retrieveData(self, mime_type: str, preferred_type: QtCore.QVariant.Type):
mp = mouse_pressed()
if not mp:
for callback in self.callbacks.copy():
self.callbacks.remove(callback)
callback()
return QtCore.QMimeData.retrieveData(self, mime_type, preferred_type)
class Navigator(QtWidgets.QTreeWidget):
def __init__(self):
super().__init__()
self.setHeaderLabels(["Name"])
QtWidgets.QTreeWidgetItem(self, ['Test1'])
QtWidgets.QTreeWidgetItem(self, ['Test2'])
QtWidgets.QTreeWidgetItem(self, ['Test3'])
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDragDropMode(self.DragDrop)
self.setDefaultDropAction(Qt.MoveAction)
self.setSelectionMode(self.ExtendedSelection)
self.setSelectionBehavior(self.SelectRows)
self.setContextMenuPolicy(Qt.CustomContextMenu)
def startDrag(self, actions):
drag = QtGui.QDrag(self)
names = [item.text(0) for item in self.selectedItems()]
mime = DelayedMimeData()
path_list = []
for name in names:
path = os.path.join(tempfile.gettempdir(), 'DragTest', name + '.txt')
os.makedirs(os.path.dirname(path), exist_ok=True)
print(path)
def write_to_file(path=path, name=name, widget=self):
with open(path, 'w+') as f:
print("Writing large file(s)...")
time.sleep(2) # Sleep to simulate long file write
f.write(f"Contents of {name}")
mime.add_callback(write_to_file)
path_list.append(QtCore.QUrl.fromLocalFile(path))
mime.setUrls(path_list)
mime.setData('application/x-qabstractitemmodeldatalist',
self.mimeData(self.selectedItems()).data('application/x-qabstractitemmodeldatalist'))
drag.setMimeData(mime)
drag.exec_(Qt.MoveAction)
super().startDrag(actions)
app = QtWidgets.QApplication([])
nav = Navigator()
nav.show()
app.exec_()
Related Topics
Why Don't I Need to Specify "Typename" Before a Dependent Type in C++20
How to Determine the Correct Size of a Qtablewidget
How to Do a C++ Style Compile-Time Assertion to Determine MAChine's Endianness
Send Email with Attachment in C++
How Does Q_Foreach (= Foreach) MACro Work and Why Is It That Complex
Difference Between Long Double and Double in C and C++
Intel Avx: 256-Bits Version of Dot Product for Double Precision Floating Point Variables
What Does 'Value Initializing' Something Mean
Dropping Privileges in C++ on Windows
Should I Return an Rvalue Reference (By Std::Move'Ing)
Visual Studio Project Out of Date
Eigen How to Concatenate Matrix Along a Specific Dimension
Std::Make_Tuple Doesn't Make References
C++ Function Call Wrapper with Function as Template Argument
Compile Time Template Instantiation Check
Difference Between Global Non-Throwing ::Operator New and Std::Malloc