QTreeWidget

目录

一. 窗口界面可视化设计

1.界面布局设计

2.QDockWidget 的属性

3.QTreeWidget 组件的可视化设计

4.设计 Action

二. QTreeWidget 类

1.QTreeWidget 组件的显示结构

2.QTreeWidget 的其他接口函数

3.QTreeWidget 的公有槽

4.QTreeWidget 的信号

三. QTreeWidgetItem 类

1.创建 QTreeWidgetItem 对象

2.访问节点各列数据的接口函数

3.对节点整体特性进行操作的接口函数

4.操作子节点的接口函数

5.父节点

四.示例中 QTreeWidget 的操作

1.本示例的目录树节点操作规则

2.目录树初始化

3.添加分组节点

4.添加图片节点

5.当前节点变化时的响应

6.删除节点

7.节点的遍历

五. 用 QLabel 和 QPixmap 显示图片

1.显示节点关联的图片

2.图片缩放与显示

六. 示例中 QDockWidget 的操作


QTreeWidget 是一种 Item Widget 组件。QTreeWidget 组件被称为树形组件,它的项(item)被称为节点,一个树形组件内的所有节点组成的结构称为目录树。树形组件适合显示具有层级结构的数据,例如 Windows 资源 管理器中显示的文件系统就是一种典型的层级结构。

设计一个示例项目运行时界面如图所示。示例的窗口基类是 QMainWindow,通过 Action 设计了菜单栏和工具栏,这个示例实现了一个简单的图片管理器,主要会演示以下几个组件的使用方法。

 • QTreeWidget 树形组件。示例使用树形组件管理目录和图片文件,可以添加或删除节点。对节点定义了类型,节点分为顶层节点、分组节点和图片节点,每个节点还存储一个用户数据,图片节点存储完整的图片文件名,以便点击节点时显示该图片。

• QDockWidget 停靠组件。QDockWidget 是可以在窗口上停靠或在桌面最上层浮动的组件。本示例会将一个树形组件放置在一个停靠组件上,设置其可以在窗口的左侧或右侧停靠,也可以浮动。

• QLabel 标签组件。窗口右侧是一个 QScrollArea 组件,在它上面放置一个标签,为标签设置一个 QPixmap 对象显示图片。通过 QPixmap 的接口函数可进行图片缩放。

一. 窗口界面可视化设计

1.界面布局设计

工作区左侧是一个 QDockWidget 组件,对象名称是 dockWidget。在 dockWidget 上放置一个QTreeWidget 组件,对象名称是 treeWidget,用水平布局使 treeWidget 填充满停靠区。

工作区右侧是一个 QScrollArea 组件,对象名称是 scrollArea。scrollArea 里放置一个 QLabel 组件,对象名称是 labPic,可以利用 QLabel 的 pixmap 属性来显示图片。scrollArea 内部的组件采用水平布局,当图片较小时,labPic 显示的图片可以自动居于 scrollArea 的中央,当 labPic 显示的图片的大小超过 scrollArea 可显示区域的大小后,scrollArea 会自动显示水平或垂直方向的卷滚条,用于移动显示区域。

在窗口类 MainWindow 的构造函数里将 scrollArea 设置为主窗口工作区的中心组件后,dockWidget 与 scrollArea 之间会自动出现分割条。

2.QDockWidget 的属性

UI 可视化设计时 QDockWidget 组件的属性如图所示。 它的几个主要属性的含义如下:

(1)floating 属性,停靠区组件是否处于浮动状态。通过函数 isFloating()可以返回此属性值, 通过函数 setFloating(bool)可以设置此属性值。

(2)features 属性,停靠区组件的特性。函数 setFeatures()用于设置此属性值,其函数原型定义如下:

void QDockWidget::setFeatures(QDockWidget::DockWidgetFeatures features) 

参数 features 是枚举类型 QDockWidget::DockWidgetFeature 的枚举值的组合。

(3)allowedAreas 属性:允许停靠区域。函数 setAllowedAreas()用于设置这个属性值,其函数原型定义如下:

void QDockWidget::setAllowedAreas(Qt::DockWidgetAreas areas)

参数 areas 是枚举类型 Qt::DockWidgetArea 的枚举值的组合,可以设置在窗口的左侧、右侧、顶部、底部停靠,也可以设置在所有区域都可停靠或不允许停靠。本示例设置为允许在左侧和右侧停靠。

(4)windowTitle 属性:停靠区窗口的标题。

上图中展示的最后两个属性并不是 QDockWidget 类中定义的属性,它们没有对应的读写函数,只是在 UI 可视化设计时被用到。例如,设置 docked 属性为 true,可以使 dockWidget 处于停靠状态;设置其为 false,则可以使 dockWidget 处于浮动状态。

3.QTreeWidget 组件的可视化设计

在 UI 可视化设计时,双击窗体上的 QTreeWidget 组件,可以打开组件的编辑器,编辑器有两个页面,可分别对 Columns 和 Items 进行设计。

Columns 页面用于设计树形组件的列。在编辑器里可以添加、删除、移动列,以及设置每一列的标题文字、字体、前景色、背景色、文字对齐方式、图标等。在 UI 可视化设计时,我们设置了两 个列,标题分别为“节点”和“节点类型”,且两个列的标题文字对齐方式不同,如下左图所示。

Items 页面用于设计树形组件的节点,如上右图所示,窗口下方有一组按钮可以进行新增节点、新增下一级节点、删除节点、改变节点层级、平级移动节点等操作。

树形组件中的一行是一个节点,一个节点可以有多列,每一列可以单独设置文字、图标、复选状态等属性。图中右侧的属性分为两组,Per column properties 组是节点的每一列单独设置的属性,包括文字、图标、字体、背景色、复选状态等;Common properties 组是节点各列共有的属性,只有一个 flags 属性,用于设置节点的标志,可以设置节点是否可选、是否可编辑、是否有复选框等。

一个节点可以有多列,在上右图所示的编辑器中可以设置一个节点每列的文字、图标等属性。树形组件的节点一般是动态创建的,我们在后面会结合 QTreeWidget 类的接口函数详细介绍节点的创建和属性设置的编程方法。

4.设计 Action

本示例的功能大多采用 Action 实现,先设计好 Action,然后利用 Action 设计主菜单和工具栏。 设计完成的 Action 如图所示,后面再介绍主要的 Action 的功能实现代码。

二. QTreeWidget 类

1.QTreeWidget 组件的显示结构

一个 QTreeWidget 组件的显示内容分为表头和目录树两部分,表头和目录树节点都是QTreeWidgetItem 对象。我们以图 4-56 中 QTreeWidget 组件显示的内容为例,介绍树形组件的显示结构和目录树构造规律。

(1)表头。树形组件有表头,表头可以只是简单的文字,也可以设置为 QTreeWidgetItem 对象。当使用 QTreeWidgetItem 对象作为表头时,不仅可以设置表头每一列的文字,还可以设置字体、对齐方式、背景色、图标等显示特性。

如果只是简单地设置表头文字,可以使用函数 setHeaderLabels()将字符串列表的内容作为表头各列的标题,其函数原型定义如下:

void QTreeWidget::setHeaderLabels(const QStringList &labels) 

如果创建了 QTreeWidgetItem 对象作为表头,就可以使用函数 setHeaderItem()设置表头,还可以用函数 headerItem()返回表头的 QTreeWidgetItem 对象指针。

void QTreeWidget::setHeaderItem(QTreeWidgetItem *item) //设置表头节点
QTreeWidgetItem *QTreeWidget::headerItem() //返回表头节点

如果使用 QTreeWidgetItem 对象作为表头,就可以通过 QTreeWidgetItem 的接口函数设置表头每一列的字体、对齐方式、背景色、图标等属性。

(2)顶层节点。目录树里一行就是一个节点,节点是 QTreeWidgetItem 对象。节点可以有子节点,子节点就是下一级的节点,子节点可以继续有其子节点,可以层层嵌套。

目录树里最上层的节点称为顶层节点,顶层节点没有父节点。目录树里可以有任意多个顶层节点,QTreeWidget 类中有如下一些用于顶层节点操作的接口函数。

int topLevelItemCount() //返回顶层节点的个数
void addTopLevelItem(QTreeWidgetItem *item) //添加一个顶层节点
void insertTopLevelItem(int index, QTreeWidgetItem *item) //插入一个顶层节点
int indexOfTopLevelItem(QTreeWidgetItem *item) //返回一个顶层节点的索引号
QTreeWidgetItem *topLevelItem(int index) //根据索引号获取一个顶层节点
QTreeWidgetItem *takeTopLevelItem(int index) //移除一个顶层节点,但是并不删除这个节点对象

获取一个顶层节点对象后,就可以访问它的所有子节点。

(3)次级节点。所有次级节点都直接或间接挂靠在某个顶层节点下面,顶层节点和次级节点都是 QTreeWidgetItem 类对象。一个节点可以访问它的所有直接子节点,可以通过递归的方法遍历其所有直接和间接子节点。所以,从顶层节点开始,就可以遍历整个目录树。

(4)隐藏的根节点。目录树中还有一个隐藏的根节点,其可以看作所有顶层节点的父节点。函数 invisibleRootItem()可以返回这个隐藏的根节点,其函数原型定义如下:

QTreeWidgetItem *QTreeWidget::invisibleRootItem() 

使用这个隐藏的根节点,就可以通过 QTreeWidgetItem 类的接口函数访问所有顶层节点,这样在实现一些对顶层节点和次级节点进行操作的函数时,可以使用相同的程序。

2.QTreeWidget 的其他接口函数

除了前面介绍的一些函数,QTreeWidget 还有如下一些常用的接口函数,各函数功能见注释。

int columnCount() //返回表头列数
void setColumnCount(int columns) //设置表头列数
void sortItems(int column, Qt::SortOrder order) //将目录树按照某一列排序
int sortColumn() //返回用于排序的列的序号
QTreeWidgetItem *currentItem() //返回当前节点
QList<QTreeWidgetItem *> selectedItems() //返回选择的节点的列表

目录树上有一个当前节点,也就是通过点击鼠标或按键移动选择的节点。函数 currentItem() 会返回当前节点,如果返回值为 nullptr,就表示没有当前节点。

如果树形组件允许多选,函数 selectedItems()会返回选择的节点的列表。通过 QTreeWidget 的上层父类 QAbstractItemView 的 selectionMode 属性能够设置选择模式,可以设置为多选。

3.QTreeWidget 的公有槽

QTreeWidget 类有如下几个公有槽函数。

void clear() //清除整个目录树,但是不清除表头
void collapseItem(const QTreeWidgetItem *item) //折叠节点
void expandItem(const QTreeWidgetItem *item) //展开节点
void scrollToItem(const QTreeWidgetItem *item, QAbstractItemView::ScrollHint hint = 
EnsureVisible) 

函数 collapseItem()将一个节点的所有子节点折叠起来。函数 expandItem()将一个节点完全展开,也就是展示其所有子节点。函数 scrollToItem()用于确保节点 item 可见,必要时自动移动树形组件的卷滚条。

4.QTreeWidget 的信号

QTreeWidget 类有如下几个信号,要注意这些信号触发条件的区别。

void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) 
void itemActivated(QTreeWidgetItem *item, int column) //点击或双击节点时
void itemChanged(QTreeWidgetItem *item, int column) 
void itemClicked(QTreeWidgetItem *item, int column) 
void itemCollapsed(QTreeWidgetItem *item) //节点折叠时
void itemDoubleClicked(QTreeWidgetItem *item, int column) 
void itemEntered(QTreeWidgetItem *item, int column) //鼠标光标移动到节点上时
void itemExpanded(QTreeWidgetItem *item) //节点展开时
void itemPressed(QTreeWidgetItem *item, int column) 
void itemSelectionChanged() 

currentItemChanged()信号在当前节点发生变化时被发射,current 是当前节点,previous 是之前节点,这两个参数的值有可能为 nullptr。注意,即使当前点击的行没有变化,但是被点击节点的列发生了变化,组件也会发射此信号。

itemChanged()信号在节点的某一列的属性发生变化时被发射,例如文字变化、复选状态变化。

itemClicked()信号在点击节点时被发射,不管当前节点的行和列有没有变化都会触发此信号。用户在目录树上按下鼠标左键或右键时,组件会发射 itemPressed()信号。

itemSelectionChanged()信号在用户选择的节点发生变化时被发射,在当前节点切换或多选节点变化时都会触发此信号。

QTreeWidget 的信号比较多,一些信号的触发条件有细微的差别,通常需要实际测试才能理解信号触发的条件。在本示例中,我们通过 Go to slot 对话框为大部分信号创建了槽函数,在槽函数里用 qDebug()显示信息,表示信号被发射了。部分代码如下:

void MainWindow::on_treeFiles_itemChanged(QTreeWidgetItem *item, int column) 
{ 
 Q_UNUSED(item); 
 Q_UNUSED(column); 
 qDebug("itemChanged() is emitted"); 
} 
void MainWindow::on_treeFiles_itemSelectionChanged() 
{ 
 qDebug("itemSelectionChanged() is emitted"); 
}

其他信号的槽函数代码与此类似。如果不希望 qDebug()输出信息,在项目的.pro 文件里增加如下的定义即可使 qDebug()失效。

DEFINES += QT_NO_DEBUG_OUTPUT 

在目录树上点击节点使当前节点切换时, 组件会发射currentItemChanged()、itemSelectionChanged()、 itemClicked()等多个信号。在程序设计时,要根据需要实现的功能选择合适的信号创建槽函数。本示例要实现在切换到一个图片节点时显示其图片,所以对 currentItemChanged()信号进行了处理,后面会显示其槽函数的具体代码。

三. QTreeWidgetItem 类

QTreeWidget 组件的表头和目录树节点都是 QTreeWidgetItem 类对象,对目录树节点的操作主要通过 QTreeWidgetItem 类的接口函数实现。QTreeWidgetItem 类没有父类,它只用来存储节点的数据和各种属性,绘制目录树由 QTreeWidget 实现。

1.创建 QTreeWidgetItem 对象

QTreeWidgetItem 类有多种参数形式的构造函数,较简单的一种定义如下:

QTreeWidgetItem(int type = Type)

可以传递一个整数表示节点的类型,这个类型参数是一个自定义的数据。通过成员函数 type() 可以返回这个类型,如何使用这个类型数据由用户程序决定。

使用这个构造函数创建节点后,还需要调用 QTreeWidgetItem 类的各种接口函数设置节点各列的文字、图标、复选状态等属性。然后,可以通过 QTreeWidget::addTopLevelItem()函数将创建的节点添加成目录树的顶层节点,或通过QTreeWidgetItem::addChild()函数将其添加成一个节点的子节点。

在创建节点时,还可以传递字符串列表作为节点各列的文字,这种构造函数定义如下:

QTreeWidgetItem(const QStringList &strings, int type = Type) 

可以直接在某个节点下创建子节点,这种构造函数定义如下:

QTreeWidgetItem(QTreeWidgetItem *parent, int type = Type) 

其中,参数 parent 是创建节点的父节点,type 是创建的节点的类型。

还可以直接在树形组件里创建顶层节点,这种构造函数定义如下:

QTreeWidgetItem(QTreeWidget *parent, int type = Type) 

其中,QTreeWidget 类型的参数 parent 是树形组件,type 是创建的节点的类型。

2.访问节点各列数据的接口函数

创建一个节点后,可以设置节点每一列的文字、字体、对齐方式、复选状态等数据,按列设置数据的接口函数主要有以下几个,函数中的参数 column 是列的编号,0 表示第一列。

void setBackground(int column, const QBrush &brush) //设置背景色
void setForeground(int column, const QBrush &brush) //设置前景色
void setText(int column, const QString &text) //设置文字
void setTextAlignment(int column, int alignment) //设置文字对齐方式
void setToolTip(int column, const QString &toolTip) //设置 toolTip 文字
void setStatusTip(int column, const QString &statusTip) //设置 statusTip 文字
void setIcon(int column, const QIcon &icon) //设置图标
void setCheckState(int column, Qt::CheckState state) //设置复选状态
void setFont(int column, const QFont &font) //设置字体

这些设置函数的作用见注释,就不具体解释了。相应地,还有读取函数,同样需要传递参数column,以返回某一列的数据。读取函数见 Qt 帮助文档里 QTreeWidgetItem 类的信息,就不列出了。

QTreeWidgetItem 还有一个函数 setData()可以为节点的某一列设置用户数据,这个数据是不显示在界面上的。例如在本示例中,我们将图片的完整文件名设置为节点的用户数据,这样,在点击节点时,就可以读取节点用户数据表示的完整文件名。设置和读取用户数据的函数定义如下:

void setData(int column, int role, const QVariant &value) 
QVariant data(int column, int role) 

参数 role 是用户数据角色,可以使用常量 Qt::UserRole 定义第一个用户数据,使用1+Qt::UserRole 定义第二个用户数据。用户数据是 QVariant 类型,可以存储各种类型的数据。

3.对节点整体特性进行操作的接口函数

QTreeWidgetItem 的一些接口函数用于设置或读取节点的整体特性,常用的有以下一些函数。

int type() //返回在创建节点时设置的 type 参数
void setExpanded(bool expand) //使节点展开或折叠
bool isExpanded() 
void setDisabled(bool disabled) //设置是否禁用此节点
bool isDisabled() 
void setHidden(bool hide) //设置是否隐藏此节点
bool isHidden() 
void setSelected(bool select) //设置此节点是否被选中
bool isSelected() 

注意,函数 type()返回的是创建节点时传递的 type 参数,节点创建后就不能更改节点类型了。

还有一个函数 setFlags()用于设置节点的一些特性,设置和读取 flags 的函数定义如下:

void setFlags(Qt::ItemFlags flags) //设置节点的标志
Qt::ItemFlags flags() //读取节点的标志

参数 flags 是标志类型 Qt::ItemFlags,也就是枚举类型 Qt::ItemFlag 的组合。各枚举值意义如下:

Qt::NoItemFlags //没有任何标志
Qt::ItemIsSelectable //节点可以被选中
Qt::ItemIsEditable //节点可以被编辑
Qt::ItemIsDragEnabled //节点可以被拖动
Qt::ItemIsDropEnabled //节点可以接受拖来的对象
Qt::ItemIsUserCheckable //可以被复选,节点前面会出现一个复选框
Qt::ItemIsEnabled //节点可用
Qt::ItemIsAutoTristate //自动决定 3 种复选状态
Qt::ItemNeverHasChildren //不允许有子节点
Qt::ItemIsUserTristate //用户决定 3 种复选状态

4.操作子节点的接口函数

一个节点可以有任意多个子节点,可以添加、插入或移除子节点。这里的子节点指一个节点的直接子节点。QTreeWidgetItem 类中用于操作子节点的接口函数主要有以下几个,函数功能见注释。

void addChild(QTreeWidgetItem *child) //添加一个子节点
QTreeWidgetItem *child(int index) //根据序号返回一个子节点
int childCount() //返回子节点的个数
int indexOfChild(QTreeWidgetItem *child) //返回一个子节点的索引号
void insertChild(int index, QTreeWidgetItem *child) //插入一个子节点
void removeChild(QTreeWidgetItem *child) //移除一个子节点
QTreeWidgetItem *takeChild(int index) //移除一个子节点,并返回这个节点的指针

注意,removeChild()和 takeChild()都是从目录树上移除一个子节点,但是这个节点对象并不会被自动从内存中删除。

5.父节点

在目录树中,除了顶层节点,其他节点都有一个父节点,用函数 parent()可以返回父节点的指针。

QTreeWidgetItem *parent() 

 如果 parent()函数返回的是 nullptr,就表示没有父节点。

虽然顶层节点没有直观的父节点,但是 QTreeWidget 类有一个函数 invisibleRootItem()可以返回目录树的隐藏根节点,这个隐藏根节点可以看作顶层节点的父节点,可以通过QTreeWidgetItem的接口函数操作所有顶层节点。

四.示例中 QTreeWidget 的操作

1.本示例的目录树节点操作规则

本示例的目录树节点操作定义了如下的一些规则。

• 目录树的节点分为 3 种类型,即顶层节点、分组节点和图片节点。

• 创建窗口时初始化目录树,初始化的目录树只有一个顶层节点,这个顶层节点不能被删除,而且不允许新建顶层节点。

• 顶层节点下可以添加分组节点和图片节点。

• 分组节点下可以添加分组节点和图片节点,分组节点的层级数无限制。

• 图片节点是终端节点,可以在图片节点同级上再添加图片节点。

• 图片节点存储图片文件完整文件名作为用户数据。

• 点击一个图片节点时,显示其关联文件的图片。

为了便于后面说明代码的实现,我们把窗口类 MainWindow 的定义先列出来,关于自定义的枚举类型、变量、函数的功能后面再具体介绍。MainWindow 类中没有自定义槽函数。

class MainWindow : public QMainWindow 
{ 
 Q_OBJECT 
private: 
//枚举类型 TreeItemType,创建节点时用作 type 参数,自定义的节点类型数据必须大于 1000 
 enum TreeItemType{itTopItem=1001, itGroupItem, itImageItem}; 
 enum TreeColNum{colItem=0, colItemType, colDate}; //目录树列的序号
 QLabel *labFileName; //用于状态栏上显示文件名
 QLabel *labNodeText; //用于状态栏上显示节点标题
 QSpinBox *spinRatio; //用于状态栏上显示图片缩放比例
 QPixmap m_pixmap; //当前的图片
 float m_ratio; //当前图片缩放比例
 void buildTreeHeader(); //构建目录树表头
 void iniTree(); //初始化目录树
 void addFolderItem(QTreeWidgetItem *parItem, QString dirName); //添加目录节点
 QString getFinalFolderName(const QString &fullPathName); //提取目录名称
 void addImageItem(QTreeWidgetItem *parItem,QString aFilename); //添加图片节点
 void displayImage(QTreeWidgetItem *item); //显示一个图片节点关联的图片
 void changeItemCaption(QTreeWidgetItem *item); //遍历改变节点标题
 void deleteItem(QTreeWidgetItem *parItem, QTreeWidgetItem *item);//删除一个节点
public: 
 MainWindow(QWidget *parent = nullptr); 
private: 
 Ui::MainWindow *ui; 
}; 

2.目录树初始化

在窗口类 MainWindow 的构造函数里,我们调用自定义函数 buildTreeHeader()重新构建目录树的表头,调用自定义函数 iniTree()初始化目录树。MainWindow 类的构造函数和相关函数代码如下:

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) 
{ 
 ui->setupUi(this); 
//创建状态栏上的组件
 labNodeText= new QLabel("节点标题",this); 
 labNodeText->setMinimumWidth(200); 
 ui->statusBar->addWidget(labNodeText); 
 spinRatio= new QSpinBox(this); //用于显示图片缩放比例的 QSpinBox 组件
 spinRatio->setRange(0,2000); 
 spinRatio->setValue(100); 
 spinRatio->setSuffix(" %"); 
 spinRatio->setReadOnly(true); 
 spinRatio->setButtonSymbols(QAbstractSpinBox::NoButtons); //不显示右侧调节按钮
 ui->statusBar->addPermanentWidget(spinRatio); 
 labFileName= new QLabel("文件名",this); 
 ui->statusBar->addPermanentWidget(labFileName); 
//初始化目录树
 buildTreeHeader(); //重新构建目录树表头
 iniTree(); //初始化目录树
 setCentralWidget(ui->scrollArea); 
} 

void MainWindow::buildTreeHeader() 
{//重新构建 treeFiles 的表头
 ui->treeFiles->clear(); //清除所有节点,但是不改变表头
 QTreeWidgetItem* header= new QTreeWidgetItem(); //创建节点
 header->setText(MainWindow::colItem, "目录和文件"); 
 header->setText(MainWindow::colItemType, "节点类型"); 
 header->setText(MainWindow::colDate, "最后修改日期"); 
 header->setTextAlignment(colItem, Qt::AlignHCenter | Qt::AlignVCenter); 
 header->setTextAlignment(colItemType, Qt::AlignHCenter | Qt::AlignVCenter); 
 ui->treeFiles->setHeaderItem(header); //设置表头节点
} 

void MainWindow::iniTree() 
{//初始化目录树,创建一个顶层节点
 QIcon icon(":/images/icons/15.ico"); 
 QTreeWidgetItem* item= new QTreeWidgetItem(MainWindow::itTopItem); 
 item->setIcon(MainWindow::colItem, icon); //设置第一列的图标
 item->setText(MainWindow::colItem, "图片"); //设置第一列的文字
 item->setText(MainWindow::colItemType, "Top Item"); //设置第二列的文字
 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable 
 | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate); 
 item->setCheckState(MainWindow::colItem, Qt::Checked); //设置为选中状态
 ui->treeFiles->addTopLevelItem(item); //添加顶层节点
} 

在 MainWindow 的构造函数里, 我们创建了在状态栏上用于信息显示的 3 个组件。其中, QSpinBox 组件 spinRatio 隐藏了右侧用于数值调节的两个按钮。

自定义函数 buildTreeHeader()重新构建了树形组件 treeFiles 的表头,演示了如何创建一个 QTreeWidgetItem 对象作为表头节点。

自定义函数 iniTree()里创建了一个节点, 并将其添加到目录树中作为顶层节点。创建节点的语句是:

QTreeWidgetItem* item= new QTreeWidgetItem(MainWindow::itTopItem); 

它给 QTreeWidgetItem 的构造函数传递了一个参数 MainWindow::itTopItem,用于设置节点类型。用函数 QTreeWidgetItem::type()就可以返回这个节点类型。

MainWindow::itTopItem 是在 MainWindow 类里定义的枚举类型 TreeItemType 的一个枚举值。枚举类型 TreeItemType 定义了节点的类型,分别表示顶层节点、分组节点和图片节点,自定义的节点类型数值必须大于 1000。

3.添加分组节点

actAddFolder 是用于添加分组节点的 Action,只有目录树上的当前节点类型是 itTopItem 或itGroupItem 时才可以添加分组节点。actAddFolder 的 triggered()信号的槽函数以及相关自定义函数的代码如下:

void MainWindow::on_actAddFolder_triggered() 
{//“添加目录”Action 
 QString dir= QFileDialog::getExistingDirectory(); //选择目录
 if (dir.isEmpty()) //目录名称为空
 return; 
 QTreeWidgetItem *parItem= ui->treeFiles->currentItem(); 
 if (parItem == nullptr) //当前节点为空
 return; 
 if (parItem->type() != itImageItem) //图片节点不能添加分组节点
 addFolderItem(parItem, dir); //在父节点下面添加一个分组节点
} 

void MainWindow::addFolderItem(QTreeWidgetItem *parItem, QString dirName) 
{//添加一个分组节点
 QIcon icon(":/images/icons/open3.bmp"); 
 QString NodeText= getFinalFolderName(dirName); //获取最后的文件夹名称
 QTreeWidgetItem *item= new QTreeWidgetItem(MainWindow::itGroupItem); 
 item->setIcon(colItem, icon); //设置图标
 item->setText(colItem, NodeText); //最后的文件夹名称,设置第一列文字
 item->setText(colItemType, "Group Item"); //设置第二列文字
 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable 
 | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate); 
 item->setCheckState(colItem, Qt::Checked); 
 item->setData(colItem,Qt::UserRole,QVariant(dirName)); //设置用户数据,存储完整目录名称
 parItem->addChild(item); //添加到父节点下面
} 

QString MainWindow::getFinalFolderName(const QString &fullPathName) 
{//从一个完整目录名称里获取最后的文件夹名称
 int cnt= fullPathName.length(); //字符串长度
 int i= fullPathName.lastIndexOf("/"); //最后一次出现的位置
 QString str= fullPathName.right(cnt-i-1); //获取最后的文件夹名称
 return str; 
}

actAddFolder 的槽函数里首先用文件对话框获取一个目录名称,再获取目录树的当前节点,然后调用自定义函数 addFolderItem()添加一个分组节点,新添加的节点将会作为当前节点的子节点。

函数 addFolderItem()根据传递来的父节点 parItem 和目录全称 dirName 创建并添加节点。首先用自定义函数 getFinalFolderName()获取目录全称的最后一级的文件夹名称,这个文件夹名称将作为新建节点的标题;然后创建一个节点,节点类型设置为 itGroupItem, 表示分组节点,设置属性和用户数据,用户数据就是目录的全路径字符串; 最后使用函数 QTreeWidgetItem::addChild()将创建的节点作为父节点的一个子节点添加到目录树中。

4.添加图片节点

actAddFiles 是添加图片节点的 Action,目录树的当前节点为任何类型时这个 Action 都可用。 actAddFiles 的 triggered()信号的槽函数以及相关自定义函数的代码如下:

void MainWindow::on_actAddFiles_triggered() 
{//“添加文件”的 Action,添加图片节点
 QStringList files= QFileDialog::getOpenFileNames(this,"选择文件","","Images(*.jpg)"); 
 if (files.isEmpty()) //一个文件都没选
 return; 
 QTreeWidgetItem *parItem, *item; 
 item= ui->treeFiles->currentItem(); //当前节点
 if (item == nullptr) //如果是空节点
 item= ui->treeFiles->topLevelItem(0); //取顶层节点
 if (item->type() == itImageItem) 
 //如果当前节点是图片节点,取其父节点作为将要添加的图片节点的父节点
 parItem= item->parent(); 
 else //否则取当前节点作为父节点
 parItem= item; 
 for (int i= 0; i < files.size(); ++i) //文件列表
 { 
 QString aFilename= files.at(i); //获取一个文件名
 addImageItem(parItem, aFilename); //添加一个图片节点
 } 
 parItem->setExpanded(true); //展开父节点
} 

void MainWindow::addImageItem(QTreeWidgetItem *parItem, QString aFilename) 
{//添加一个图片节点
 QIcon icon(":/images/icons/31.ico"); 
 QFileInfo fileInfo(aFilename); //QFileInfo 用于获取文件信息
 QString NodeText= fileInfo.fileName(); //不带有路径的文件名
 QDateTime birthDate= fileInfo.lastModified(); //文件的最后修改日期
 QTreeWidgetItem *item; 
 item= new QTreeWidgetItem(MainWindow::itImageItem); //节点类型为 itImageItem 
 item->setIcon(colItem, icon); 
 item->setText(colItem, NodeText); //第一列文字
 item->setText(colItemType, "Image Item"); //第二列文字
 item->setText(colDate, birthDate.toString("yyyy-MM-dd")); //第三列文字
 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable 
 | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate); 
 item->setCheckState(colItem,Qt::Checked); 
 item->setData(colItem, Qt::UserRole, QVariant(aFilename)); //设置用户数据,存储完整文件名
 parItem->addChild(item); //在父节点下面添加子节点
} 

在 actAddFiles 的槽函数里,静态函数 QFileDialog::getOpenFileNames()会显示打开文件对话框,用于选择多个 JPG 图片文件。

通过函数 QTreeWidget::currentItem()可以获得目录树的当前节点 item。item->type()将返回节点的类型,如果当前节点类型是 itImageItem(图片节点),就使用当前节点的父节点作为将要添加的图片节点的父节点,否则就用当前节点作为父节点。

然后遍历所选图片文件列表,调用自定义函数 addImageItem()逐一添加图片节点到父节点下。

自定义函数 addImageItem()根据图片文件名称,创建一个节点并将其添加到父节点下。在使用函数 setData()设置节点的用户数据时,将带有路径的图片文件名作为节点的用户数据,这个数据在点击节点打开图片时会用到。

在函数 addImageItem()里用到了 QFileInfo 类,这个类用于获取文件的各种信息,如不带有路径的文件名、文件的最后修改日期、文件大小等。

5.当前节点变化时的响应

目录树上当前节点变化时,组件会发射 currentItemChanged()信号。为此信号创建槽函数,实现当前节点类型判断、几个 Action 的使能控制、显示图片等功能,代码如下:

void MainWindow::on_treeFiles_currentItemChanged(QTreeWidgetItem *current, QTreeWid
getItem *previous) 
{ 
 Q_UNUSED(previous); 
 qDebug("currentItemChanged() is emitted"); 
 if (current == nullptr) //当前节点为空
 return; 
 if (current == previous) //没有切换节点,只是列变化
 return; 
 int var= current->type(); //节点的类型
 switch(var) 
 { 
 case itTopItem: //顶层节点
 ui->actAddFolder->setEnabled(true); 
 ui->actAddFiles->setEnabled(true); 
 ui->actDeleteItem->setEnabled(false); //不允许删除顶层节点
 break; 
 case itGroupItem: //分组节点
 ui->actAddFolder->setEnabled(true); 
 ui->actAddFiles->setEnabled(true); 
 ui->actDeleteItem->setEnabled(true); 
 break; 
 case itImageItem: //图片节点
 ui->actAddFolder->setEnabled(false); //图片节点下不能添加目录节点
 ui->actAddFiles->setEnabled(true); 
 ui->actDeleteItem->setEnabled(true); 
 displayImage(current); //显示图片
 } 
} 

参数 current 是变化后的当前节点,通过 current->type()获得当前节点的类型,根据节点类型控制界面上 3 个 Action 的使能状态。如果当前节点是图片节点,还要调用函数 displayImage()显示节点关联的图片。

6.删除节点

除了顶层节点,选中的节点都可以被删除。actDeleteItem 是实现删除节点功能的 Action,其槽函数和相关自定义函数代码如下:

void MainWindow::on_actDeleteItem_triggered() 
{ 
 QTreeWidgetItem *item= ui->treeFiles->currentItem(); //当前节点
 if(item == nullptr) 
 return; 
 QTreeWidgetItem *parItem= item->parent(); //当前节点的父节点
 deleteItem(parItem, item); 
} 

void MainWindow::deleteItem(QTreeWidgetItem *parItem, QTreeWidgetItem *item) 
{//彻底删除一个节点及其子节点而递归调用的函数
 if (item->childCount() >0) //如果有子节点,需要先删除所有子节点
 { 
 int count= item->childCount(); //子节点个数
 QTreeWidgetItem *tempParItem= item; //临时父节点
 for (int i=count-1; i>=0; i--) //遍历子节点
 deleteItem(tempParItem, tempParItem->child(i)); //递归调用自己
 } 
//删除完子节点之后,再删除自己
 parItem->removeChild(item); //移除节点
 delete item; //从内存中删除对象
} 

节点不能移除自己,所以需要获取其父节点,使用父节点的 removeChild()函数来移除节点。函数 removeChild()可以移除节点,但是不从内存中删除节点对象,所以还要用 delete 删除节点对象。

在删除节点时,还要考虑到节点可能有多重子节点,因此需要从下往上删除所有子节点。我们定义了一个函数 deleteItem(),这个函数是递归调用的,能彻底删除一个节点及其所有子节点。如果不是调用函数 deleteItem()来递归删除节点,而是直接删除当前节点,那么当前节点的所有子节点将不能被彻底删除,从而造成内存泄漏。

7.节点的遍历

目录树的节点都是 QTreeWidgetItem 类对象,可以嵌套多层节点。有时需要在目录树中遍历所有节点, 例如按条件查找某些节点、统一修改节点的标题等。遍历节点时需要用到 QTreeWidgetItem 类的一些关键函数,还需要设计递归调用的函数。

actScanItems 是工具栏上“遍历节点”按钮关联的 Action,其槽函数和相关自定义函数代码如下:

void MainWindow::on_actScanItems_triggered() 
{//遍历节点
 for (int i=0; i< ui->treeFiles->topLevelItemCount(); i++) 
 {
  QTreeWidgetItem *item= ui->treeFiles->topLevelItem(i); //顶层节点
 changeItemCaption(item); //更改节点标题
 } 
} 

void MainWindow::changeItemCaption(QTreeWidgetItem *item) 
{//改变节点的标题
 QString str= "*" + item->text(colItem); //节点标题前加“*”
 item->setText(colItem,str); //设置节点标题
 if (item->childCount()>0) //如果有子节点
 for (int i=0; i<item->childCount(); i++) //遍历子节点
 changeItemCaption(item->child(i)); //递归调用自己
} 

槽函数的 for 循环访问所有顶层节点,获取一个顶层节点 item 之后,调用 changeItemCaption(item) 改变这个节点及其所有子节点的标题。

函数 changeItemCaption()是一个递归调用函数,即在这个函数里会调用它自己。它的前两行代码更改传递来的节点 item 的标题,即在标题前加“*”。后面的代码根据 item->childCount()是否大于 0 来判断这个节点是否有子节点,如果有子节点,再逐一获取子节点,调用函数 changeItemCaption() 修改子节点的标题。

五. 用 QLabel 和 QPixmap 显示图片

1.显示节点关联的图片

在目录树上点击一个节点后,如果节点类型为 itImageItem(图片节点),就会调用自定义函数 displayImage()显示节点关联的图片。函数 displayImage()的代码如下:

void MainWindow::displayImage(QTreeWidgetItem *item) 
{ 
 QString filename= item->data(colItem,Qt::UserRole).toString(); //节点存储的文件名
 labFileName->setText(filename); //状态栏显示
 labNodeText->setText(item->text(colItem)); //状态栏显示
 m_pixmap.load(filename); //从文件加载图片
 ui->actZoomFitH->trigger(); //触发 triggered()信号,运行其关联的槽函数
 ui->actZoomFitH->setEnabled(true); 
 ui->actZoomFitW->setEnabled(true); 
 ui->actZoomIn->setEnabled(true); 
 ui->actZoomOut->setEnabled(true); 
 ui->actZoomRealSize->setEnabled(true); 
}

QTreeWidgetItem 的函数 data()返回节点存储的用户数据,也就是用函数 setData()设置的用户数据。前面在添加图片节点时,我们将图片文件的带路径全名存储为节点的用户数据,函数 displayImage()的第一行语句就可以获取节点存储的图片文件全名。

m_pixmap 是在 MainWindow 类中定义的一个 QPixmap 类型的变量,函数 QPixmap::load()直接从一个文件加载图片。然后运行 ui->actZoomFitH->trigger(),这会触发 actZoomFitH 的 triggered() 信号,运行其关联的槽函数,即 on_actZoomFitH_triggered(),以适合高度的形式显示图片。

2.图片缩放与显示

有几个 Action 能实现图片的缩放与显示,包括适合宽度、适合高度、放大、缩小、实际大小等,部分槽函数代码如下:

void MainWindow::on_actZoomFitH_triggered() 
{//以适合的高度显示图片
 int H= ui->scrollArea->height(); 
 int realH= m_pixmap.height(); //原始图片的实际高度
 m_ratio= float(H)/realH; //当前显示比例,必须转换为浮点数
 spinRatio->setValue(100*m_ratio); //状态栏上显示缩放百分比
 QPixmap pix= m_pixmap.scaledToHeight(H-30); //图片缩放到指定高度
 ui->labPic->setPixmap(pix); //显示图片
} 

void MainWindow::on_actZoomIn_triggered() 
{//放大显示
 m_ratio= m_ratio*1.2; //在当前比例基础上乘 1.2 
 spinRatio->setValue(100*m_ratio); //状态栏上显示缩放百分比
 int w= m_ratio*m_pixmap.width(); //显示宽度
 int h= m_ratio*m_pixmap.height(); //显示高度
 QPixmap pix= m_pixmap.scaled(w,h); //图片缩放到指定高度和宽度,保持长宽比例
 ui->labPic->setPixmap(pix); 
} 

void MainWindow::on_actZoomRealSize_triggered() 
{//以实际大小显示
 m_ratio= 1; 
 spinRatio->setValue(100); 
 ui->labPic->setPixmap(m_pixmap); 
} 

QPixmap 类存储图片数据,它有以下几个函数可以用来缩放图片。

• scaledToHeight(int height):返回一个缩放后的图片副本,图片缩放到高度 height。

• scaledToWidth(int width):返回一个缩放后的图片副本,图片缩放到宽度 width。

• scaled(int width, int height):返回一个缩放后的图片副本,图片缩放到宽度 width 和高度height,默认是不保持比例。

变量 m_pixmap 保存了图片的原始副本,要实现缩放只需调用 QPixmap 的相应函数,会返回缩放后的图片副本。在窗口上有一个 QLabel 组件 labPic 用于显示图片,使用 QLabel 的 setPixmap() 函数即可显示图片,其函数原型定义如下:

 void QLabel::setPixmap(const QPixmap &) 

标签 labPic 放置在一个 QScrollArea 组件上,当 labPic 显示的图片超过 QScrollArea 组件的显示区域时,将自动出现水平或垂直卷滚条。

六. 示例中 QDockWidget 的操作

程序运行时,窗口上的 QDockWidget 组件可以被拖动,在窗口的左、右两侧停靠或在桌面上浮动。工具栏上“窗口浮动”和“窗口可见”两个按钮可以用代码来控制停靠区是否浮动、是否可见,其槽函数代码如下:

void MainWindow::on_actDockFloat_triggered(bool checked) 
{//“窗口浮动”按钮
 ui->dockWidget->setFloating(checked); 
} 
void MainWindow::on_actDockVisible_triggered(bool checked) 
{//“窗口可见”按钮
 ui->dockWidget->setVisible(checked); 
} 

这两个槽函数都是 Action 的 triggered(bool)信号的槽函数,因为这两个 Action 都是可复选的,点击工具按钮会改变 Action 的复选状态,复选状态作为 triggered(bool)信号中的 bool 类型参数。 QDockWidget 类有如下几个信号,信号发射的时机见注释。

void allowedAreasChanged(Qt::DockWidgetAreas allowedAreas) //allowedAreas属性值变化时
void dockLocationChanged(Qt::DockWidgetArea area) //移动到其他停靠区时
void featuresChanged(QDockWidget::DockWidgetFeatures features) //features 属性值变化时
void topLevelChanged(bool topLevel) //floating 属性值变化时
void visibilityChanged(bool visible) //visible 属性值变化时

其中,topLevelChanged()信号在 floating 属性值变化时被发射,参数 topLevel 为 true 表示组件处于浮动状态;visibilityChanged()信号在停靠组件的 visible 属性值变化时被发射。为这两个信号编写槽函数就可以更新两个 Action 的复选状态,槽函数代码如下: 

void MainWindow::on_dockWidget_visibilityChanged(bool visible) 
{//停靠区 visible 属性值变化
 ui->actDockVisible->setChecked(visible); 
} 
void MainWindow::on_dockWidget_topLevelChanged(bool topLevel) 
{//停靠区 floating 属性值变化
 ui->actDockFloat->setChecked(topLevel); 
} 
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值