利用QtQuick 2.0(qml)实现叶子节点可以拖动的强大的树形结构

利用QtQuick 2.0(qml)实现叶子节点可以拖动的强大的树形结构

 

引子:Qt是跨平台开发的利器,其中Qml更是利器中的神器。Qml很多人用过,开发自由灵活美观的程序界面是其优点,不管是桌面程序开发还是移动应用开发,都可以胜任,QmlUI对触摸的支持也很好。如果用C++开发内部的有大量运算处理的代码,用qml来开发界面就完美无缺了。

下面通过一个简洁的小例子讲讲用qml开发一个复杂的树形结构,并且该树形结构的叶子可以自由拖动。

下图看起来是一个很普通的树:

但是它的叶子节点是可以自由拖动的,比如可以方便的把这个树拖动成下面这个样子:

非叶子节点既可以展开,也可以收起:

 

利用Qml强大的界面美化功能,我们可以很容易的把这棵树做成一个很美丽的树结构。

 

好了,基本功能就是这样,进入正题,是怎么实现的呢?废话不说,上代码:

 

importQtQuick2.0

importQtQuick.Controls1.0

importQtQuick.Window2.0

importQtQuick.Layouts1.0

importQtQuick.Controls1.0

Rectangle{

   id:objRoot

   objectName:"objRoot"

   width:600

   height:600

   color:"black"

   ListModel{

      id:objModel

      objectName:"objModel"

      Component.onCompleted:{

         objModel.append({"name":qsTr("Zero"),"level":0,"parentModel":objModel,"subNode":[]})//添加第一层的节点

         objModel.append({"name":qsTr("One"),"level":0,"parentModel":objModel,"subNode":[]})

         objModel.append({"name":qsTr("Two"),"level":0,"parentModel":objModel,"subNode":[]})

         objModel.get(1).subNode.append({"name":qsTr("Three"),"level":1,"parentModel":objModel.get(1).subNode,"subNode":[]})//添加第二层的节点

         objModel.get(1).subNode.append({"name":qsTr("Four"),"level":1,"parentModel":objModel.get(1).subNode,"subNode":[]})

         objModel.get(1).subNode.get(0).subNode.append({"name":qsTr("Five"),"level":2,"parentModel":objModel.get(1).subNode.get(0).subNode,"subNode":[]})//添加第三层的节点

      }

   }

   Component{

      id:objRecursiveDelegate

      Column{

        id:objRecursiveColumn

        objectName:"objRecursiveColumn"

        propertyintm_iIndex:model.index//当前节点的序号

        propertyvarm_parentModel:model.parentModel//指向当前节点的父节点

        propertyvarm_currentModel:model

        clip:true

        MouseArea{

           id:objMouseArea

           objectName:"objMouseArea"

           width:objRow.implicitWidth

           height:objRow.implicitHeight

           onDoubleClicked:{

               for(vari=0;i<parent.children.length;++i){

                  if(parent.children[i].objectName!=="objMouseArea"){

                     parent.children[i].visible=!parent.children[i].visible

                  }

               }

           }

           drag.target:objDragRect

           onReleased:{

               if(objDragRect.Drag.target){

                  objDragRect.Drag.drop()

               }

           }

           Row{

               id:objRow

               Item{

                  id:objIndentation

                  height:20

                  width:model.level*20

               }

               Rectangle{//处理拖动后落下操作的矩形

                 id:objDisplayRowRect

                  height:objNodeName.implicitHeight+5

                  width:objCollapsedStateIndicator.width+objNodeName.implicitWidth+5

                  border.color:"green"

                  border.width:2

                  color:"#31312c"

                  DropArea{

                     //keys:[model.parentModel]//设置接受落下操作的范围

                     anchors.fill:parent

                     onEntered:objValidDropIndicator.visible=true

                     onExited:objValidDropIndicator.visible=false

                     onDropped:{//执行落下操作

                        objValidDropIndicator.visible=false

 

                         //判断是否有子节点

                         if(drag.source.m_objTopParent.m_parentModel.get(drag.source.m_objTopParent.m_iIndex).subNode.count===0)//如果没有子节点

                         {

 

                             objRecursiveColumn.m_parentModel.get(model.index).subNode.append({"name":drag.source.m_objTopParent.m_parentModel.get(drag.source.m_objTopParent.m_iIndex).name,

                                                                                                 "level":objRecursiveColumn.m_parentModel.get(model.index).level+1,

                                                                                                 "parentModel":objRecursiveColumn.m_parentModel.get(model.index).subNode,

                                                                                                 "subNode":[]});  //在新的位置添加一个子节点

 

                           //  console.debug(objRecursiveColumn.m_parentModel.get(model.index).name)//拖动的目标位置名称

                             drag.source.m_objTopParent.m_parentModel.remove(drag.source.m_objTopParent.m_iIndex,1);  //移除原来位置的节点

 

                         }

                     }

                     Rectangle{//进入范围显示动画的矩形

                        id:objValidDropIndicator

                        anchors.fill:parent

                        visible:false

                        onVisibleChanged:{

                           visible?objAnim.start():objAnim.stop()

                        }

                        SequentialAnimationoncolor{

                           id:objAnim

                           loops:Animation.Infinite

                           running:false

                           ColorAnimation{from:"#31312c";to:"green";duration:400}

                           ColorAnimation{from:"green";to:"#31312c";duration:400}

                        }

                     }

                  }

                  Rectangle{

                     id:objDragRect

                    propertyvarm_objTopParent:objRecursiveColumn

                     Drag.active:objMouseArea.drag.active

                     //Drag.keys:[model.parentModel]

                     border.color:"magenta"

                     border.width:2

                     opacity:.85

                     states:State{

                        when:objMouseArea.drag.active

                        AnchorChanges{

                           target:objDragRect

                           anchors{horizontalCenter:undefined;verticalCenter:undefined}

                        }

                        ParentChange{

                           target:objDragRect

                           parent:objRoot

                        }

                     }

                     anchors{horizontalCenter:parent.horizontalCenter;verticalCenter:parent.verticalCenter}

                     height:objDisplayRowRect.height

                     width:objDisplayRowRect.width

                     visible:Drag.active

                     color:"red"

                     Text{

                        anchors.fill:parent

                        horizontalAlignment:Text.AlignHCenter

                        verticalAlignment:Text.AlignVCenter

                        text:model.name

                        font{bold:true;pixelSize:18}

                        color:"blue"

                     }

                  }

                  Text{

                     id:objCollapsedStateIndicator

                     anchors{left:parent.left;top:parent.top;bottom:parent.bottom}

                     width:20

                     horizontalAlignment:Text.AlignHCenter

                     verticalAlignment:Text.AlignVCenter

                     text:objRepeater.count>0?objRepeater.visible?qsTr("-"):qsTr("+"):qsTr("")

                     font{bold:true;pixelSize:18}

                     color:"yellow"

                  }

                  Text{

                     id:objNodeName

                    anchors{left:objCollapsedStateIndicator.right;top:parent.top;bottom:parent.bottom}

                     text:model.name

                     color:objRepeater.count>0?"yellow":"white"

                     font{bold:true;pixelSize:18}

                     verticalAlignment:Text.AlignVCenter

                  }

 

               }

           }

        }

        Rectangle{

           id:objSeparator

           anchors{left:parent.left;right:parent.right;}

           height:1

           color:"black"

        }

        Repeater{

           id:objRepeater

           objectName:"objRepeater"

           model:subNode

           delegate:objRecursiveDelegate

        }

      }

   }

   ColumnLayout{

      objectName:"objColLayout"

      anchors.fill:parent

      ScrollView{

        Layout.fillHeight:true

        Layout.fillWidth:true

        ListView{

           objectName:"objListView"

           model:objModel

           delegate:objRecursiveDelegate

           interactive:false

        }

 

      }

   }

}

 

 

关于Qml语言的基本语法就不说啦,教程一大堆,下面详述本程序是怎么实现这个树结构的。

 

首先引入一些必要的包

importQtQuick2.0

importQtQuick.Controls1.0

importQtQuick.Window2.0

importQtQuick.Layouts1.0

importQtQuick.Controls1.0

 

 

 

然后是我们程序的根元素objRoot。在这个根元素中,首先定义了一个ListModel其中存储了树结构所有节点的数据。

 

向其中插入元素,不是本文的重点,在这里不做详述,但是本例中还是使用了相关插入元素的语句

objModel.append({"name":qsTr("Zero"),"level":0,"parentModel":objModel,"subNode":[]})//添加第一层的节点

         objModel.append({"name":qsTr("One"),"level":0,"parentModel":objModel,"subNode":[]})

         objModel.append({"name":qsTr("Two"),"level":0,"parentModel":objModel,"subNode":[]})

         objModel.get(1).subNode.append({"name":qsTr("Three"),"level":1,"parentModel":objModel.get(1).subNode,"subNode":[]})//添加第二层的节点

         objModel.get(1).subNode.append({"name":qsTr("Four"),"level":1,"parentModel":objModel.get(1).subNode,"subNode":[]})

         objModel.get(1).subNode.get(0).subNode.append({"name":qsTr("Five"),"level":2,"parentModel":objModel.get(1).subNode.get(0).subNode,"subNode":[]})//添加第三层的节点

 

其中name是每个节点的名字,level是节点所在的层次,parentModel是其父节点,subNode是其子节点。

 

有了数据,还得显示,在后面定义了一个ListViewListModel中的数据就是通过ListView显示成了一个树结构。ListModel是内容,ListView是形式。

 

那么ListView具体怎么显示ListModel中的每一个节点数据呢,这个是通过另一个元素来定义的,这个元素是idobjRecursiveDelegate的Component元素。这个元素定义了每个节点具体怎么显示,在节点中如果有子节点,那么又要递归的显示子节点,所以其中有一个Repeater元素,起到了递归显示子节点的作用,如下:

Repeater{

           id:objRepeater

           objectName:"objRepeater"

           model:subNode//子节点

           delegate:objRecursiveDelegate//递归调用自己显示子节点

         }

 

双击展开和收起节点,通过下面这个方法来实现:

onDoubleClicked:{

               for(vari=0;i<parent.children.length;++i){

                  if(parent.children[i].objectName!=="objMouseArea")//鼠标区域不能参与,否则鼠标区域一旦隐藏了,就出不来了,哈哈

{

                     parent.children[i].visible=!parent.children[i].visible

                  }

               }

            }

显示的问题解决了,那么拖动的问题如何解决呢

首先,鼠标区域objMouseArea必须定义自己的拖动目标对象:

drag.target:objDragRect

 

被拖动的对象objDragRect在被拖动时将会被显示出来:

Drag.active:objMouseArea.drag.active

visible:Drag.active

同时必须在拖动时取消对objDragRect的锚定,否则是拖不动的,呵呵:

states:State{

                        when:objMouseArea.drag.active

                       AnchorChanges{

                           target:objDragRect

                           anchors{horizontalCenter:undefined;verticalCenter:undefined}

                        }

                        ParentChange{

                           target:objDragRect

                           parent:objRoot

                        }

                     }

 

拖动时,一旦进入了某些区域,UI上会给出提示,objValidDropIndicator这个对象通过一个动画完成了任务:

SequentialAnimationoncolor{

                           id:objAnim

                           loops:Animation.Infinite

                           running:false

                           ColorAnimation{from:"#31312c";to:"green";duration:400}

                           ColorAnimation{from:"green";to:"#31312c";duration:400}

                        }

 

拖动总要有落下的时候,我们的DropArea元素会处理我们把东西放下时应执行的动作:

onDropped:{//执行落下操作

                        objValidDropIndicator.visible=false

 

                         //判断是否有子节点

                         if(drag.source.m_objTopParent.m_parentModel.get(drag.source.m_objTopParent.m_iIndex).subNode.count===0)//如果没有子节点

                         {

 

                             objRecursiveColumn.m_parentModel.get(model.index).subNode.append({"name":drag.source.m_objTopParent.m_parentModel.get(drag.source.m_objTopParent.m_iIndex).name,

                                                                                                  "level":objRecursiveColumn.m_parentModel.get(model.index).level+1,

                                                                                                 "parentModel":objRecursiveColumn.m_parentModel.get(model.index).subNode,

                                                                                                 "subNode":[]});  //在新的位置添加一个子节点

                             drag.source.m_objTopParent.m_parentModel.remove(drag.source.m_objTopParent.m_iIndex,1);  //移除原来位置的节点

 

                         }

                     }

 

每个节点有没有子节点,是通过文字的颜色来区分的:

color:objRepeater.count>0?"yellow":"white"

 

每个非叶子节点,有没有展开也是要区分的:

text:objRepeater.count>0?objRepeater.visible?qsTr("-"):qsTr("+"):qsTr("")

 

分割线objSeparator要不要真的无所谓

 

好了程序大体上就是这么工作的,其中定义的一些属性,如果你看懂了上面的方法代码,很快就能理解这些属性是什么意思了。

 

是不是很简单呢,如果你有任何问题,欢迎讨论。

阅读更多

扫码向博主提问

明天继续

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • C/C++软件开发
  • Android 开发
  • 图形图像处理
  • 音视频处理
去开通我的Chat快问
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页