QT最佳实践

界面

主窗口界面设计

标题栏:直接设Window-Title属性;Window-icon属性可加图标。
底部状态栏:在属性中设stylesheet可调颜色、字体、渐变等。在MainWindow.cpp中用statusBar()->showMessage(tr("版权所有……")); 方法可加文字(需#include <QStatusBar)。其实这是临时文字的设法,可加显示时长。持久显示应addWidget。
QLabel *statuslabel = new QLabel(this);
statuslabel->setText(tr("版权所有……"));
statusBar() -> addWidget(statusLabel);
这个文字只能显示在左侧。

界面设计存在两种方法:使用QtDesigner或直接用代码生成。最终决定用代码生成。原因是:
(1)代码更简洁,没有额外生成的ui类;(2)控制属性和方法更加自由;(3)官网和书上的示例都不用designer;
(4)可摆脱标准控件自定义外观,子类化部件进行自定义开发。

用代码法创建程序外观:

新建主窗口 新建QtWidgets Application - Class name:MainWindow; Basic Class: QMainWindow;Header File:MainWindow.h; Source File:MainWindow.cpp *不要选 Generate Form。
选择完后会自动生成上述文件(会生成一个.pro项目文件)。图标等单独建立qrc资源文件。
头文件里定义各种公私属性、函数名。 主要类的构造函数和方法都在MainWindow.cpp里。main.cpp只负责启动主窗口。
在MainWindow.cpp中:

include "mainwindow.h"

include <QtWidgets> //包含所有的控件

//创建主窗体;
resize(1000,650); //确定主窗口尺寸
setWindowTitle(tr("人体成分测量与管理系统 | Bodivista V4.0")); //创建标题栏
setWindowIcon(QIcon(":/image/icon-16.png")); //标题图标

//创建底部状态栏
QLabel *statusLabel = new QLabel(this);
statusLabel ->setText(tr(" 版权所有©2016 同方健康科技(北京)股份有限公司"));
statusBar()->addWidget(statusLabel);

注意:Layout(布局)不能直接作用于窗体,只能创设一个centerWidget,再在其上使用layout。
如下例:
//创建主布局和子布局
QWidget *contentWidget = new QWidget(this); //定义中心内容组件
this->setCentralWidget(contentWidget);

QVBoxLayout *MainLayout = new QVBoxLayout(this); //主布局
contentWidget->setLayout(MainLayout); //将主布局用于中心内容
//子布局
QHBoxLayout *TopLayout = new QHBoxLayout;
QGridLayout *LeftLayout = new QGridLayout;
QGridLayout *RightLayout = new QGridLayout;
QVBoxLayout *BottomLayout = new QVBoxLayout;

//有的板块使用groupbox
QGroupBox *LeftGroupBox = new QGroupBox(tr("测试人员信息"));
LeftGroupBox->setLayout(LeftLayout); //layout要适用于groupbox,而不能相反

//将子布局加入到主布局中
MainLayout->addLayout(TopLayout);
MainLayout->addLayout(BottomLayout);
TopLayout->addWidget(LeftGroupBox); //groupbox要用addWidget方法
TopLayout->addLayout(RightLayout);

左侧区域的子布局填充控件
//应用布局
LeftLayout -> addWidget(NameLabel,0,0);
LeftLayout -> addWidget(NameInput,0,1);
LeftLayout -> addWidget(NameLimit,0,2);
……

创建弹出子窗口

Qt的popup、page、ApplicationWindow等都是Qt5.7以后的组件。要是在5.6用,就只能用最简单的Window。
其实很简单,就是点击就把visible属性改为TRUE。

调用DLL

有显示调用和隐式调用两种。
隐式调用可以直接在Qt程序中使用dll中提供的函数,十分方便,但需.dll/.h/.lib三个文件。
(1)将这三个文件都放到Qt工程文件的源码文件夹。xxx.lib 改名为libxxx.a。如Stdapi.lib文件改名为libStdapi.a。
(2)在Qt的pro文件中下面加上
LIBS += -L 绝对路径 -l xxx(库文件名) 如:
LIBS += -L D:/workspace/card/card -l Stdapi
(注意,一定要用绝对路径,否则找不到)
(3)在debug或release生成的可执行文件的同级目录内放置xxx.dll文件。
(4)在Qt程序(如MainWindow)中,首先引用头文件xxx.h,之后就可以直接使用dll中的函数了。
(5)在发行包内要带有xxx.dll文件。

DLL显式加载,只需要DLL文件即可,不需要.H和.LIB文件
需要将DLL放到可执行目录中。
QLibrary lib("AlzData.dll")
这种比较繁琐。
首先写第一个方法调用,需判断一下是否正确加载
void MainWindow::openPort()
{
if (lib.load()) //判断是否正确加载
{
qDebug() << "AlzData.dll load ok!";
typedef int(*OPENPORT)(int);
OPENPORT OpenPort = (OPENPORT)lib.resolve("OpenSerialPort");
qDebug() << "OpenSerialPort 2, result:" << OpenPort(2);

console->setEnabled(true);

}else {
qDebug() << "load error!";
}
}
��
然后写调用DLL中方法的函数
void MainWindow::checkStatus()
{
typedef void(PERIODIC)();
typedef int(
CHECKSTATUS)(); //定义一个函数指针
PERIODIC PeriodicFunction= (PERIODIC)lib.resolve("PeriodicFunction"); //指明需解析的函数名
CHECKSTATUS CheckStatus = (CHECKSTATUS)lib.resolve("CheckStatus"); //实例化一个Qt函数
PeriodicFunction(); //调用
qDebug() << "CheckStaus , result:" << CheckStatus();
}

值的传递

在写程序时,难免会碰到多窗体之间进行传值的问题。依照自己的理解,我把多窗体传值的可以使用的方法归纳如下:
  1.使用QT中的Signal&Slot机制进行传值;
  2.使用全局变量;
  3.使用public形式的函数接口;
  4.使用QT中的Event机制(这种没有把握,但是感觉应该是可以的),但是实现起来应该比前几种复杂,这里不做讨论。

参见http://blog.csdn.net/hanxing0/article/details/9087237
实践中比较简单的是使用全局变量-但采用类的方式。

1.先在一个.h文件中声明一个类:
//Data.h

ifndef DATA_H

define DATA_H

class Data
{
public:
static int flag; //注意关键字static
static int size;
};

endif

2.在.cpp文件里类中的成员进行定义:
//Data.cpp

include "Data.h"

int Data::flag = 0;
int Data::size = 5;

Data::Data()
{
}
3.只要有了前面两步,这些变量就可以在其他的文件里用了,并且可以随时修改,例如:
//Form.cpp

include

include "Data.h"

Form::Form(QWidget *parent) : QWidget(parent)
{
ui.setupUi(this);
Data::flag = 1; //随时可以修改Data中的几个变量
Data::size = 10;
...
}
以上的方法对多个源文件之间的数据交互很有用处,当然,需要持久化的数据需要写到数据库中。

数据库

数据库的建立

Qt安装后自带QSQLITE driver,所以可以直接连接SQLITE数据库。所以客户端推荐使用SQLITE。
首先,pro项目文件中需要有
QT += sql widgets
集成相应的库。

默认的数据库连接是操作内存数据库,写不写数据库名字都可以。我们要持久化数据,就要写一个硬盘路径,以便保存到硬盘数据库中。
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("ex1.db");
数据库文件默认保存在项目build文件夹中,打包输出时再打包到合适的位置。

SQLITE数据库是QT默认的,所以最简单,而SQL数据库就复杂些。首先,您的QT得有SQL SERVER驱动(ODBC驱动),这在默认的QT里是没有的,要自己编译。编译就费了牛劲,先要有Visual Studio(要里面的nmake工具),然后是qmake和nmake不在系统路径中,添加到PATH后重启,解决了。
单独编译qt安装包里的~\src\qtbase\plugins\sqldrivers\odbc
qmake -t vclib odbc.pro
nmake (注:windows中据说可以用mingw32-make,但没搞通)

编译后,在~\plugins\sqldrivers\下应该有qsqlodbcd4.dll(debug)或qsqlodbc4.dll(或从odbc文件夹里拷贝过去)。Qt 5.10发行包里plugin/sqldrivers/下面有qsqlodbc.dll这个也可以。拷贝到自己的发行包的/sqldrivers文件夹下即可。

当然,你要先装上SQL SERVER(推荐2008),里面有数据库,在安全性方面添加登录用户,设用户登录名、密码,主数据库、权限。
然后,非常重要!要把你的SQL SERVER设为可以用户名、密码登录。否则只能系统用户登录。
打开【SQL Server Managemenet Studio】窗口,在【对象资源管理器】窗口中右键单击服务器,在弹出的快捷菜单中选择【属性】命令,弹出【服务器属性】对话框。
在【服务器属性】对话框中选择【安全性】选项,打开【安全性】页。选择SQL SERVER和身份验证模式。

然后,在QT的文件里这样写SQL :
QSqlDatabase db = QSqlDatabase::addDatabase("QODBC");
db.setDatabaseName(QString("DRIVER={SQL Server};"
"SERVER=%1;"
"DATABASE=%2;"
"UID=%3;"
"PWD=%4;")
.arg("PYG") //服务器名,就是计算机名字,不是IP。这个可以用IP地址
.arg("KRVBP9WorkStation") //数据库名
.arg("webuser") //登录用户名
.arg("cstf158")); //登录密码

在连接之前,可以用SQL SERVER的数据管理工具先拿这个用户连接试试。
可以同时连多个数据库,给每个连接起一个名字。
QSqlDatabase db1 = QSqlDatabase::addDatabase("QODBC", "cnn1");
db1.setDataBaseName(QString("DRIVER={SQL Server};"
......
QSqlDatabase db2 = QSqlDatabase::addDatabase("QODBC", "cnn2");
db2.setDataBaseName(QString("DRIVER={SQL Server};"
......
QSqlDatabase db3 = QSqlDatabase::addDatabase("QODBC", "cnn3");
db1.setDataBaseName(QString("DRIVER={SQL Server};"
......
后面可以根据数据库名称来写model和query语句:
QSqlQueryModel *model1 = new QSqlQueryModel;
model1->setQuery("select * from table1", db1);
QTableView *view1 = new QTableView(this);
view1->setModel(model1)
view1->show();

QSqlQueryModel *model2 = new QSqlQueryModel;
model2->setQuery("select * from table2", db2);
QTableView *view2 = new QTableView(this);
view2->setModel(model2)
view2->show();

......
连接MySQL最困难。因为Qt自带的驱动和DLL都不能普遍适用各个MySQL版本。最后发现可以使用ODBC驱动连接MySQL。需要在客户端电脑上安装MySQL connector/ODBC(从MySQL官网下载),装上后就可以调用启动的驱动MySQL ODBC 8.0 unicode driver。
db2 = QSqlDatabase::addDatabase("QODBC","conn2");
dsn = QString("Driver={MySQL ODBC 8.0 unicode driver};SERVER=%1;PORT=%2;DATABASE=%3;UID=%4;PWD=%5;")
.arg(DBserver)
.arg(3306)
.arg(DBname)
.arg(DBaccount)
.arg(DBpassword);
db2.setDatabaseName(dsn);
实践发现不用在ODBC管理器中配置数据源,QT自己就可以连上了。但需要注意:MySQL connector/ODBC要依照MySQL Server是32/64位安装对应的版本。

Tip:配置MySQL server的注意事项
-默认是不支持中文的,需要在新建schema时选charset=gb2312/gbk
-默认只能本地访问,需要在Administration-User Privileages 里设置root的访问权限为Limit to hosts:%
就可以从网上访问了。如果考虑安全,可以新建其他用户开放网络访问权限。

当然也可以连ACCESS数据库:
QSqlDatabase db = QSqlDatabase::addDatabase("QODBC");
db.setDatabaseName("DRIVER={Microsoft Access Driver (*.mdb)}; FIL=(MS Access); DBQ=xxxx.mdb");
xxx.mdb就是ACCESS数据库文件。

关于ODBC连接

ODBC是微软提出的开放数据库接口,只要遵循这个接口的数据库,都可以使用ODBC驱动连接。
ODBC包括不同数据库的驱动程序、驱动管理器(windows中自带)等部分。SqlServer/MySql/PostgresQL乃至access、excel都可以通过ODBC连接。
以Qt连接MySql数据库为例,首先,驱动管理器在Windows中自带,不用下载。
主要的,需要安装Mysql的ODBC 驱动,这个可以从www.mysql.com下载。叫做MySQL connector/ODBC,最新版是8.0,适合MySQL 5.5以上版本,如果是5.5以前的MySql(比如现在还有很多人用的MySQL5.1),需要安装connector5.3,它支持MySQL4.1以上版本。下载connector需要根据OS 位数选择32位还是64位版本。

标准写法

每个程序需要一个dbinit.h头文件用来创立数据库-建立数据库连接-注入初始数据,写法如下:

include <QMessageBox>

include <QSqlDatabase>

include <QSqlError>

include <QSqlQuery>

static bool createDatabase()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("maindb.db");
//如果是内存数据库,就用db.setDatabaseName(":memory:");
//连接失败的提示
if (!db.open()) {
QMessageBox::critical(0, qApp->tr("Cannot open database"),
qApp->tr("Unable to establish a database connection.\n"
"This example needs SQLite support. Please read "
"the Qt SQL driver documentation for information how "
"to build it.\n\n"
"Click Cancel to exit."), QMessageBox::Cancel);
return false;
}
//建表和注入数据
QSqlQuery query;
//建立person表
//如果在测试阶段,可删除可能预先存在的表,防止重复建表
query.exec("drop table person");
query.exec("drop table images");

query.exec("create table person (id int primary key, "
"firstname varchar(20), lastname varchar(20))");
query.exec("insert into person values(101, 'Danny', 'Young')");
query.exec("insert into person values(102, 'Christine', 'Holand')");
query.exec("insert into person values(103, 'Lars', 'Gordon')");
query.exec("insert into person values(104, 'Roberto', 'Robitaille')");
query.exec("insert into person values(105, 'Maria', 'Papadopoulos')");
//建立item表
query.exec("create table images (itemid int, file varchar(20))");
query.exec("insert into images values(0, 'images/qt-logo.png')");
query.exec("insert into images values(1, 'images/qt-quick.png')");
query.exec("insert into images values(2, 'images/qt-creator.png')");
query.exec("insert into images values(3, 'images/qt-project.png')");
……
return true;
}

如果是内存数据库,退出后就会自动消失。如果是硬盘上的数据库,会生成在/build-项目名文件夹中。

数据库的访问

用model/view模式更灵活。一般是TableModel搭配TableView.一个model可以对应多个view。
每个model对应一张报表。如果是需要使用SQL语句查出部分内容生成一个表格,可使用QueryModel。

main.cpp实现主页面的渲染,基本不动。

widget.cpp文件中写道:

include "widget.h"

include "ui_widget.h"

include "initdb.h"

include <QtWidgets>

include <QtSql>

bool db = createDatabase(); //生成数据库

//用QSqlQueryModel的初始化函数
void initializeModelPerson(QSqlQueryModel *model)
{
//model->setTable("person"); //确定表
//model->select(); //调取该表的全部数据
model->setQuery("SELECT id, firstname,lastname FROM person");
//设置表头
model->setHeaderData(0, Qt::Horizontal, QObject::tr("ID"));
model->setHeaderData(1, Qt::Horizontal, QObject::tr("First name"));
model->setHeaderData(2, Qt::Horizontal, QObject::tr("Last name"));
}
//用TableModel的初始化函数
void initializeModelImages(QSqlTableModel *model)
{
model->setTable("images"); //确定表
model->select(); //调取该表的全部数据
//设置表头
model->setHeaderData(0, Qt::Horizontal, QObject::tr("ItemID"));
model->setHeaderData(1, Qt::Horizontal, QObject::tr("FilePath"));
}

Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//声明两个model实例,注意是指针类型
QSqlQueryModel *modelPerson = new QSqlQueryModel;
QSqlTableModel *modelImages = new QSqlTableModel;

initializeModelPerson(modelPerson); //初始化模型
initializeModelImages(modelImages);
//数据显示部分,全写在ui部分
//ui->tableView1表示UI界面中的tableView1构件。如果是代码生成的UI组件,直接使用就可以了。
ui->tableView1->setModel(modelPerson);
//只要setModel就可以显示出来了
ui->tableView2->setModel(modelImages);
}

Widget::~Widget()
{
delete ui;
}

�#数据的CRUD操作--model法
Qt可以使用SQL语句完成对数据库的常规操作。如果不需要复杂的查询,QSqlTableModel模型基本可以满足需求。本文将针对QSqlTableModel模型操作sqlite进行说明。 通常,Qt要进行SQL数据库操作,需要在 .pro 文件中加上这么一句:QT += sql

    Qt默认的情况下会加载sqlite驱动,下面一段代码为创建一个数据库连接:

QSqlDatebase db = QSqlDatabase:addDatabase("QSQLITE");

db
.setDatabaseName( "dbname.db");
if (!db.open()) {

QMessageBox::critical(0, QObject::tr("Database Error")), db.lastError().text());

}

    关于addDatabase(),可以指定名字(暂不叙述),如不指定,将采用系统默认的 QSqlDatabase::defaultConnection 这一名字。此时,Qt会创建一个默认的连接。此后,我们并不需要指定操作的是哪个数据库,而是使用当前的连接。
    接下来,我们需要为模型设置表:

QSqlTableModel model;

model.setTable("tablename");

model->setEditStrategy(QSqlTableModel::OnManualSubmit);

// 设置要操作的数据库表。并没有从表中选择数据,而是获取它的字段。
// 如要用表中的数据填充模型,需调用 select()

设置表后,我们就可以应用QSqlTableModel模型进行数据库的基本操作了,即增、删、改、查。
一、查
model.setFilter("age > 20 and age < 25");
if (model.select()) {

for (int i = 0; i < model.rowCount(); ++i) {

QSqlRecord record = model.record(i);

QString name = record.value("name").toString();

int age = record.value("age").toInt();

qDebug() << name << ": " << age;

}

}

    在QTableModel中,使用QSqlRecord来取出每一条数据,用value()返回相应字段的数据,随后我们可以根据需要转化为我们想要的数据类型。
    下面我们重点说一下setFilter(),如下是Qt帮助文档中对setFilter()的说明:

//filter是一个没有关键字WHERE的SQL语句

//如果当前模型已经从数据库中填充了数据,则立即应用新的过滤条件。否则,过滤条件将在下一次调用select()时生效。

    可知,setFilter()一般与select()一起使用。
    如下是Qt帮助文档中对select()的说明:

//如下是Qt对select的帮助文档

bool QSqlTableModel::select()

Populates the model with data from the table that was set via setTable(), using the specified filter and sort condition, and returns true if successful; otherwise returns false.

//根据指定的筛选和排序条件,用setTable所设置的表的数据填充模型,如果成功则返回true,否则返回false

    因此,我们可以这样做:① 在setTable后就调用select(),而不必每次调用select() ; ② 在每次调用setFilter()后调用select().
      二、增

QSqlRecord record = model->record();

record.setValue("name", "张三");

record.setValue("age", 12);

model->insertRecord(row, record);

model->submitAll();

    通常使用 insertRecord()来增加一行。

bool QSqlTableModel::insertRecord(int row, const QSqlRecord & record)

Inserts the record at position row. If row is negative, the record will be appended to the end. Calls insertRows() and setRecord() internally.

Returns true if the record could be inserted, otherwise false.

Changes are submitted immediately for OnFieldChange and OnRowChange. Failure does not leave a new row in the model.

//在参数row的位置插入一行。如果行不存在,则该记录将被附到结尾。

//若编辑策略为 OnManualSubmit或 OnRowChange 时,修改将立即生效。

bool QSqlTableModel::submitAll()

Submits all pending changes and returns true on success. Returns false on error, detailed error information can be obtained with lastError().

In OnManualSubmit, on success the model will be repopulated. Any views presenting it will lose their selections.

//提交待定的修改,成功则返回true。

//在OnManualSubmit模式下,成功后数据模型将会被重新填充。任何应用这个数据模型进行显示的控件将会失去原来的选择。

    若我们选用OnManualSubmit模式,则需要使用submitAll();

    三、改
    与insertRecord()类似,使用setRecord()来修改某一条记录,并同时需要注意在需要时调用submitAll()。

bool QSqlTableModel::setRecord(int row, const QSqlRecord & values)

    四、删
    删较简单,使用removeRow,或removeRows实现。同样要注意submitAll()的使用。

bool QAbstractItemModel::removeRow(int row, const QModelIndex & parent = QModelIndex())

bool QAbstractItemModel::removeRows(int row, int count, const QModelIndex & parent = QModelIndex())

数据的修改

点击submit按钮后统一提交修改。
submitButton = new QPushButton(tr("Submit"));
submitButton->setDefault(true);
//用信号-槽连接
connect(submitButton, SIGNAL(clicked()), this, SLOT(submit()));
//提交修改的槽函数,使用model/view模式
void TableEditor::submit()
{
model->database().transaction();
if (model->submitAll()) {
model->database().commit();
} else {
model->database().rollback();
QMessageBox::warning(this, tr("Cached Table"),
tr("The database reported an error: %1")
.arg(model->lastError().text()));
}
}

//QSqlQuery类提供执行和操作的SQL语句的方法。先prepare再exec。使用binding绑定数据 。
//可以用来执行DML(数据操作语言)语句,如SELECT、INSERT、UPDATE、DELETE,
//以及DDL(数据定义语言)语句,例如CREATE TABLE。
//也可以用来执行那些不是标准的SQL的数据库特定的命令。
QSqlQuery sql_query;

QString create_sql = "create table student (id int primary key, name varchar(30), age int)";
QString select_max_sql = "select max(id) from student";
QString insert_sql = "insert into student values (?, ?, ?)";
QString update_sql = "update student set name = :name where id = :id";
QString select_sql = "select id, name from student";
QString select_all_sql = "select * from student";
QString delete_sql = "delete from student where id = ?";
QString clear_sql = "delete from student";

sql_query.prepare(create_sql);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
qDebug()<<"table created!";
}

//查询最大id
int max_id = 0;
sql_query.prepare(select_max_sql);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
while(sql_query.next())
{
max_id = sql_query.value(0).toInt();
qDebug()<<QString("max id:%1").arg(max_id);
}
}
//插入数据
sql_query.prepare(insert_sql);
sql_query.addBindValue(max_id+1);
sql_query.addBindValue("name");
sql_query.addBindValue(25);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
qDebug()<<"inserted!";
}

//更新数据
sql_query.prepare(update_sql);
sql_query.bindValue(":name", "Qt");
sql_query.bindValue(":id", 1);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
qDebug()<<"updated!";
}

//查询部分数据
if(!sql_query.exec(select_sql))
{
qDebug()<<sql_query.lastError();
}
else
{
while(sql_query.next())
{
int id = sql_query.value("id").toInt();
QString name = sql_query.value("name").toString();

qDebug()<<QString("id:%1 name:%2").arg(id).arg(name);
}
}

//查询所有数据
sql_query.prepare(select_all_sql);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
while(sql_query.next())
{
int id = sql_query.value(0).toInt();
QString name = sql_query.value(1).toString();
int age = sql_query.value(2).toInt();

qDebug()<<QString("id:%1 name:%2 age:%3").arg(id).arg(name).arg(age);
}
}

//删除数据
sql_query.prepare(delete_sql);
sql_query.addBindValue(max_id);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
qDebug()<<"deleted!";
}

//清空表
sql_query.prepare(clear_sql);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
qDebug()<<"cleared";
}
}

关于表格

在界面上实现表格有Table Widget和Table View两种方法。Table Widget可以精确地对表格的外观进行设置,还有QTableWidgetItem类可以对每个单元格进行精确设置,十分方便。但无法与QSqlTableModel绑定,也就是无法显示数据库的数据。因此,在大多数数据显示的场合,只能使用Table View,也就是model/view模式。使用model/view模式的另一个好处是同一份数据可以同时显示在多个view中,而且可以自动更新。

QTableWidget是QT程序中常用的显示数据表格的空间,很类似于VC、C#中的DataGrid。说到QTableWidget,就必须讲一下它跟QTabelView的区别了。QTableWidget是QTableView的子类,主要的区别是QTableView可以使用自定义的数据模型来显示内容(也就是先要通过setModel来绑定数据源),而QTableWidget则只能使用标准的数据模型,并且其单元格数据是QTableWidgetItem的对象来实现的(也就是不需要数据源,将逐个单元格内的信息填好即可)。这主要体现在QTableView类中有setModel成员函数,而到了QTableWidget类中,该成员函数变成了私有。使用QTableWidget就离不开QTableWidgetItem。QTableWidgetItem用来表示表格中的一个单元格,正个表格都需要用逐个单元格构建起来。

在数据库部分已经说了如何显示一张表。设置表格中的静态内容可以用setIndexWidget,如setIndexWidget(index, new QLineEdit); 而这个index就是QModelIndex
�如果表格中有部分单元格是静态内容,其他部分是动态的,则应使用QTableWidget,便于设置每个单元格的内容。

QDataWidgetMapper将一个数据库记录字段反映到其映射的窗口部件中,同时将窗口部件中所做出的更改反映回数据库,关键是关联一个model和一组widget一、步骤
1、创建 QDataWidgetMapper 对象
2、关联 model
3、关联 widgets,并创建其与model中section的映射
4、定位到某个record

画图

使用QPaint类,可绘制2D图形和文字。
首先#include<QPainter>
在MainWindow中创立一个画板(使用PaintArea类)
paintArea =new PaintArea();
一切绘画内容将在画板中呈现。 可以在layout中布局画板。
QHBoxLayout mainLayout =new QHBoxLayout(this); //整体的布局
mainLayout->addWidget(paintArea);
可以在其他槽函数中控制画板(通过调用画板的方法)
void MainWidget::ShowShape(int value)
{
PaintArea::Shape shape = PaintArea::Shape(shapeComboBox->itemData(value,Qt::UserRole).toInt());
paintArea->setShape(shape);
}
那么,画板中的初始图形在哪里定义呢?原来,PaintArea类有个paintEvent方法,可在其中规定画布上呈现的内容。在MainWindow中可重写paintEvent函数绘制所需内容。
void MyMainWindow::paintEvent(QPaintEvent
)
//paintEvent函数由系统自动调用,用不着我们人为的去调用。
{
painter=new QPainter; //实例一个画笔
painter->begin(this);
painter->setPen(QPen(Qt::blue,4,Qt::DashLine));//设置画笔形式
painter->setBrush(QBrush(Qt::red,Qt::SolidPattern));//设置画刷形式
painter->drawRect(20,20,160,160);
//设置字体:微软雅黑、点大小50、斜体
QFont font; font.setFamily("Microsoft YaHei");
font.setPointSize(50);
font.setItalic(true);
paint.setFont(font);
// 绘制文本
painter.drawText(rect(), Qt::AlignCenter, "Qt");
painter->end();
}
这样,只要主窗口运行,画板上的内容就呈现出来了。QPainter的方法有很多,什么都能画。

怎样在对话框中画图

有时需要在弹出的对话框中画图,怎么办呢?
1.创建对话框:在点击按钮的槽函数中实例化一个QDialog对象。
2.自定义PaintArea类,创建painarea.h和paintarea.cpp文件,在paintarea.cpp中实现paintEvent方法。
3.在对话框中实例化一个paintArea画板,以对话框为父对象。
4.将这个画板放到layout中布局。

实际用到的painarea.h文件模板如下:

ifndef PAINTAREA_H

define PAINTAREA_H

include <QWidget>

include <QPen>

include <QBrush>

class PaintArea : public QWidget
{
Q_OBJECT
public:

explicit PaintArea(QWidget *parent = 0);

void paintEvent(QPaintEvent *);

signals:

public slots:
private:

};

endif // PAINTAREA_H

实际用到的painarea.cpp文件模板如下:

include "paintarea.h"

include <QPainter>

PaintArea::PaintArea(QWidget *parent) :
QWidget(parent)
{

}

void PaintArea::paintEvent(QPaintEvent *)
{
QPainter painter(this);

/****************************自定义绘图内容****************************************/

painter.drawText(10,20, "Welcome to Qt paint world");

}
在paintarea.cpp的paintEvent方法中就可以尽情书写你自己的图案了!

打印

在Qt中,打印与在 QWidget,QPixmap或者QImage绘图很相似,一般步骤如下:
1、创建绘图设备的QPrinter;
2、弹出打印对话框,QPrintDialog,允许用户选择打印机,设置属性等;
3、创建一个以QPrinter为父对象的QPainter;
4、用QPainter绘制一页(painter.drawText...);
5、调用QPrinter::newPage(),然后绘制下一页;
6、重复步骤4,5,直到绘制完所有页。
7、运行,在打印对话框中选择“确定”,则会把绘制的内容逐页打印出来。

在Windows和Mac OS X平台,QPrinter使用系统的打印驱动程序。在Unix上,QPrinter生成脚本并把脚本发送给lp或者lpr(或者发送给程序,打印程序有函 数QPrinter::setPrintProgram())。调用QPrinter::setOutputFormat (QPrinter::PdfFormat)QPrinter也可以生成PDF文件。

如果绘制的内容较多,无法都写在主程序中,可以把上述内容写在一个头文件的函数中,在主文件中调用。

QT程序的打包发布

在开发机上调试好的QT程序拿到别的电脑上不能运行,这是因为需要的支撑环境不存在。这需要将“QT环境”也移植过去。QT环境包括:必要的动态库文件、引用的私有DLL文件、配置文件、数据库驱动、数据库和平台相关文件等。林林总总还真不少。
Qt5 主要依赖链接库说明
1.QT模块库
Qt5Core.dll #QT核心库
Qt5Gui.dll #QT Gui库
Qt5Widgets.dll #QT Widgets库,QT 5中GUI程序基本都需要此dll

还有其他程序用到的Qt5XXX.dll

2.ICU依赖库
icudt51.dll
icuin51.dll
icuuc51.dll
3.QT插件库(新增库,路径必须正确)
根据不同的程序,需要不同的插件库
plugins/platforms/qwindows.dll
plugins/accessible/qtaccessiblewidgets.dll
4.EGL依赖库,为OpenGL,OpenGL es提供接口
libEGL.dll
libGLESv2.dll
5.mingw依赖库(msvc编译则无需这些库)
libgcc_s_dw2-1.dll
libstdc++-6.dll
libwinpthread-1.dll
6.VC运行库(mingw编译则无需这些库)
msvcr110.dll(对应VS2012)
msvcp110.dll

检测可执行程序依赖模块可下载工具:Dependency Walker

静态编译和动态编译

Qt程序发布的时候,通常使用两种方式:
(1)静态编译
(2)动态编译
静态编译:把相关联的库一并引入可执行程序,虽然发布简单,但可执行程序较大。而且编译时间很长。
动态编译:相关联的库,以dll的形式引用,不被包含进可执行程序,发布不方便,但可执行程序较小。
实践中一般用的动态编译方式,结合打包工具提供给用户安装包。

Debug版本和Release版本

Debug版本带有各种调试信息,体积较大。对应的qt库文件是****d.dll文件。
Release版本不含各种调试信息,体积较小。对应的qt库文件是****.dll文件。
一般使用release版本发布。

文件结构

以我的bodivista文件为例,用于发布的文件结构一般如下:
bodivista.exe //主程序,可执行文件
Qt5Core.dll //以下为Qt的库文件,用到哪方面就包括哪个
Qt5Gui.dll
Qt5PrintSupport.dll
Qt5SerialPort.dll
Qt5Sql.dll
Qt5Widgets.dll
kernel32.dll
libgcc_c_dw2-1.dll
libstdc++-6.dll
libwinpthread-1.dll
msvcrt.dll
shell32.dll
qt.conf //配置文件,指明了路径
AlzData.dll //私用的动态库文件
mainDB //SQLite数据库文件
/plugins 插件集
/imageformats 图片插件
/platforms 平台依赖插件
/sqldrivers 数据库插件

qt.conf是设置路径的配置文件,没有它插件就无法找到
标准的qt.conf文件是这样的(来自于Qt/qt5.6.0/Tools/QtCreator/bin):
[Paths]
Prefix=.
Libraries=.
Plugins=plugins
Imports=imports
Qml2Imports=qml

这里规定了plungins,library等的路径,相关文件夹就要按这个放,否则找不到。
qt.conf的配置功能很强大,值得好好研究。

关于数据库的路径

有时发布的程序无法启动或连接数据库,而数据库文件(如mainDB)已经和可执行exe文件放在一个文件夹里了,怎么回事呢?
原来,qt连接各种数据库需要相应驱动,这些驱动在/plugins/sqldrivers里。exe文件要找到这些驱动,要看编译它时qt环境下的驱动路径和发布包中的驱动路径以及qt.conf的配置是否一致!
比如,你的编译环境(Qt/qt5.6.0/Tools/QtCreator/bin)中sqlite数据库驱动在bin文件夹下的/plugs/sqldrivers里,qt.conf写作Prefix=. Plugins=plugins,那么,编译时exe文件就会记住这个路径。在你的发布包中,sqlite数据库驱动也必须放在exe文件夹的/plugs/sqldrivers里,qt.conf也必须写作Prefix=. Plugins=plugins,否则就找不到。
到了Qt5.8,干脆只认系统路经,也就是你必须把sqldrivers放在与编译时同样的路经下。比如我的Qt编译时安装在C:\Qt\Qt5.8.0\5.8\mingw53_32\plungins中,打包发布时就要把sqlservers放在同样的路径中。也就是必须在C盘建个同样的文件夹。

打包

1.使用QT自带的windeployqt工具
首先,用release方式编译好release文件(如我的bodivista.exe),放到一个单独的文件夹中,如D:\pack。
进入QT的安装目录,如我的Qt/5.6.mingw49_32/bin,运行qtenv2.bat,启动QT的命令行界面。
在这个命令行界面中进入我的pack目录,然后启动windeployqt工具:
D:\pack>windeployqt bodivista.exe
会自动把所有依赖的库和插件都拷贝到这个目录中。包括sqldrivers,printsupport,imageformats,iconengines和translations。注意,sqldrivers必须放到一个plugins子目录中!其他的放在pack目录下即可。
把数据库文件maindb也放到bodivista.exe所在的目录。
写一个qt.conf配置文件(用记事本即可),里面写上:
[path]
Prefix = /
Plugins = plugins
Translatons = translations

发布文件夹准备好了,可以拷贝到某个没装QT的电脑上试试看可否执行。如果exe文件可以执行,则说明具备了可移植性。

QML程序的发布需要的依赖更多些。基本上把QT库里的dll文件(除了以d结尾的debug专用的)都拷贝过来,外面再放一个qml文件夹,里面要包括源码中qml相关的所有库(如Qt/5.6/mingw49_32/qml文件夹中的相关子目录)。

2.使用打包工具。
Windows的打包工具有很多,如小工具EnigmaVirtual Box。
免费而功能完整的推荐 NSIS。 NSIS(Nullsoft Scriptable Install System)是一个开源的 Windows 系统下安装程序制作程序。它提供了安装、卸载、系统设置、文件解压缩等功能。参见http://blog.sina.com.cn/s/blog_a6fb6cc90101fer8.html

QML的model-view模式以及数据库连接

在QML中,你可以用最简单的ListModel来构建复杂的数据模型,如
ListModel {
id: appModel
ListElement { name: "Music"; cost: 2.45; description: "Core" }
ListElement { name: "Movies"; cost: 1.95; description: "Deciduous"}
ListElement { name: "Camera"; cost: 2.09; description: "Citrus" }
ListElement { name: "Calendar"; cost: 3.87; description: "Tropical" }
ListElement { name: "Messaging"; cost: 4.46; description: "Seedless" }
}
下面可用ListView来展示,用delegate来设计展示样式。

// The delegate for each section header
Component {
id: sectionHeading
Rectangle {
width: container.width
height: childrenRect.height
color: "lightsteelblue"

 Text {
     text: section
     font.bold: true
     font.pixelSize: 20
 }

}
}

ListView {
id: view
anchors.top: parent.top
anchors.bottom: buttonBar.top
width: parent.width
model: animalsModel
delegate: Text { text: name; font.pixelSize: 18 }

section.property: "size"
section.criteria: ViewSection.FullString
section.delegate: sectionHeading
}
ListView可以有各种样式,生成各种动态效果。但一般使用的都是简单的表格,我推荐使用简单的GridView,自带delegate。
GridView {
anchors.fill: parent
cellWidth: 100; cellHeight: 100
focus: true
model: appModel
header:
Rectangle {
width: 500; height: 30; color:"lightblue";
Text {
text: "This is header"
font.pixelSize: 20
}
}
footer:
Rectangle {
width: 500; height: 30; color:"lightblue";
Text {
text: "This is footer"
font.pixelSize: 20
}
}

 highlight: Rectangle { width: 80; height: 80; color: "lightsteelblue" }

 delegate:
      Item {
      width: 100; height: 100

  Text {
     id:text1
     y: 20; anchors.horizontalCenter: parent.horizontalCenter
     text: name
    }
   Text {
      id:text2
      anchors { top: text1.bottom; horizontalCenter: parent.horizontalCenter }
      text: cost
     }
   Text {
       id:text3
       anchors { top: text2.bottom; horizontalCenter: parent.horizontalCenter }
        text: description
     }
     MouseArea {
         anchors.fill: parent
         onClicked: parent.GridView.view.currentIndex = index
       }
     }

}
}

由于QML没有直接连接数据库的能力(除了LocalStorage接口可访问本地SQLite外),一般使用C++在后台连接数据库,完成model的建立和操作,然后将数据发送给QML在view中展示。
实践中推荐两种方式:返回list和返回object。详细的说明见QT文档
http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html

返回list适用于简单的一维数据关系。
在QML中建立一个ListView,设它的模型是myModel,文字元素来自modelData。

ListView {
width: 100; height: 100

model: myModel
delegate: Rectangle {
    height: 25
    width: 100
    Text { text: modelData }
}

}
在后台的C++ 代码中,生成Datalist,使用setContextProperty("myModel", QVariant::fromValue(dataList))这样的双下文映射将dataList返给myModel。

QStringList dataList;
dataList.append("Item 1");
dataList.append("Item 2");
dataList.append("Item 3");
dataList.append("Item 4");

QQuickView view;
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(dataList));

这样,QML中一调用myModel,就会得到dataList。

对于复杂的数据,适合采用返回object。
在C++后台中建立一个DataObject类
//头文件

ifndef DATAOBJECT_H

define DATAOBJECT_H

include <QObject>

class DataObject : public QObject
{
Q_OBJECT

Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)

public:
DataObject(QObject *parent=0);
DataObject(const QString &name, const QString &color, QObject *parent=0);

QString name() const;
void setName(const QString &name);

QString color() const;
void setColor(const QString &color);

signals:
void nameChanged();
void colorChanged();

private:
QString m_name;
QString m_color;
};

//CPP文件
class DataObject : public QObject
{
Q_OBJECT

Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)
...

};

int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);

QList<QObject*> dataList;
dataList.append(new DataObject("Item 1", "red"));
dataList.append(new DataObject("Item 2", "green"));
dataList.append(new DataObject("Item 3", "blue"));
dataList.append(new DataObject("Item 4", "yellow"));

QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(dataList));

name和color是这个object的两个属性,使用dataList.append(new DataObject("Item 1", "red"));的方法将生成的object实例装入dataList中,使用
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
模板将属性设为可读、可写,并且命名了写函数和通知signal。
然后就可在QML中直接使用color和name属性啦!
ListView {
width: 100; height: 100

model: myModel
delegate: Rectangle {
    height: 25
    width: 100
    color: model.modelData.color
    Text { text: name }
}

}

怎样使用C++控件?

在QML中使用C++代码库和控件,有两种做法,一是使用注册,二是使用上下文。
先说说最简单的上下文(context)。
比如,我在上面的GridView的delegate中加入一个Text4:
Text {
id:text4
anchors { top: text3.bottom; horizontalCenter: parent.horizontalCenter }
text: currentDateTime
}
这里调用了QCurrentDateTime类型。
需要在main.cpp中

include <QDateTime>

include <QQuickView>

include <QQmlContext>

然后这样写main.cpp:
nt main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
view.rootContext()->setContextProperty("currentDateTime", QDateTime::currentDateTime());
view.setSource(QStringLiteral("qrc:/main.qml"));
return app.exec();
}
quickView是在C++代码中显示QML的“浏览器”,为它的根上下文(rootContext)设上下文属性(setContextProperty)“currentDateTime”为QDateTime::currentDateTime()即可。刚才的Text4的
text: currentDateTime就会显示出来。

那么,怎样在QML中使用C++部分的变量呢?最简单的办法是在C++中把数据都存入一个list,在QML中把model设为这个list。
//main.cpp

include <QQuickView>

include <QQmlContext>

include <QStringList>

int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);

QStringList dataList;
dataList.append("Item 1");
dataList.append("Item 2");
dataList.append("Item 3");
dataList.append("Item 4");

QQuickView view;
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(dataList));

//设完上下文属性后,使用了QVariant::fromValue()方法
view.setSource(QUrl("qrc:view.qml"));
view.show();

return app.exec();

}

//view.qml
import QtQuick 2.0

ListView {
width: 100; height: 100
model: myModel
delegate: Rectangle {
height: 25
width: 100
Text { text: modelData }
}
}
dataList中的数据就可以显示出来了,好神奇啊!
当然,这样的方法只能使用,不能修改变量。如果完全控制变量,需要用上文说的DataObject类的方法,返回object。

互联网实时交互程序#

可以使用QT+sock.io打造实时互联网交互程序
在服务器端使用node.js, sock.io, sqlite搭建服务器。客户端使用QT的QML,使用JavaScript。
socket.io可以让客户端和服务器端实时交互。

服务器端(server.js)的典型写法:
var app = require('express')();
var http = require('http').Server(app);
var io = require(socket.io')(http);
http.listen(3000,fucntion() {
console.log('Server is listening on port:3000......');
});
这是让socket的监听加入的app设置的http模块了
显示对应URL的网页:
app.get('/', funcition(req, res) {
res.sendFile(_dirname+ '/index.html');
});

发送与接收数据:
io.on('connnection', function(socket) {
socket.emit('news', {hello: 'world'}); //服务器端发送数据
socket.on('my other event', function (data) { //服务器端接受数据
console.log(data);
});
});

//index.html
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost');
socket.on('news', function (data) {
document.getElementById('hello').innerText = data.hello;
socket.emit('my other event', { my: 'data' });
});
</script>
<p id="hello" type="text">这里会插入发来的结果</p>

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 75,948评论 12 117
  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 78,785评论 25 511
  • 15.4 普通 Qt 课程 QObject 类构成了 Qt 的基础,但框架中还有更多的类。在我们继续关注 QML ...
    赵者也阅读 577评论 0 3
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 12,997评论 7 168
  • 又降温了,天气预报说周六会有雨夹雪。 居然有点期待的雀跃呢。 记忆中的雪景还在孩提时,上中学以前。 那时候的雪还可...
    语悦轩阅读 26评论 0 0