实例概述
此实例主要介绍QTreeWidget、QDockWidget的使用,以及用QLabel显示图片的方法,实例主要使用QTreeWidget,创建一个照片管理器,实例运行界面效果如下:
此实例的主窗口是从QMainWindow继承而来,主要演示了下面几个组件的使用:
- QTreeWidget目录树组件
QTreeWidget类是创建和管理目录树结构的类,实例使用了一个QTreeWidget组件管理照片目录,可以添加、删除节点,每个节点设置一个自定义类型,另外,还设置了一个自定义数据,图片节点储存完整文件名,以便单击节点时显示该图片
- QDockWidget停靠区域组件
QDockWidget是可以在QMainWidget窗口停靠,或在桌面最上层浮动的界面组件,实例将一个QTreeWidget组件放置在一个QDockWidget组件之上,设置其在主窗口的左右停靠或浮动
- QLabel组件显示图片
窗口右侧是一个QScrollArea组件,ScrollArea上面放置一个QLabel组件,通过QLabel设置一个QPixmap显示图片,通过QPixmap操作可进行缩放显示,包括放大、缩小、实际大小、适合宽度、适合高度等。
界面设计
界面布局设计
此实例界面采用可视化设计,程序功能主要采用Action实现,主菜单和主工具栏也都由其实现
工作区的左侧是一个QDockWidget组件,在其上方放置一个QTreeWidget组件,用水平布局使得treeWidget组件填充整个停靠区
工作区的右侧是一个QScrollArea组件,QScrollArea组件里放置一个QLabel,利用QLabel的pixmap属性显示图片。scrollArea内部的组件采用水平布局,当图片较小时,Label显示的图片可以自动居于scrollArea中间;当Label显示的图片超过scrollArea可显示区域的大小后,scrollArea会自动显示水平或垂直方向的卷滚条,用于显示更大的区域
在主窗口构造函数中将ScrollArea组件设置为主窗口中心组件后,DockWidget与ScrollArea之间自动出现分割条,可以分割两个组件的大小
QDockWidget组件属性设置
在UI设计器中对DockWidget组件的主要属性进行设置,主要属性如下:
- allowedAreas属性,设置允许停靠的区域
由函数setallowedAreas()函数设置允许停靠区,参数是枚举类型Qt::DockWidgetArea的值得组合,可以设置在窗口的左、右、顶、底停靠,所有的区域都能停靠或不允许停靠,本实例设置为左右两侧停靠
- features属性,设置停靠区组件的特性
由setFeatures()函数设置停靠区组件的特性,参数features是枚举类型QDockWidget::DockWidgetFeature的值得组合,枚举值如下:
- QDockWidget::DockWidgetClosable:停靠区可关闭
- QDockWidget::DockWidgetMovalbe:停靠区可移动
- QDockWidget::DockWidgetFloatable:停靠去可浮动
- QDockWidget::DockWidgetVerticalTitleBar:在停靠区左侧显示垂直标题栏
- QDockWidget::AllDockWidgetFeatures:使用所有特征
- QDockWidget::NoDockWidgetFeatures:不使用所有特征
QTreeWidget组件的设置
在UI设计器中,双击界面上的QTreeWidget组件,可以打开设计器,该设计器由两页,可以进行Item和Columns的设计
Columns页用于设计目录树的列,在设计器中可以添加、删除、移动列,设置列的文字、字体、前景色、背景色等属性
Items页面用于设计目录树的节点,可以对每个节点的属性进行设置,如文字、字体等,特别是flags属性,可以设置节点是否可选、是否可编辑、是否由CheckBox等,还可以设置节点的CheckState。在上图的下方有一组按钮可以新增节点,新增下级节点、移动节点、删除节点、该变节点级别等
使用设计器设计目录树的列和节点,适用于创建固定结果的目录树,但是目录树一般是根据内容动态创建的,需要运用代码实现节点的创建
Action设计
本例的大多数功能采用Action实现,在Action Editor里设计Action,然后利用Action设计主菜单和主工具栏
注意将QToolBar中的toolButtonStytle设置为ToolButtonTextUnderIcon,因为默认只显示图标,该属性是将Text显示在图标下方
QTreeWidget操作
此实例的目录树节点操作定义如下一些规则
- 将目录树的节点分为三种:顶层节点、分组节点、图片节点
- 窗口创建时初始化目录树,它只有一个顶层节点,这个顶层节点不能被删除,而且不允许创建新的顶层节点
- 顶层节点下允许添加分组节点和图片节点
- 分组节点下允许添加分组节点和图片节点,分组节点的级数无限制
- 图片节点就是终端节点,可以在图片节点同级再添加图片节点
- 每个节点创建时设置其类型信息,图片节点储存其完整文件名作为自定义数据
- 单击一个图片节点时,显示其关联文件的图片
由于在接下来的编程过程中会使用到一些数据类型和自定义函数需要提前在主窗口类中声明,先将MainWindow中的内容列出来,具体声明如下:
class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();private:Ui::MainWindow *ui;//枚举类型treeItemType,创建节点时用作type参数,自定义类型必须大于1000enum treeItemType{itTopItem = 1001, itGroupItem, itImageItem};//目录树列的编号enum treeColNum{colItem = 0, colItemType = 1};QLabel *LabFileName; //用于状态栏文件名显示QPixmap curPixmap; //当前图片float pixRatio; //当前图片缩放比例void iniTree(); //目录树初始化void addFolderItem(QTreeWidgetItem *parItem, QString dirName); //添加目录QString getFinalFolderName(const QString &fullPathName); //提取目录名称void displayImage(QTreeWidgetItem *item); //显示一个图片节点的图片void changeItemCaption(QTreeWidgetItem *item); //遍历改变节点标题
};
目录树初始化添加顶层节点
主窗口MainWindow的构造函数会调用自定义函数iniTree(),对目录树进行初始化,窗口构造函数和iniTree()代码如下:
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);LabFileName = new QLabel("");ui->statusBar->addWidget(LabFileName);this->setCentralWidget(ui->scrollArea); //将scrollArea设置为主窗口工作区中心组件iniTree(); //初始化目录树
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::iniTree()
{//初始化目录树QString dataStr = ""; //Item的Data储存的stringQIcon icon;icon.addFile("../images/文件夹.png"); //设置图标为:文件夹.pngQTreeWidgetItem* item = new QTreeWidgetItem(MainWindow::itTopItem);item->setIcon(MainWindow::colItem,icon); //第一列的图标item->setText(MainWindow::colItem, "图片文件"); //第一列的文字item->setText(MainWindow::colItemType,"type=itTopItem"); //第二列item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled| Qt::ItemIsAutoTristate);item->setCheckState(colItem,Qt::Checked);item->setData(MainWindow::colItem, Qt::UserRole,QVariant(dataStr));ui->treeWidget->setHeaderItem(item); //添加顶层节点
}
QTreeWidget的每个节点都是一个QTreeWidgetItem对象,添加一个节点前先要创建它,并且设置好相关属性
创建节点的语句是:
QTreeWidgetItem* item = new QTreeWidgetItem(MainWindow::itTopItem);
该函数传递了一个枚举常量MainWindow::itTopItem作为构造函数的参数,表示节点的类型。在构造函数中传递了一个类型值后,就可以使用QTreeWidgetItem::type()返回这个节点的类型值
itTopItem是在MainWindow里定义的枚举类型treeItemType的一个常量值。枚举类型treeItemType定义了节点的类型,自定义的节点类型值必须大于1000
QTreeWidgetItem中的setIcon()和setText()都需要传递一个列号作为参数,指定对哪个列进行设置。列号可以使用数字,但是为了便于理解和统一修改,在MainWindow中定义了枚举类型treeColNum,colItem表示第一列,colItemType表示第二列
setFlags()函数设置节点的一些属性标记,是Qt::ItemFlag枚举常量的组合
setData()函数为节点的某一列设置一个角色数据,setData()函数原型为:
void QTreeWidgetItem::setData(int column, int role,const QVariant &dataStr);
其中,column是列号,role是角色的值,value是一个QVariant类型的数
在目录树的初始化函数中,setData()函数为节点的第一列,角色UserRole()设置了一个字符串数据dataStr。Qt::UserRole是枚举类型Qt::ItemDataRole中一个预定义的值,节点创建完成之后,使用setHeaderItem()函数将此节点设置为顶层节点
添加目录节点
actAddFolder是用于添加组节点的Action,当目录树上的当前节点类型是itTopItem或itGropItem类型时,才可以添加组节点。actAddFolder的tiggered()信号的响应槽函数及相关自定义函数如下:
void MainWindow::on_actAddFolder_triggered()
{//添加组节点(目录)QString dir = QFileDialog::getExistingDirectory(); //选择目录if(!dir.isEmpty()){QTreeWidgetItem *parItem = ui->treeWidget->currentItem();addFolderItem(parItem,dir); //在父节点下面添加一个组节点}
}void MainWindow::addFolderItem(QTreeWidgetItem *parItem, QString dirName)
{QIcon icon("../images/文件夹.png");QString NodeText=getFinalFolderName(dirName); //获取最后的文件夹的名字QTreeWidgetItem *item;item = new QTreeWidgetItem(MainWindow::itGroupItem); //节点类型为itGroupItemitem->setIcon(colItem,icon);item->setText(colItem,NodeText);item->setText(colItemType,"type=itGroupItem");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);qDebug()<<str<<endl;return str;
}
actAddFolder的槽函数首先用一个文件对话框获取一个文件名称,在获取目录树的当前节点,然后调用自定义函数addFloderItem添加一个组节点,新添加的节点将会作为当前节点的子节点
addFolderItem()函数根据传递过来的父节点parItem和目录全程dirName,创建并添加子节点。首先用自定义函数getFinalFolderName()获取目录全称的最后一级文件夹的名字,这个文件夹名称将作为新建节点的标题;然后创建一个节点,创建时设置其节点类型为itGroupItem,表示分节点,再设置属性和关联数据,关联数据就是目录的全路径字符串;最后调用QTreeWidgetItem::addChild()函数,将创建的子节点作为父节点的一个子节点添加到目录树
添加图片文件节点
actAddFiles是添加图片文件节点的Action,目录树的当前节点为任何类型时这个Action都可以使用。actAddFiles的槽函数以及相关自定义函数的代码如下:
void MainWindow::on_actAddFile_triggered()
{//添加图片文件节点,支持多选QStringList files = QFileDialog::getOpenFileNames(this,"选择图片文件","","Images(*.jpg)");QTreeWidgetItem *item;item=ui->treeWidget->currentItem();for (int i=0; i < files.size(); i++) {QString aFilename = files.at(i);addImageItem(aFilename);}
}void MainWindow::addImageItem(QString aFilename)
{//添加一个图片节点QIcon icon("../images/图片.png");QString NodeText = getFinalFolderName(aFilename);QTreeWidgetItem *item;item = new QTreeWidgetItem(MainWindow::itImageItem);item->setIcon(colItem,icon);item->setText(colItem,NodeText);item->setText(colItemType,"type=itImageItem");item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled| Qt::ItemIsAutoTristate);item->setData(MainWindow::colItem, Qt::UserRole,QVariant(aFilename));ui->treeWidget->currentItem()->addChild(item);
}
该槽函数首先使用QFileDialog::getOpenFileNames(),获取图片文件列表,通过QTreeWidgetItem::currentItem()函数获得目录树的当前节点item
函数使用files.at(i)获取当前图片文件的文件名,调用addImageItem()函数,将一个或多个图片文件节点添加到当前列表下
列表变化的响应
目录树上的列表项发生变化时,会发射currentItemChanged()信号,为此信号创建槽函数,实现当前节点类型的判断、Action按钮的使能设置、显示图片等响应功能
在UI设计器中的treewidget组件中,点击右键,在转到槽之后选择currentItemChanged()信号,编写该信号的响应槽函数
void MainWindow::on_treeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
{//列表树的变化响应槽函数Q_UNUSED(previous); //Q_UNUSED()函数能够消除为引用参数的警告if(current == NULL)return;int var = current->type();switch (var) {case itTopItem:ui->actAddFile->setEnabled(true);ui->actAddFolder->setEnabled(true);ui->actDelete->setEnabled(false);break;case itGroupItem:ui->actAddFile->setEnabled(true);ui->actAddFolder->setEnabled(true);ui->actDelete->setEnabled(true);break;case itImageItem:ui->actAddFile->setEnabled(false);ui->actAddFolder->setEnabled(false);ui->actDelete->setEnabled(true);displayImage(current);break;}
}
current是变化后的当前节点,通过current->type()获取当前节点的类型,根据节点类型调整相应的Action的使能关系和其它功能(显示图片),其中显示图片相关函数会在后面详细介绍
删除节点
除了顶层节点外,其它节点都可以在选中之后将其删除。详细代码如下:
void MainWindow::on_actDelete_triggered()
{//删除节点QTreeWidgetItem *item,*parItem;item = ui->treeWidget->currentItem();parItem = item->parent();parItem->removeChild(item); //父节点移除子节点,但不会删除该节点delete item;
}
但是,一个节点不能移除自己,因此需要使用item->parent()函数获取其父节点,让其父节点移除自己,之后再使用delete将其删除
若要删除顶层节点,则使用QTreeWidget::takeTopLevelItem(int index)函数,参数index为顶层列表的序号
QLabel和QPixmap显示图片
显示节点关联的图片
在目录树上单击一个节点后,如果其类型为图片节点,就会调用displayImage()函数显示节点的图片,当前节点为函数传递参数
void MainWindow::displayImage(QTreeWidgetItem *item)
{//显示图片,节点item保存了图片文件名QString filename = item->data(colItem,Qt::UserRole).toString();LabFileName->setText(filename);curPixmap.load(filename);on_actZoomIn_triggered(); //自动适应高度显示
}
QTreeWidgetItem::data()返回节点储存的数据,也就是用setDataa()设置的数据,在添加图片节点时,已经将文件名的全名存储为节点的数据,因此可以使用item->data()获取文件节点信息
curPixmap是在MainWindow中定义的一个QPixmap类型的变量,用于操作图片。QPixmap::load(QString &filename)直接将图片文件载入
最后直接调用on_actZoomIn_triggered()显示图片,这是actZoomFiH的槽函数,以自动适应高度的形式显示图片
图片的显示与缩放
在此实例中,包括适合宽度、适合高度、放大、缩小、实际大小等多个图片的缩放的Action,详细代码如下:
void MainWindow::on_actZoomIn_triggered()
{//放大显示pixRatio = pixRatio*1.2;int w = pixRatio*curPixmap.width();int h = pixRatio*curPixmap.height();QPixmap pix = curPixmap.scaled(w,h);ui->label->setPixmap(pix);
}void MainWindow::on_actZoomHig_triggered()
{//适合高度显示int H = ui->scrollArea->height();int realH = curPixmap.height();pixRatio = float(H)/realH; //获取当前页面显示比例,且必须转化为floatQPixmap pix = curPixmap.scaledToHeight(H-30); //图片缩放到指定高度ui->label->setPixmap(pix);
}void MainWindow::on_actZoomOut_triggered()
{//缩小显示pixRatio = pixRatio*0.8;int w = pixRatio*curPixmap.width();int h = pixRatio*curPixmap.height();QPixmap pix = curPixmap.scaled(w,h);ui->label->setPixmap(pix);
}void MainWindow::on_actZoomRealSize_triggered()
{//实际大小pixRatio = 1;ui->label->setPixmap(curPixmap);
}void MainWindow::on_actQuit_triggered()
{//退出按钮this->close();
}void MainWindow::on_actZoomWid_triggered()
{//适合宽度显示int W = ui->scrollArea->width();int realW = curPixmap.width();pixRatio = float(W)/realW; //获取当前页面显示比例,且必须转化为floatQPixmap pix = curPixmap.scaledToWidth(W-30); //图片缩放到指定宽度ui->label->setPixmap(pix);
}
QPixmap储存了图片数据,可以缩放图片,有以下几个函数:
- QPixmap scaledToHeight(int height):返回一个缩放后的图片的副本,图片缩放到指定高度height
- QPixmap scaledToWidtht(int width):返回一个缩放后的图片的副本,图片缩放到指定宽度width
- QPixmap scaled(int width,int height):返回一个缩放后的图片的副本,图片缩放到指定高度和宽度
变量curPixmap保存了图片的原始副本,要缩放只需要调用curPixmap的相应函数,返回缩放后的图片副本
在界面上的标签label上显示图片,使用了setPixmap()函数
QDockWidget操作
程序运行时,主窗口上的DockWidget组件可以被拖动,在主窗口的左、右两侧停靠,或者是在桌面上浮动。工具栏上的窗口浮动和窗口可见按钮可以用代码控制组件是否浮动、是否可见,代码如下:
void MainWindow::on_actFloat_triggered(bool checked)
{//窗口浮动ui->dockWidget->setFloating(checked);
}void MainWindow::on_actVisible_toggled(bool arg1)
{//窗口可见ui->dockWidget->setVisible(arg1);
}
当点击DockWidget组件标题栏的关闭按钮时,会隐藏停靠区并发射信号visibilityChanged(bool)信号,当拖动DockWidget组件,使其浮动或停靠时,会发射信号topLevelChanged(bool)。为这两个信号编写槽函数,可以更新两个Aciton的状态
在UI设计器中点击DockWidget组件,单击右键转到槽,之后选择visibilityChanged(bool),为其编写响应槽函数
同理为topLevelChanged(bool)信号编写响应槽函数,详细代码如下:
void MainWindow::on_dockWidget_visibilityChanged(bool visible)
{//停靠区域可见性变化ui->actVisible->setChecked(visible);
}void MainWindow::on_dockWidget_topLevelChanged(bool topLevel)
{//停靠区浮动性变化ui->actFloat->setChecked(topLevel);
}