原创文章,欢迎转载,转载请注明:http://www.fishcode.cn/qt.html
CSDN: blog.csdn.net/jjzhoujun2010
作者:Dream Fly
毕业了,整理下思绪,把我之前在实验室弄的一个Qt终端界面开发的程序的整体思路以及源代码共享开来,以便于更好地让Qt为人所知,为开源贡献点我的微薄之力。
一. 项目介绍
项目背景的简要介绍:通过串口从数据采集板传输数据到终端板上面,自行设计一个界面用来显示收集到的数据,以及画出所对应的坐标点。终端显示界面的板子是基于S3C6410芯片的,板子是师兄根据Mini6410开发板电路图画出来的,做了部分裁剪。
由于考虑到跨平台以及移植性问题,我选择了Qt进行开发,分别开发了Windows以及Linux版本,并且移植到开发板上面。以下截图1,图2所示:
图1 Windows版本 图2 Linux版本
编写控制Qt串口通信终端界面时候,串口通信部分是参考yafeilinux的串口通信教程:http://blog.csdn.net/yafeilinux/article/details/4717706 Windows, Linux需要相应改写第三方类,yafeilinux里面已经有介绍。由于我要用的poll查询机制,利用settime(sec),因此更改了"*_qextserialport.cpp"文件里面的对应函数,如下图3所示:
图3 修改*_qextserialport.cpp文件
下图3是Windows下的项目文件截图:
图3 Windows下项目源文件
二. Windows平台编写过程
整个项目我是利用QT的designer和手写代码互相配合完成的。新建了一个带有ui的项目以后,添加第三方串口类源文件。在ui里面画出相应的东西,我是用了tabWidget来切换几个界面,在主界面中还用了tableView表格来显示各个参数,如上图1所示,对应的源程序代码如下。
头文件mainwindow.h,定义声明了各种类和变量。
//******************* mainwindow.h ************//
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QString>
#include <QStandardItemModel>
//#include "posix_qextserialport.h"
#include "win_qextserialport.h"
#include <QDebug>
#include <QTime>
#include <QTimer>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
// void paintEvent(QPaintEvent *);
private:
Ui::MainWindow *ui;
Win_QextSerialPort *myCom;
// Posix_QextSerialPort *myCom;
QStandardItemModel *model;
int row, column; //table size
QTimer *myReadTimer; //采用polling查询的方式进行
// QTimer *tableDisTimer;
int flag_startToConvert;
float QBArray2Float(QByteArray temp);
// void paintEvent(QPaintEvent *);
void draw();
//QPushButton *iButton;
//QLabel *iLabel;
protected:
bool eventFilter(QObject *obj, QEvent *e);
private slots:
// int delayUpdate();
// void delayUpdate();
void readMyCom();
void on_openMyComBtn_clicked();
void on_closeMyComBtn_clicked();
void on_helpBtn_clicked();
void on_inputBtn_clicked();
};
#endif // MAINWINDOW_H
mainwindow.cpp文件,具体实现各个子函数的功能。
//****************************** mainwindow.cpp *****************//
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QDebug>
#include <QtGui>
float QBArray2Float(QByteArray temp);
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->radarWidget->installEventFilter(this); // 使之能够直接在radarWidget中绘图
ui->closeMyComBtn->setEnabled(false);
setWindowTitle(tr("汽车防撞雷达界面显示"));
// 初始化第一个表格
row = 13;
column = 3;
model = new QStandardItemModel(row, column);
ui->tableView->setModel(model);
ui->tableView->verticalHeader()->hide(); // Hide the vertical No.
model->setHeaderData(0, Qt::Horizontal, tr("目标"));
model->setHeaderData(1, Qt::Horizontal, tr("距离/ (米)"));
model->setHeaderData(2, Qt::Horizontal, tr("方位/ (度)"));
}
MainWindow::~MainWindow(){
delete ui;
}
// ASCII --> Float
float floatData[20][2] = {{0, 0}}; // 全局变量,让coordinate.cpp文件也可以调用
int ti = 0, tj = 0; // 为了避免将i, j当成全局变量时候,其他函数可能会做相应的修改, 不能定义成static,
// 不然外部文件coordinate.cpp就无法使用ti了。
int dcFlag = 0; // 标志位,当其为1的时候表示坐标轴上也相应显示出图点
//static int disFlag = 0; // 当为1时候表示经过延时后才显示出来数据,用定时器控制disFlag的值
void MainWindow::readMyCom()
{
int rflag = 0;
int serialNum = 0;
int num = 0;
static int rNum = 2; // 一个目标有2个参数需要显示:距离、方位
// myReadTimer->stop(); // turn off timer
ui->radarWidget->update(); //刷新坐标系,实时显示图形
// if(1 == disFlag) //延时显示标志
// {
model->removeRows(0, model->rowCount());
model->setRowCount(row);
model->setColumnCount(column);
ui->tableView->update();
// }
QByteArray testFrame = myCom->read(5); //帧头"Star"有5个字符,因此read(5)
if(testFrame.startsWith("Star"))
{
dcFlag = 1;
qDebug()<<"Start to go"<<endl;
rflag = 1; // can receive frame
for(int i = 0; i < 50; i++)
{
serialNum = myCom->bytesAvailable();
if(serialNum >= num)
{
serialNum = 0;
break;
}
}
QByteArray dataFrame = myCom->read(4); //从数据采集板上传送过来的数据是4个字节1组
num = QBArray2Float(dataFrame);
for(ti = 0; ti < num; ti++)
{
// if(1 == disFlag) // 延时0.5s标志
// {
QStandardItem *itemTarget = new QStandardItem(QString::number(ti + 1));
model->setItem(ti, 0, itemTarget); //如何实现居中显示???
// }
for(tj = 0; tj < rNum; tj++)
{
dataFrame = myCom->read(4);
floatData[ti][tj] = QBArray2Float(dataFrame);
// if(1 == disFlag)
// {
QString str;
QStandardItem *itemFloat = new QStandardItem(str.sprintf("%0.2f", floatData[ti][tj]));
model->setItem(ti, tj + 1, itemFloat);
// }
}
}
// disFlag = 0;
}
else
{
qDebug()<<"receive"<<endl;
rflag = 0;
myCom->readAll(); // read off the data
dcFlag = 0; // 同时坐标轴不显示红点
// myReadTimer->start();
}
rflag = 0;
// myReadTimer->start(); // turn on the timer
}
//void MainWindow::delayUpdate()
//{
// disFlag = 1;
// return disFlag;
//}
//将传输过来的字符串直接转换浮点数函数
float MainWindow::QBArray2Float(QByteArray temp)
{
unsigned int i;
float f;
temp.resize(4);
i=0;
i|=((unsigned char) temp.at(3));
i=i<<8;
i|=((unsigned char) temp.at(2));
i=i<<8;
i|=((unsigned char) temp.at(1));
i=i<<8;
i|=((unsigned char) temp.at(0));
f = *(float*)&i;
return f;
}
//打开串口的信号与槽自动关联函数
void MainWindow::on_openMyComBtn_clicked()
{
// QString portName = "/dev/" + ui->portNameComboBox->currentText(); //获取串口名
QString portName = ui->portNameComboBox->currentText(); //获取串口名
// myCom = new Posix_QextSerialPort(portName, QextSerialBase::Polling);
myCom = new Win_QextSerialPort(portName, QextSerialBase::Polling);
//定义串口对象,并传递参数,在构造函数里对其进行初始化
if(myCom->open(QIODevice::ReadWrite))
{ //注意:得要先打开串口,然后再设置串口的参数,不然设置无效!!!
myCom->flush(); //存入缓冲区内待读取
//设置波特率
if(ui->baudRateComboBox->currentText() == tr("9600")) //根据组合框内容对串口进行设置
myCom->setBaudRate(BAUD9600);
else if(ui->baudRateComboBox->currentText() == tr("115200"))
myCom->setBaudRate(BAUD115200);
//设置数据位
if(ui->dataBitsComboBox->currentText() == tr("8"))
myCom->setDataBits(DATA_8);
else if(ui->dataBitsComboBox->currentText() == tr("7"))
myCom->setDataBits(DATA_7);
//设置奇偶校验
if(ui->parityComboBox->currentText() == tr("无"))
myCom->setParity(PAR_NONE);
else if(ui->parityComboBox->currentText() == tr("奇校验"))
myCom->setParity(PAR_ODD);
else if(ui->parityComboBox->currentText() == tr("偶校验"))
myCom->setParity(PAR_EVEN);
//设置停止位
if(ui->stopBitsComboBox->currentText() == tr("1"))
myCom->setStopBits(STOP_1);
else if(ui->stopBitsComboBox->currentText() == tr("2"))
myCom->setStopBits(STOP_2);
myCom->setFlowControl(FLOW_OFF); //设置数据流控制,我们使用无数据流的默认设置
//myCom->setTimeout(500); //设置延时
myCom->setTimeout(10); //设置延时 --Modify 改小点
ui->StatusLabel->setText(tr("串口状态:打开成功"));
}
else
{
ui->StatusLabel->setText(tr("串口状态:打开失败"));
return;
}
//定义出具体定时器,然后触发后开始Polling查询
myReadTimer = new QTimer(this);
myReadTimer->setInterval(10);
connect(myReadTimer, SIGNAL(timeout()), this, SLOT(readMyCom())); //信号和槽函数关联,当串口缓冲区有数据时,进行读串口操作
this->myReadTimer->start(); //开始poll查询操作
// 定义延时计数器
// tableDisTimer = new QTimer(this);
// tableDisTimer->setInterval(500);
// connect(tableDisTimer, SIGNAL(timeout()), this, SLOT(delayUpdate()));
// this->tableDisTimer->start();
ui->openMyComBtn->setEnabled(false);
ui->closeMyComBtn->setEnabled(true);
ui->helpBtn->setEnabled(true);
ui->portNameComboBox->setEnabled(false);
ui->baudRateComboBox->setEnabled(false);
ui->dataBitsComboBox->setEnabled(false);
ui->stopBitsComboBox->setEnabled(false);
ui->parityComboBox->setEnabled(false);
}
void MainWindow::on_closeMyComBtn_clicked()
{
this->myReadTimer->stop(); //关闭poll操作
// this->tableDisTimer->stop();
myCom->close();
model->removeRows(0, model->rowCount());
model->setRowCount(row);
model->setColumnCount(column);
dcFlag = 0;
ui->radarWidget->update(); //刷新坐标系,实时显示背景
ui->openMyComBtn->setEnabled(true);
ui->helpBtn->setEnabled(true);
ui->portNameComboBox->setEnabled(true);
ui->baudRateComboBox->setEnabled(true);
ui->dataBitsComboBox->setEnabled(true);
ui->stopBitsComboBox->setEnabled(true);
ui->parityComboBox->setEnabled(true);
ui->StatusLabel->setText(tr("串口状态:关闭"));
}
void MainWindow::on_helpBtn_clicked()
{
QMessageBox::about(this, tr("帮助信息"), tr("1.选定好具体串口设置,点击打开串口即可收到信息" "\n"
"2.防撞雷达项目小组共同努力制作" "\n"
"3.Qt界面作者联系方式:周军 QQ: 380774082" "\n"
"4.欢迎访问个人博客 http://blog.csdn.net/jjzhoujun2010"));
}
// 通过终端板子串口将数据下传到数据采集板中
void MainWindow::on_inputBtn_clicked()
{
QByteArray absoluteByte = ui->absoluteLineEdit->text().toAscii().data();
QByteArray fastByte = ui->fastLineEdit->text().toAscii().data();
// BYTE allByte_char[5] = {'0'};
//QT中的回车只有一个字符\n,而windows下需要解释为\r\n; 根据FPGA接收端程序设计,需要中间加个0
QByteArray allByte = absoluteByte + '0' + fastByte + '\r' + '\n';
myCom->write(allByte);
qDebug()<<absoluteByte<<fastByte<<allByte;
}
coordinate.cpp文件中,由于我是在tabWidget里面绘图的,不能直接调用QPainter类,相应的解决方法有两种: 方法(1): 在子窗口ui.---派生一个类,在PaintEvent里画图。 我所遇到的问题:不清楚自己构造的派生类如何跟ui.---相关联起来,归根到底就是自己C++使用得不熟悉。 方法(2):事件过滤法。给ui.---安装事件监视器,在QEvent::paint事件时QPainter paint(ui->---)画图。我使用的是第二种方法。
坐标图显示如下图4所示,由于在QT里面慢慢画直线、弧线等各种坐标点很麻烦,工作量大,因此我就确定好尺寸后,用PS自己弄了背景图片,计算好相应的坐标点,直接在上面显示目标点。
图4 坐标图显示
//*************************** coordinate.cpp *********************
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPainter>
#include <stdlib.h>
#include <QtCore/qmath.h>
// radarWidget中画图
bool MainWindow::eventFilter(QObject *obj, QEvent *e)
{
if(obj == ui->radarWidget)
{
if(e->type() == QEvent::Paint)
{
draw();
qDebug()<<"Workinggggggggggggggggggggggggggg................................";
return true;
}
else
{
qDebug()<<"Waiting";
}
}
return QMainWindow::eventFilter(obj, e);
}
void MainWindow::draw()
{
extern float floatData[20][2]; // 声明下在mainwindow.cpp里面定义的全局变量
extern int dcFlag; // dcFlag = 1 时候相当不断刷新, 等于0时候相当于关闭,进行清屏操作,留背景
extern int ti;
QPainter painter(ui->radarWidget);
QPixmap pix;
pix.load(":/coordinate05-18.png");
painter.drawPixmap(0, 0, 400, 440, pix);
painter.setBrush(Qt::red); //先设定好画笔颜色
painter.translate(194, 432); // 变换坐标原点
float radian = 0.140; //设定弧度,让其按照指定的角度偏移
// dcFlag = 1;
qDebug()<<dcFlag<<endl;
if(dcFlag == 1)
{
int x = 0;
// 2012-05-18,考虑角度偏移的坐标确定,改小号图像
for(int j = 0; j < ti; j++) // ti为全局变量 不能实时传送,只能当ti等于最后的数的时候才传过来??
{
x = int(floatData[j][0] / 10);
switch(x)
{
case 0:
if(floatData[j][1] == 0)
{
painter.drawEllipse(0, -(floatData[j][0] * 4.5), 10, 10); // 0m - 10m内,0度偏角
}
else if(floatData[j][1] == 4)
{
painter.drawEllipse((floatData[j][0] * 4.5) * qSin(radian),
-((floatData[j][0] * 4.5) * qCos(radian)), 10, 10); // 4度偏角
}
qDebug()<<(floatData[j][0] * 4.5)<<(floatData[j][0] * 4.5) * qSin(radian)
<<((floatData[j][0] * 4.5) * qCos(radian));
break;
case 1:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(45 + (floatData[j][0] - 10) * 4.4), 10, 10); // 10m - 20m内
else if(floatData[j][1] == 4)
painter.drawEllipse((45 + (floatData[j][0] - 10) * 4.4) * qSin(radian),
-((45 + (floatData[j][0] - 10) * 4.4) * qCos(radian)), 10, 10); // 10m - 20m内
break;
case 2:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(89 + (floatData[j][0] - 20) * 4.2), 10, 10); // 20m - 30m内
else if(floatData[j][1] == 4)
painter.drawEllipse((89 + (floatData[j][0] - 20) * 4.2) * qSin(radian),
-(89 + (floatData[j][0] - 20) * 4.2) * qCos(radian), 10, 10);
break;
case 3:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(131 + (floatData[j][0] - 30) * 4.2), 10, 10); // 30m - 40m内
else if(floatData[j][1] == 4)
painter.drawEllipse((131 + (floatData[j][0] - 30) * 4.2) * qSin(radian),
-(131 + (floatData[j][0] - 30) * 4.2) * qCos(radian), 10, 10); // 4度偏角
break;
case 4:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(174 + (floatData[j][0] - 40) * 4.3), 10, 10); // 40m - 50m内
else if(floatData[j][1] == 4)
painter.drawEllipse((174 + (floatData[j][0] - 40) * 4.3) * qSin(radian),
-(174 + (floatData[j][0] - 40) * 4.3) * qCos(radian), 10, 10); // 4度偏角
break;
case 5:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(218 + (floatData[j][0] - 50) * 4.4), 10, 10); // 50m - 60m内
else if(floatData[j][1] == 4)
painter.drawEllipse((218 + (floatData[j][0] - 50) * 4.4) * qSin(radian),
-(218 + (floatData[j][0] - 50) * 4.4) * qCos(radian), 10, 10);
break;
case 6:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(261 + (floatData[j][0] - 60) * 4.3), 10, 10); // 60m - 70m内
else if(floatData[j][1] == 4)
painter.drawEllipse((261 + (floatData[j][0] - 60) * 4.3) * qSin(radian),
-(261 + (floatData[j][0] - 60) * 4.3) * qCos(radian), 10, 10);
break;
case 7:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(305 + (floatData[j][0] - 70) * 4.4), 10, 10); // 70m - 80m内
else if(floatData[j][1] == 4)
painter.drawEllipse((305 + (floatData[j][0] - 70) * 4.4) * qSin(radian),
-(305 + (floatData[j][0] - 70) * 4.4) * qCos(radian), 10, 10);
break;
case 8:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(343 + (floatData[j][0] - 80) * 3.8), 10, 10); // 80m - 90m内
else if(floatData[j][1] == 4)
painter.drawEllipse((343 + (floatData[j][0] - 80) * 3.8) * qSin(radian),
-(343 + (floatData[j][0] - 80) * 3.8) * qCos(radian), 10, 10);
break;
case 9:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(378 + (floatData[j][0] - 90) * 3.5), 10, 10); // 90m - 100m内
else if(floatData[j][1] == 4)
painter.drawEllipse((378 + (floatData[j][0] - 90) * 3.5) * qSin(radian),
-(378 + (floatData[j][0] - 90) * 3.5) * qCos(radian), 10, 10);
break;
case 10:
if(floatData[j][1] == 0)
painter.drawEllipse(0, -(410 + (floatData[j][0] - 100) * 3.2), 10, 10); // 90m - 100m内
else if(floatData[j][1] == 4)
painter.drawEllipse((410 + (floatData[j][0] - 100) * 3.2) * qSin(radian),
-(410 + (floatData[j][0] - 100) * 3.2) * qCos(radian), 10, 10);
break;
default:
break;
}
qDebug()<<x<<floatData[j][0]<<floatData[j][1]<<j<<endl;
}
qDebug()<<ti<<endl;
// testing local coordinate
// painter.drawEllipse(0, 0, 10, 10); // 0m
// painter.drawEllipse(0, -(45), 10, 10); // 10m
// painter.drawEllipse(0, -(89), 10, 10); // 20m
// painter.drawEllipse(0, -(131), 10, 10); // 30m
// painter.drawEllipse(0, -(174), 10, 10); // 40m
// painter.drawEllipse(0, -(218), 10, 10); // 50m
// painter.drawEllipse(0, -(261), 10, 10); // 60m
// painter.drawEllipse(0, -(305), 10, 10); // 70m
// painter.drawEllipse(0, -(343), 10, 10); // 80m
// painter.drawEllipse(0, -(378), 10, 10); // 90m
// painter.drawEllipse(0, -(410), 10, 10); // 100m
//
// painter.drawEllipse(45 * qSin(radian), -(45 * qCos(radian)), 10, 10); // 4度偏角
// painter.drawEllipse(89 * qSin(radian), -(89 * qCos(radian)), 10, 10); // 4度偏角
// painter.drawEllipse(131 * qSin(radian), -(131 * qCos(radian)), 10, 10); // 4度偏角
// painter.drawEllipse(174 * qSin(radian), -(174 * qCos(radian)), 10, 10); // 4度偏角
// painter.drawEllipse(218 * qSin(radian), -(218 * qCos(radian)), 10, 10); // 4度偏角
// painter.drawEllipse(261 * qSin(radian), -(261 * qCos(radian)), 10, 10); // 4度偏角
// painter.drawEllipse(305 * qSin(radian), -(305 * qCos(radian)), 10, 10); // 4度偏角
// painter.drawEllipse(343 * qSin(radian), -(343 * qCos(radian)), 10, 10); // 4度偏角
// painter.drawEllipse(378 * qSin(radian), -(378 * qCos(radian)), 10, 10); // 4度偏角
// painter.drawEllipse(410 * qSin(radian), -(410 * qCos(radian)), 10, 10); // 4度偏角
}
}
//************************ main.cpp *****************//
// 2012-05-18 版本,去掉延时显示,进行实时显示,未完成的部分:1.坐标轴的坐标对应的地方,以及刷新的问题;2.调试界面的实现。
#include <QtGui/QApplication>
#include <QTextCodec> //加入头文件
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); //使程序可处理中文
QTextCodec::setCodecForTr(QTextCodec::codecForName("GB2312")); // For Window 7
// QTextCodec::setCodecForTr(QTextCodec::codecForName("System")); // For all
// QTextCodec::setCodecForLocale(QTextCodec::codecForName("GB2312"));
// QTextCodec::setCodecForCStrings(QTextCodec::codecForName("GB2312"));
MainWindow w;
w.show();
return a.exec();
}
三、Linux版本
四、移植到Mini6410上面运行
五、总结
《Mini6410 Qt4和Qtopia编程开发指南》:http://download.csdn.net/detail/jjzhoujun2010/4393908