在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";
}
}
}
代码效果如下:
代码数量较少,比较简单,自己可以多看看。