Qt實(shí)現(xiàn)拖拽功能圖文教程(支持拖放文件、拖放操作)
拖放
拖放是在一個(gè)應(yīng)用程序內(nèi)或者多個(gè)應(yīng)用程序之間傳遞信息的一種直觀的現(xiàn)代操作方式。除了為剪貼板提供支持外,通常它還提供數(shù)據(jù)移動(dòng)和復(fù)制的功能。
拖放操作包括兩個(gè)截然不同的動(dòng)作:拖動(dòng)和放下。Qt窗口部件可以作為拖動(dòng)點(diǎn)(darg site)、放下點(diǎn)(drop site)或者同時(shí)作為拖動(dòng)點(diǎn)和放下點(diǎn)。
Qt程序接受其他程序的拖拽
我們經(jīng)常將文本文件推拽到notepate++等類(lèi)型文本編輯器軟件中。那么如何讓Qt程序也能夠支持這種操作呢?
我們需要在主窗口重新實(shí)現(xiàn)了來(lái)自父類(lèi)的dragEnterEvent()
和 dropEvent()
函數(shù)。
protected: virtual void dragEnterEvent(QDragEnterEvent* event) override; virtual void dropEvent(QDropEvent* event) override;
在構(gòu)造函數(shù)中,創(chuàng)建了一個(gè)QTextEdit
并且把它設(shè)置為中央窗口部件。默認(rèn)情況下,QTextEdit
可以接受來(lái)自其他應(yīng)用程序文本的拖動(dòng),并且如果用戶(hù)在它上面放下一個(gè)文件,它將會(huì)把這個(gè)文件的內(nèi)容填充到QTextEdit部件中。
由于拖放事件是從子窗口部件傳遞給父窗口部件的,所以通過(guò)禁用QTextEdit上的放下操作以及啟用主窗口上的放下操作,就可以在整個(gè)MainWindow窗口中獲得放下事件。
#include <QMimeData> #include <QTextStream> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); textEdit = new QTextEdit(this); setCentralWidget(textEdit); textEdit->setAcceptDrops(false); setAcceptDrops(true); setWindowTitle("Text Editor"); }
當(dāng)用戶(hù)把一個(gè)對(duì)象拖動(dòng)到這個(gè)窗口部件上時(shí),就會(huì)調(diào)用dragEnterEvent()
。如果對(duì)這個(gè)事件調(diào)用acceptProposedAction()
,就表明用戶(hù)可以在這個(gè)窗口部件上拖放對(duì)象。默認(rèn)情況下,窗口部件是不接受拖動(dòng)的。Qt會(huì)自動(dòng)改變光標(biāo)來(lái)向用戶(hù)說(shuō)明這個(gè)窗口部件是不是有效的放下點(diǎn)。
這里,我們希望用戶(hù)拖動(dòng)的只能是文件,而非其他類(lèi)型的東西。為了實(shí)現(xiàn)這一點(diǎn),我們可以檢查拖動(dòng)的MIME
類(lèi)型。
MIME
類(lèi)型中的text/uri-list
用于存儲(chǔ)一系列的統(tǒng)一資源標(biāo)識(shí)符(Universal Re-source Identifier ,URI),它們可以是文件名、統(tǒng)―資源定位器(Uniform Resource Locator , URL,如 HTTP或者FTP路徑),或者其他全局資源標(biāo)識(shí)符。標(biāo)準(zhǔn)的MIME類(lèi)型是由國(guó)際因特網(wǎng)地址分配委員會(huì)(Internet Assigned Numbers Authority , IANA)定義的,它們由類(lèi)型、子類(lèi)型信息以及分隔兩者的斜線(xiàn)組·成。MME類(lèi)通常由剪貼板和拖放系統(tǒng)使用,以識(shí)別不同類(lèi)型的數(shù)據(jù)。可以從下面的網(wǎng)站得到正式的 MIME類(lèi)型列表: http://www.iana.org/assignments/media-types/
void MainWindow::dragEnterEvent(QDragEnterEvent *event) { if(event->mimeData()->hasFormat("text/uri-list")) { event->acceptProposedAction(); } }
當(dāng)用戶(hù)在窗口部件上放下一個(gè)對(duì)象時(shí),就會(huì)調(diào)用dropEvent()
。.我們調(diào)用函數(shù)QMimeData::urls()
來(lái)獲得QUrl
列表。通常,用戶(hù)一次只拖動(dòng)一個(gè)文件,但是通過(guò)拖動(dòng)一個(gè)選擇區(qū)域來(lái)同時(shí)拖動(dòng)多個(gè)文件也是可能的。如果要拖放的URL不止一個(gè),或者要拖放的URL,不是一個(gè)本地文件名,則會(huì)立即返回到原調(diào)用處。
QWidget也提供 dragMoveEvent()和 dragLeaveEvent()函數(shù),但是在絕大多數(shù)應(yīng)用程序中并不需要重新實(shí)現(xiàn)它們。
void MainWindow::dropEvent(QDropEvent *event) { QList<QUrl> urls = event->mimeData()->urls(); if(urls.empty()) return; QString fileName = urls.first().toLocalFile(); if(fileName.isEmpty()) return; if(ReadFile(fileName)) { setWindowTitle(QString("%1-%2").arg(fileName).arg("Drag File")); } } bool MainWindow::ReadFile(const QString &filename) { QFile file(filename); file.open(QIODevice::ReadOnly | QIODevice::Text | QIODevice::Truncate); if(false == file.isOpen()) { return false; } QTextStream stream(&file); textEdit->insertPlainText(stream.readAll()); file.flush(); file.close(); return true; }
看看效果吧
部件/控件之間相互拖放
我們將實(shí)現(xiàn)類(lèi)似于一個(gè)下圖的效果,但不需要向左向右的按鍵,通過(guò)拖拽目標(biāo)實(shí)現(xiàn)移動(dòng)。
思路:創(chuàng)建一個(gè)支持拖拽的QListWidget
子類(lèi)ProjectListWidget
,并且作為該界面的一個(gè)部件。
在ProjectListWidget類(lèi)中需要重寫(xiě)父類(lèi)的五個(gè)事件,和一個(gè)私有方法以及一個(gè)坐標(biāo)記錄
protected: virtual void mousePressEvent(QMouseEvent* event) override; virtual void mouseMoveEvent(QMouseEvent* event) override; virtual void dragEnterEvent(QDragEnterEvent* event) override; virtual void dragMoveEvent(QDragMoveEvent* event) override; virtual void dropEvent(QDropEvent* event) override; private: void performDrag(); private: QPoint startPos;
在構(gòu)造函數(shù)中,我們使列表框上的放下生效。
#include <QApplication> #include <QDrag> #include <QMimeData> ProjectListWidget::ProjectListWidget(QWidget* parent) :QListWidget{parent} { setAcceptDrops(true); }
當(dāng)用戶(hù)按下鼠標(biāo)左鍵,就把鼠標(biāo)位置保存到statPos
私有變量中。然后我們正常調(diào)用QListWidget 中mousePressEvent()
。
void ProjectListWidget::mousePressEvent(QMouseEvent *event) { if(event->button() == Qt::LeftButton) { startPos = event->pos(); } QListWidget::mousePressEvent(event); }
當(dāng)用戶(hù)按住鼠標(biāo)左鍵并移動(dòng)鼠標(biāo)光標(biāo)時(shí),就認(rèn)為這是一個(gè)拖動(dòng)的開(kāi)始。我們計(jì)算當(dāng)前鼠標(biāo)位置和原來(lái)鼠標(biāo)左鍵按下的點(diǎn)之間的距離-—這個(gè)“曼哈頓長(zhǎng)度”(Manhattan Length)其實(shí)是從坐標(biāo)原點(diǎn)到該矢量長(zhǎng)度快速計(jì)算的近似值。如果這個(gè)距離大于或等于QApplication推薦的拖動(dòng)起始距離值(通常是4個(gè)像素),那么就調(diào)用私有函數(shù)performDrag()
以啟動(dòng)拖動(dòng)操作。這可以避免用戶(hù)因?yàn)槭治帐髽?biāo)抖動(dòng)而產(chǎn)生拖動(dòng)。
void ProjectListWidget::mouseMoveEvent(QMouseEvent *event) { if(event->buttons() & Qt::LeftButton) { if(int distance = (event->pos() - startPos).manhattanLength(); distance >= QApplication::startDragDistance()) { performDrag(); } } QListWidget::mouseMoveEvent(event); }
在perfomDrag()
中,創(chuàng)建了一個(gè)類(lèi)型為QDrag的對(duì)象,并且把this作為它的父對(duì)象。這個(gè)QDrag對(duì)象將數(shù)據(jù)存儲(chǔ)在QMimeData
對(duì)象中。在這個(gè)實(shí)例中,我們利用QMineData::setText()
提供了作為text/plain
字符串的數(shù)據(jù)。QMimeData 提供了一些可用于處理最常用拖放類(lèi)型(諸如圖像、URL、顏色,等等)的函數(shù),同時(shí)也可以處理任意由QByteArrays表宗的MiME類(lèi)型。QDrag::setPiximap()
調(diào)用則可以在拖放發(fā)生時(shí)使圖標(biāo)隨光標(biāo)移動(dòng)。
QDrag::exec()
調(diào)用啟動(dòng)并執(zhí)行拖動(dòng)操作;直到用戶(hù)放下或取消此次拖動(dòng)操作才會(huì)停止。它把所有支持的“拖放動(dòng)作"(如 Qi: : CopyAction, Qt : : MoveAction和 Qt: : LinkAction)的組合作為其參數(shù),并且返回被執(zhí)行的拖放動(dòng)作(如果沒(méi)有執(zhí)行任何動(dòng)作,則返回Qt: IgnoreAction)。至于執(zhí)行的是哪`個(gè)動(dòng)作,取決于放下發(fā)生時(shí)源窗口部件是否允許、目標(biāo)是否支持及按下了哪些組合鍵。在 exec()調(diào)用后,Qt擁有拖動(dòng)對(duì)象的所有權(quán)并且可以在不需要它的時(shí)候刪除它。
void ProjectListWidget::performDrag() { if(QListWidgetItem* item = currentItem(); nullptr != item) { QMimeData* mineData = new QMimeData(); mineData->setText(item->text()); QDrag* drag = new QDrag(this); drag->setMimeData(mineData); drag->setPixmap(QPixmap(":/icon.jpg")); if(drag->exec(Qt::MoveAction) == Qt::MoveAction) { delete item; item = nullptr; } } }
ProjectListWidget窗口部件不僅能發(fā)起拖動(dòng),還可以接收同一個(gè)應(yīng)用程序中來(lái)自另外一個(gè)ProjectListWidget部件的拖動(dòng)。如果窗口部件是同個(gè)應(yīng)用程序的一部分,QDragEnterEvent::source()
返回一個(gè)啟動(dòng)這個(gè)拖動(dòng)的窗口部件的指針;否則,返回一個(gè)空指針值。
void ProjectListWidget::dragEnterEvent(QDragEnterEvent *event) { ProjectListWidget* source = reinterpret_cast<ProjectListWidget*>(event->source()); if(nullptr != source && source != this) { event->setDropAction(Qt::MoveAction); event->accept(); } }
dragMoveEvent()中的代碼與dragEnterEvent()中編寫(xiě)的代碼基本相同。因?yàn)樾枰貙?xiě)QListWidget的函數(shù)實(shí)現(xiàn)(實(shí)際上是 QAbstractItemView的函數(shù)實(shí)現(xiàn))。
void ProjectListWidget::dragMoveEvent(QDragMoveEvent *event) { ProjectListWidget* source = reinterpret_cast<ProjectListWidget*>(event->source()); if(nullptr != source && source != this) { event->setDropAction(Qt::MoveAction); event->accept(); } }
在dropEvent()中,我們使用QMimeData::text()
重新找回拖動(dòng)的文本并隨文本創(chuàng)建一個(gè)拖動(dòng)項(xiàng)。還需要將事件作為“移動(dòng)動(dòng)作”來(lái)接受,從而告訴源窗口部件現(xiàn)在可以刪除原來(lái)的拖動(dòng)項(xiàng)了。
void ProjectListWidget::dropEvent(QDropEvent *event) { ProjectListWidget* source = reinterpret_cast<ProjectListWidget*>(event->source()); if(nullptr != source && source != this) { addItem(event->mimeData()->text()); event->setDropAction(Qt::MoveAction); event->accept(); } }
效果如下
總結(jié)
拖放是在應(yīng)用程序之間傳遞數(shù)據(jù)的有力機(jī)制。但是在某些情況下;,有可能在執(zhí)行拖放時(shí)并未使用Qt的拖放工具。如果只是想在一個(gè)應(yīng)用程序的窗口部件中移動(dòng)數(shù)據(jù),通常只要重新實(shí)現(xiàn)mousePressEvent()和 mouseReleaseEvent()函數(shù)就可以了。
到此這篇關(guān)于Qt實(shí)現(xiàn)拖拽功能(支持拖放文件、拖放操作)的文章就介紹到這了,更多相關(guān)Qt實(shí)現(xiàn)拖拽功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言八皇后問(wèn)題解決方法示例【暴力法與回溯法】
這篇文章主要介紹了C語(yǔ)言八皇后問(wèn)題解決方法,簡(jiǎn)單描述了八皇后問(wèn)題并結(jié)合實(shí)例形式分析了C語(yǔ)言基于暴力法與回溯法解決八皇后的具體操作技巧,需要的朋友可以參考下2018-01-01FFmpeg實(shí)現(xiàn)多線(xiàn)程編碼并保存mp4文件
這篇文章主要為大家介紹了FFmpeg如何持續(xù)的從指定內(nèi)存中讀取原始數(shù)據(jù),再將解碼數(shù)據(jù)存入隊(duì)列中,并通過(guò)單獨(dú)的線(xiàn)程進(jìn)行編碼,最后保存為mp4文件,感興趣的可以了解下2023-08-08Visual Studio Code配置C/C++開(kāi)發(fā)環(huán)境的教程圖解
這篇文章主要介紹了Visual Studio Code配置C/C++開(kāi)發(fā)環(huán)境的教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06C++知識(shí)點(diǎn)之inline函數(shù)、回調(diào)函數(shù)和普通函數(shù)
這篇文章主要給大家介紹了關(guān)于C++知識(shí)點(diǎn)之inline函數(shù)、回調(diào)函數(shù)和普通函數(shù)的相關(guān)使用方法,以及回調(diào)函數(shù)和普通函數(shù)的區(qū)別小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-07-07C++中靜態(tài)成員函數(shù)訪(fǎng)問(wèn)非靜態(tài)成員的實(shí)例
這篇文章主要介紹了C++中靜態(tài)成員函數(shù)訪(fǎng)問(wèn)非靜態(tài)成員的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-07-07C++類(lèi)繼承時(shí)的構(gòu)造函數(shù)
這篇文章主要介紹了C++類(lèi)繼承時(shí)的構(gòu)造函數(shù),C++中,子類(lèi)繼承父類(lèi)除去構(gòu)造函數(shù)和析構(gòu)函數(shù)以外的所有成員。因此,子類(lèi)需要編寫(xiě)自己的構(gòu)造函數(shù)和析構(gòu)函數(shù)。更多相關(guān)詳情需要的小伙伴可以參考下面文章介紹2022-03-03C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)順序表中的增刪改(頭插頭刪)教程示例詳解
這篇文章主要為大家介紹了C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)順序表中增刪改關(guān)于頭插頭刪的教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02