在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 是不可见的,它定义了一个可以接收拖放的区域。它的 entered 信号在有物体被拖入区域时发射,exited 信号在物体被拖出区域时发射,当物体在区域内被拖着来回移动时会不断发射 positionChanged 信号,当用户释放了物体,dropped 信号被发射。在DropArea 中有以下几个Properties。containsDrag此属性标识DropArea当前是否包含任何拖动的项目,drag.x, drag.y 表示的是拖拽的坐标。

 

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 这个类一般是附着在可能被拖动的 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";
        }
    }
}

代码效果如下:

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

 

  • 12
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值