在二十章里,简单的介绍了用于数据库连接的类,QSqlDatabase和QSqlQuery,通过这两个类可以有效的和数据库连接,然后通过sql语句才对数据库进行各种操作,但这里这里忽略了一个问题,如何向用户显示这些数据库中的数据/如何让用户对这些数据进行添加改删?这些操作可以用过sql语句来实现,但显然我们不能要求每个程序的用户都能熟练的使用sql语句,假设你设计了一个用于医院的病人管理系统,你显然不能要求每个医生都会使用sql语句来操作数据。
将数据库中的数据在GUI程序中显示出来,并且让用户可以(有限的)操作这些数据是一直非常常见的功能,这个问题也可以扩展下,数据不一定来自数据库,有些程序可能不会用到数据库。但问题是一致的,就是向用户显示数据,同时允许用户在一定的规则内修改这些数据。接下来几章里,将会详细的讨论这个问题,因为程序一旦设计到人机交互,问题往往会变的复杂起来。
关于数据的显示,最常见的就是表格了,当然还有其他很对显示的方式。数据的显示方式大致的可以分为两种,一种以文本方式显示,比如文字列表,各种表格,树形表等等,另一种则以图像显示,现在有一个较为流行的名词叫做“数据可视化”。对于文本显示的数据,Qt提供了模型/视图结构,而对于“数据可视化”,Qt则提供了QtCharts模块,该模块在前面的章节里有提过。这里先介绍下模型/视图。
关于模型/视图结构,基本概念来自MVC架构,通俗的讲,就是将数据和显示分开,减少两者之间的依赖。当然,如果你读到这里对于数据模型/视图没有任何概念的话也没有任何关系,关于数据显示的内容,你可以先完全无视模型/视图的概念,因为这些概念最初完全用不到。这里我们先撇开这些概念,先看几个简单的显示数据的例子。
第一个例子来自QListWidget,该类用于显示列表类型的数据,程序大致是这个样子的
首先原来我的偷工减料,这里我没去找其他的图片,然后文字也是用的数据。这个程序可以用于显示人名,每个人都有一个头像或者一张照片。
头文件
class ShowNameList : public QDialog
{
Q_OBJECT
private:
QListWidget* showName_ListWidget;
QPushButton* cancel_PushButton;
public:
ShowNameList(QWidget *parent = 0);
~ShowNameList();
void setName();
};
然后是cpp文件
ShowNameList::ShowNameList(QWidget *parent)
: QDialog(parent)
{
showName_ListWidget = new QListWidget;
cancel_PushButton = new QPushButton(tr("Cancel"));
QHBoxLayout* button_Layout = new QHBoxLayout;
button_Layout->addStretch();
button_Layout->addWidget(cancel_PushButton);
QVBoxLayout* main_Layout = new QVBoxLayout;
main_Layout->addWidget(showName_ListWidget);
main_Layout->addLayout(button_Layout);
setLayout(main_Layout);
main_Layout->setSizeConstraint(QLayout::SetFixedSize);
connect(cancel_PushButton,SIGNAL(clicked()),this,SLOT(close()));
setName();
}
void ShowNameList::setName()
{
for(int i = 0 ; i < 10 ; ++i)
{
QListWidgetItem* items = new QListWidgetItem(QString::number(i)); //注释1
items->setIcon(QIcon(tr("/home/vimer/pixs/smile.jpeg"))); //注释2
showName_ListWidget->addItem(items);
}
}
注释1 :对于一个QListWidget来说,每一项都是一个QListWidgetItem,这个QListWidgetItem可以显示图片,也可以显示文字,也可以像这里同时显示文字和图片
注释2 :这里偷工减料所有的图片都是一张.
第二个例子是一张表格,这是数据显示中最常见的模式了,Qt提供了一个QTableWidget类来实现简单的表格
这个表格用于储存一系列的二维坐标,同时允许用户添加和修改这些坐标值,最后程序上有个输出按钮,点击该按钮可以将这些坐标按照设定的格式在控制台(或者其他地方)输出。
首先是头文件
class ShowPoint : public QDialog
{
Q_OBJECT
private:
QPushButton* addRow_PushButton;
QPushButton* output_PushButton;
QPushButton* cancel_PushButton;
QTableWidget* points_TableWidget;
private slots:
void addPoint();
void outPutPoint();
public:
ShowPoint(QWidget *parent = 0);
~ShowPoint();
void initPoint(); //注释1
};
注释1:该私有函数会在程序启动是添加10个坐标作为最初的坐标
然后是构造函数代码
const int INIT_COUNT = 10;
ShowPoint::ShowPoint(QWidget *parent)
: QDialog(parent)
{
addRow_PushButton = new QPushButton(tr("Add"));
output_PushButton = new QPushButton(tr("OutPut"));
cancel_PushButton = new QPushButton(tr("Cancel"));
points_TableWidget = new QTableWidget(INIT_COUNT,2);
QStringList headers;
headers<<tr("X")<<tr("Y");
points_TableWidget->setHorizontalHeaderLabels(headers);
points_TableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); //注释1
QHBoxLayout* buttons_Layout = new QHBoxLayout;
buttons_Layout->addWidget(addRow_PushButton);
buttons_Layout->addWidget(output_PushButton);
buttons_Layout->addWidget(cancel_PushButton);
QVBoxLayout* main_Layout = new QVBoxLayout;
main_Layout->addWidget(points_TableWidget);
main_Layout->addLayout(buttons_Layout);
setLayout(main_Layout);
main_Layout->setSizeConstraint(QLayout::SetFixedSize);
connect(addRow_PushButton,SIGNAL(clicked()),this,SLOT(addPoint()));
connect(output_PushButton,SIGNAL(clicked()),this,SLOT(outPutPoint()));
connect(cancel_PushButton,SIGNAL(clicked()),this,SLOT(close()));
initPoint();
}
注释1:其实QTableWidget的结构包括3个大部分,表格,水平表头,垂直表头。其中表头为QHeaderView类,horizontalHeader()函数返回的是水平表头,即一个指向QHeaderView的指针,通过函数名可以很容易猜测出返回垂直表头的函数的名称。QHeaderView的setSectionResizeModel()函数用于设置表头在变化时的模式,参数为枚举值
QHeaderView::Interactive 表头可以被用户(用鼠标或其他)改变,也可以被程序中其他代码改变
QHeaderView::Fixed 表头不能被用户改变,但可以被程序中其他代码改变
QHeaderView::Stretch 表头将填满所有(水平或垂直方向上的)空间,并且不能被用户或者其他代码改变
QHeaderView::ResizeToContents 表头将会以Qt认为最优的方式设定大小,并且不能被用户或其他代码改变
PS:以上内容翻译自文档,因本人英语老师英年早逝,以上翻译内容精供参考。
如果把改行代码注释掉,那程序会是这个样子
这里可以明显看出右侧有空间没有被填满
然后是自动添加坐标的函数
void ShowPoint::initPoint()
{
for(int i = 0 ; i < INIT_COUNT ; ++i)
{
QTableWidgetItem* itemX = new QTableWidgetItem(QString::number((i)));
QTableWidgetItem* itemY = new QTableWidgetItem(QString::number((i)));
itemX->setTextAlignment(Qt::AlignCenter);
itemY->setTextAlignment(Qt::AlignCenter);
points_TableWidget->setItem(i,0,itemX);
points_TableWidget->setItem(i,1,itemY);
}
}
该函数同样使用了QTableWidgetItem来作为QTableWidget的项,注意这里添加项的时候需要制定项的位置,即该项的行数与列数。这个函数只是用于演示所以简单花了,也没什么实际意义,有兴趣的可以修改下这个函数,利用十九章演示的数据库功能,在数据库里建一张表,然后利用SQL语句来读取数据库里的数据,而不是像这里按顺序生成坐标
添加坐标的函数
void ShowPoint::addPoint()
{
int cRows = points_TableWidget->rowCount();
points_TableWidget->insertRow(cRows);
QTableWidgetItem* itemX = new QTableWidgetItem(tr("0"));
QTableWidgetItem* itemY = new QTableWidgetItem(tr("0"));
itemX->setTextAlignment(Qt::AlignCenter);
itemY->setTextAlignment(Qt::AlignCenter);
points_TableWidget->setItem(cRows,0,itemX);
points_TableWidget->setItem(cRows,1,itemY);
}
最后述输出坐标
void ShowPoint::outPutPoint()
{
int cs = points_TableWidget->rowCount();
for(int i = 0 ; i < cs ; ++i)
{
QString X = points_TableWidget->item(i,0)->text();
QString Y = points_TableWidget->item(i,1)->text();
qDebug()<<QString::number(i+1)+tr("th ")<<X<<":"<<Y;
}
}
由于程序只是用于演示,所以我只是把坐标通过qDebug()函数输出到控制台上
最后一个例子是将会可以显示电脑里大部分目录
这个程序使用了树形结构,这非常适合表示系统文件结构,这个程序非常简单,用户显示程序中所有的目录,同时显示该目录下包含文件总数以及子目录的数目。为了实现这样一个树形结构需要用到QTreeWidget。
程序的头文件很简单,该类继承自QTreeWidget
#include <QTreeWidget>
class DirctoryPrevoew : public QTreeWidget
{
Q_OBJECT
private:
void searchRoot(); //注释1
void searchChildDir(const QString& parentDir,QTreeWidgetItem* parentItem); //注释2
public:
DirctoryPrevoew(QWidget* parent = 0);
~DirctoryPrevoew();
};
注释1:这里会首先检索“根目录”,linux下是“/”,而win下是“C:/”,“D:/”等等。由于根目录没有父目录,所以代码上需要区别对待
注释2:这个函数是一个递归函数,用于检索非根目录的目录。
然后看这个程序的具体实现,先是构造函数。
DirctoryPrevoew::DirctoryPrevoew(QWidget* parent)
: QTreeWidget(parent)
{
setFixedSize(800,800);
//setSelectionMode(QAbstractItemView::ExtendedSelection);
setColumnCount(3); //注释1
QStringList headerName;
headerName<<tr("目录名")<<tr("包含文件数目")<<tr("包含目录数目");
setHeaderLabels(headerName); //注释2
header()->setSectionResizeMode(0,QHeaderView::Stretch);
setColumnWidth(1,150);
setColumnWidth(2,150);
searchRoot();
}
注释1:设定该类有3列,分别是目录的路径,包含文件述,包含目录数
注释2:设置水平表头,参数QStringList是QList<QString>的别名,该别名在Qt很多类中都会用到。
程序的第一步是找到所有的“根目录”,这些目录没有父目录所以要区别对待
void DirctoryPrevoew::searchRoot()
{
QFileInfoList rootNames = QDir::drives();
for(auto A : rootNames)
{
QTreeWidgetItem* items = new QTreeWidgetItem(this,QStringList(A.path()));
addTopLevelItem(items); //注释1
searchChildDir(A.path(),items); //注释2
}
}
注释1:对于树形结构来说,他必须有一个或者多个最顶层的项,这里将检测到的根目录作为顶层项。
注释2:把所有的“根目录”都检查出来后,开始搜索每个目录
最后是这个程序核心内容,searchChildDir()函数.
void DirctoryPrevoew::searchChildDir(const QString& parentDir,QTreeWidgetItem* parentItem)
{
static int counts = 0;
++counts;
qDebug()<<parentDir<<"+++++"<<counts; //注释1
QDir pDir(parentDir);
pDir.setFilter(QDir::Dirs);
int cs = pDir.count();
for(int i = 0 ; i < cs ; ++i)
{
if(pDir[i] == tr(".") || pDir[i] == tr("..")) //注释2
continue;
#ifdef Q_OS_LINUX //注释3
if(pDir[i] == tr("proc") || pDir[i] == tr("sys"))
continue;
#endif
QTreeWidgetItem* items = new QTreeWidgetItem(parentItem);
QDir cDir(parentDir+pDir[i]);
int ccs = cDir.count();
items->setText(0,pDir[i]);
items->setText(1,QString::number(ccs));
cDir.setFilter(QDir::Dirs);
ccs = cDir.count();
int fcs = 0;
for(int i = 0 ; i < ccs ; ++i)
{
if(cDir[i] != tr(".") && cDir[i] != tr(".."))
{
++fcs;
}
}
items->setText(2,QString::number(fcs)); //注释4
QString newParent = parentDir+pDir[i]+tr("/");
if(fcs != 0) //注释5
searchChildDir(newParent,items);
}
}
注释1:由于该程序需要历便系统上所有的目录,所有在有些目录文件较多的电脑上执行,启动需要较长的事件,我的win10系统里由于文件比较多,启动这个程序(执行构造函数)话费了5分钟左右的事件,通过输出这个静态变量可以知道程序究极实在启动/执行过程中,还是出错了。
注释2:在二十章里就演示了如果通过设置过滤器来屏蔽掉“.”和“..”这两个目录,这里使用另一种比较笨拙的方法,但这种方法可以屏蔽掉任意名称的目录。
注释3:Qt为了是的代码能做到“一次编写,到处编译”的功能,所以提供了系统相关的宏,这些宏还有Q_OS_WIN,Q_OS_MAC等等,通过这些宏可以是的代码只会在制定系统上编译,如果上面代码在win系统上,#ifdef和#endif直接的代码将会被忽略掉。由于linux下/proc和/sys下会有大量的文件,如果包含这两个目录,那这个函数可能一个小时内都执行不完,所以这里就不检索这两个目录了。
注释4:对于QTreeWidgetItem来说,当他作为最顶层的项时和其他项有着相当大的区别,这里演示了如何添加一个非顶层的项,可以和前一个函数里添加顶层项做下对比。
注释5:这里做一个判断,如果一个目录内的目录数量不为0,则继续递归调用该函数,这样不断递归下去,就可以显示整个系统的目录结构了