在Qt quick5.10-qml中使用drag and drop进行拖拽,及qml拖拽的Bug


在Qt5.10中qml实现的拖拽并不完善,以下Bug已在Qt5.12,Qt5.13中进行了修复。
在Qt Quick与 drag and drop 相关的几个QML Type:
DropArea 
DropArea 是不可见的,它定义了一个可以接收拖放的区域。它的 entered 信号在有物体被拖入区域时发射,exited 信号在物体被拖出区域时发射,当物体在区域内被拖着来回移动时会不断发射 positionChanged 信号,当用户释放了物体,dropped 信号被发射。在DropArea 中有以下几个Properties。containsDrag此属性标识DropArea当前是否包含任何拖动的项目,drag.x, drag.y 表示的是拖拽的坐标。

containsDrag : bool
drag
drag.x : qreal
drag.y : qreal
DragEvent
DragEvent ,它描述一个拖动事件的相关信息的, DropArea 的 entered 、 positionChanged 、 dropped 信号的参数都是 DragEvent 。它的Properties比较多,有需要就去看文档吧,这里挑几个比较常用的说说吧。

accepted :表示是否接受事件的布尔值,如果你处理了 entered 信号,需要把它设置为 true 。

x , y :拖动事件的位置,你可以根据它来显示点什么,我们的实例显示了一个如影随形的矩形。

action : 拖动来源正在执行的动作的标识,有 Qt.CopyAction 、 Qt.MoveAction 、 Qt.LinkAction 、 Qt.IgnoreAction四种。

proposedActions :建议的动作集合。

supportedActions :来源支持的动作集合

其它的属性还有 hasColor 、 hasUrls 、 hasText 、 hasHtml 等等与 MIME 相关的属性用来判断在拖动时是否携带了某种数据,对应的就有 colorData 、 urls 、 text 、 html 等属性表示实际的数据。

       DragEvent 还定义了一些方法:

    accept(Action) :调用它可以接受某一个动作,比如你接受 Qt.CopyAction

    accept() :接受拖动事件,表明你处理了这个事件了。

    acceptProposedAction() : 接受被拖动物体(来源)建议的动作

    getDataAsString(format) :获取某个格式对应的数据并转换为字符串,我们的实例里用这个来提取传输的数据

Drag
Drag 这个类一般是附着在可能被拖动的 Item 上,用来设置一些拖动相关的信息。

它提供很多附加属性:

active :指示当前是否处在拖动状态。我们可以把这个属性和一个 MouseArea 的 drag 属性绑定,这样当用户拖动鼠标时就会产生拖动事件。当然你也可以手动设置它为 true ,那样会以被拖动 Item 的当前位置产生一个拖动进入事件。如果你设置 active 为 false ,会产生一个拖动离开事件。
dragType :一个枚举值,表示拖动类型,可以是 Drag.None(不自动开始拖动)、Drag.Automatic(自动开始拖动)、Drag.Internal(自动开始前向兼容的拖动)。我们用到了这个,待会儿看代码就明白了。
mimeData : 存放MIME数据以及自定义数据,可以传递给 DropArea 。Qt Quick 会把 mimeData 定义的数据打包到 DragEvent 里,带着它四处旅行,谁感兴趣都可以看看。他需要与Qt.CopyAction一块使用,进行数据传输。在Image和Text中比较常用。
supportedActions :指定支持的动作。对应 DropArea 收到的 DragEvent 的 supportedActions 。
proposedActions : 指定推荐的动作。对应 DropArea 收到的 DragEvent 的 proposedActions 。
source : 指定拖动的来源对象
target :当 active 为 true (拖动处于活跃状态)时,这个属性保存被拖动物体进入的那个 DropArea ,如果被拖动物理和谁都没交集,那它就为 null 。如果拖动没被激活,那它保存最后一个接受 drop 事件的对象,要是没人招惹过被拖动物体,那 target 就为 null
Drag 还有一些附加信号,可以让我们对拖动的过程增进了解,比如 dragStarted 、 dragFinished 。

Drag 也提供了一些方法,如 cancel 、 drop 、 start 、 startDrag ,允许我们手动控制拖动

以下代码实现的是图片的拖动实例
本实例使用的是Qt.CopyAction,在Qt5.10中使用Qt.CopyAction类型进行拖动时,会出现一个Bug,需要用高版本的Qt能够解决。当你將Target,拖到指定的dargArea区域时,MouseArea区域的的onPressed不会再你松开鼠标时立即执行,需要你在次使用鼠标点击(当再次有相同的点击事件发生时)任意区域,上次的onPressed事件才会执行,这就会导致你想要再次拖图片时,如果不在任意地方点击一次,你对图片的拖动就不会成功,因为在你下次进行操作之前,你需要将上次操作的onPressed释放掉。

还有如果你是新手,进行Image加载时,将图片放到本地路径仍然加载不上(找不到),这时候就需要将要加载的图片,添加到执行的列表中,如下图:

直接上代码:

import QtQuick 2.9
import QtQuick.Window 2.2

Window {

id:win
visible: true
width: 640
height: 480
title: qsTr("Hello World")

Image{
    id: sourceImage
    height: 36
    width: 36
    anchors.left: parent.left
    anchors.leftMargin: 100
    anchors.verticalCenter: parent.verticalCenter
    source: "2.png"

    Drag.active: dragArea1.drag.active;
    Drag.supportedActions: Qt.CopyAction;
    Drag.dragType: Drag.Automatic;
    //Drag.dragType:Drag.Internal
    Drag.mimeData: {"pic": source}

    Drag.hotSpot.x: (sourceImage.width)*0.5
    Drag.hotSpot.y: sourceImage.height

    MouseArea {
        id: dragArea1;
        anchors.fill: sourceImage;
        drag.target: sourceImage;

        onPressed: {
            console.log(2222)
        }
        
        //在Qt5.10中不会自动执行此步骤,需要再次点击才能释放上次的onReleased操作
        onReleased: {
            console.log(33333)

        }
    }
}

Component.onCompleted: {
    sourceImage.grabToImage(function(result) {
        sourceImage.Drag.imageSource = result.url
    })
}

Rectangle{
    anchors.right: parent.right
    border.color: "blue"
    border.width: 1
    height: parent.height
    width: 300


    Image{
        id: targetImage
        height: sourceImage.height
        width: sourceImage.width
        clip: true

    }

    DropArea {
        id: dropContainer1
        anchors.fill: parent;
        onEntered: {
            console.log(444444)

        }

        onDropped: {
            console.log(55555)
            if (drop.supportedActions == Qt.CopyAction){

                targetImage.source = drop.getDataAsString("pic")
                targetImage.x = drop.x - (sourceImage.width)*0.5
                targetImage.y = drop.y - sourceImage.height

                drop.acceptProposedAction()
            }

        }

        onExited: {
            console.log(66666)
        }

    }
}

}
代码中对拖拽的输出顺序用console.log()进行输出,可以很直观的看到。如果使用Qt5.10就能看到文中提到的Bug,官方已在更高的Qt版本中进行了修复。Qt5.13运行的效果如下:

为了更好的理解Qml中拖拽,下面在多上两端代码。
下面代码演示的是将窗口分为两个区域,在随机位置上,生成随机颜色并带编码的方块。可以对这些方块进行拖动,并使用动画增加了弹簧效果,在不同区域限制了拖动的效果。此代码在Qt5.10中也会遇到相同的Bug。

直接上代码:

import QtQuick 2.9
import QtQuick.Window 2.2

Window {

id:win
visible: true
width: 640
height: 480
title: qsTr("Hello World")

Repeater {
    model: 10
    Rectangle {
        id: rect
        width: 50
        height: 50
        z: mouseArea.drag.active ||  mouseArea.pressed ? 2 : 1
        color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
        x: Math.random() * (win.width / 2 - 100)
        y: Math.random() * (win.height - 100)
        property point beginDrag
        property bool caught: false
        border { width:2; color: "white" }
        radius: 5
        Drag.active: mouseArea.drag.active

        Text {
            anchors.centerIn: parent
            text: index
            color: "white"
        }
        MouseArea {
            id: mouseArea
            anchors.fill: parent
            drag.target: parent
            onPressed: {
                rect.beginDrag = Qt.point(rect.x, rect.y);
            }
            onReleased: {
                if(!rect.caught) {
                    backAnimX.from = rect.x;
                    backAnimX.to = beginDrag.x;
                    backAnimY.from = rect.y;
                    backAnimY.to = beginDrag.y;
                    backAnim.start()
                }
            }

        }
        ParallelAnimation {
            id: backAnim
            SpringAnimation { id: backAnimX; target: rect; property: "x"; duration: 500; spring: 2; damping: 0.2 }
            SpringAnimation { id: backAnimY; target: rect; property: "y"; duration: 500; spring: 2; damping: 0.2 }
        }
    }
}

Rectangle {
    anchors {
        top: parent.top
        right:  parent.right
        bottom:  parent.bottom
    }
    width: parent.width / 2
    color: "gold"
    DropArea {
        anchors.fill: parent
        onEntered: drag.source.caught = true;
        onExited: drag.source.caught = false;
    }
}

}
代码效果如下:

下面这段代码演示的也是方块的拖拽,与上面设计的方法有点不同,界面顶部是一些色块,只支持 Qt.CopyAction ,鼠标可以拖动,把它们拖到下面的浅蓝色区域内。一旦色块被拖放到浅蓝色区域,我会动态创建一个支持 Qt.MoveAction 的矩形,复制拖放的矩形的大小、颜色等参数。这样蓝色区域内新创建的这些 Rectangle 就可以被移动。单是移动没有限制区域,它可以全屏移动。

上代码:

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Window 2.2

Window {

id: root;
visible: true;
width: 480;
height: 400;

//drag source item should not use anchors to layout! or drag will failed
Component {
    id: dragColor;
    Rectangle {
        id: dragItem;
        x: 0;
        y: 0;
        width: 60;
        height: 60;
        Drag.active: dragArea.drag.active;
        Drag.supportedActions: Qt.CopyAction;
        Drag.dragType: Drag.Automatic;
        Drag.mimeData: {"color": color, "width": width, "height": height}

        MouseArea {

            id: dragArea;
            anchors.fill: parent;
            drag.target: parent;

            onPressed: {
                console.log(2222)
                parent.grabToImage(function(result) {
                    dragColor.Drag.imageSource = result.url
                })
            }

            onReleased: {
                console.log(333333)


                if(parent.Drag.supportedActions === Qt.CopyAction){
                    dragItem.x = 0;
                    dragItem.y = 0;
                }
            }
        }
    }
}

Row {
    id: dragSource;
    anchors.top: parent.top;
    anchors.left: parent.left;
    anchors.margins: 4;
    anchors.right: parent.right;
    height: 64;
    spacing: 4;
    z:-1;
    Loader {
        width: 60;
        height: 60;
        z: 2;
        sourceComponent: dragColor;
        onLoaded: item.color = "red";

    }
    Loader {
        width: 60;
        height: 60;
        z: 2;
        sourceComponent: dragColor;
        onLoaded: item.color = "black";
    }
    Loader {
        width: 60;
        height: 60;
        z: 2;
        sourceComponent: dragColor;
        onLoaded: item.color = "blue";
    }
    Loader {
        width: 60;
        height: 60;
        z: 2;
        sourceComponent: dragColor;
        onLoaded: item.color = "green";
    }
}

DropArea {
    id: dropContainer;
    anchors.top: dragSource.bottom;
    anchors.left: parent.left;
    anchors.right: parent.right;
    anchors.bottom: parent.bottom;
    z: -1;

    onEntered: {
        drag.accepted = true;
        followArea.color = drag.getDataAsString("color");
        console.log("onEntered, formats - ", drag.formats, " action - ", drag.action);
    }

    onPositionChanged: {
        console.log(11111111)
        drag.accepted = true;
        console.log(1,followArea.x,followArea.y)
        console.log(2,drag.x,drag.y)
        followArea.x = drag.x ;
        followArea.y = drag.y ;
    }

    onDropped: {
        console.log(44444)
        if(drop.supportedActions == Qt.CopyAction){
            var obj = dragColor.createObject(destArea,{
                                                 "x": drop.x,
                                                 "y": drop.y,
                                                 "width": parseInt(drop.getDataAsString("width")),
                                                 "height": parseInt(drop.getDataAsString("height")),
                                                 "color": drop.getDataAsString("color"),
                                                 "Drag.supportedActions": Qt.MoveAction,
                                                 "Drag.dragType": Drag.Internal

                                             });

        }else if(drop.supportedActions == Qt.MoveAction){
            console.log("move action, drop.source - ", drop.source, " drop.source.source - ", drop.source.source);
        }
        drop.acceptProposedAction();
        drop.accepted = true;
    }

    Rectangle {
        id: followArea;
        z: 2;

        width: 68;
        height: 68;
        border.width: 2;
        border.color: "yellow";
        visible: parent.containsDrag;
    }

    Rectangle {
        id: destArea;
        anchors.fill: parent;
        color: "lightsteelblue";
        border.width: 2;
        border.color: parent.containsDrag ? "blue" : "gray";
    }
}

}
代码效果如下:

代码数量较少,比较简单,自己可以多看看。

上一篇:tesseract api C++使用例子


下一篇:3年大合辑:算法、研发、Java开发、Android开发、机器学习免费电子书一键下载