目標
結合前面的2篇文章, 繼續升級QML版本的ListView: 又要拖拽, 又要可編輯, 還得支持多個控件.
循序漸進
本文基於前一篇的基礎: Qt-可編輯的ListView 要循序漸進的學習.
幾個關鍵點
- 要用拖拽, 就不能用Layout了 (大部分情況應該是)
- 條條大路通羅馬, 但是沒有找到官方的示例...只好自己寫
- 盡量簡潔, 而不是自己控制所有狀態(雖然也可以做到, 就是太麻煩)
示意圖
代碼如下
main.cpp, main.qml沒啥變化, 和之前的一樣.
主要的EditDragList.qml代碼如下, 里面注釋很多, 還有很多調試打印, 自己可以去除:
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
import QtQml.Models 2.1
/**
1. 通過設置currentIndex, 屬性自動變化. 支持鍵盤
2. 支持拖拽
*/
Rectangle {
id: root
//列表容器
Rectangle {
id: itemRoot
width: root.width
height: root.height - itemOp.height
//調試顯示
//color: "blue"
//border.width: 2
Component {
id: delegateItem
MouseArea {
id: mouseArea
width: itemRoot.width
height: itemRect.height
anchors {
left: parent.left
right: parent.right
}
//hoverEnabled: true
//拖拽設置
drag.smoothed: false
drag.target: itemRect
drag.axis: Drag.YAxis
onClicked: {
console.debug("onClicked")
//方法1: 設置當前
listView.currentIndex = index
console.log(("MouseArea Click listview currentIndex: "
+ listView.currentIndex + " index: " + index))
console.log(("MouseArea Click ListView isCurrentItem: "
+ ListView.isCurrentItem))
// 在onFocusChanged 中設置了, 此處不需要了
//textinput.focus = true;
}
onFocusChanged: {
if (focus) {
console.debug("=====got focus of mouseArea, start")
console.debug(("got focus of mouseArea, listview currentIndex: "
+ listView.currentIndex + " index: " + index))
console.debug("got focus of mouseArea, isCurrentItem: " + mouseArea.ListView.isCurrentItem)
console.debug("got focus of mouseArea, drag is active: " + drag.active)
console.debug("got focus of mouseArea, textInput visible: " + textinput.visible)
textinput.focus = true
console.debug("=====got focus of mouseArea, end!")
}
else {
console.debug(("lost focus of mouseArea, listview currentIndex: "
+ listView.currentIndex + " index: " + index))
console.debug("lost focus of mouseArea, isCurrentItem: " + mouseArea.ListView.isCurrentItem)
}
}
//FIXME: 目前某行處於編輯狀態, 然后其他行拖動和此行交換, 則會crash, 原因待查 2020.4.21
//目前解決的思路: 一旦開始拖拽, 則退出編輯狀態
drag.onActiveChanged: {
if (mouseArea.drag.active) {
//開始拖動時: 設置當前Item為空
listView.currentIndex = -1
}
}
//實際顯示內容
Rectangle {
id: itemRect
height: 40
width: mouseArea.width
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
//拖拽設置
Drag.active: mouseArea.drag.active
Drag.source: mouseArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
//拖拽的狀態變化
states: State {
when: itemRect.Drag.active
ParentChange {
target: itemRect
parent: itemRoot
}
AnchorChanges {
target: itemRect
anchors {
horizontalCenter: undefined
verticalCenter: undefined
}
}
}
CheckBox {
id: chkbox
width: 50
//height: parent.height
anchors.bottom: parent.bottom
//底部留點空間
bottomPadding: 3
checked: model.done
onClicked: model.done = checked
}
Rectangle {
id: textSection
height: parent.height
width: parent.width - chkbox.width
anchors.left: chkbox.right
Text {
id: textShow
text: model.description
anchors.bottom: parent.bottom
//控制可見: 不是當前
visible: !mouseArea.ListView.isCurrentItem
//底部留點空間
bottomPadding: 3
} //end textShow
TextInput {
id: textinput
anchors.bottom: parent.bottom
color: "blue"
//底部留點空間
bottomPadding: 3
text: model.description
//控制是否編輯狀態
visible: mouseArea.ListView.isCurrentItem
enabled: mouseArea.ListView.isCurrentItem
//focus: true
selectByMouse: true
//Debug: 不變不會進入的, 例如已經focus, 再次設置不會觸發此事件
onFocusChanged: {
if (focus) {
console.debug("got focus " + "textInput")
}
else {
console.debug("lost focus " + "textInput")
}
}
onEditingFinished: {
console.debug("=== start onEditingFinished ")
model.description = textinput.text
//方法1: 設置index
if (listView.currentIndex == index) {
listView.currentIndex = -1
}
console.log(("TextInput listview currentIndex: "
+ listView.currentIndex + " index: " + index))
console.log(("TextInput ListView isCurrentItem: "
+ mouseArea.ListView.isCurrentItem))
console.debug("=== end onEditingFinished ")
}
} //end TextInput
} //end textSection Rectangle
Frame {
width: itemRect.width
height: 1
anchors.bottom: itemRect.bottom
//anchors.topMargin: 1
}
} //end itemRect Rectangle
DropArea {
anchors {
fill: parent
margins: 10
}
onEntered: {
console.debug("===== start DropArea onEntered")
console.debug("drag.source.DelegateModel.itemsIndex: " + drag.source.DelegateModel.itemsIndex)
console.debug("mouseArea.DelegateModel.itemsIndex: " + mouseArea.DelegateModel.itemsIndex )
//移動Delegate
visualModel.items.move(
drag.source.DelegateModel.itemsIndex,
mouseArea.DelegateModel.itemsIndex, 1)
//移動Model: 不移動的話model和delegate就不同步了
visualModel.model.move(
drag.source.DelegateModel.itemsIndex,
mouseArea.DelegateModel.itemsIndex, 1)
console.debug("===== end DropArea onEntered")
}
}
//end DropArea
} //end MouseArea
} //end Component
DelegateModel {
id: visualModel
model: MyModel {}
delegate: delegateItem
}
ListView {
id: listView
width: parent.width
height: parent.height
keyNavigationEnabled: true
//clip: true
model: visualModel
//delegate: delegateItem
//默認不要是第一個, 否則第一個是可編輯狀態(針對方法1)
Component.onCompleted: {
currentIndex = -1
}
//默認焦點
focus: true
spacing: 0
}
}
//操作區容器
Rectangle {
id: itemOp
width: root.width
//指定高度
height: 50
//和上一個區域挨着
anchors.top: itemRoot.bottom
Button {
text: qsTr("Add New Item")
width: parent.width
onClicked: {
var c = listView.model.model.rowCount()
//插入在第一個
listView.model.model.insert(0, {
"description": "Buy a new book " + (c + 1),
"done": false
})
//追加
//listView.model.model.append({ "description": "Buy a new book " + (c + 1), "done": false })
//設置焦點, 否則listView就沒焦點了
listView.focus = true
listView.currentIndex = 0
}
}
}
}
項目地址
和前一篇一樣, 還在同一個項目里面: https://github.com/cnscud/learn/tree/master/qt/editListView