首页 Qt 学习之路 2 Qt 学习之路 2(42):QListWidget、QTreeWidget 和 QTableWidget

Qt 学习之路 2(42):QListWidget、QTreeWidget 和 QTableWidget

40 8

上一章我们了解了 model/view 架构的基本概念。现在我们从最简单的QListWidgetQTreeWidgetQTableWidget三个类开始了解最简单的 model/view 的使用。这部分内容的确很难组织。首先,从最标准的 model/view 开始,往往会纠结于复杂的代码;但是,如果从简单的 QListWidgetQTreeWidgetQTableWidget开始,由于这三个类都是继承自各自的 view 类,很难避免 model/view 的相关内容。于是,我们这部分的组织是,首先进行简单的数据显示,更复杂的设置则放在后面的章节。

QListWidget

我们要介绍的第一个是QListWidget。先来看下面的代码示例:

label = new QLabel(this);
label->setFixedWidth(70);

listWidget = new QListWidget(this);

new QListWidgetItem(QIcon(":/Chrome.png"), tr("Chrome"), listWidget);
new QListWidgetItem(QIcon(":/Firefox.png"), tr("Firefox"), listWidget);

listWidget->addItem(new QListWidgetItem(QIcon(":/IE.png"), tr("IE")));
listWidget->addItem(new QListWidgetItem(QIcon(":/Netscape.png"), tr("Netscape")));
listWidget->addItem(new QListWidgetItem(QIcon(":/Opera.png"), tr("Opera")));
listWidget->addItem(new QListWidgetItem(QIcon(":/Safari.png"), tr("Safari")));
listWidget->addItem(new QListWidgetItem(QIcon(":/TheWorld.png"), tr("TheWorld")));
listWidget->addItem(new QListWidgetItem(QIcon(":/Traveler.png"), tr("Traveler")));

QListWidgetItem *newItem = new QListWidgetItem;
newItem->setIcon(QIcon(":/Maxthon.png"));
newItem->setText(tr("Maxthon"));
listWidget->insertItem(3, newItem);

QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(label);
layout->addWidget(listWidget);

setLayout(layout);

connect(listWidget, SIGNAL(currentTextChanged(QString)),
        label, SLOT(setText(QString)));

QListWidget是简单的列表组件。当我们不需要复杂的列表时,可以选择QListWidgetQListWidget中可以添加QListWidgetItem类型作为列表项,QListWidgetItem即可以有文本,也可以有图标。上面的代码显示了三种向列表中添加列表项的方法(实际是两种,后两种其实是一样的),我们的列表组件是listWidget,那么,向listWidget添加列表项可以:第一,使用下面的语句

new QListWidgetItem(QIcon(":/Chrome.png"), tr("Chrome"), listWidget);

第二,使用

listWidget->addItem(new QListWidgetItem(QIcon(":/IE.png"), tr("IE")));
// 或者
QListWidgetItem *newItem = new QListWidgetItem;
newItem->setIcon(QIcon(":/Maxthon.png"));
newItem->setText(tr("Maxthon"));
listWidget->insertItem(3, newItem);

注意这两种添加方式的区别:第一种需要在构造时设置所要添加到的QListWidget对象;第二种方法不需要这样设置,而是要调用addItem()或者insertItem()自行添加。如果你仔细查阅QListWidgetItem的构造函数,会发现有一个默认的type参数。该参数有两个合法值:QListWidgetItem::Type(默认)和QListWidgetItem::UserType。如果我们继承QListWidgetItem,可以设置该参数,作为我们子类的一种区别,以便能够在QListWidget区别处理不同子类。

我们的程序的运行结果如下:

QListWidget 示例

我们可以利用QListWidget发出的各种信号来判断是哪个列表项被选择,具体细节可以参考文档。另外,我们也可以改变列表的显示方式。前面的列表是小图标显示,我们也可以更改为图标显示,只要添加一行语句:

listWidget->setViewMode(QListView::IconMode);

结果如下:

QListWidget IconMode 示例

QTreeWidget

我们要介绍的第二个组件是QTreeWidget。顾名思义,这是用来展示树型结构(也就是层次结构)的。同前面说的QListWidget类似,这个类需要同另外一个辅助类QTreeWidgetItem一起使用。不过,既然是提供方面的封装类,即便是看上去很复杂的树,在使用这个类的时候也是显得比较简单的。当不需要使用复杂的QTreeView特性的时候,我们可以直接使用QTreeWidget代替。

下面我们使用代码构造一棵树:

QTreeWidget treeWidget;
treeWidget.setColumnCount(1);

QTreeWidgetItem *root = new QTreeWidgetItem(&treeWidget,
                                            QStringList(QString("Root")));
new QTreeWidgetItem(root, QStringList(QString("Leaf 1")));
QTreeWidgetItem *leaf2 = new QTreeWidgetItem(root, QStringList(QString("Leaf 2")));
leaf2->setCheckState(0, Qt::Checked);

QList<QTreeWidgetItem *> rootList;
rootList << root;
treeWidget.insertTopLevelItems(0, rootList);

treeWidget.show();

首先,我们创建了一个QTreeWidget实例。然后我们调用setColumnCount()函数设定栏数。这个函数的效果我们会在下文了解到。最后,我们向QTreeWidget添加QTreeWidgetItemQTreeWidgetItem有很多重载的构造函数。我们在这里看看其中的一个,其余的请自行查阅文档。这个构造函数的签名如下:

QTreeWidgetItem(QTreeWidget *parent, const QStringList &strings, int type = Type);

这里有 3 个参数,第一个参数用于指定这个项属于哪一个树,类似前面的QListWidgetItem,如果指定了这个值,则意味着该项被直接添加到树中;第二个参数指定显示的文字;第三个参数指定其类型,同QListWidgetItemtype参数十分类似。值得注意的是,第二个参数是QStringList类型的,而不是QString类型。我们会在下文了解其含义。

在这段代码中,我们创建了作为根的QTreeWidgetItemroot。然后添加了第一个叶节点,之后又添加一个,而这个则设置了可选标记。最后,我们将这个 root 添加到一个QTreeWidgetItem的列表,作为QTreeWidget的数据项。此时你应该想到,既然QTreeWidget接受QList作为项的数据,它就能够支持多棵树的一起显示,而不仅仅是单根树。下面我们来看看运行结果:

QTreeWidget 示例

从代码来看,我们能够想象到这个样子,只是这个树的头上怎么会有一个 1?还记得我们跳过去的那个函数吗?下面我们修改一下代码看看:

QTreeWidget treeWidget;

QStringList headers;
headers << "Name" << "Number";
treeWidget.setHeaderLabels(headers);

QStringList rootTextList;
rootTextList << "Root" << "0";
QTreeWidgetItem *root = new QTreeWidgetItem(&treeWidget, rootTextList);

new QTreeWidgetItem(root, QStringList() << QString("Leaf 1") << "1");
QTreeWidgetItem *leaf2 = new QTreeWidgetItem(root,
                                   QStringList() << QString("Leaf 2") << "2");
leaf2->setCheckState(0, Qt::Checked);

QList<QTreeWidgetItem *> rootList;
rootList << root;
treeWidget.insertTopLevelItems(0, rootList);

treeWidget.show();

这次我们没有使用setColumnCount(),而是直接使用QStringList设置了 headers,也就是树的表头。接下来我们使用的还是QStringList设置数据。这样,我们实现的是带有层次结构的树状表格。利用这一属性,我们可以比较简单地实现类似 Windows 资源管理器的界面。

QTreeWidget 多栏示例

如果你不需要显示这个表头,可以调用setHeaderHidden()函数将其隐藏。

QTableWidget

我们要介绍的最后一个是 QTableWidgetQTableWidget并不比前面的两个复杂到哪里去,这点我们可以从代码看出来:

QTableWidget tableWidget;
tableWidget.setColumnCount(3);
tableWidget.setRowCount(5);

QStringList headers;
headers << "ID" << "Name" << "Age" << "Sex";
tableWidget.setHorizontalHeaderLabels(headers);

tableWidget.setItem(0, 0, new QTableWidgetItem(QString("0001")));
tableWidget.setItem(1, 0, new QTableWidgetItem(QString("0002")));
tableWidget.setItem(2, 0, new QTableWidgetItem(QString("0003")));
tableWidget.setItem(3, 0, new QTableWidgetItem(QString("0004")));
tableWidget.setItem(4, 0, new QTableWidgetItem(QString("0005")));
tableWidget.setItem(0, 1, new QTableWidgetItem(QString("20100112")));

tableWidget.show();

这段代码运行起来是这样子的:

QTableWidget 示例

首先我们创建了QTableWidget对象,然后设置列数和行数。接下来使用一个QStringList,设置每一列的标题。我们可以通过调用setItem()函数来设置表格的单元格的数据。这个函数前两个参数分别是行索引和列索引,这两个值都是从 0 开始的,第三个参数则是一个QTableWidgetItem对象。Qt 会将这个对象放在第 row 行第 col 列的单元格中。有关QTableWidgetItem的介绍完全可以参见上面的QListWidgetItemQTreeWidgetItem

40 评论

gavin 2013年2月21日 - 11:20

第一段代码的第3行、第22行、第26行的list是什么?是不是应该改为listWidget?

回复
豆子 2013年2月21日 - 12:23

感谢指出哦,已经修改过了~

回复
msccreater 2013年2月24日 - 17:44

在介绍QTableWidget的时候表头有四个标题,但是在设置:tableWidget.setColumnCount(3);的时候设置了3个,有一个sex没显示出来 😛
性感是必须的 😆

回复
豆子 2013年2月25日 - 11:11

😯 说得好~

回复
小宇爷 2013年7月23日 - 16:58

❓ 大神呐,问个问题,我用QFile file("z.txt")新建一个file对象,然后读写数据,执行起来正常,但始终找不到建的z.txt在哪,怎么破

回复
豆子 2013年7月23日 - 20:39

一般是在 exe 同目录或者 build-XXX 目录下,你可以在文件夹中搜索下名字就好了哦。

回复
小宇爷 2013年7月24日 - 11:33

还赈灾build目录下,说得对!

回复
菜菜 2016年3月18日 - 16:17

有没有办法把第一的程序的图改变大小呢?

回复
dingdinglhz 2013年8月3日 - 10:01

神犇,菜鸟问一个问题,treeWidget是建在栈上的,root是建在堆上的,这样没有问题吗?
谢谢!

回复
豆子 2013年8月3日 - 10:59

没有问题的。Qt 一般推荐 main() 函数中在栈上建立窗口,而窗口中的组件大多都是在堆上创建的

回复
疯子 2013年9月9日 - 11:25

读到后面的章节又开始模糊了,对model/view架构还是不够清晰,忍不住又返回来看。这一节中的三个widget类相当于架构中的view部件,负责显示;而对应的Item类则可理解为对应的model,其内部已经建立了与data的联系。这样理解对吗?

回复
豆子 2013年9月9日 - 14:36

差不多是这个样子。注意这三个 widget 都是集成自各自的 view ,因此主要还是把 view 当做第一位。model 则是直接集成在 view 里面,作为一个类而不是分开的两个类来处理。

回复
enockipp 2013年9月26日 - 08:36

在QTreeWidget例子中,去掉下面,是一样的吧,上面在创建root的时候已经指定了parent了:
QList rootList;
rootList << root;
treeWidget.insertTopLevelItems(0, rootList);

回复
豆子 2013年9月26日 - 08:50

如果是单根树,应该是一样的

回复
enockipp 2013年9月26日 - 09:16

我感觉不是单根树还是多根树问题吧(我是将root看成树根的),insertTopLevelItem是将未添加到树中的节点添加进来吧,像下面的代码,感觉只有treeWidget.insertTopLevelItems(2, rootList)起作用:
QTreeWidget *treeWidget=new QTreeWidget(this);

QStringList headers;
headers << "Name" <setHeaderLabels(headers);

QStringList rootTextList;
rootTextList << "Root" << "0";

QTreeWidgetItem *root = new QTreeWidgetItem(treeWidget, rootTextList);
QTreeWidgetItem *root1 = new QTreeWidgetItem(treeWidget, QStringList() << QString("root") << "1");
QTreeWidgetItem *root2=new QTreeWidgetItem( QStringList() << QString("root") << "2");

new QTreeWidgetItem(root, QStringList() << QString("Leaf 1") << "1");
QTreeWidgetItem *leaf2 = new QTreeWidgetItem(root,QStringList() << QString("Leaf 2") <setCheckState(0, Qt::Checked);

QList rootList;
rootList<<root<<root1<insertTopLevelItems(0, rootList);
//treeWidget->insertTopLevelItems(1,rootList);
treeWidget->insertTopLevelItems(2,rootList);
this->setCentralWidget(treeWidget);

回复
豆子 2013年9月26日 - 10:36

不太明白为什么说“只有treeWidget.insertTopLevelItems(2, rootList)起作用”?是哪里与预期不一致的?

回复
enockipp 2013年9月26日 - 12:44

我的意思是多棵树的话(如root和root1),当root和root1都指定了treeWidget为parent,这时就不需要insertTopLevelItems了,当再加入一棵没指定parent的root2时,才需要将root2 insert进去,突然发现我好像理解错了,是不是应该将treeWidget看作是树根?
谢谢豆子对新手的热心回复,上午办公室断了下电,没及时回复

豆子 2013年9月26日 - 14:14

是的,insertTopLevelItems() 就是添加没有指定 QTreeWidget 的 item 作为根。你说的没有错误,文中的 treeWidget.insertTopLevelItems(0, rootList) 一句的确是不需要的。至于根的话,不纠结的说,就说是最外一层就好了,只要知道属于哪个节点应该就可以了 ;-P

不知道 2014年7月17日 - 17:36

如果要把TableWidegt 编辑的东西留下来,就是下次打开依旧是上次编辑的东西,该怎么做呢

回复
豆子 2014年7月20日 - 16:05

如果新编辑的数据不能覆盖旧的数据,只能将新输入的数据保存起来

回复
Wangzhe 2014年8月25日 - 16:32

为什么QListWidget的代码提示“QWidget::setLayout: Attempting to set QLayout "" on MainWindow "", which already has a layout”?(这段代码应该是写到MainWindow::MainWindow(QWidget *parent)里的对吧。)

回复
豆子 2014年8月26日 - 09:52

因为 QMainWindow 有自己的 layout,你再设置就会有这个警告。你可以不使用 QMainWindow,只使用一个普通的 QWidget 即可。

回复
weiqi 2014年8月31日 - 11:17

我想问下使用QTableWidget时,有没有什么函数可以一次性将一整行进行处理,比如使用QStringList一次性进行一整行的赋值,而不用一次只能改变一个QTableWidgetItem.

回复
豆子 2014年9月2日 - 09:31

Qt 本身没有提供类似的函数。你可以自己封装一个,或者使用QAbstractTableModel这种模型类。

回复
wcoix 2015年4月25日 - 03:25

豆子哥你好,我在QT5中使用QTableWidget的cellChanged槽函数时遇到了一个奇怪的问题,在cellChanged的槽函数中我想读取整行的内容,使用ui->tablewidget->item(row, 0)->text()可以获得该行第一列的内容,但是一旦使用ui->tablewidget->item(row, 1)->text()来获取第二列的内容时,程序根本无法运行,直接crash。后来我用另一个信号cellDoubleClicked的槽函数做实验,用这样的方法又是可以的,我想请教一下这是为何呢?

回复
豆子 2015年4月27日 - 09:27

这个最好能把代码发送到邮箱看一下怎么回事

回复
hello 2015年4月26日 - 19:00

您好,我刚学pyqt,请问怎样才能使qtreewidget只有一列并且宽度不占据整个窗口。我的qtreewidget在gridlayout中一添加宽度就会占满整个窗口。

回复
豆子 2015年4月27日 - 09:29

可能是 GridLayout 把 QTreeWidget 进行了拉伸,先不加入试试

回复
mark 2015年7月19日 - 14:41

请问一下会有什么原因造成QtableWidget的新号cellActivated(int,int)无效啊?

回复
克成 2015年8月7日 - 17:42

楼主可以把QListWidget的代码完整的发给我吗?我关于布局那部分总是没完成,列表部分总是在最左边。
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle(tr("ListWidget"));
resize(400, 300);

labelleft = new QLabel("Left Description:");
vlayoutleft = new QVBoxLayout;
vlayoutleft->addWidget(labelleft);
vlayoutleft->addStretch();

labelright = new QLabel("Right");
labelright->setFixedWidth(100);
listWidget = new QListWidget(this);

new QListWidgetItem(QIcon(":/photos/512.png"), tr("Chrome"), listWidget);
new QListWidgetItem(QIcon(":/photos/513.png"), tr("Firefox"), listWidget);

listWidget->addItem(new QListWidgetItem(QIcon(":/photos/514.png"), tr("IE")));
listWidget->addItem(new QListWidgetItem(QIcon(":/photos/515.png"), tr("Netscape")));
listWidget->addItem(new QListWidgetItem(QIcon(":/photos/516.png"), tr("Opera")));
listWidget->addItem(new QListWidgetItem(QIcon(":/photos/517.png"), tr("Safari")));
listWidget->addItem(new QListWidgetItem(QIcon(":/photos/518.png"), tr("TheWorld")));
listWidget->addItem(new QListWidgetItem(QIcon(":/photos/p01.png"), tr("Traveler")));

QListWidgetItem *newItem = new QListWidgetItem;
newItem->setIcon(QIcon(":/photos/p02.png"));
newItem->setText(tr("Maxthon"));
listWidget->insertItem(3, newItem);

vlayoutright = new QVBoxLayout;
vlayoutright->addWidget(labelright);
vlayoutright->addWidget(listWidget);

QHBoxLayout *layout = new QHBoxLayout;
layout->addLayout(vlayoutleft);
layout->addLayout(vlayoutright);

setLayout(layout);
layout->setSpacing(30);
//listWidget->setViewMode(QListView::IconMode);
connect(listWidget, SIGNAL(currentTextChanged(QString)),
labelleft, SLOT(setText(QString)));
}
我以为这样是可以的,可是还是不行,究竟是什么原因呢?

回复
克成 2015年8月7日 - 23:02

原来是MainWindow已经有layout了

回复
AryGone 2016年4月11日 - 15:06

麻烦问下楼主这里的
new QListWidgetItem(QIcon(":/Chrome.png"), tr("Chrome"), listWidget);
new QListWidgetItem(QIcon(":/Firefox.png"), tr("Firefox"), listWidget);
是产生QListWidgetItem的临时变量么?我是小白,不太懂,望解答

回复
豆子 2016年4月11日 - 21:53

按照 C++ 标准,使用 new 运算符是在堆上面创建的,必须有相应的 delete 析构。但是这里最后一个参数 listWidget 指定该 item 是隶属于 listWidget 的,当 listWidget 销毁时,这个 item 也会被自动销毁。这是由 Qt 自己实现的半自动化内存管理,与标准 C++ 不一致(表现为没有显式的 delete,但并不代表没有 delete,只是这个 delete 是由 Qt 完成的)。因此说,item 并不会像临时变量一样会在超出作用域的时候被销毁,而是在 listWidget 销毁的时候才会被销毁。

回复
AryGone 2016年4月12日 - 09:11

谢谢您的回复,还是麻烦问一下,这里并没用声明一个 QListWidgetItem对象,直接new加构造函数,那么这样就是直接在堆上申请空间,类似一个临时变量,构造一个部件,因为后边用不到这个对象所以不用声明;需要使用的对象的话就使用下面声明定义QListWidgetItem *newItem = new QListWidgetItem;
newItem->setIcon(QIcon(":/Maxthon.png"));
newItem->setText(tr("Maxthon"));
listWidget->insertItem(3, newItem);
这样理解是否正确?

回复
豆子 2016年4月22日 - 16:39

是的,就是这个样子;因为我们不需要指针来引用这个创建的 item 对象, 所以没有声明指针。

回复
ccppaa 2016年7月26日 - 09:08

大神,我用treewidget能把样式分类搭起来,如果显示的文本从表里查询应该怎么做

回复
慢慢慢慢 2016年8月24日 - 15:35

豆子大大,我想请教一个问题,我用qtablewidget实现了一个表格,然后想实现在每一行右键都有菜单弹出,我采用的是重新实现contextMenuEvent()这个事件的方法,然后出现的问题是不论我在窗口的哪个部分点击右键都会出现菜单,我看网上有人这样写
QPoint point = event->pos(); //得到窗口坐标
QTableWidgetItem *item = this->itemAt(point);
if(item != NULL)
{
//菜单出现的位置为当前鼠标的位置
pop_menu->exec(QCursor::pos());
event->accept();
}
但是为什么我调用itemAt这个函数返回值总是0呢?

回复
canid 2016年9月3日 - 15:24

QTableWidget 为什么不setRowCount(或者在构造函数设置行数)就无法显示呢?

回复
Simon_陈 2021年2月20日 - 11:38

看不到图片怎么办呢

回复
豆子 2021年2月23日 - 17:10

之前图床有些问题,现在应该好了

回复

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2