QT拖拽功能簡介

Drag和Drop是兩個完全不同的動作。Qt中的控件可以作爲拖動(drag)的地點,也可以作爲鬆開(drop)的地點,或者同時作爲拖動和鬆開的地點。
第一個例子用來說明一個Qt應用程序接受另一個程序觸發的拖動事件。該Qt應用程序是一個QTextEdit爲中央控件的主窗口。當用戶從桌面或者一個文件瀏覽器中拖動一個文本文件到Qt程序時鬆開,程序把文件顯示在QTextEdit控件中。
下面是主窗口的定義
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow();
protected:
    void dragEnterEvent(QDragEnterEvent *event);
    void dropEvent(QDropEvent *event);
private:
    bool readFile(const QString &fileName);
    QTextEdit *textEdit;
};
 
在MainWindow類中,重新實現了QWidget的函數dragEnterEvent()和dropEvent()。由於這個例子主要用來顯示託拽,主窗口的很多其他功能都省略了。
 
MainWindow::MainWindow()
{
    textEdit = new QTextEdit;
    setCentralWidget(textEdit);
    textEdit->setAcceptDrops(false);
    setAcceptDrops(true);
    setWindowTitle(tr("Text Editor"));
}
 
在構造函數中,我們創建了一個QTextEdit控件,並設置爲中央控件。缺省情況下,QTextEdit接受來自其他應用程序拖拽來的文本,把文件名顯示出來。由於drop事件是由子控件向父控件傳播的,通過禁止QTextEdit控件的drop事件,允許主窗口得到drop事件,我們就得到了MainWindow中的整個窗口的drop事件。
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasFormat("text/uri-list"))
        event->acceptProposedAction();
}
 
任何時候用戶拖動一個對象到一個控件上,函數dragEnterEvent()都會被調用。如果在這個事件處理函數中調用函數acceptProposeAction(),說明我們允許用戶把這個對象拖拽到這個控件上。默認情況,控件不接收drag事件。Qt會自動改變光標狀態指示用戶當前的控件是否是一個合法的drop地點。
在這裏我們只允許用戶drag一個文本文件,因此,我們檢查這個這個drag的MIME類型。MIME類型text/uri-list用來保存URL的一個地址列表,可以是文件名,URL(HTTP或者FTP路徑),也可以是其他的全局資源標識。標準的MIME類型由IANA(Internet Assigned Numbers Authority)定義,由一個類型名/子類型名組成。MIME類型用於在剪貼板和拖拽使用時區別不同的數據類型,MIME類型列表可以點擊訪問http://www.iana.org/assignments/media-types/得到
void MainWindow::dropEvent(QDropEvent *event)
{
    QList<QUrl> urls = event->mimeData()->urls();
    if (urls.isEmpty())
       return;
    QString fileName = urls.first().toLocalFile();
    if (fileName.isEmpty())
        return;
    if (readFile(fileName))
        setWindowTitle(tr("%1 - %2").arg(fileName)
                                    .arg(tr("Drag File")));
}
 
當用戶將一個對象放在控件上drop時,函數dropEvent()就會調用。QMineData::urls()得到一個QUrls列表。通常用戶一次只會拖動一個文件,但是拖動多個文件也是允許的。如果用戶拖動了多個URLs,或者URL不是一個文件名,程序立即返回。
QWidget還提供了dragMoveEvent()和dragLeaveEvent(),但是對於大多數應用程序,這兩個函數都不需要重寫。
第二個例子來說明怎樣進行drag,怎樣接受drop。我們將會創建一個QListWidget子類,它可以接受drag和drop。並把它作爲Project Choonser 程序的一個組件,如9.1所示:
Figure 9.1. The Project Chooser application
Project Chooser程序由兩個名字列表控件組成。每一個列表控件表示一個項目。用戶可以drag和drop列表中的名字,把一個名字從一個項目移到另一個項目中。
在列表控件的子類中實現了drag和drop部分的代碼。下面是類的定義:
class ProjectListWidget : public QListWidget
{
    Q_OBJECT
public:
    ProjectListWidget(QWidget *parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void dragEnterEvent(QDragEnterEvent *event);
    void dragMoveEvent(QDragMoveEvent *event);
    void dropEvent(QDropEvent *event);
private:
    void startDrag();
    QPoint startPos;
};
該類重新實現了5個QWidget中聲明的事件處理函數。 
ProjectListWidget::ProjectListWidget(QWidget *parent)
    : QListWidget(parent)
{
    setAcceptDrops(true);
}
在構造函數中,我們讓列表控件允許接受drop。
 
void ProjectListWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        startPos = event->pos();
    QListWidget::mousePressEvent(event);
}
當用戶點擊了鼠標左鍵時,我們把鼠標位置保存在startPos變量中。然後調用基類QListWidget的mousePressEvent(),使QListWidget能處理鼠標點擊事件,即列表控件的鼠標點擊事件進入程序的消息循環。
 
void ProjectListWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) {
        int distance = (event->pos() - startPos).manhattanLength();
        if (distance >= QApplication::startDragDistance())
            startDrag();
    }
    QListWidget::mouseMoveEvent(event);
}
如果用戶按住鼠標左鍵同時移動鼠標,把這個過程認爲是一個drag。計算當前鼠標位置和鼠標第一次點擊的位置之間的距離,如果這個距離大於QApplication認定的拖動的最小距離(通常爲四個象素),調用私有函數startDrag()開始拖動。通過判斷距離可以避免因爲用戶手抖動引起的誤操作。
 
void ProjectListWidget::startDrag()
{
    QListWidgetItem *item = currentItem();
    if (item) {
        QMimeData *mimeData = new QMimeData;
        mimeData->setText(item->text());
        QDrag *drag = new QDrag(this);
        drag->setMimeData(mimeData);
        drag->setPixmap(QPixmap(":/images/person.png"));
        if (drag->start(Qt::MoveAction) == Qt::MoveAction)
            delete item;
    }
}
在startDrag()中,我們創建一個QDrag對象,this指針表示父類爲當前的列表控件ProjectListWidget。QDrag對象保存了一個QMimeData對象中的數據。在這個例子中,我們只是使用QMimeData::setText()保存了一個text/plain 串。QMimeData提供了很多函數處理經常用到的拖動類型(如圖像,URLs,顏色等等),對於用QByteArrays表示的任意MIME類型都可以處理。當drag發生時,函數QDrag::setPixmap()設置了跟隨鼠標的圖標。
調用QDrag::start()開始拖動,直到用戶drop或者取消了拖動。函數的參數爲多個“drag actions”的組合(Qt::CopyAction, Qt::MoveAction, Qt::LinkAction)。返回值爲執行拖動的“drag action”,如果沒有執行拖動的操作,則返回Qt::IgnoreAction。具體執行哪個action取決於源控件允許的操作,目標控件允許的操作和drop的同時是否有附加鍵按下。調用start()後,Qt擁有被拖動的對象,在不需要時將其刪除。
void ProjectListWidget::dragEnterEvent(QDragEnterEvent *event)
{
    ProjectListWidget *source =
            qobject_cast<ProjectListWidget *>(event->source());
    if (source && source != this) {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
}
ProjectListWidget不但可以產生drag,還可以接受來自程序中其他ProjectListWidget控件的drag。如果drag發生在同一個應用程序中,QDragEnterEvent::source()得到產生drag控件的指針。如果不是一個程序,則返回空指針。qobject_cast<T>()可以確保拖動來自與一個ProjectListWidget控件。如果一切正常,則調用accept()通知Qt接受這個action作爲一個移動操作
 
void ProjectListWidget::dragMoveEvent(QDragMoveEvent *event)
{
    ProjectListWidget *source =
            qobject_cast<ProjectListWidget *>(event->source());
    if (source && source != this) {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
}
函數dragMoveEvent()和dragEnterEvent()的代碼是相同的。這有必要,因爲需要覆蓋QListWidget的(實際上是QAbstractItemView的)函數實現。
 
void ProjectListWidget::dropEvent(QDropEvent *event)
{
    ProjectListWidget *source =
            qobject_cast<ProjectListWidget *>(event->source());
    if (source && source != this) {
        addItem(event->mimeData()->text());
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
}
在dropEvent()中,調用QMimeData::text()得到拖動的文本,並用這個文本創建一個列表項目。我們還需要調用event->setDropAction(Qt::MoveAction),用參數Qt::MoveAction通知源控件可以刪除原來拖動的項目。
在程序間需要轉移數據時,拖拽是一個非常有用的機制。但是有時候不用拖拽機制也可以實現拖拽同樣的操作。如果我們只是想在同一個程序同一個控件中移動數據,只需要重寫mousePressEvent()和mouseReleaseEvent()就可以。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章