Qt----拖拽


最近比較忙,今天此才有時間來繼續學習下Qt。Qt的拖拽可以按字面意思分為兩部分。一般來說我們常見的拖拽分別由兩個程序合作完成。例如我們經常把桌面的文件拖拽進其他目錄:

desktop-drag

這個拖拽在Qt中由兩方合作完成,一個是桌面窗口另一個是目錄窗口,桌面發起“拖”動作,目錄窗口接受“放”動作。如果你細心觀察還可以發現,在不同的情況下拖拽還可以產生不同的結果:

drag-copy drag-move drag-link

拖拽方可以發起多個拖拽類型(復制、鏈接、移動等),接收方可以選擇接受其中某個類型或者直接拒絕。除此之外,拖拽支持多種數據格式,並且還可以在同一個應用程序中進行(托和拽操作均由同一個exe發起)。我們通過一個示例程序來學習,這個程序包含以下功能:

  • 內部發起拖拽。
  • 切換可接受的拖拽動作:移動、拷貝、鏈接。
  • 支持多種拖拽數據:image、html、text、color等。
  • 拖拽區域高亮。

1. 內部拖拽

我們在主窗口內部放置了3個Label,分別可以用來演示3種不同數據類型的拖拽效果。如果鼠標左鍵在這幾個標簽區域內按下並且移動距離超過5個像素,我們就發起拖拽動作,可能的拖拽類型為:Copy、Move、Link。通過QDrag類我們就能發起一個拖拽動作,這個類還提供了setPixmap()setHotSpot()函數類供我們自定義拖拽時的圖像和位置,這里我們就不設置了。

調用QDrag::exec()就能進入拖拽的事件處理循環了,注意在Linux和Mac上,這個exec並不會阻塞全局的消息循環;在Windows上,它是阻塞的:

void MainWindow::mousePressEvent(QMouseEvent *event)
{
    if (event->button() & Qt::LeftButton) {
        m_dragStartPos = event->pos();

        if (m_imageLabel->geometry().contains(event->pos()))
            m_dragLabel = m_imageLabel;
        else if (m_colorLabel->geometry().contains(event->pos()))
            m_dragLabel = m_colorLabel;
        else if (m_htmlLabel->geometry().contains(event->pos()))
            m_dragLabel = m_htmlLabel;
        else
            m_dragStartPos = QPoint();
    }

    QMainWindow::mousePressEvent(event);
}

void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    QDrag drag(this);

    if (m_dragStartPos.isNull())
        goto end;
    if ((event->pos() - m_dragStartPos).manhattanLength() < 5)
        goto end;

    ...

    drag.exec(Qt::CopyAction | Qt::MoveAction | Qt::LinkAction);

end:
    QMainWindow::mouseMoveEvent(event);
}

2. 可接受拖拽類型切換

這個功能我們通過菜單來實現,因為同一時間只接受一種類型,因此這幾個菜單要求必須互斥。互斥菜單這個概念我們在代碼里選擇使用QActionGroup,雖然自己實現切換也不麻煩,但是這樣更快更方便。每個子菜單觸發時會切換可接受拖拽類型,Qt提供了3種基本類型:Move、Copy、Link。拖拽顯示時,Copy類型是一個+號,Move類型是一個,Link類型是一個粗體的斜箭頭:

void MainWindow::createMenus()
{
    auto menu = this->menuBar()->addMenu(tr("Acceptable Drags"));

    auto copyAction = menu->addAction("Only Copy",
                        this, SLOT(onlyAcceptCopyDrag()),
                        QKeySequence(Qt::CTRL + Qt::Key_C));
    copyAction->setCheckable(true);
    copyAction->setChecked(true);
    m_actionGroup.addAction(copyAction);

    auto moveAction = menu->addAction("Only Move",
                        this, SLOT(onlyAcceptMoveDrag()),
                        QKeySequence(Qt::CTRL + Qt::Key_M));
    moveAction->setCheckable(true);
    moveAction->setChecked(false);
    m_actionGroup.addAction(moveAction);

    auto linkAction = menu->addAction("Only Link",
                        this, SLOT(onlyAcceptLinkDrag()),
                        QKeySequence(Qt::CTRL + Qt::Key_L));
    linkAction->setCheckable(true);
    linkAction->setChecked(false);
    m_actionGroup.addAction(linkAction);
}

3. 多種拖拽數據類型

我們在發起拖拽的時候設置傳遞的數據及類型,通過QDrag::setMimeData()函數來設置傳入的數據及類型。這個函數接受一個QMimeData類型的指針,這個類用MIME來區分數據類型。對於常見的類型它提供了方便的函數setHtml()setColorData()等,我們只需要傳入數據就可以了:html和texx類型的數據以字符串表示,Image和Color類型數據以QVariant表示;對於自定義類型,我們需要調用setData()函數,同時傳入MIME類型和對應的數據(因為使用簡單,代碼中沒有演示)。數據獲取則通過對應的html()imageData()data()等函數:

void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    ...

    QMimeData* mimeData = new QMimeData();
    if (m_dragLabel == m_htmlLabel)
        mimeData->setHtml("<html>This is a html page</html>");
    if (m_dragLabel == m_colorLabel)
        mimeData->setColorData(QVariant(QColor(Qt::cyan)));
    if (m_dragLabel == m_imageLabel)
        mimeData->setImageData(QVariant(QPixmap(":/img/image.jpg")));
    drag.setMimeData(mimeData);

    ...
}

其實個人覺得QMimeData不是很好用,沒有獲取當前類型的功能,只能約定好或者一個一個去嘗試。

4. 拖拽區域高亮

  • 當拖拽進入窗口之中時,會觸發dragEnterEvent()事件,我們在這里高亮窗口背景;
  • 當我們接受拖拽事件(通過QDragEnterEvent::accept()或者QDropEvent::acceptProposedAction())后,窗口會收到dragMoveEvent()事件。QDragMoveEvent事件包含一個answerRect()函數,返回相對於窗口的當前拖拽范圍,我們通過判斷來選擇是否繪制中心的高亮區域。有趣的是這個范圍大小始終是1x1,它被當成點來處理。
  • 當拖拽離開窗口時,會觸發dragLeaveEvent()事件,我們進行重繪。

下面是該部分代碼:

void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
    event->setDropAction(m_acceptableDropAction);
    event->accept();
    m_currentBkColor = m_dragEnteredColor;

    QWidget::dragEnterEvent(event);
}

void MainWindow::dragLeaveEvent(QDragLeaveEvent *event)
{
    m_currentBkColor = m_dragLeavedColor;

    repaint();
    QWidget::dragLeaveEvent(event);
}

void MainWindow::dragMoveEvent(QDragMoveEvent *event)
{
    if (m_acceptableRect.contains(event->answerRect()))
        m_currentDragableAreaColor = m_highlightColor;
    else
        m_currentDragableAreaColor = m_unhighlightColor;

    repaint();
}

5. 運行結果

result-1 result-2

代碼詳見鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM