制作完美的 Qt 三态树列表

5 篇文章 0 订阅

第一节 建立工程-种树

  不管你是用纯代码写的窗口,还是通过 Qt Creator 或者 Visual Studio 的可视化环境堆出来的东东,你必须保证:有一个窗口里面放了个树形列表(QTreeWidget),这个树形列表里已经长满了“枝丫和叶子”(项目和子项目),首先保证你这个东西在没搞成三态树之前能正常运行,否则后果自负,嘿!
  我用的是Qt SDK V1.2.1(内含 Qt 4.8.1、Qt Creator 2.4.1,一个开发基础,一个开发工具,官方网站上有下)。
打开 Qt Creator,菜单“文件(F)”,选择“新建文件或工程...”,或者直接按“Ctrl+N”,弹出“新建”对话框。在“新建”对话框里选择“Qt 控件 项目”中的“Qt Gui 应用”,然后单击对话框右下角的“选择(C)...”按钮,弹出“Qt Gui 应用”设置向导对话框,在“名称”栏输入“TriStateTree”,当然你也可以用你喜欢名字,最后狂点“下一步(N)>”,并“完成(F)”生成预置工程。
参照一下你的工程,大概有下图中内容就行了,有些朋友用的老的English版 Qt Creator,自己“看着办”吧。

  
  三态树三态树,当然要先“种”棵树咯,双击打开“mainwindow.ui”,来到窗口可视化编辑界面,在左边的控件栏中拖一个“Tree Widget”(注意不是“Tree View”,原因我不解释),到你的窗口中,随便拉拉吧,那玩意儿太小,或者用布局工具整整,鉴于这里不是重点,我就不说了,直接放图,给大家看成品。
  
  接下来说说里面的那些“项目”、“属性”呀怎么添加的,还有为什么“树形列表”右边多了个“属性列表”。
  对拖到窗口中的“树形列表”(QTreeWidget)单击“右键”,在弹出菜单中选择“编辑项目...”,弹出的“编辑树窗口部件”对话框如下图。

  
  这么人性化的编辑界面,不用我说太多了吧,自己整吧。如果你不嫌烦,并且为了体验成功的喜悦,你可以像我一样整多些、复杂些,呵呵。重点说说那个复选框“CheckBox”是怎么出来的,看到“属性”栏中的“checkState”属性了么,你用鼠标动动它看看。嘿,看到效果了吧,对了,你可以在这里初始化复选框的状态,我全选的“UnChecked”。另外讲点儿小知识,如果你要取消复选框,就点一下“checkState”属性编辑框右边的红色小箭头按钮,这个按钮的意思是“恢复默认”。
  好了,按“确定”回到你的工程界面吧,现在你的基础打好了,座子安好了,试着运行一下,看看有木有问题。当然,只要你认真照做了,问题是不会有的,唯一的问题就是你的树形列表是二态的,而且父、子节点不能联动,觉得不爽吧,不爽就继续阅读下文。

第二节 前期准备-嫁接

  看到这篇文章的朋友除了碰巧就是有意的(废话),都应该知道Qt程序各对象间主要通过信号与槽(函数)的机制进行通信。树形列表(QTreeWidget)中第1列(程序内部的序号为第0列)中的复选框发生变化后会产生多种信号,这里我们用的是“itemChanged(QTreeWidgetItem*, int)”这个信号,它代表了任意的项目发生了,例如复选框状态改变、文字改变、增删图标等等——你所看得到的变化。接下来要做的就是将这个“信号”与它的“槽(函数)”连接起来,那么,“槽(函数)”在哪里捏?果断放图先:对“树形列表”(QTreeWidget)点击右键,在弹出菜单中选择“转到槽...”,在“转到槽”对话框中选择“itemChanged(QTreeWidgetItem*, int)”信号,然后点击“确定”。
  
  完成上面操作后,程序会自动在头文件(mainwindow.h)和实现文件(mainwindow.cpp)中生成相应的,
  函数声明:
------mainwindow.h---------------

private slots:

    void on_treeWidget_itemChanged(QTreeWidgetItem *item, int column);

---------------------------------------------------------------------------------------------

  框架代码:
------mainwindow.cpp-----------

void MainWindow::on_treeWidget_itemChanged(QTreeWidgetItem *item, int column)

{

}

--------------------------------------------------------------------------------------
  以上代码是系统生成的,先不管它。到目前为止,你的三态树制作前期准备工作已经完成了。

第三节 算法设计和代码编写-修枝

  先从直观的角度来想想,当某个节点的复选框状态被用户改变后,我们希望该节点的父节点和子节点都能同步更新自身的状态,但是父、子节点的状态被改变后又会触发新的信号,新的信号再调用槽(函数)从而形成的递归调用。如果控制不好,这个递归调用将只能“递”不能“归”,因为它会不停的触发父、子节点状态的改变,这样的情形借用核物理学中的话来说就是“连锁反应”,你的程序就会因为“核爆”而崩溃。那么在递归过程中的关键就是控制好递归调用的流向,让递归不重复。
  为了控制好递归调用过程中的程序走向,我们先定义好几个用于控制程序流程的标志变量和刷新父、子节点状态的函数,它们都是MainWindow的私有成员,代码如下:

------mainwindow.h-----------------------------------
private:
    bool changeFromUser; // true:这个状态变化是由用户触发的;false:由程序调用 setCheckState 函数触发的
    bool changeToParent; // 某个节点 checkState 变化后,同步更新其它节点的方向,true:向父节点方向;false:向子节点方向
    void changeParents(QTreeWidgetItem *item, Qt::CheckState state); // 朝父节点方向刷新 CheckState
    void changeChildren(QTreeWidgetItem *item, Qt::CheckState state); // 朝子节点方向刷新 CheckState

---------------------------------------------------------------------------------------------
  在 MainWindow 的构造函数中将 changeFromUser 初始化为 true,这表示首次触发的树形列表复选框状态改变肯定是由用户操作产生的,除非你的动作快得过计算机的运算。
------mainwindow.cpp--------------------------------------------------
changeFromUser = true; // 设置状态改变由用户触发(代码位于 MainWindow 构造函数中)
-----------------------------------------------------------------------------
  现在,编写代码前的所有准备工作已经完成。接下来,我们要来实现以下三个函数了。


  void on_treeWidget_itemChanged(QTreeWidgetItem *item, int column); // private slots 私有槽(函数)
  void changeParents(QTreeWidgetItem *item, Qt::CheckState state); // 朝父节点方向刷新 CheckState
  void changeChildren(QTreeWidgetItem *item, Qt::CheckState state); // 朝子节点方向刷新 CheckState


  我把具体的算法思想体现在程序的注释语句中,这样就能边写代码边讲程序了。

------mainwindow.cpp------------------------------
void MainWindow::on_treeWidget_itemChanged(QTreeWidgetItem *item, int column)
{
    if (column != 0) return; // 树形列表位于第0列,当然,不是第0列的其它地方我们就不管了
    if (changeFromUser) // 当用户触发某项目 CheckState 状态变化时,首次调用它绑定的槽函数
    {
        changeFromUser = false; // 必须立即将这个标志置为 false,因为接下来的状态改变操作都用函数 setCheckState 触发
        changeToParent = true; // 用户触发的第一次必须朝着父节点方向进行刷新,原因我解释一下
        // 为什么要选朝父节点方向刷新?
        // 因为我们要在朝某个方向刷新后的终结位置改变方向,每个项目只可能有一个父节点,因此它可以确定而方便的找到刷新终结位置
        // 如果先选择朝子节点方向刷新,那么子节点的个数是不确定的,再加上分支会越来越多,所以它的终结位置很不好确定
        // 因此,我们在用户第一次触发 CheckState 改变的操作时强制先朝父节点方向,然后再终结位置转变为子节点方向\
        // 所以,这个转变是在函数:changeParents 中实现的,这里大家一定要思路清晰,不然很突然想不到它们是怎样构成回路的
        changeParents(item, item->checkState(0)); // 函数里隐含了刷新方向的改变,所以在调用 changeChildren 前无需再强制设置
        changeChildren(item, item->checkState(0)); // 朝子节点方向刷新
        changeFromUser = true; // 真正的递归调用环节在两个函数里完成,只有用户触发的首次状态改变才朝两个方向刷新,而后由程序代码触发的状态改变只朝着一个方向
                               // 而这个 changeFromUser 就是用来区别这两种情况。大家可以在脑子里想想,简单说就是每次状态改变都会调用此函数(形成递归调用)
                               // 所以必须在这个函数里对各种情况进行区分,使其有正确的流向。
        // 从全局的角度来看,这个 if 下面的语句就完成了所有节点的刷新,里面的递归调用就包含在 changeParents、changeChildren 两个函数里
    }
    else
    {
        // 只有递归调用时才可能执行到这里的代码,因为递归调用位于 changeFromUser 的 true 和 false 状态之间,呵呵,和上面讲的吻合了吧
        // changeToParent 这个标志是用来在递归调用时控制方向的,不然……你会明白出现什么状况的,这里我不解释^_^
        // 递归调用会根据当前的 changeToParent 状态选择正确的流向,哈哈
        if (changeToParent) changeParents(item, item->checkState(0));
        else changeChildren(item, item->checkState(0));
    }
    // 此处特别说明:在 if (changeFromUser) {...} 中的 item 其实是用户触发时对应的 item
    // 而通过递归调用执行到 else {...} 中的代码时,这里的 item 可就是各个地方不同的 item 了哦,这里要想清楚
}

void MainWindow::changeParents(QTreeWidgetItem *item, Qt::CheckState state)
{
    // 前面提到,在这个函数里,在刷新的终结点处会将 changeToParent 状态变为 false 即接下来朝着子节点方向进行刷新了
    // 那么终结点在哪里呢,终结点啊终结点,呵呵,别急
    QTreeWidgetItem *iParent = item->parent();
    if (iParent == 0) // 这里不就是终结点么,嘿!因为要调用到这个函数,那么 item 所指向的节点已经被刷新才能触发信号
    {
        // 所以呢,节点本身已经被刷新,又没有父节点,你说接下来该做什么捏^_^
        changeToParent = false; // 这就是所谓有隐藏剧情了
        return; // 没你事儿了,递个归,回去吧
    }
    else // 当然,如果人家还有老爹,你就有事儿做咯
    {
        // 那怎样给老爹上状态呢?当然是看你们兄弟齐心不齐心咯,下面看我的小白解释法
        // 所有兄弟节点为 Checked ,老爹也是 checked
        // 所有兄弟节点为 UnChecked,老爹也是 unChecked
        // 所有兄弟节点为 PartiallyChecked,老爹是神马呢?自己猜!
        // 一旦有哪个兄弟节点不一样,那老爹就伤心咯(PartiallyChecked)
        // 所以,看代码!!!
        int i,k=iParent->childCount(); // k 表示兄弟的个数,i 用来点名的
        for (i=0;i<k;i++)
        {
            if (iParent->child(i)->checkState(0) != state) // 某个兄弟和你的状态不一样了,一票否决制,不多说
                break;
        }
        // 看你点名点完了没有,如果 i = k,就说明点完名都没发现不同的兄弟(这里有些巧用,仔细想想,看看上面的 for 循环)
        if (i==k) iParent->setCheckState(0, state); // 注意,这里出现递归调用了,因为 setCheckState 触发发 itemChanged 信号
        else iParent->setCheckState(0, Qt::PartiallyChecked); // 同上,而且这里是程序代码触发
        // 在没到终结点之前 changeToParent 为 true,递归会继续朝着父节点的方向进行,再看看那个槽函数吧(为什么要叫槽呢,我没想清楚)
        // 老爹照顾完了,再管管儿子闺女们些吧。太乱了,呵呵^_^
    }
}
----------------------------------------------------------------------
void MainWindow::changeChildren(QTreeWidgetItem *item, Qt::CheckState state)
{
    int i,k=item->childCount(); // k 你儿子闺女的个数(支持计划生育,养不起啊养不起);i 老蛋,老一,老二,老三……
    for (i=0;i<k;i++)
    {
        item->child(i)->setCheckState(0, state); // 儿女跟爹走
        // 这里触发 itemChanged,你儿子女儿自己会管它们的子女(递归调用),因为在照顾完父亲后,已经在终结点将 changeToParent 改成了朝子节点
        // 所以此时的递归调用会朝着子节点的方向流动,不信看上面那个槽函数
        // 如果到了最后子节点(只能说是某一分支的最后),它没有子女了(k=0),就没有这里的事儿了
        // 至此结束,谢谢!
    }
}

---------------------------------------------------------------------------------------------------------

  来个效果图吧!
  


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: qt获取列表的单位是QModelIndex。QModelIndex是qt中一个通用的模型索引类,用于指定模型中的特定项。在列表中,每个节点都有一个唯一的QModelIndex来标识它。可以使用QModelIndex来访问节点的数据、行号、列号以及父节点和子节点的索引。 要获取列表的单位,首先需要创建一个QModelIndex对象来表示的根节点。然后,使用QModelIndex的方法和属性来遍历的节点。通过调用QModelIndex的parent()方法可以获取某个节点的父节点的索引。通过调用QModelIndex的child()方法可以获取某个节点的子节点的索引。通过调用QModelIndex的row()和column()方法可以获取某个节点在模型中的行号和列号。通过调用QModelIndex的data()方法可以获取某个节点的数据。 在使用QModelIndex时,需要注意列表的节点是按照深度优先的顺序进行排序的。也就是说,首先遍历的第一个子节点及其所有后代节点,然后再遍历的第二个子节点及其所有后代节点,以此类推。 通过QModelIndex,我们可以方便地访问列表的节点,获取节点的数据,并进行一系列的操作,如展开、折叠、插入、删除等。进行列表的操作时,需要使用QAbstractItemModel或其子类来管理模型数据,并通过QTreeView或QTreeWidget等控件来显示列表的内容。 ### 回答2: 在Qt中,我们可以使用QTreeView类来创建和显示列表。在列表中,每个节点都可以表示一个单位。为了获取列表中的单位,我们可以执行以下步骤: 1. 创建一个QTreeView对象,并设置其模型为QStandardItemModel。模型是存储形结构数据的容器。 2. 创建根节点,可以使用QStandardItemModel的insertRow()方法插入一行数据,并使用QStandardItem对象表示节点。 3. 创建子节点,可以使用根节点的appendRow()方法添加子节点,同样使用QStandardItem对象表示节点。 4. 重复步骤3,直到创建完整的形结构。 5. 使用QTreeView的setModel()方法将模型设置给QTreeView对象,以供显示。 6. 使用QTreeView的indexAt()方法获取鼠标单击位置处的索引。 7. 使用模型的data()方法获取节点的数据。 8. 可以使用自定义的方法或属性,将节点的数据解释为单位,并对其进行处理。 通过以上步骤,我们可以使用Qt来获取列表中的单位。具体的实现代码可以参考Qt的官方文档和相关教程。使用Qt提供的丰富的功能和类库,我们可以轻松地获取列表的单位,并对其进行处理和操作。 ### 回答3: Qt中可以使用QTreeView和QStandardItemModel来获取列表的单位。 QTreeView是Qt中用于显示形结构的控件,能够以形方式展示数据。通过QTreeView,可以方便地查看和操作结构数据。 QStandardItemModel是Qt中的一个数据模型类,可以用来存储和操作形结构的数据。通过QStandardItemModel,可以创建带有父子关系的数据项,并将其作为QTreeView的数据源。 获取列表的单位,首先需要创建一个QStandardItemModel对象,并设置好形结构的数据项。可以通过QStandardItemModel的方法addItem()或insertRow()来添加列表的单位。每个数据项可以设置文本、图标、数据等信息。 然后,将QStandardItemModel对象设置给QTreeView,使用QTreeView的setModel()方法即可。这样,QTreeView就能够根据QStandardItemModel中的数据来显示列表的单位。 在QTreeView中,可以通过QModelIndex来访问和操作列表的单位。QModelIndex表示一个索引,可以用来定位列表中的一个具体单位。可以使用QModelIndex的方法来获取单位的父索引、子索引、数据等。 总之,Qt提供了QTreeView和QStandardItemModel两个类来获取列表的单位。通过创建QStandardItemModel对象,并将其设置给QTreeView,可以方便地操作和显示形结构的数据。使用QModelIndex可以定位具体的单位,进行进一步的操作和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值