Essential Qt 第二十一章 数据显示(一) 项/视图类

本文介绍Qt中使用QListWidget、QTableWidget及QTreeWidget显示数据的方法,涵盖列表、表格和树状结构数据的展示,并提供了示例代码。

  在二十章里,简单的介绍了用于数据库连接的类,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,则继续递归调用该函数,这样不断递归下去,就可以显示整个系统的目录结构了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值