This commit is contained in:
朱子楚\zhuzi 2023-09-15 01:28:03 +08:00
parent c47fa5ebc7
commit e6d9de34ea
15 changed files with 295 additions and 890 deletions

View File

@ -28,7 +28,6 @@ Window {
FluApp.init(app)
FluTheme.darkMode = FluThemeType.System
FluTheme.enableAnimation = true
FluTheme.nativeText = true
FluApp.routes = {
"/":"qrc:/example/qml/window/MainWindow.qml",
"/about":"qrc:/example/qml/window/AboutWindow.qml",

View File

@ -302,7 +302,7 @@ FluObject{
onTap:{ navigationView.push(url) }
}
FluPaneItem{
title:"TreeView"
title:"TreeView(Todo)"
url:"qrc:/example/qml/page/T_TreeView.qml"
onDropped:{ FluApp.navigate("/pageWindow",{title:title,url:url}) }
onTap:{ navigationView.push(url) }

View File

@ -9,49 +9,10 @@ FluScrollablePage {
title:"TreeView"
function randomName() {
var names = ["张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十"]
return names[Math.floor(Math.random() * names.length)]
}
function randomCompany() {
var companies = ["阿里巴巴", "腾讯", "百度", "京东", "华为", "小米", "字节跳动", "美团", "滴滴"]
return companies[Math.floor(Math.random() * companies.length)]
}
function randomDepartment() {
var departments = ["技术部", "销售部", "市场部", "人事部", "财务部", "客服部", "产品部", "设计部", "运营部"]
return departments[Math.floor(Math.random() * departments.length)]
}
function createEmployee() {
var name = randomName()
return tree_view.createItem(name, false)
}
function createSubtree(numEmployees) {
var employees = []
for (var i = 0; i < numEmployees; i++) {
employees.push(createEmployee())
}
return tree_view.createItem(randomDepartment(), true, employees)
}
function createOrg(numLevels, numSubtrees, numEmployees) {
if (numLevels === 0) {
return []
}
var subtrees = []
for (var i = 0; i < numSubtrees; i++) {
subtrees.push(createSubtree(numEmployees))
}
return [tree_view.createItem(randomCompany(), true, subtrees)].concat(createOrg(numLevels - 1, numSubtrees, numEmployees))
}
function treeData(){
const dig = (path = '0', level = 4) => {
const list = [];
for (let i = 0; i < 10; i += 1) {
for (let i = 0; i < 6; i += 1) {
const key = `${path}-${i}`;
const treeNode = {
title: key,
@ -68,65 +29,16 @@ FluScrollablePage {
}
FluArea{
id:layout_actions
Layout.fillWidth: true
Layout.topMargin: 20
height: 50
Layout.topMargin: 10
paddings: 10
RowLayout{
spacing: 14
FluDropDownButton{
id:btn_selection_model
Layout.preferredWidth: 140
text:"None"
FluMenuItem{
text:"None"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.Equal
}
}
FluMenuItem{
text:"Single"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.SizeToContent
}
}
FluMenuItem{
text:"Muiltple"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.Compact
}
}
}
FluFilledButton{
text:"获取选中的数据"
onClicked: {
if(tree_view.selectionMode === FluTreeViewType.None){
showError("当前非选择模式,没有选中的数据")
}
if(tree_view.selectionMode === FluTreeViewType.Single){
if(!tree_view.signleData()){
showError("没有选中数据")
return
}
showSuccess(tree_view.signleData().text)
}
if(tree_view.selectionMode === FluTreeViewType.Multiple){
if(tree_view.multipData().length===0){
showError("没有选中数据")
return
}
var info = []
tree_view.multipData().map((value)=>info.push(value.text))
showSuccess(info.join(","))
}
}
}
height: 60
FluText{
text:"共计:%1条数据".arg(tree_view.count())
anchors.verticalCenter: parent.verticalCenter
}
}
FluArea{
Layout.fillWidth: true
Layout.topMargin: 10
@ -140,25 +52,6 @@ FluScrollablePage {
left:parent.left
bottom:parent.bottom
}
onItemClicked:
(model)=>{
showSuccess(model.text)
}
Component.onCompleted: {
var org = createOrg(3, 3, 3)
createItem()
updateData(org)
}
}
FluTreeView2{
id:tree_view_2
width:240
anchors{
top:parent.top
bottom:parent.bottom
}
x:260
Component.onCompleted: {
var data = treeData()
dataSource = data
@ -174,15 +67,10 @@ FluScrollablePage {
width:240
height:600
Component.onCompleted: {
var datas = []
datas.push(createItem("Node1",false))
datas.push(createItem("Node2",false))
datas.push(createItem("Node2",true,[createItem("Node2-1",false),createItem("Node2-2",false)]))
updateData(datas)
var data = treeData()
dataSource = data
}
}
'
}
}

View File

@ -28,7 +28,6 @@ Window {
FluApp.init(app)
FluTheme.darkMode = FluThemeType.System
FluTheme.enableAnimation = true
FluTheme.nativeText = true
FluApp.routes = {
"/":"qrc:/example/qml/window/MainWindow.qml",
"/about":"qrc:/example/qml/window/AboutWindow.qml",

View File

@ -302,7 +302,7 @@ FluObject{
onTap:{ navigationView.push(url) }
}
FluPaneItem{
title:"TreeView"
title:"TreeView(Todo)"
url:"qrc:/example/qml/page/T_TreeView.qml"
onDropped:{ FluApp.navigate("/pageWindow",{title:title,url:url}) }
onTap:{ navigationView.push(url) }

View File

@ -10,105 +10,36 @@ FluScrollablePage {
title:"TreeView"
function randomName() {
var names = ["张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十"]
return names[Math.floor(Math.random() * names.length)]
}
function randomCompany() {
var companies = ["阿里巴巴", "腾讯", "百度", "京东", "华为", "小米", "字节跳动", "美团", "滴滴"]
return companies[Math.floor(Math.random() * companies.length)]
}
function randomDepartment() {
var departments = ["技术部", "销售部", "市场部", "人事部", "财务部", "客服部", "产品部", "设计部", "运营部"]
return departments[Math.floor(Math.random() * departments.length)]
}
function createEmployee() {
var name = randomName()
return tree_view.createItem(name, false)
}
function createSubtree(numEmployees) {
var employees = []
for (var i = 0; i < numEmployees; i++) {
employees.push(createEmployee())
}
return tree_view.createItem(randomDepartment(), true, employees)
}
function createOrg(numLevels, numSubtrees, numEmployees) {
if (numLevels === 0) {
return []
}
var subtrees = []
for (var i = 0; i < numSubtrees; i++) {
subtrees.push(createSubtree(numEmployees))
}
return [tree_view.createItem(randomCompany(), true, subtrees)].concat(createOrg(numLevels - 1, numSubtrees, numEmployees))
function treeData(){
const dig = (path = '0', level = 4) => {
const list = [];
for (let i = 0; i < 6; i += 1) {
const key = `${path}-${i}`;
const treeNode = {
title: key,
key,
};
if (level > 0) {
treeNode.children = dig(key, level - 1);
}
list.push(treeNode);
}
return list;
};
return dig();
}
FluArea{
id:layout_actions
Layout.fillWidth: true
Layout.topMargin: 20
height: 50
Layout.topMargin: 10
paddings: 10
RowLayout{
spacing: 14
FluDropDownButton{
id:btn_selection_model
Layout.preferredWidth: 140
text:"None"
FluMenuItem{
text:"None"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.Equal
}
}
FluMenuItem{
text:"Single"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.SizeToContent
}
}
FluMenuItem{
text:"Muiltple"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.Compact
}
}
}
FluFilledButton{
text:"获取选中的数据"
onClicked: {
if(tree_view.selectionMode === FluTreeViewType.None){
showError("当前非选择模式,没有选中的数据")
}
if(tree_view.selectionMode === FluTreeViewType.Single){
if(!tree_view.signleData()){
showError("没有选中数据")
return
}
showSuccess(tree_view.signleData().text)
}
if(tree_view.selectionMode === FluTreeViewType.Multiple){
if(tree_view.multipData().length===0){
showError("没有选中数据")
return
}
var info = []
tree_view.multipData().map((value)=>info.push(value.text))
showSuccess(info.join(","))
}
}
}
height: 60
FluText{
text:"共计:%1条数据".arg(tree_view.count())
anchors.verticalCenter: parent.verticalCenter
}
}
FluArea{
Layout.fillWidth: true
Layout.topMargin: 10
@ -122,15 +53,9 @@ FluScrollablePage {
left:parent.left
bottom:parent.bottom
}
onItemClicked:
(model)=>{
showSuccess(model.text)
}
Component.onCompleted: {
var org = createOrg(3, 3, 3)
createItem()
updateData(org)
var data = treeData()
dataSource = data
}
}
}
@ -143,15 +68,10 @@ FluScrollablePage {
width:240
height:600
Component.onCompleted: {
var datas = []
datas.push(createItem("Node1",false))
datas.push(createItem("Node2",false))
datas.push(createItem("Node2",true,[createItem("Node2-1",false),createItem("Node2-2",false)]))
updateData(datas)
var data = treeData()
dataSource = data
}
}
'
}
}

View File

@ -148,6 +148,7 @@ void FluentUI::registerTypes(const char *uri){
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluStaggeredView.qml"),uri,major,minor,"FluStaggeredView");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluProgressButton.qml"),uri,major,minor,"FluProgressButton");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluLoadingButton.qml"),uri,major,minor,"FluLoadingButton");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluTreeItem.qml"),uri,major,minor,"FluTreeItem");
qmlRegisterUncreatableMetaObject(Fluent_Awesome::staticMetaObject, uri,major,minor,"FluentIcons", "Access to enums & flags only");
qmlRegisterUncreatableMetaObject(FluHttpType::staticMetaObject, uri,major,minor,"FluHttpType", "Access to enums & flags only");

View File

@ -0,0 +1,21 @@
import QtQuick 2.15
QtObject {
property string key
property string title
property var children: []
property int depth: 0
property bool isExpanded: true
property var __parent
property bool __expanded:{
var p = __parent;
while (p) {
if(!p.isExpanded){
return false
}
p = p.__parent;
}
return true
}
property int index: 0
}

View File

@ -2,291 +2,143 @@ import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import Qt.labs.qmlmodels 1.0
import FluentUI 1.0
Item {
property int selectionMode: FluTreeViewType.None
property var currentElement
property var currentParentElement
property var rootModel: tree_model.get(0).items
signal itemClicked(var item)
id:root
ListModel{
id:tree_model
ListElement{
text: "根节点"
expanded:true
items:[]
key:"123456"
multipSelected:false
multipIndex:0
multipParentKey:""
property var dataSource
id:control
QtObject {
id:d
signal refreshLayout()
onRefreshLayout: {
table_view.forceLayout()
}
}
Component{
id: delegate_root
Column{
width: calculateWidth()
property var itemModel: model
Repeater{
id: repeater_first_level
model: items
delegate: delegate_items
function handleTree(treeData) {
var comItem = Qt.createComponent("FluTreeItem.qml");
if (comItem.status !== Component.Ready) {
return
}
function calculateWidth(){
var w = 0;
for(var i = 0; i < repeater_first_level.count; i++) {
var child = repeater_first_level.itemAt(i)
if(w < child.width_hint){
w = child.width_hint;
var stack = []
var rawData = []
for (var item of treeData) {
stack.push({node:item,depth:0,isExpanded:true,__parent:undefined})
}
stack = stack.reverse()
var index =0
while (stack.length > 0) {
const { node, depth,isExpanded,__parent} = stack.pop();
node.depth = depth;
node.isExpanded = isExpanded;
node.__parent = __parent;
var objItem = comItem.createObject(table_view);
objItem.title = node.title
objItem.key = node.key
objItem.depth = node.depth
objItem.isExpanded = node.isExpanded
objItem.__parent = node.__parent
objItem.children = node.children
objItem.index = index
index = index + 1;
rawData.push({display:objItem})
if (node.children && node.children.length > 0) {
const children = node.children.reverse();
for (const child of children) {
stack.push({ node: child, depth: depth + 1,isExpanded:true,__parent:objItem});
}
}
return w;
}
return rawData
}
}
Component{
id:delegate_items
Column{
id:item_layout
property real level: (mapToItem(list_root,0,0).x+list_root.contentX)/0.001
property var text: model.text??"Item"
property bool hasChild : (model.items !== undefined) && (model.items.count !== 0)
property var items: model.items??[]
property var expanded: model.expanded??true
property int width_hint: calculateWidth()
property bool singleSelected: currentElement === model
property var itemModel: model
function calculateWidth(){
var w = Math.max(list_root.width, item_layout_row.implicitWidth + 10);
if(expanded){
for(var i = 0; i < repeater_items.count; i++) {
var child = repeater_items.itemAt(i)
if(w < child.width_hint){
w = child.width_hint;
}
}
}
return w;
onDataSourceChanged: {
table_model.clear()
var data = d.handleTree(dataSource)
table_model.rows = data
table_view.forceLayout()
console.debug("共计:%1条数据".arg(table_model.rowCount))
}
TableModel {
id:table_model
TableModelColumn { display: "display" }
}
TableView{
id:table_view
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: table_model
clip: true
anchors.fill: parent
rowHeightProvider: function(row) {
if(table_model.getRow(row).display.__expanded){
return 38
}
Item{
id:item_layout_rect
width: list_root.contentWidth
height: item_layout_row.implicitHeight
Rectangle{
anchors.fill: parent
anchors.margins: 2
color:{
if(FluTheme.dark){
if(item_layout.singleSelected && selectionMode === FluTreeViewType.Single){
return Qt.rgba(62/255,62/255,62/255,1)
}
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(62/255,62/255,62/255,1):Qt.rgba(0,0,0,0)
}else{
if(item_layout.singleSelected && selectionMode === FluTreeViewType.Single){
return Qt.rgba(0,0,0,0.06)
}
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(0,0,0,0.03):Qt.rgba(0,0,0,0)
}
}
Rectangle{
width: 3
color:FluTheme.primaryColor.dark
visible: item_layout.singleSelected && (selectionMode === FluTreeViewType.Single)
radius: 3
height: 20
anchors{
left: parent.left
verticalCenter: parent.verticalCenter
return 0
}
delegate: Item {
implicitWidth: 46 + item_layout_text.width + 30*display.depth
RowLayout{
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 14 + 30*display.depth
FluIcon{
rotation: display.isExpanded?0:-90
iconSource:FluentIcons.ChevronDown
iconSize: 15
Layout.alignment: Qt.AlignVCenter
opacity: {
if(display.children){
return true
}
return false
}
MouseArea{
id:item_layout_mouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
item_layout_rect.onClickItem()
display.isExpanded = !display.isExpanded
d.refreshLayout()
}
}
}
function onClickItem(){
if(selectionMode === FluTreeViewType.None){
itemClicked(model)
Rectangle{
id:item_layout_text
radius: 4
Layout.preferredWidth: item_text.implicitWidth+14
Layout.preferredHeight:item_text.implicitHeight+14
Layout.alignment: Qt.AlignVCenter
HoverHandler{
id:item_hover_text
}
if(selectionMode === FluTreeViewType.Single){
currentElement = model
if(item_layout.parent.parent.parent.itemModel){
currentParentElement = item_layout.parent.parent.parent.itemModel
color: {
if(FluTheme.dark){
if(item_hover_text.hovered){
return Qt.rgba(1,1,1,0.03)
}
return Qt.rgba(0,0,0,0)
}else{
if(item_layout.parent.itemModel){
currentParentElement = item_layout.parent.itemModel
}
}
itemClicked(model)
}
if(selectionMode === FluTreeViewType.Multiple){
}
}
RowLayout{
id:item_layout_row
anchors.verticalCenter: item_layout_rect.verticalCenter
Item{
width: 15*level
Layout.alignment: Qt.AlignVCenter
}
FluCheckBox{
id:item_layout_checkbox
text:""
checked: itemModel.multipSelected
visible: selectionMode === FluTreeViewType.Multiple
Layout.leftMargin: 5
function refreshCheckBox(){
const stack = [tree_model.get(0)];
const result = [];
while (stack.length > 0) {
const curr = stack.pop();
result.unshift(curr);
if (curr.items) {
for(var i=0 ; i<curr.items.count ; i++){
curr.items.setProperty(i,"multipIndex",i)
curr.items.setProperty(i,"multipParentKey",curr.key)
stack.push(curr.items.get(i));
}
}
}
for(var j=0 ; j<result.length-1 ; j++){
var item = result[j]
let obj = result.find(function(o) {
return o.key === item.multipParentKey;
});
if((item.items !== undefined) && (item.items.count !== 0)){
var items = item.items
for(var k=0 ; k<items.count ; k++){
if(items.get(k).multipSelected === false){
obj.items.setProperty(item.multipIndex,"multipSelected",false)
break
}
obj.items.setProperty(item.multipIndex,"multipSelected",true)
}
}
}
}
clickListener:function(){
if(hasChild){
const stack = [itemModel];
while (stack.length > 0) {
const curr = stack.pop();
if (curr.items) {
for(var i=0 ; i<curr.items.count ; i++){
curr.items.setProperty(i,"multipSelected",!itemModel.multipSelected)
stack.push(curr.items.get(i));
}
}
}
refreshCheckBox()
}else{
itemModel.multipSelected = !itemModel.multipSelected
refreshCheckBox()
}
}
}
FluIconButton{
id:item_layout_expanded
color:"#00000000"
opacity: item_layout.hasChild
onClicked: {
if(!item_layout.hasChild){
item_layout_rect.onClickItem()
return
}
model.expanded = !model.expanded
}
contentItem: FluIcon{
rotation: item_layout.expanded?0:-90
iconSource:FluentIcons.ChevronDown
iconSize: 15
Behavior on rotation {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
if(item_hover_text.hovered){
return Qt.rgba(0,0,0,0.03)
}
return Qt.rgba(0,0,0,0)
}
}
FluText {
text: item_layout.text
Layout.alignment: Qt.AlignVCenter
topPadding: 7
bottomPadding: 7
id:item_text
text: display.title
anchors.centerIn: parent
}
}
}
Item{
id:item_sub
visible: {
if(!hasChild){
return false
}
return item_layout.expanded??false
}
width: item_sub_layout.implicitWidth
height: item_sub_layout.implicitHeight
x:0.001
Column{
id: item_sub_layout
Repeater{
id:repeater_items
model: item_layout.items
delegate: delegate_items
MouseArea{
anchors.fill: parent
onClicked: {
d.refreshLayout()
}
}
}
}
}
}
ListView {
id: list_root
anchors.fill: parent
delegate: delegate_root
contentWidth: contentItem.childrenRect.width
model: tree_model
flickableDirection: Flickable.HorizontalAndVerticalFlick
clip: true
boundsBehavior: ListView.StopAtBounds
ScrollBar.vertical: FluScrollBar {}
ScrollBar.horizontal: FluScrollBar { }
function count(){
return table_model.rowCount
}
function updateData(items){
rootModel.clear()
rootModel.append(items)
}
function signleData(){
return currentElement
}
function multipData(){
const stack = [tree_model.get(0)];
const result = [];
while (stack.length > 0) {
const curr = stack.pop();
if(curr.multipSelected){
result.push(curr)
}
for(var i=0 ; i<curr.items.count ; i++){
stack.push(curr.items.get(i));
}
}
return result
}
function createItem(text="",expanded=true,items=[],data={}){
return {text:text,expanded:expanded,items:items,key:uniqueRandom(),multipSelected:false,multipIndex:0,multipParentKey:"",data:data};
}
function uniqueRandom() {
var timestamp = Date.now();
var random = Math.floor(Math.random() * 1000000);
return timestamp.toString() + random.toString();
}
}

View File

@ -96,4 +96,5 @@ FluRangeSlider 1.0 Controls/FluRangeSlider.qml
FluStaggeredView 1.0 Controls/FluStaggeredView.qml
FluProgressButton 1.0 Controls/FluProgressButton.qml
FluLoadingButton 1.0 Controls/FluLoadingButton.qml
FluTreeItem 1.0 Controls/FluTreeItem.qml
plugin fluentuiplugin

View File

@ -96,5 +96,6 @@
<file>FluentUI/Controls/ColorPicker/Content/PanelBorder.qml</file>
<file>FluentUI/Controls/ColorPicker/Content/SBPicker.qml</file>
<file>FluentUI/Controls/FluLoadingButton.qml</file>
<file>FluentUI/Controls/FluTreeItem.qml</file>
</qresource>
</RCC>

View File

@ -21,6 +21,7 @@ T.ScrollBar {
property int minLine : 2
property int maxLine : 6
}
z: horizontal? 10 : 20
verticalPadding : vertical ? 15 : 3
horizontalPadding : horizontal ? 15 : 3
background: Rectangle{

View File

@ -7,4 +7,15 @@ QtObject {
property int depth: 0
property bool isExpanded: true
property var __parent
property bool __expanded:{
var p = __parent;
while (p) {
if(!p.isExpanded){
return false
}
p = p.__parent;
}
return true
}
property int index: 0
}

View File

@ -2,291 +2,146 @@ import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
import FluentUI
Item {
property int selectionMode: FluTreeViewType.None
property var currentElement
property var currentParentElement
property var rootModel: tree_model.get(0).items
signal itemClicked(var item)
id:root
ListModel{
id:tree_model
ListElement{
text: "根节点"
expanded:true
items:[]
key:"123456"
multipSelected:false
multipIndex:0
multipParentKey:""
property var dataSource
id:control
QtObject {
id:d
signal refreshLayout()
onRefreshLayout: {
table_view.forceLayout()
}
}
Component{
id: delegate_root
Column{
width: calculateWidth()
property var itemModel: model
Repeater{
id: repeater_first_level
model: items
delegate: delegate_items
function handleTree(treeData) {
var comItem = Qt.createComponent("FluTreeItem.qml");
if (comItem.status !== Component.Ready) {
return
}
function calculateWidth(){
var w = 0;
for(var i = 0; i < repeater_first_level.count; i++) {
var child = repeater_first_level.itemAt(i)
if(w < child.width_hint){
w = child.width_hint;
var stack = []
var rawData = []
for (var item of treeData) {
stack.push({node:item,depth:0,isExpanded:true,__parent:undefined})
}
stack = stack.reverse()
var index =0
while (stack.length > 0) {
const { node, depth,isExpanded,__parent} = stack.pop();
node.depth = depth;
node.isExpanded = isExpanded;
node.__parent = __parent;
var objItem = comItem.createObject(table_view);
objItem.title = node.title
objItem.key = node.key
objItem.depth = node.depth
objItem.isExpanded = node.isExpanded
objItem.__parent = node.__parent
objItem.children = node.children
objItem.index = index
index = index + 1;
rawData.push({display:objItem})
if (node.children && node.children.length > 0) {
const children = node.children.reverse();
for (const child of children) {
stack.push({ node: child, depth: depth + 1,isExpanded:true,__parent:objItem});
}
}
return w;
}
return rawData
}
}
Component{
id:delegate_items
Column{
id:item_layout
property real level: (mapToItem(list_root,0,0).x+list_root.contentX)/0.001
property var text: model.text??"Item"
property bool hasChild : (model.items !== undefined) && (model.items.count !== 0)
property var items: model.items??[]
property var expanded: model.expanded??true
property int width_hint: calculateWidth()
property bool singleSelected: currentElement === model
property var itemModel: model
function calculateWidth(){
var w = Math.max(list_root.width, item_layout_row.implicitWidth + 10);
if(expanded){
for(var i = 0; i < repeater_items.count; i++) {
var child = repeater_items.itemAt(i)
if(w < child.width_hint){
w = child.width_hint;
onDataSourceChanged: {
table_model.clear()
var data = d.handleTree(dataSource)
table_model.rows = data
table_view.forceLayout()
console.debug("共计:%1条数据".arg(table_model.rowCount))
}
TableModel {
id:table_model
TableModelColumn { display: "display" }
}
ListView{
anchors.fill: parent
TableView{
id:table_view
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: table_model
clip: true
anchors.fill: parent
rowHeightProvider: function(row) {
if(table_model.getRow(row).display.__expanded){
return 38
}
return 0
}
delegate: Item {
implicitWidth: 46 + item_layout_text.width + 30*display.depth
RowLayout{
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 14 + 30*display.depth
FluIcon{
rotation: display.isExpanded?0:-90
iconSource:FluentIcons.ChevronDown
iconSize: 15
Layout.alignment: Qt.AlignVCenter
opacity: {
if(display.children){
return true
}
return false
}
}
}
return w;
}
Item{
id:item_layout_rect
width: list_root.contentWidth
height: item_layout_row.implicitHeight
Rectangle{
anchors.fill: parent
anchors.margins: 2
color:{
if(FluTheme.dark){
if(item_layout.singleSelected && selectionMode === FluTreeViewType.Single){
return Qt.rgba(62/255,62/255,62/255,1)
MouseArea{
anchors.fill: parent
onClicked: {
display.isExpanded = !display.isExpanded
d.refreshLayout()
}
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(62/255,62/255,62/255,1):Qt.rgba(0,0,0,0)
}else{
if(item_layout.singleSelected && selectionMode === FluTreeViewType.Single){
return Qt.rgba(0,0,0,0.06)
}
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(0,0,0,0.03):Qt.rgba(0,0,0,0)
}
}
Rectangle{
width: 3
color:FluTheme.primaryColor.dark
visible: item_layout.singleSelected && (selectionMode === FluTreeViewType.Single)
radius: 3
height: 20
anchors{
left: parent.left
verticalCenter: parent.verticalCenter
}
}
MouseArea{
id:item_layout_mouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
item_layout_rect.onClickItem()
}
}
}
function onClickItem(){
if(selectionMode === FluTreeViewType.None){
itemClicked(model)
}
if(selectionMode === FluTreeViewType.Single){
currentElement = model
if(item_layout.parent.parent.parent.itemModel){
currentParentElement = item_layout.parent.parent.parent.itemModel
}else{
if(item_layout.parent.itemModel){
currentParentElement = item_layout.parent.itemModel
}
}
itemClicked(model)
}
if(selectionMode === FluTreeViewType.Multiple){
}
}
RowLayout{
id:item_layout_row
anchors.verticalCenter: item_layout_rect.verticalCenter
Item{
width: 15*level
id:item_layout_text
radius: 4
Layout.preferredWidth: item_text.implicitWidth+14
Layout.preferredHeight:item_text.implicitHeight+14
Layout.alignment: Qt.AlignVCenter
}
FluCheckBox{
id:item_layout_checkbox
text:""
checked: itemModel.multipSelected
visible: selectionMode === FluTreeViewType.Multiple
Layout.leftMargin: 5
function refreshCheckBox(){
const stack = [tree_model.get(0)];
const result = [];
while (stack.length > 0) {
const curr = stack.pop();
result.unshift(curr);
if (curr.items) {
for(var i=0 ; i<curr.items.count ; i++){
curr.items.setProperty(i,"multipIndex",i)
curr.items.setProperty(i,"multipParentKey",curr.key)
stack.push(curr.items.get(i));
}
}
}
for(var j=0 ; j<result.length-1 ; j++){
var item = result[j]
let obj = result.find(function(o) {
return o.key === item.multipParentKey;
});
if((item.items !== undefined) && (item.items.count !== 0)){
var items = item.items
for(var k=0 ; k<items.count ; k++){
if(items.get(k).multipSelected === false){
obj.items.setProperty(item.multipIndex,"multipSelected",false)
break
}
obj.items.setProperty(item.multipIndex,"multipSelected",true)
}
}
}
HoverHandler{
id:item_hover_text
}
clickListener:function(){
if(hasChild){
const stack = [itemModel];
while (stack.length > 0) {
const curr = stack.pop();
if (curr.items) {
for(var i=0 ; i<curr.items.count ; i++){
curr.items.setProperty(i,"multipSelected",!itemModel.multipSelected)
stack.push(curr.items.get(i));
}
}
color: {
if(FluTheme.dark){
if(item_hover_text.hovered){
return Qt.rgba(1,1,1,0.03)
}
refreshCheckBox()
return Qt.rgba(0,0,0,0)
}else{
itemModel.multipSelected = !itemModel.multipSelected
refreshCheckBox()
}
}
}
FluIconButton{
id:item_layout_expanded
color:"#00000000"
opacity: item_layout.hasChild
onClicked: {
if(!item_layout.hasChild){
item_layout_rect.onClickItem()
return
}
model.expanded = !model.expanded
}
contentItem: FluIcon{
rotation: item_layout.expanded?0:-90
iconSource:FluentIcons.ChevronDown
iconSize: 15
Behavior on rotation {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
if(item_hover_text.hovered){
return Qt.rgba(0,0,0,0.03)
}
return Qt.rgba(0,0,0,0)
}
}
FluText {
id:item_text
text: display.title
anchors.centerIn: parent
}
MouseArea{
anchors.fill: parent
onClicked: {
d.refreshLayout()
}
}
}
FluText {
text: item_layout.text
Layout.alignment: Qt.AlignVCenter
topPadding: 7
bottomPadding: 7
}
}
}
Item{
id:item_sub
visible: {
if(!hasChild){
return false
}
return item_layout.expanded??false
}
width: item_sub_layout.implicitWidth
height: item_sub_layout.implicitHeight
x:0.001
Column{
id: item_sub_layout
Repeater{
id:repeater_items
model: item_layout.items
delegate: delegate_items
}
}
}
}
}
ListView {
id: list_root
anchors.fill: parent
delegate: delegate_root
contentWidth: contentItem.childrenRect.width
model: tree_model
flickableDirection: Flickable.HorizontalAndVerticalFlick
clip: true
boundsBehavior: ListView.StopAtBounds
ScrollBar.vertical: FluScrollBar {}
ScrollBar.horizontal: FluScrollBar { }
function count(){
return table_model.rowCount
}
function updateData(items){
rootModel.clear()
rootModel.append(items)
}
function signleData(){
return currentElement
}
function multipData(){
const stack = [tree_model.get(0)];
const result = [];
while (stack.length > 0) {
const curr = stack.pop();
if(curr.multipSelected){
result.push(curr)
}
for(var i=0 ; i<curr.items.count ; i++){
stack.push(curr.items.get(i));
}
}
return result
}
function createItem(text="",expanded=true,items=[],data={}){
return {text:text,expanded:expanded,items:items,key:uniqueRandom(),multipSelected:false,multipIndex:0,multipParentKey:"",data:data};
}
function uniqueRandom() {
var timestamp = Date.now();
var random = Math.floor(Math.random() * 1000000);
return timestamp.toString() + random.toString();
}
}

View File

@ -1,144 +0,0 @@
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
import FluentUI
Rectangle {
color:"#33000000"
property var dataSource
clip: true
function iterateTree(treeData) {
var comItem = Qt.createComponent("FluTreeItem.qml");
if (comItem.status !== Component.Ready) {
return
}
const stack = [{ node: treeData[0], depth: 0 , isExpanded: true, __parent: undefined}];
while (stack.length > 0) {
const { node, depth,isExpanded,__parent} = stack.pop();
node.depth = depth;
node.isExpanded = isExpanded;
node.__parent = __parent;
var objItem = comItem.createObject(table_view);
objItem.title = node.title
objItem.key = node.key
objItem.depth = node.depth
objItem.isExpanded = node.isExpanded
objItem.__parent = node.__parent
objItem.children = node.children
table_model.appendRow({itemData:objItem})
if (node.children && node.children.length > 0) {
const children = node.children.reverse();
for (const child of children) {
stack.push({ node: child, depth: depth + 1,isExpanded:true,__parent:objItem});
}
}
}
console.debug("共计:%1条数据".arg(table_model.rowCount))
}
onDataSourceChanged: {
table_model.clear()
iterateTree(dataSource)
}
TableModel {
id:table_model
TableModelColumn { display: "itemData" }
}
Component{
id:com_item
Rectangle {
implicitWidth: 120 + 50*itemData.depth
implicitHeight: 38
RowLayout{
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin:14+50*itemData.depth
FluIcon{
rotation: itemData.isExpanded?0:-90
iconSource:FluentIcons.ChevronDown
iconSize: 15
Layout.alignment: Qt.AlignVCenter
Behavior on rotation {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
}
opacity: {
if(itemData.children){
return true
}
return false
}
MouseArea{
anchors.fill: parent
onClicked: {
itemData.isExpanded = !itemData.isExpanded
}
}
}
Rectangle{
radius: 4
Layout.preferredWidth: item_text.width+14
Layout.preferredHeight:item_text.height+14
Layout.alignment: Qt.AlignVCenter
HoverHandler{
id:item_hover_text
}
color: {
if(FluTheme.dark){
if(item_hover_text.hovered){
return Qt.rgba(1,1,1,0.03)
}
return Qt.rgba(0,0,0,0)
}else{
if(item_hover_text.hovered){
return Qt.rgba(0,0,0,0.03)
}
return Qt.rgba(0,0,0,0)
}
}
Text {
id:item_text
text: itemData.title
anchors.centerIn: parent
}
}
}
}
}
TableView{
id:table_view
anchors.fill: parent
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: table_model
delegate: Loader{
property var itemData: display
sourceComponent: {
if(!itemData.__parent){
return com_item
}
if(itemData.__parent.isExpanded){
return com_item
}
return undefined
}
}
}
}