英文:
QML Drag-and-drop ListView always on first element
问题
我需要能够移动ListView
的行,例如:如果我将第2行拖动到第4项上,则应在第4项之前插入。我遵循了多个示例,但我唯一能够使拖动工作的事情如下所示,但是DropArea
始终位于第一个元素之上。这是我的代码,只去掉了样式:
DragDropEx.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQml.Models 2.1
Window {
width: 784; height: 250; visible: true;
Flickable {
anchors.fill: parent
property var listOfStrings: ["Hello", "world", "I", "need", "help"]
ListModel { id: listOfStringsModel }
Component.onCompleted: {
if (visible) {
listOfStringsModel.clear();
for (var i = 0; i < listOfStrings.length; i++) {
listOfStringsModel.append({ "textEntry": listOfStrings[i] });
}
textListView.currentIndex = listOfStrings.length - 1;
}
}
Rectangle {
anchors.fill: parent; color: "lightblue"
ListView {
id: textListView
x: 10; y: 10; width: parent.width - 20; height: parent.height - 20
spacing: 4
model: DelegateModel {
id: visualModel
model: listOfStringsModel
delegate: TextListDelegate {
id: textListDelegateItem
height: 32; width: parent.width - 16
anchors { horizontalCenter: parent.horizontalCenter }
}
}
}
}
}
}
TextListDelegate.qml
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQml.Models 2.1
MouseArea {
id: delegateRoot
property bool held: false
property int visualIndex: DelegateModel.itemsIndex
drag.target: held ? textEntryContainerRect : undefined
drag.axis: Drag.YAxis
drag.filterChildren: true // this is because of the text field
onPressAndHold: {
held = true;
textEntryContainerRect.opacity = 0.5;
}
onReleased: {
if (held === true) {
held = false;
textEntryContainerRect.opacity = 1;
textEntryContainerRect.Drag.drop();
}
}
Rectangle {
id: textEntryContainerRect
anchors.fill: parent
Drag.active: delegateRoot.drag.active
Drag.source: delegateRoot
Drag.hotSpot.x: textListDelegateItem.width / 2
Drag.hotSpot.y: textListDelegateItem.height / 2
states: [
State {
when: textEntryContainerRect.Drag.active
ParentChange {
target: textEntryContainerRect
parent: textListView
}
AnchorChanges {
target: textEntryContainerRect
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
]
TextField {
id: textListEntryField
anchors { fill: parent; leftMargin: 8 }
height: 30; text: textEntry
}
}
DropArea {
id: dropArea
anchors { fill: parent; }
onDropped: {
var sourceIndex = drag.source.visualIndex;
var targetIndex = delegateRoot.visualIndex;
listOfStringsModel.move(sourceIndex, targetIndex, 1);
}
Rectangle {
anchors.fill: parent
color: "hotpink"
visible: parent.containsDrag
}
}
}
我为了显示DropArea
已经上色,以显示每次我按住并尝试移动行时它都顽固地跳到第一个元素。如何使它跳转到鼠标所在的行,类似于网上的所有示例?
英文:
I need to be able to move ListView
rows, example: if I drag row 2 over item 4, it should insert before item 4. I followed multiple examples, the only thing I could make dragging work shows below, but the DropArea
is always over the first element. This is my code, only with styling removed:
DragDropEx.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQml.Models 2.1
Window {
width: 784; height: 250; visible: true;
Flickable {
anchors.fill: parent
property var listOfStrings: ["Hello", "world", "I", "need", "help"]
ListModel { id: listOfStringsModel }
Component.onCompleted: {
if (visible) {
listOfStringsModel.clear();
for (var i = 0; i < listOfStrings.length; i++) {
listOfStringsModel.append({ "textEntry": listOfStrings[i] });
}
textListView.currentIndex = listOfStrings.length - 1;
}
}
Rectangle {
anchors.fill: parent; color: "lightblue"
ListView {
id: textListView
x: 10; y: 10; width: parent.width - 20; height: parent.height - 20
spacing: 4
model: DelegateModel {
id: visualModel
model: listOfStringsModel
delegate: TextListDelegate {
id: textListDelegateItem
height: 32; width: parent.width - 16
anchors { horizontalCenter: parent.horizontalCenter }
}
}
}
}
}
}
TextListDelegate.qml
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQml.Models 2.1
MouseArea {
id: delegateRoot
property bool held: false
property int visualIndex: DelegateModel.itemsIndex
drag.target: held ? textEntryContainerRect : undefined
drag.axis: Drag.YAxis
drag.filterChildren: true // this is because of the text field
onPressAndHold: {
held = true;
textEntryContainerRect.opacity = 0.5;
}
onReleased: {
if (held === true) {
held = false;
textEntryContainerRect.opacity = 1;
textEntryContainerRect.Drag.drop();
}
}
Rectangle {
id: textEntryContainerRect
anchors.fill: parent
Drag.active: delegateRoot.drag.active
Drag.source: delegateRoot
Drag.hotSpot.x: textListDelegateItem.width / 2
Drag.hotSpot.y: textListDelegateItem.height / 2
states: [
State {
when: textEntryContainerRect.Drag.active
ParentChange {
target: textEntryContainerRect
parent: textListView
}
AnchorChanges {
target: textEntryContainerRect
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
]
TextField {
id: textListEntryField
anchors { fill: parent; leftMargin: 8 }
height: 30; text: textEntry
}
}
DropArea {
id: dropArea
anchors { fill: parent; }
onDropped: {
var sourceIndex = drag.source.visualIndex;
var targetIndex = delegateRoot.visualIndex;
listOfStringsModel.move(sourceIndex, targetIndex, 1);
}
Rectangle {
anchors.fill: parent
color: "hotpink"
visible: parent.containsDrag
}
}
}
I colored the drop area to show that it is stubbornly jumping to first element every time I press and hold, then try to move a row. How can I make it go to the row where the mouse is , similar to all examples on the web ?
Qt version: 5.12 - 5.17
Notes:
-
the
DelegateModel
was used in this Qt tutorial which is my most successful version. -
press-and-hold needed for embedded
-
TextField
is accompanied by other controls, and the model has other fields, that is why it may look more complicated than needed, solution needs to work with them.
答案1
得分: 0
我尝试为您创建了一个最小的ListView / DelegateModel拖放示例。
我删除了Flickable,因为我不确定它的作用,而且您已经有ListView和带有拖动的MouseArea,所以我不想在那里加入Flickable以免混淆事情。
我保留了您的ListModel,但缩短了填充它所需的代码行数。
我根据Qt的文档 https://doc.qt.io/qt-6/qml-qtquick-drag.html 重新编写了您的代理,其中显示了一个父项,其中包含您的DropArea和TextField。在TextField内部,您有一个MouseArea。这种排列很重要,因为父项是不可见的且不可移动的。当您完成拖动操作时,您希望TextField回到原位,即重置'y'位置,因为我们将在视觉模型中移动它,我们希望撤消拖动操作。
我删除了DelegateModel的所有痕迹。正确使用DelegateModel涉及对DelegateModelGroup的操作。看到您的示例实际上操作了底层的ListModel,我觉得在这里没有真正需要使用DelegateModel,而且删除它的好处是大大减少了示例的代码复杂性。
我将visualIndex重命名为index / _index,因为这是为所有代理提供的内置索引。跟踪此索引最终将使我们能够在拖放完成后正确调用move来获取源ListModel中的记录。
之后,我通过MouseArea的onReleased和相应的DropArea的onDropped将其连接起来,对于您来说基本是正确的。我将sourceIndex和targetIndex从变量更改为属性,因为在调试时,我发现将这些值属性绑定很有用(我已经在最终结果中删除了调试,但保留了这些属性,因为它使代码看起来更精简)。
我发现在早期版本中,代理的z顺序堆叠在一起,因此拖动的项目并不总是位于顶部。为了解决这个问题,我需要提升与当前正在拖动的项目对应的代理的z顺序。
最后,不清楚您使用的Qt版本是哪个,但我选择使用Qt6语法来缩短我的导入。
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
Rectangle { anchors.fill: parent; color: "lightblue" }
property var listOfStrings: ["Hello", "world", "I", "need", "help"]
ListModel { id: listOfStringsModel }
Component.onCompleted: {
for (let textEntry of listOfStrings) listOfStringsModel.append({textEntry});
}
ListView {
anchors { fill: parent; margins: 10 }
spacing: 4
model: listOfStringsModel
delegate: TextListDelegate { }
}
}
// TextListDelegate.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models
Rectangle {
height: 32
width: ListView.view.width
z: dragArea.drag.active ? 1 : 0
border.color: "grey"
DropArea {
id: dropArea
property int sourceIndex: drag.source._index
property int targetIndex: index
anchors { fill: parent; }
onDropped: listOfStringsModel.move(sourceIndex, targetIndex, 1);
Rectangle {
anchors.fill: parent
visible: parent.containsDrag
color: "steelblue"
opacity: 0.5
Label { anchors.centerIn: parent; text: dropArea.sourceIndex + " -> " + dropArea.targetIndex }
}
}
Label {
id: label
width: parent.width
height: parent.height
property int _index: index
text: textEntry
Drag.active: dragArea.drag.active
Drag.hotSpot.x: 10
Drag.hotSpot.y: 10
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
drag.axis: Drag.YAxis
onReleased: { parent.Drag.drop(); parent.y = 0; }
}
}
}
英文:
I tried to create a minimal ListView / DelegateModel drag-n-drop example for you.
I removed the Flickable, I wasn't sure what the purpose it had, and, you already had ListView and MouseArea with dragging so I didn't really want Flickable there confusing things.
I kept your ListModel, but, I shortened the lines of code needed to populate it.
I rewrote your delegate based on Qt's documentation https://doc.qt.io/qt-6/qml-qtquick-drag.html which showed a parent Item of which inside it you have your DropArea and the TextField. Inside the TextField you have a MouseArea. This arrangement is important because the parent Item is invisible an immovable. When you finish your drag action the TextField you want to snap back into place, i.e. reset the 'y' position, since, we're going to move it in the visual model, we want to undo the move from the drag.
I remove all traces on DelegateModel. Correct usage of DelegateModel involves manipulation of a DelegateModelGroup. Seeing your example really manipulates the underlying ListModel I felt that there was no real need for DelegateModel here, and, a benefit of removing it is it reduces the code complexity of the example dramatically.
I renamed visualIndex to just index / _index. Since this is the built-in index given to all delegates. Tracking this index will ultimately give us the record in the source ListModel which will enable us to call move correctly after the drag-drop is completed.
After that, I wired it up with a minimal implementation on MouseArea's onReleased and the corresponding DropArea onDropped which, for you pretty much had right. I changed the sourceIndex and targetIndex from being a variable to being a property since, when I was debugging I found it useful to property bind to these values so I can see them (I've removed the debugging in the final result but I've kept those as properties since it made the code look leaner).
I found in earlier versions that the delegates the z-order was stacked so that the dragged item wasn't always on top. In order to fix that problem I needed to elevate the z-order of the delegate corresponding to the current item currently being dragged.
Lastly, it wasn't clear which version of Qt you were using, but, I've opted to use Qt6 syntax to shorten my imports.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
Rectangle { anchors.fill: parent; color: "lightblue" }
property var listOfStrings: ["Hello", "world", "I", "need", "help"]
ListModel { id: listOfStringsModel }
Component.onCompleted: {
for (let textEntry of listOfStrings) listOfStringsModel.append({textEntry});
}
ListView {
anchors { fill: parent; margins: 10 }
spacing: 4
model: listOfStringsModel
delegate: TextListDelegate { }
}
}
// TextListDelegate.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models
Rectangle {
height: 32
width: ListView.view.width
z: dragArea.drag.active ? 1 : 0
border.color: "grey"
DropArea {
id: dropArea
property int sourceIndex: drag.source._index
property int targetIndex: index
anchors { fill: parent; }
onDropped: listOfStringsModel.move(sourceIndex, targetIndex, 1);
Rectangle {
anchors.fill: parent
visible: parent.containsDrag
color: "steelblue"
opacity: 0.5
Label { anchors.centerIn: parent; text: dropArea.sourceIndex + " -> " + dropArea.targetIndex }
}
}
Label {
id: label
width: parent.width
height: parent.height
property int _index: index
text: textEntry
Drag.active: dragArea.drag.active
Drag.hotSpot.x: 10
Drag.hotSpot.y: 10
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
drag.axis: Drag.YAxis
onReleased: { parent.Drag.drop(); parent.y = 0; }
}
}
}
You can Try it Online!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论