Qt全屏显示函数 showFullScreen()
Qt最大化显示函数 showMaximized()
Qt最小化显示函数 showMinimized()
Qt固定尺寸显示函数 resize(x,y)
Qt设置最大尺寸函数 setMaximumSize(w,h)
Qt设置最小尺寸函数 setMinimumSize(w,h)
ui调整界面布局
ui->widget->setAttribute(Qt::WA_StyledBackground,true);
// ui->widget->setStyleSheet("background:blue");
ui->widget->setStyleSheet("background-color: rgb(35,0, 255)");//设置左边框体颜色
#include "mainwindow.h"
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//MainWindow w;
//w.show();
QFont font("ZYSong18030" , 12);
a.setFont(font);
QSplitter *splitterMain = new QSplitter(Qt::Horizontal, 0); //新建主分割窗口,水平分割
QTextEdit *textLeft = new QTextEdit(QObject::tr("左部件"),splitterMain);
textLeft->setAlignment(Qt::AlignCenter);
QSplitter *splitterRight = new QSplitter(Qt::Vertical, splitterMain); //右分割窗口,并以主分割窗口作为父窗口
splitterRight->setOpaqueResize(false);
QTextEdit *textUp = new QTextEdit(QObject::tr("上部件"),splitterRight);
textUp->setAlignment(Qt::AlignCenter);
QTextEdit *textMiddle = new QTextEdit(QObject::tr("中间部件"),splitterRight);
textMiddle->setAlignment(Qt::AlignCenter);
QTextEdit *textBottom = new QTextEdit(QObject::tr("底部部件"),splitterRight);
textBottom->setAlignment(Qt::AlignCenter);
splitterMain->setStretchFactor(1,1);
splitterMain->setWindowTitle(QObject::tr("分割窗口"));
splitterMain->show();
return a.exec();
}
QTreeWidgetItem::addChild(QTreeWidgetItem*) //用于根节点添加子节点
QTreeWidget::setColumnCount ; //用于设置表中的列的列数目,在表头中会有对应的显示,
QTreeWidget::setHeaderHidden(); //隐藏表头
QTreeWidgetItem::setSortingEnabled(bool) //可以用来设置是否可以排序,当为true时候,点击表头,会自动排序
openPersistentEditor和closePersistentEditor //用来控制某一item是否可以编辑
QTreeWidgetItem * QTreeWidget::currentItem () //const返回当前的item指针,
int QTreeWidget::currentColumn () // const放回当前item的列编号
QTreeWidgetItem * QTreeWidget::itemAt ( int x, int y ) const返回给定的位置的item指针
QTreeWidgetItem * QTreeWidget::itemBelow ( const QTreeWidgetItem * item ) const //返回指定的item下面item
QTreeWidgetItem * QTreeWidget::itemAbove ( const QTreeWidgetItem * item ) const //返回指定的item上面item
查找item时候,通QListWidget和QTableWidget一样,同样有多个查找匹配模式
QList QTreeWidget::findItems ( const QString & text, Qt::MatchFlags flags, int column = 0 ) const
//显示隐藏列:
setSectionHidden(int, bool);isSectionHidden(int);
增加删除顶层用:
addTopLevelItem(QTreeWidgetItem*);
takeTopLeveltem(int);
topLevelItem(int); //返回
topLevelItemCount();
增加删除子层:
addChild(QTreeWidgetItem*);
addChildren(const QList&);
takeChild(int);
takeChildren();
child(int) //返回
childCount();
//排序
treeWidget->setSortingEnabled(true);
treeWidget->header()->setSortIndicatorShown(true);
treeWidget->header()->setSortIndicator(0, Qt::AscendingOrder);
//要自定义就用信号
connect( treeWidget->header(), SIGNAL( sectionClicked(int) ), this, SLOT( sectionClickedSlot(int) ) );
添加表头的两种方法
方法一:
QStringList header;
header<<"ECJTU"<<"CERT";
treewidget->setHeaderLabels(header); //设置表头
方法二:
QStringList header;
header<<"ECJTU"<<"CERT";
QTreeWidgetItem *head=new QTreeWidgetItem((QTreeWidget*)0,header);
treewidget->setHeaderItem(head);
初始化用基本用到的:
//this->setMouseTracking(true);
this->setRootIsDecorated(false);
this->setSelectionMode(QAbstractItemView::ExtendedSelection);
this->setStyleSheet("QTreeWidget::item{height:25px}"); //设置行宽, 我也找不到提供的接口 setStyleSheet很强大建议看
this->setColumnWidth(0, 100); //设置列宽
this->setColumnCount(2);
QStringList lists;
lists << "NO" << "name";
this->setHeaderLabels(lists);
for (int i=0;i<10;i++)
{
QStringList contentList;
contentList << QString("00%1").arg(i) << "boy";
QTreeWidgetItem *pNewItem = new QTreeWidgetItem(this, contentList); //增加
pNewItem->setBackgroundColor(0, QColor(150,0,0));
pNewItem->setFont(0, QFont());
}
//pNewItemRoot->setExpanded(true); //展开子项
进阶:
//加checkbox, icon
pNewItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable |Qt::ItemIsEnabled);
pNewItem->setCheckState(0, Qt::Unchecked);
//pNewItem->setIcon(int , QIcon);
//向ITEM里放一些QT的控件,如QSpinBox,QComboBox,QPushButton等
this->setItemWidget(pNewItem, 0, new QSpinBox());
//获取某项的QRect, 有时候会很有用,如显缩略图:
QRect rect = this->visualItemRect(QTreeWidgetItem*);
//右键菜单重写
contextMenuEvent( QContextMenuEvent * event );或者使用信号itemPressed(QTreeWidgetItem*, int);
//对头的操作,可以用QLabel或者QTreeWidgetItem对头进行初始化,
//隐藏头
header()->hide();
m_pHeaderView->setClickable(true); //能够发射sectionClicked(int)信号,可做菜单,
m_pHeaderView->setMovable(true);
m_pHeaderView->setResizeMode(0, QHeaderView::Fixed); //固定列宽
m_pHeaderView->setDefaultSectionSize(100); //默认
//排序
treeWidget->setSortingEnabled(true);
treeWidget->header()->setSortIndicatorShown(true);
treeWidget->header()->setSortIndicator(0, Qt::AscendingOrder);
treeWidget->header()->setSectionHidden(1, true);//隐藏列
//要自定义就用信号
connect( treeWidget->header(), SIGNAL( sectionClicked(int) ), this, SLOT( sectionClickedSlot(int) ) );
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
void MainWindow::paintEvent(QPaintEvent *)
{
//声明画家类,
QPainter painter(this);
//设置画笔
QPen pen(QColor(255,0,0));
//设置画笔宽度
pen.setWidth(5);
//设置画笔风格
// pen.setStyle(Qt::DotLine);
//设置画刷
QBrush brush(QColor(255,0,0,100)); //最后一个参数是透明度
brush.setStyle(Qt::Dense7Pattern);
painter.setBrush(brush);
//让画家使用这个笔
painter.setPen(pen);
//画线
painter.drawLine(QPoint(0,0),QPoint(1000 ,500));
//画圆
painter.drawEllipse(QPoint(100,100),100,100);
//画矩形
painter.drawRect(QRect(QPoint(50,50),QPoint(500,500)));
//画文字
painter.drawText(QRect(QPoint(50,50),QPoint(250,250)),"天天学习,天天向上");
// painter.drawText(QRect());
//高级设置
//抗锯齿
painter.setRenderHint(QPainter::Antialiasing);
//画矩形
painter.drawRect(QRect(QPoint(20,20),QPoint(50,50)));
//移动画家
painter.translate(30,0);
// painter.drawRect(QRect(QPoint(20,20),QPoint(50,50)));
painter.drawRect(QRect(QPoint(20,20),QPoint(50,50)));
//保存画家状态
painter.save();
painter.translate(100,0);
//恢复画家状态到保存的时间点
painter.restore();
painter.drawRect(QRect(QPoint(20,20),QPoint(50,50)));
}
MainWindow::~MainWindow()
{
delete ui;
}
Qt帮助文档里的函数声明:
void QPainter::drawImage ( const QRect & target, const QImage & image, const QRect & source, Qt::ImageConversionFlags flags = Qt::AutoColor )
举例:
QRect target(10.0, 20.0, 80.0, 60.0); //建立目标矩形,该区域是显示图像的目的地
QRect source(0.0, 0.0, 70.0, 40.0); //建立源矩形,用来划定来自外部的源图像文件中需要显示的区域
QImage image(":/images/myImage.png"); //建立QImage类对象
QPainter painter(this);
painter.drawImage(target, image, source); //将源图像文件中(0.0, 0.0, 70.0, 40.0)位置的图像画到目标绘图设备中的(10.0, 20.0, 80.0, 60.0)位置上,大小自适应
painter1.drawImage(QRect( 0,50,2400,200),image);//改变painter对画布进行draw操纵时候的操作 即可实现放大缩小,超级简单
菜单栏最多有一个
QMenuBar *bar= MenuBar;//创建菜单栏
setMenuBar(bar);//添加菜单栏
QMenu *fileMenu=bar->addMenu("文件");//创建菜单
QAction *newAction =fileMenu->addAction("新建");//创建菜单项
fileMenu->addSeparator();//添加分割线
工具栏可以有多个
QToolBar* bar=new QToolBar(this);
addToolBar(停靠区域,bar);// Qt::LeftToolBarArea
设置后期停靠区域,设置浮动,设置移动(总开关)
bar->setMovable(false);
bar->setAllowedAreas();
状态栏只能有一个
QStatusBar*stBar=statusBar(this);
setStatusBar(stBar);//添加
stBar->addWidget(label);//添加左侧信息
stBar->addPermanentWidget(label);//放右侧信息
浮动窗口 可以多个
QDockeWidget
addDockWidget(默认停靠区域,浮动窗口指针)
设置后期停靠区域
setCentralWidget()
右键项目->添加新项目->Qt->Qt Resource File -> 给资源文件起名
res 生成 res.qrc
open in editor
ui->setupUi(this);
ui->actionXinjian->setIcon(QIcon(":/image/1.jpg"));
this->setVisible(0);
this->setVisible(1);
// 隐藏控件在屏幕上显示、不再占位置会改变布局。
this->hide();
this->show();
// 通过析构和重新new实现显示/隐藏、不再占位置会改变布局。
this->setFixedSize(36, 25);
this->setFixedSize(36, 0);
// 通过改变大小实现隐藏/显示、不再占位置会改变布局。
QString btn_background=":xxx.png";
this->setStyleSheet("border:none;background-image:url();");
this->setStyleSheet(QString("border:none;background-image:url(%1);").arg(btn_background));
// 通过改变背景图片实现显示/隐藏,占位置不改变布局,隐藏时候鼠标在该位置按下还会发送信号
QString btn_backgrounds=":xxx.png||:xxxx.png";
QStringList tmp = sheet.split("||");
this->setStyleSheet(
QString("QPushButton:checked{"
"border:none;"
"background-image:url(%1);}"
"QPushButton:!checked"
"{border: none;"
"background-image:url(%2);}").arg(tmp[0]).arg(tmp[1]));
// 通过自身状态改变背景图片实现显示/隐藏,这里用的是checked,可以换成自定义属性,占位置不改变布局,隐藏时候鼠标在该位置按下还会发送信号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pg2ZDmXC-1606889098614)(C:\Users\homily\AppData\Roaming\Typora\typora-user-images\image-20200914205747459.png)]
**窗口:**逻辑环境中的一个矩形框,使用逻辑坐标,图中世界坐标和窗口坐标外围的虚线框即为窗口。窗口具有原点和长宽,这也就决定了窗口的位置和大小。**窗口存在的意义即是为了确定人类世界中的某个物体需要显示的位置和范围。**如我要显示地图中的江苏部分,我就需要将窗口的原点位置设置为江苏,长宽设置为江苏省的外接矩形大小。如果我要显示地图中的中国部分,则将窗口扩大,原点设置为中国,长宽设置为中国的外接矩形大小。在视口不变的情况下,扩大窗口其实就是相当于对地图进行了缩小。
**视口:**设备环境中的一个矩形框,使用物理坐标,和设备的大小密切相关,超出设备外的视口区域不予显示。视口存在的意义是指定在显示设备的哪个地方,以多大的范围完全显示指定的窗口内容。
QPainter painter(this);
int w = width();
int h = height();
int side = qMin(w,h);
QRect rect(0,0,side,side);//在自己的框框里,从哪里开始画图
// QRect rect((w-side)/2,(h-side)/2,side,side);
painter.drawRect(rect);
painter.setViewport(rect); // 设置ViewPort
painter.setWindow(0,0,200,200); // 设置窗口 逻辑坐标,在真正的框框中 从哪里开始画图
QPen pen(Qt::red);
painter.setPen(pen);
painter.drawEllipse(0,0,100,50);
painter.drawEllipse(50,50,100,50);
painter.drawEllipse(100,50,100,50);
0x02 包含charts头文件并引用QT charts命名空间
// 包含line chart需要的头文件
#include
#include
// 引用命名空间
QT_CHARTS_USE_NAMESPACE
int main(){
...
}
123456789
0x03 创建QLineSeries并添加数据
折线图的实现需要创建一个QLineSeries
对象用于保存和绘制折线数据:
// new 一个 QLineSeries实例
QLineSeries *series = new QLineSeries();
// 添加实验数据,可以用 append 方法或者 >> 操作符
series->append(0,2);
series->append(QPointF(2,6));
series->append(3,8);
series->append(7,9);
series->append(11,3);
*series << QPointF(11,2) << QPointF(15,5) << QPointF(18,4) << QPointF(19,2);
1234567891011
0x04 创建QChart用于显示数据
创建好series
后,需要创建一个QChart
实例并关联series
,创建坐标才能将数据显示出来:
QChart *chart = new QChart();
// 将图例隐藏
chart->legend()->hide();
// 关联series,这一步很重要,必须要将series关联到QChart才能将数据渲染出来:
chart->addSeries(series);
// 开启OpenGL,QLineSeries支持GPU绘制,Qt其他有的图表类型是不支持的。
series->setUseOpenGL(true);
// 创建默认的坐标系(笛卡尔坐标)
chart->createDefaultAxes();
// 设置图表标题
chart->setTitle(QStringLiteral("Qt line chart example"));
1234567891011
0x05 创建QChartView对象并显示图表
这里创建QChartView
对象是为了将最终结果显示到界面,如果不想用QChartView
,也可以选择QGraphicsView scene
来显示。
QChartView *view = new QChartView(chart);
// 开启抗锯齿,让显示效果更好
view->setRenderHint(QPainter::Antialiasing);
view->resize(400,300);
// 显示图表
view->show();
Qt中提供了强大的2D绘图系统,可以使用相同的API在屏幕和绘图设备上进行绘制,它主要基于QPainter、QPaintDevice和QPaintEngine这三个类。它们三者的关系如下图所示:
QPainter用来执行绘图操作;
QPaintEngine提供了一些接口,可以用于QPainter在不同的设备上进行绘制;
QPaintDevice提供绘图设备,它是一个二维空间的抽象,可以使用QPainter在其上进行绘制。
绘图系统中由QPainter来完成具体的绘制操作,提供了大量髙度优化的函数来完成GUI编程所需要的大部分绘制工作。QPainter可以绘制一切想要的图形,从最简单的一条直线到其他任何复杂的图形,还可以用来绘制文本和图片。QPainter可以在继承自QPaintDevice类的任何对象上进行绘制操作。
QPainter—般在一个部件重绘事件( PaintEvent )的处理函数paintEvent ()中进行绘制,首先要创建QPainter对象(画笔),然后进行图形的绘制, 最后销毁QPainter对象。
回到顶部
一、基本图形的绘制
在QPainter中提供了一些方便的函数来绘制常用的图形,而且还可以设置线条和边框的画笔以及进行填充的画刷。
新建Qt Gui应用,项目名称为 myDrawing,基类选择QWidget,类名为Widget。建立完成后,在widget.h文件中声明重绘事件处理函数:
protected:
void paintEvent(QPaintEvent *);
然后到widget.cpp文件中添加头文件#include 。
1.1 绘制图形
在widget.cpp文件中对paintEvent()函数进行如下定义:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
//绘制线条
painter.drawLine(QPoint(0, 0), QPoint(100, 100));
}
这里先创建了—个QPainter 对象,使用了QPainter::QPainter(QPaintDevice *device)构造函数,并指定了this为绘图设备,即表明在该部件上进行绘制。使用这个构造函数创建的对象会立即开始在设备上绘制,自动调用begin()函数,然后在QPainter的析构函数中调用end()函数结束绘制。
如果在构建QPainter对象时不想指定绘制设备,那么可以使用不带参数的构造函数,然后使用QPainter:: begin (QPaintDevice *device)在开始绘制时指定绘制设备,等绘制完成后再调用end()函 数结束绘制。上面函数中的代码等价于:
QPainter painter;
painter.begin(this);
painter.drawLine(QPoint(0, 0), QPoint(100, 100));
painter.end();
这两种方式都可以完成绘制,无论使用哪种方式,都要指定绘图设备,否则将无法进行绘制。第二行代码使用drawLine()函数绘制了一条线段,这里使用了该函数的一种重载形式QPainter::drawLine ( const QPoint & p1, const QPoint & p2 )其中p1和p2分别是线段的起点和终点。这里的QPoint(0, 0)就是窗口的原点,默认是窗口的左上角(不包含标题栏)。效果如下图所示。
除了绘制简单的线条以外,QPainter中还提供了一些绘制其他常用图形的函数, 其中最常用的几个如下表所示。
函数 功能
drawArc() 绘制圆弧
drawChord() 绘制弦
drawConvexPolygon() 绘制凸多边形
drawEllipse() 绘制椭圆
drawLine() 绘制线条
drawPie() 绘制扇形
drawPoint() 绘制点
drawPolygon() 绘制多边形
drawPolyline() 绘制折线
drawRect() 绘制矩形
drawRoundedRect() 绘制圆角矩形
另外我们将光标定位到QPainter类名上,然后按下键盘上的F1按键,这时会自动跳转到该类的帮助页面。当然,也可以到帮助模式,直接索引查找该类名。在帮助里面我们可以看到很多相关的绘制函数,如下图所示。
我们任意点击一个函数名,就会跳转到该函数的介绍段落。例如我们点击drawEllipse()函数,就跳转到了该函数的介绍处,上面还提供了一个例子。如下图所示。我们可以直接将例子里面的代码复制到paintEvent()函数里面,测试效果。
1.2 使用画笔
QPen定义了用于QPainter应该怎样画线或者轮廓线。画笔具有样式style() 、宽度width() 、画刷brush() 、笔帽样式capStyle()和连接样式joinStyle()等属性。先介绍QPen类的构造函数:
QPen(const QBrush &brush, qreal width, Qt::PenStyle s = Qt::SolidLine,
Qt::PenCapStyle c = Qt::SquareCap, Qt::PenJoinStyle j = Qt::BevelJoin);
画刷brush()用于填充画笔所绘制的线条。
画笔的样式style()定义了线的样式。
笔帽样式capStyle()定义了使用QPainter绘制的线的末端;
连接样式joinStyle()则定义了两条线如何连接起来。
画笔宽度width()或widthF()定义了画笔的宽。注意,不存在宽度为 0 的线。假设你设置 width 为 0,QPainter依然会绘制出一条线,而这个线的宽度为 1 像素。
这么多参数既可以在构造时指定,也可以使用 set 函数指定,完全取决于你的习惯。使用setWidth(),setBrush(),setCapStyle()和setJoinStyle()函数可以轻松修改各种设置。
画笔样式
再将paintEvent()函数的内容更改如下:
void Widget::paintEvent(QPaintEvent *)
{
//创建画笔
QPen pen(Qt::green, 5, Qt::DotLine, Qt::RoundCap, Qt::RoundJoin);
//使用画笔绘制圆弧
painter.setPen(pen);
QRectF rectangle(70.0, 40.0, 80.0, 60.0);
int startAngle = 30 * 16;
int spanAngle = 120 * 16;
painter.drawArc(rectangle, startAngle, spanAngle);
}
上面创建完画笔后,使用了setPen()来为painter设置画笔,然后使用画笔绘制了一个圆弧。绘制圆弧函数的一种重载形式为QPainter::drawArc ( const QRectF & rectangle, int startAngle, int spanAngle ),这里的三个参数分别对应于需要指定弧线所在的矩形、起始角度和跨越角度,如下图所示。
QRectF:: QRectF (qreal x, qreal y, qreal width, qreal height)可以使用浮点数为参数来确定一个矩形,它需要指定左上角的坐标(x,y)、宽width和髙height。如果只想使用整数来确定一个矩形,那么可以使用QRect类。这里角度的数值为实际度数乘以16,在时钟表盘中,0度指向3时的位置,角度数值为正则表示逆时针旋转,角度数值为负则表示顺时针旋转,整个一圈的数值为5760(即360X16)。
1.3 使用画刷
QBrush类提供了画刷来填充图形,一个画刷使用它的颜色和风格(例如它的填充模式)来定义。先介绍QBrush类的构造函数:
QBrush(const QColor &color, Qt::BrushStyle bs=Qt::SolidPattern);
在Qt中使用的颜色一般都由QColor类来表示,它支持RGB、HSV和CMYK等颜色模型。里面如果是三个参数,那么分别是红、绿、蓝分量的值,也就是经常说的rgb,取值范围都是0-255,比如这里的(255, 0, 0)就表明红色分量为255,其他分量为0,那么出来就是红色。如果是四个参数,最后一个参数alpha是设置透明度的,取值范围也是0-255,0表示完全透明,而255表示完全不透明。在Qt中还提供了20种预定义的颜色,如下图所示。
QBrush样式的填充模式使用Qt::BrushStyle枚举变量来定义,包含了基本模式填充、渐变填充和纹理填充,所有枚举变量如下图所示。默认的风格是Qt :: NoBrush(取决于你如何构建画笔),不填充形状。标准的填充风格是Qt :: SolidPattern。设置画刷风格的方式有两种,一种是利用构造函数,另外一种是利用setstyle函数。
再将paintEvent()函数的内容更改如下:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPen pen; //画笔
pen.setColor(QColor(255, 0, 0));
QBrush brush(QColor(0, 255, 0, 125)); //画刷
painter.setPen(pen); //添加画笔
painter.setBrush(brush); //添加画刷
painter.drawRect(50, 50, 200, 100); //绘制矩形
}
这里分别新建了一个画笔QPen和画刷QBrush。其中画笔使用了setColor()函数为其设置了颜色,而画刷是在构建的时候直接为其设置的颜色。然后我们将画笔和画刷设置到了painter上,并使用drawRect()绘制了一个矩形,其左上角顶点在(50, 50),宽为200,高为100。运行程序,效果如下图所示。
回到顶部
二、渐变填充
在画刷中也可以使用渐变填充。QGradient类就是用来和QBrush一起指定渐变填充的。Qt现在支持三种类型的渐变填充:
线性渐变(linear gradient)在开始点和结束点之间插入颜色;
辐射渐变(radial gradient)在焦点和环绕它的圆环间插入颜色;
锥形渐变(Conical)在圆心周围插入颜色。
这三种渐变分别由QGradient的三个子类来表示,QLinearGradient表示线性渐变,QRadialGradient表示辐射渐变,QConicalGradient表示锥形渐变。
(1)线性渐变
QLinearGradient::QLinearGradient ( const QPointF & start, const QPointF & finalStop )
线性渐变需要指定开始点start和结束点finalStop,然后将开始点和结束 点之间的区域进行等分,开始点的位置为0.0,结束点的位置为1.0,它们之间的位置按照距离比例进行设定,然后使用
QGradient::setColorAt (qreal position, const QColorj &color)函数在指定的位置position插人指定的颜色color,当然,这里的position的值要在0〜1之间。
这里还可以使用setSpread()函数来设置填充的扩散方式,即指明在指定区域以外的区域怎样进行填充。扩散方式由QGradient::Spread枚举变量定义,它一共有3个 值,分别是QGradiem::PadSpread,使用最接近的颜色进行填充,这是默认值;QGradient:: ReflectSpread在渐变区域以外将反射渐变;QGradiem:: RepeatSpread在渐变区域以外的区域重复渐变。要使用渐变填充,可以直接在setBrush()中使用,这时画刷风格会自动设置为相对应的渐变填充。在线性渐变中这3种扩散方式的效果如下图所示。
(2)辐射渐变
QRadialGradient::QRadialGradient ( const QPointF & center, qreal radius, const QPointF & focalPoint )
辐射渐变需要指定圆心 center 和半径 radius,这样就确定 了一个圆,然后再指定一个焦点focalPoint。焦点的位置为0,圆环的位置为1,然后在焦点和圆环间插人颜色。辐射渐变也可以使用setSpread()函数设置渐变区域以外区域的扩散方式,3种扩散方式的效果如下图所示。
(3)锥形渐变
QConicalGradient::QConicalGradient ( const QPointF & center, qreal angle )
锥形渐变需要指定中心点center和一个角度angle(其值在0到360之间),然后沿逆时针从给定的角度开始环绕中心点插入颜色。这里给定的角度沿逆时针方向开始的位置为0,旋转一圈后为1。setSpread()函数对于锥形渐变没有效果。
(4)示例程序
示例程序如下:
void Widget::paintEvent(QPaintEvent *)
{
//线性渐变
QLinearGradient linearGradient(QPointF(40, 190),QPointF(70, 190));
//插入颜色
linearGradient.setColorAt(0, Qt::yellow);
linearGradient.setColorAt(0.5, Qt::red);
linearGradient.setColorAt(1, Qt::green);
//指定渐变区域以外的区域的扩散方式
linearGradient.setSpread(QGradient::RepeatSpread);
//使用渐变作为画刷
QPainter painter(this);
painter.setBrush(linearGradient);
painter.drawRect(100, 100, 90, 40);
//辐射渐变
QRadialGradient radialGradient(QPointF(100, 190),50,QPointF(275,200));
radialGradient.setColorAt(0, QColor(255, 255, 100, 150));
radialGradient.setColorAt(1, QColor(0, 0, 0, 50));
painter.setBrush(radialGradient);
painter.drawEllipse(QPointF(100, 200), 50, 50);
//锥形渐变
QConicalGradient conicalGradient(QPointF(250, 190), 60);
conicalGradient.setColorAt(0.2, Qt::cyan);
conicalGradient.setColorAt(0.9, Qt::black);
painter.setBrush(conicalGradient);
painter.drawEllipse(QPointF(250, 200), 50, 50);
}
执行程序,效果如下:
QDialog dlg(this)
dlg.exec();
可以对其他窗口进行操作
为防止一闪而过,把dialog加到堆上
QDiaolg *dialog=new QDiaolg(this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitele(tr("hello"));
dialog->show;
QString str = QCoreApplication::applicationDirPath();
在窗口类中,如mainwindow中
新建一个按钮,然后 setparent(this) 再show 就能显示了
1. 先在头文件声明 检测函数
2. 然后在cpp文件实现函数
3. 如果是鼠标移动监测的话,需要 先打开 centralWidget.setMouseTracking(true); 和 mainWindow的这个参数。
ui->centralWidget->setMouseTracking(true);
this->setMouseTracking(true);
在Qt中要捕捉鼠标移动事件需要重写MouseMoveEvent,但是MouseMoveEvent为了不太耗资源,默认状态下是要鼠标按下才能捕捉到。要想鼠标不按下时的移动也能捕捉到,需要setMouseTracking(true)。
QWidget中使用是没有问题的,但是,对于QMainWindow即使使用了setMouseTracking(true)依然无法捕捉到鼠标没有按下的移动,只有在鼠标按下是才能捕捉。
解决办法:要先把QMainWindow的CentrolWidget使用setMouseTracking(true)开启移动监视。然后在把QMainWindow的setMouseTracking(true)开启监视。之后就一切正常了。
原因:CentrolWIdget是QMainWindow的子类,你如果在子类上响应鼠标事件,只会触发子类的mouseMoveEvent,根据C++继承和重载的原理,所以子类也要setMouseTracking(true); 所以如果你想响应鼠标事件的控件被某个父控件包含,则该控件及其父控件或容器也需要setMouseTracking(true);*
无法运行lambda表达式
CONFIG += C++11
connect 连接的时候,要用lambda 需要去掉signal
如果是 = 值传递, 则局部变量无法使用,只有头文件变量可以使用, 否则显示只读。
[var]
[=]值传递方式捕捉所有父作用域的变量(包括this)
[&var]
[&] 引用传递方式捕捉所有父作用域的变量(包括this)
[this] 值传递的方式捕捉this
( 1)QString转int
直接调用toInt()函数
例:
QString str("100");
int tmp = str.toInt();
或者:
bool ok;
QString str("100");
int tmp = str.toInt(&ok);
注:ok表示转换是否成功,成功则ok为true,失败则ok为false。
2)int转QString
QString::number();
例:
int tmp = 100;
QString str = QString::number(tmp);
QString::number(变量,'f',2); 保留小数点后两位
f改成g 就是只保留两位数
QString::number(i);
QString str;
char* ch;
QByteArray ba = str.toLatin1(); // must
ch=ba.data();
setFocusPolicy(Qt::StrongFocus);//键盘
先定义 路径 QString path=QFileDialog::getOpenFileName(this,"标题","路径");
QFile file(path);//默认支持 utf-8
QTextCodec *codec=QTextCodec::codecForName("gbk");//编码格式
file.open(OpenMode); QIODevice::ReadOnly
QByteArray array=file.readAll();
ui.textEdit()->setText(array);
codec->toUnicode(array);
file.open(QIODevice::Append);//追加的方式进行写
file.write("垃圾垃圾");
file.close();
//利用QFileInfo读取文件信息
QFileInfo info(路径)
大小 info.size() 后缀info.suffix() 文件名称 info.fileName() 创建日期 info.created().toString(yy/mm/dd hh:mm:ss);
//构建菜单栏
QMenu * fileMenu=ui->menuBar->addMenu("文件");
QAction *newAction= fileMenu->addAction("新建");
model = new QStandardItemModel; //创建一个标准的条目模型
this->ui->tableView->setModel(model); //将tableview设置成model这个标准条目模型的模板, model设置的内容都将显示在tableview上
this->model->setHorizontalHeaderItem(0, new QStandardItem("日期") );
this->model->setHorizontalHeaderItem(1, new QStandardItem("开盘价"));
this->model->setHorizontalHeaderItem(2, new QStandardItem("最高价"));
this->model->setHorizontalHeaderItem(3, new QStandardItem("最低价"));
this->model->setHorizontalHeaderItem(4, new QStandardItem("收盘价"));
//this->model->setHorizontalHeaderItem(5, new QStandardItem("兴趣"));
this->ui->tableView->setColumnWidth(0, 100); //设置列的宽度
this->ui->tableView->setColumnWidth(1, 100);
this->ui->tableView->setColumnWidth(2, 100);
this->ui->tableView->setColumnWidth(3, 100);
this->ui->tableView->setColumnWidth(4, 100);
//隐藏左边那一列
ui->tableView->verticalHeader()->hide();
/*设置行字段名*/
model->setRowCount(3);
model->setHeaderData(0,Qt::Vertical, "行0");
model->setHeaderData(1,Qt::Vertical, "行1");
model->setHeaderData(2,Qt::Vertical, "行2");
MainWindow::connect(newAction,&QAction::triggered,[=]()
{
FILE* pFile = fopen("C:\\Users\\homily\\Desktop\\1A0001.day", "rb");
if (NULL != pFile)
{
qDebug()<<"sb";
int j=0;
StockDay dayS;
while(!feof(pFile))
{
StockDay dayK;
fread(&dayK, sizeof(StockDay), 1, pFile);
// for(int i=0;i<5;i++)
// {
this->model->setItem(j, 0, new QStandardItem( QString::number(dayK.m_lDate)));
this->model->setItem(j, 2, new QStandardItem( QString::number(dayK.m_lMaxPrice)));
this->model->setItem(j, 3, new QStandardItem( QString::number(dayK.m_lMinPrice)));
this->model->setItem(j, 1, new QStandardItem( QString::number(dayK.m_lOpenPrice)));
this->model->setItem(j, 4, new QStandardItem( QString::number(dayK.m_lClosePrice)));
// }
j++;
// qDebug()<model->setItem(1, 1, new QStandardItem (QString::number (dayS.m_lDate)));
qDebug()<
FILE* pFile = fopen("600001.day", "rb");
if (NULL != pFile)
{
while(!feof(pFile))
{
DZH5Day dayK;
fread(&dayK, sizeof(DZH5Day), 1, pFile);
......
}
}
QString path=QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\homily\\Desktop\\");
QFile file(path);
QDataStream out(&file);
if ( file.open(QIODevice::ReadOnly))
{
while(!out.atEnd())
{
StockDay daySs;
char* temp = new char[sizeof (StockDay)];
memset(&daySs,0,sizeof(StockDay));
//读取数据,并存入成结构体
out.readRawData(temp,sizeof (StockDay));
memcpy(&daySs,temp,sizeof (StockDay));
g_Day_Data.append(daySs);
// double k=startTime+(a-b)*binSize;
// currentBinData.key=data2.at(j)->key;
// j++;
}
途中的Lable
的拖动效果,是因为我重写了QWidget
的mouseMoveEvent
和mousePressEvent函数
。代码大概是这样的:
void Widget::mousePressEvent(QMouseEvent *event) {
mousePosition = event->globalPos();
}
void Widget::mouseMoveEvent(QMouseEvent *event) {
const QPoint position = pos() + event->globalPos() - mousePosition;
move(position.x(), position.y());
mousePosition = event->globalPos();
}
鼠标事件
鼠标进入事件 enterEvent
鼠标离开事件 leaveEvent
鼠标按下事件 mousePressEvent(QMouseEvent ev)
鼠标释放 mouseReleaseEvent
鼠标移动 mouseMoveEvent
ev->x()坐标 ev->y()
ev->button() 可以判断所有按键 Qt::LeftButton Qt::RightButton
ev->buttons() 判断组合按键 判断move时候的左右键 结合&操作符
格式化字符串 QString("%1 %2").arg().arg()
setMouseTracking(true); 设置鼠标追踪
用途:用于事件的分发,也可用于事件的拦截,(不推荐)
bool event(QEvent *e);
返回值, 如果是true代表用户自己处理这个事件,不向下分发
e->type()== 事件;
在程序将事件分发到事件分发器之前,可以利用过滤器做拦截
需要包含头文件:
#include
添加元素:
QVector strArray;
strArray.append("Hello"); //可以这样
strArray<<"World!"; //也可以这样
strArray<<"MyName"<<"is"<<"LEO";//也可以这样加上个元素
//现在strArray总共有5个字符串元素,strArray.count()==5
遍历:
QVector::iterator iter;
for (iter=strArray.begin();iter!=strArray.end();iter++)
qDebug() << *iter << "\0";
插入:
strArray.insert(1,"这就是在hello和world之间添加");
删除:
strArray.remove(1); //删除第一个元素,从0开始
strArray.remove(1,3); //从1开始,删除3个元素
复制(取代):
strArray.replace(1,"LEO"); //删除第一个元素,从0开始
上述,除了append()和replace()这两个函数外,其它函数会比较慢,因为在内存中移动一个位置时,这些函数会使向量容器内的对象要移动许多次!如果你想要一个能够在中部快速插入和删除的容器时,可以使用QList或者QLinkedList。
调用at()函数来读取对象会比使用operator[]()读取速度更快,因为这不会使用深度复制(deep copy)。
调用data()函数也可以访问保存在QVector的数据。这个函数会返回指向向量容器的第一个对象的指针。这样,你就可以使用指针来访问和修改向量容器内的对象。你可以使用指针将一个QVector向量容器传递给接收普通C++数组的函数。
contains()函数是用来查找向量容器内是否含有某个对象。
count()函数可以找出某个对象出现的次数。
resize()函数可以在任何时候改变QVector向量容器的体积。如果新的向量容器体积比以前的要大,QVector也许需要重新分配整个向量容器。QVector会预先分配两倍于实际数据的大小空间,从而减少再分配的次数。
reserve()函数,如果你事先知道向量容器大概包含多少个对象,你可以调用这个函数来预先分配一定的内存大小空间
capacity()函数会告诉你向量容器所占内存的实际大小空间。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b5B7WS2V-1606889098634)(C:\Users\homily\AppData\Roaming\Typora\typora-user-images\image-20200911150407394.png)]
/***********************************************************************************************************************
* @brief QScrollArea的简单使用
* @author xiaolei
* @copyright -
* @version V1.0
* @data 2019-10-24
* @note 层次结构:MainWindow->centralWidget->QScrollArea->scrollAreaWidgetContents->QHBoxLayout->QLabel
* @note scrollAreaWidgetContents的最小尺寸很重要,关乎到是否出现滑动条,一般设置与QScrollArea的尺寸一样
************************************************************************************************************************/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
#include
#include
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->resize(480,640);
qDebug()<<"MainWindow geometry:"<geometry();
QWidget *centralWidget = new QWidget(this);
this->setCentralWidget(centralWidget);
centralWidget->setGeometry(0,0,480,640);
qDebug()<<"centralWidget size:"<size();
qDebug()<<"centralWidget geometry:"<geometry();
QScrollArea *scrollArea = new QScrollArea(centralWidget);
scrollArea->setBackgroundRole(QPalette::Dark);
//scrollArea->setWidgetResizable(true); //小部件适应QScrollArea自动改变大小
//scrollArea->setAlignment(Qt::AlignCenter); //居中对齐
scrollArea->setGeometry(40,70,400,400);
qDebug()<<"scrollArea size:"<size();
QWidget *scrollAreaWidgetContents = new QWidget();
scrollAreaWidgetContents->setMinimumSize(QSize(400, 400)); //设置最小尺寸,很重要,关系到是否出现滑动条
QHBoxLayout *labelLayout=new QHBoxLayout(scrollAreaWidgetContents);
QLabel *imageLabel = new QLabel(scrollAreaWidgetContents);
imageLabel->resize(360,480);
QLabel *imageLabel2 = new QLabel(scrollAreaWidgetContents);
imageLabel2->resize(360,480);
QPixmap pixmap("F:/磊神图片/1.jpg");
pixmap.scaled(QSize(imageLabel->width(),imageLabel->height()),Qt::KeepAspectRatioByExpanding);
imageLabel->setPixmap(pixmap);
QPixmap pixmap2("F:/磊神图片/焰灵姬.jpg");
pixmap2.scaled(QSize(imageLabel2->width(),imageLabel2->height()),Qt::KeepAspectRatioByExpanding);
imageLabel2->setPixmap(pixmap2);
labelLayout->addWidget(imageLabel);
labelLayout->addWidget(imageLabel2);
scrollArea->setWidget(scrollAreaWidgetContents);
}
MainWindow::~MainWindow()
{
delete ui;
}
ui制作界面
工具—外部—qt语言家—更新翻译
然后用软件 语言家 打开 ts文件 进行翻译 然后发布成 qm 文件
把qm文件 转移到 build-guojihua-Desktop_Qt_5_5_0_MinGW_32bit-Debug
void Widget::on_comboBox_activated(int index)
{
switch (index) {
case 0:
m_translator.load(":/09_17_Translate_CN.qm");
qApp->installTranslator(&m_translator);
ui->retranslateUi(this);
ui->comboBox->setCurrentIndex(0);
break;
case 1:
m_translator.load(":/09_17_Translate_EN.qm");
qApp->installTranslator(&m_translator);
ui->retranslateUi(this);
ui->comboBox->setCurrentIndex(1);
break;
}
}
void MainWindow::on_comboBox_activated(int index)
{
switch (index) {
case 0 :
m_Translator->load("Translate_CN.qm");
qApp->installTranslator(m_Translator);
ui->retranslateUi(this);
ui->comboBox->setCurrentIndex(0);
break;
case 1:
m_Translator->load("Tranlate_EN.qm");
qApp->installTranslator(m_Translator);
ui->retranslateUi(this);
ui->comboBox->setCurrentIndex(1);
break;
default:
break;
}
}
信号和槽函数都可以自己定义
connect(发送者,&QMainWindow::信号,接受者,&QMianWindow::槽函数)
有两个凹槽的为槽函数
有两条弧线像信号发射的为信号
例:
QPushButton * startBtn = new QPushButton("开始游戏",this);
connect(startBtn,&QPushButton::clicked,this,&GameWindow::startGame);
12
connect(发送者,SIGNAL(信号),接受者,SLOT(槽函数))
例:
QPushButton * startBtn = new QPushButton("开始游戏",this);
connect(startBtn,SIGNAL(clicked()),this,SLOT(startGame()));
12
[=](){}
)形式信号需要以指针形式,不能用SIGNAL
connect(startBtn,&QPushButton::clicked,[=](){
this->close(); // 写槽函数需要实现的内容
});
Qt 中的信号和槽应该是最熟悉不过的了,连接信号和槽的关键字 connect有五种连接类型,今天不是介绍这五种连接类型,而是简单的总结一下 connect 的几种新旧写法,其实在新版本中几种写法都能适用,看个人习惯吧。
首先来看看老版本的 connect 写法,比较复杂些,需要将信号和槽进行明确的指定,包括形参。
看一个示例:
为方便演示,先自定义一个 Button,然后定义两个重载的信号
class MyButton : public QWidget
{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
signals:
void sigClicked();
void sigClicked(bool check);
};12345678910
那么在用这个 Button 的时候连接这两个信号,按照旧版本的写法,应该是这样:
connect(m_pBtn,SIGNAL(sigClicked()),this,SLOT(onClicked()));
connect(m_pBtn,SIGNAL(sigClicked(bool)),this,SLOT(onClicked(bool)));12
这种写法比较麻烦,常常在用的时候缺少括号,不过该写法很明确,一眼就能看出来是将哪个信号连接到哪个槽。
接着上面的示例,在 Qt5.0以后推出一种新的写法,如下:
connect(m_pBtn,&MyButton::sigClicked,this,&Widget::onClicked);1
这种写法看起来很简洁,但是存在一些坑需要注意,这句写法如果用在上面的示例中,会报错下面的错误:
error: no matching member function for call to 'connect' connect(m_pBtn,&MyButton::sigClicked,this,&Widget::onClicked);
^~~~~~~12
这是因为我们自定义的 Button 中存在两个重载信号,然后用这种 connect 的方式会无法识别到底想要连接哪个信号。所以,如果信号是重载的话,需要用下面的写法来替换:
connect(m_pBtn, static_cast(&MyButton::sigClicked), this, &Widget::onClicked);1
问题又来了,如果我的onClicked槽也是重载的话,还是会报同样的错误。因为编译器不知道你想要真正连接哪个槽。所以这里建议,如果信号重载,可以用上面的方法来写,如果槽重载…还是用第一种方法来 connect 吧,比较保险,虽然比较麻烦点。
最后来看一种最新的写法,忘记是在 Qt 的哪个版本推出的了,主要针对重载信号的连接做了调整,会更简单些:
同样是上面的示例:
connect(m_pBtn, QOverload::of(&MyButton::sigClicked),this,&Widget::onClicked);1
很显然这种写法相对于第二种会比较简单些,但依然不能连接到重载的槽函数,如果连接重载槽函数,还是会报之前的错误。
个人比较喜欢用lambda函数的方式,如果槽函数中的内容比较简单的话,没必要再去单独定义一个槽来连接, 直接用Lambda 函数会更简单。
来看一下示例:
connect(m_pBtn, QOverload::of(&MyButton::sigClicked),
[=](bool check){
/* do something.. */
});1234
connect(m_pBtn, static_cast(&MyButton::sigClicked), this, [=](bool check){
//do something
});
线程有6种状态:新建,运行(可运行),阻塞,等待,计时等待和终止。
新建:当使用new操作符创建新线程时,线程处于“新建“状态
运行(可运行):调用start()方法
阻塞:当线程需要获得对象的内置锁,而该锁正在被其他线程拥有
等待:当线程等待其他线程通知调度表可以运行时
计时等待:对于一些含有时间参数的方法,如Thread类的sleep()
终止:当run()方法运行完毕或出现异常时关闭
创建线程对象,不能指定父对象。
myT =new MyThread;
2)QThread子线程对象
QThread *thread = new QThread(this)
自定义线程类,加入到子线程
启动子线程 只是把线程开启了 并没有启动线程处理函数
线程函数的启动,必须通过 signal -slot 如果直接调用,线程处理函数和主线程在一个线程。
connect 第五个参数,在多线程才有意义,连接方式():默认、队列、直接
多线程时才有意义
默认的时候
如果是多线程则使用队列,如果是单线程 默认使用直接的方式
队列:槽函数所在的线程和接收者一样
直接:槽函数所在线程和发送者一样
//自定义类对象,分配空间,不可以指定父对象
myT=new MyThread;
//创建子线程
thread=new QTherad(this);
//把自定义模块加到子线程
my->moveToThread(thread);
//启动子线程但是没有启动线程处理函数
thread->start();
//线程处理函数 ,必须通过 signal--slot调用
connect()
//退出子线程
thread->quit();
//回收资源
thread->wait();
delete myT;
子线程不能动ui布局,传给主线程,来更新。通过connect 信号传参数 给槽函数
通常情况下,程序中的多个线程是互相协调和互相联系的,多线程之间有互斥和同步。
多个线程之间有共享资源(shared resource)时会出现互斥现象。
设有若干线程共享某个变量,而且都对变量有修改。如果它们之间不考虑相互协调工作,就会产生混乱。比如,线程A和B共用变量x,都对x执行增1操作。由于A和B没有协调,两线程对x的读取、修改和写入操作相互交叉,可能两个线程读取相同个x值,一个线程将修改后的x新值写入到x后,另一个线程也把自己对x修改后的新值写入到x。这样,x只记录后一个线程的修改作用。
临界段:多线程互斥使用共享资源的程序段,在操作系统中称为临界段。临界段是一种加锁的机制,与多线程共享资源有关。
临界段的作用是在任何时刻一个共享资源只能供一个线程使用。当资源未被占用,线程可以进入处理这个资源的临界段,从而得到该资源的使用权;当线程执行完毕,便退出临界段。如果一个线程已进入某个共享资源,并且还没有使用结束,其他线程必须等待。
在JAVA中使用关键字synchronized定义临界段,能对共享对象进行上锁操作。
多线程之间除了有互斥情况外,还有线程同步。当线程A使用某个对象,而此对象又需要线程B修改后才能符合本线程的需要,此时线程A就要等待线程B完成修改工作。这种线程相互等待称为线程的同步。
为实现同步,JAVA语言提供了wait()、notify()和notifyAll()三个方法供线程在临界段中使用。
在临界段中使用wait()方法,使执行该方法的线程等待,并允许其他线程使用这个临界段。wait()常用两种格式:
wait()——让线程一直处于等待队列,知道被使用了notify()或notifyAll()方法唤醒。
wait(long timeout)——让线程等待到被唤醒,或经过指定时间后结束等待。
当线程使用完临界段后,用notify()方法通知由于想使用这个临界段而处于等待状态的线程结束等待。notify()方法只是通知第一个处于等待的线程。
如果某个线程在使用完临界段方法后,其他早先等待的线程都可以结束等待,一起重新竞争CPU,则可以使用notifyAll()方法。
利用定时器类QTimer
创建对象QTimer*timer=new QTimer(this)
启动定时器 time->start(毫秒)
每隔一毫秒,发一个信号 timeout,进行监听
time->stop
在Qt里利用TCP/IP协议,socket套接字设计实现结构体的收发,类似实现简单的自定义通信协议。
发送的结构体包含帧头header(占两字节)、数据长度dataLength(占一字节)、数据my_data(不多于64字节)、校验和check_sum(前面所有数据所占字节和,本身只占一个字节)。
发送方的结构体:
这里要特别注意== #pragma pack(1) ==的使用,涉及到结构体内存对齐,使用这行可以设置结构体对齐方式为1字节,这点特别重要,我在这个坑里绕了好久才走出来!!这样设置主要是因为后面要使用到结构体的大小sizeof(senddata)。
#define DATA_LEN 64
#pragma pack(1) //设置结构体为1字节对齐
typedef struct sendData
{
uchar header[2]; //帧头(2字节) uchar才能存十六进制数
uchar dataLength; //数据个数(1字节),小于64
char my_data[DATA_LEN]; //数据(小于64字节)
uchar check_sum; //校验和(1字节) 前面所有字节累加和
}senddata;
#pragma pack() //结束结构体对齐设置
12345678910
注意:结构体中的数据存储最好不要用 char* 类型,在后面用到结构体强转、结构体转QByteArray数组时容易出错。转的时候可能只拷贝了char的地址,没有拷贝到数据;也有可能由于char数据长度不定,发送的时候出现问题;也有可能接收方收到解析的时候出现问题。
总体思路:先封装填好帧头部分,然后从界面获取用户输入的数据,将其存进结构体my_data[ ]数组中(具体操作:获取的数据是字符串,要借助QByteArray作为中间桥梁进行转换),填好数据长度和校验和,至此要发送的结构体就封装好了。然后再将封装好的结构体转为QByteArray数组(因为传输都是Byte类型数据,直接发结构体会报错),然后由于发送的数据长度每次不同,my_data数组可能就因此没有占满,此处对校验和这个数据的放置位置有个处理细节,将其放在了my_data数据后面,下面有解释。
char data[64];
QString str="12fff";
QByteArray ba=str.toLatin1();
char *temp=ba.data();
memcpy(data,temp,ba.length());
12345
sendData st_senddata;
QByteArray get_data, sendTcpData;
char *temp;
QString str;
//senddata.header.resize(2);
st_senddata.header[0] = 0x55; //假设帧头就是0X55 0XAA
st_senddata.header[1] = 0xAA;
str = ui->textEdit_Send->toPlainText().toLocal8Bit();
//数据超长提醒
if(str.length() > 64)
{
QMessageBox::information(this,tr("提示"),tr("数据长度限制为64!"),QMessageBox::Yes);
ui->textEdit_Send->clear();
return;
}
//填好数据
get_data=(QByteArray)ui->textEdit_Send->toPlainText().toLocal8Bit(); //直接获取用户输入的同时将QString转成QByteArray
temp=get_data.data(); //将QByteArray转成char*
memcpy(st_senddata.my_data,temp,get_data.length()); //不拷贝内存传结构体时就只会传一个指针过去
//填好数据长度
st_senddata.dataLength = get_data.length();
//填好校验和,就是my_data长度+header两字节+datalength一字节
st_senddata.check_sum = get_data.length() + 3;
//使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)
//直接sizeof(senddata)内存会变小,设置了对齐方式解决,,,只给他赋予数据长度加帧头、校验和所占字节
sendTcpData.resize((get_data.length()+3));
//将封装好的结构体转为QByteArray数组,因为传输都是Byte类型
memcpy(sendTcpData.data(),&st_senddata,(get_data.length()+3));
/*因为数据长度可能没有占满64字节,校验和又是存在数据之后的,所以有一段内存可能是空的,因此此处手动
把校验和值添加在QByteArray数组最后,这样发送出去的数据就是连续的*/
sendTcpData.append(st_senddata.check_sum);
//发送完整的QByteArray数组,包含所有结构体信息
socket->write(sendTcpData);
ui->textEdit_Recv->insertPlainText("send:"+str+"\n");
socket->flush(); //释放socket缓存
ui->textEdit_Send->clear(); //发送出去后将发送文本清空
//释放指针、清空QByteArray数组
free(temp);
temp = NULL;
get_data.clear();
get_data.squeeze();
sendTcpData.clear();
sendTcpData.squeeze();
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
总体思路:与发送端类似,此时收到的是QByteArray数组,需要将它再转成与发送方同样的结构体。先定义一个结构体指针,将收到的QByteArray数组强转为结构体,再利用结构体指针读取里面的值,存在新的结构体变量里,方便值的读取,最后再把结构体里的my_data数据显示在接收方的文本里。校验和的读取下面也有说明。
接收方的结构体:
#define DATA_LEN 64
#pragma pack(1)
//接收数据的格式
typedef struct receiveData
{
uchar header[2]; //帧头
uchar dataLength; //数据个数(1字节),小于64
char my_data[DATA_LEN]; //数据(小于64字节)
uchar check_sum; //校验和(1字节) 前面所有字节累加和
}st_receivedata;
#pragma pack()
123456789101112
receiveData st_receiveTcpData, *get_Data;
QByteArray buffer;
QString str;
//读取缓冲区数据
buffer = socket->readAll();
if(!buffer.isEmpty())
{
memset(&st_receiveTcpData,0,sizeof (st_receiveTcpData));
get_Data = (receiveData*)buffer.data(); //强转为结构体,需要用结构体指针接收
//读取帧头
for(int i = 0; i < sizeof (st_receiveTcpData.header); i++)
{
st_receiveTcpData.header[i] = get_Data->header[i];
}
//读取数据长度
st_receiveTcpData.dataLength = get_Data->dataLength;
//读取数据
for(int i = 0; i < buffer.length() - 4; i++)//buffer的长度减去header、datalength、check_sum的长度就是数据的长度
{
st_receiveTcpData.my_data[i] = get_Data->my_data[i];
}
//读取校验和,因为发送的时候避免my_data有空内容,所以把校验和放在了my_data后面,所以此处只
//需要读取my_data后面的值就是校验和了。
get_Data->check_sum = get_Data->my_data[buffer.length()-4];
st_receiveTcpData.check_sum = get_Data->check_sum;
//将my_data数据转为QString
str = QString(QLatin1String(st_receiveTcpData.my_data));
//将my_data在文本框显示
ui->textEdit_Recv->insertPlainText("receive:"+str+"\n");
//释放内存
free(get_Data);
buffer.clear();
buffer.squeeze();
}
1234567891011121314151617181920212223242526272829303132333435363738
总的来说,思路很简单,但是坑也很多,尤其是数据转换问题,稍不注意就出错。
单播:客户端与服务器建立一个单独的数据通道,从一台服务器送出的数据包只能到特定的客户端。
广播:向子网中所有计算机发送消息。
组播:又叫多路广播,消息从服务器发送到子网中,同时计算机也可以加入制定的组播中来接受消息。
对于发送端 QT中单播、组播、广播的写法相似 需要把地址改了就行了
udpsocket.writeDatagram(arr,QHostAddress(ip_send),port);
例如 当ip_send 是正常的IP 比如局域网内 192.168.1.112 这就是单播
当ip_send是D类地址 这就是组播
当ip_send是QHostAddress::Broadcast 这是广播
对于接收端 单播、组播、广播步骤都一样
1 创建套接字
read=new QUdpSocket(this);
2 绑定地址端口号
read->bind(port,QUdpSocket::ShareAddress);//这个是绑定某个端口
组播,广播这样绑定 bind(QHostAddress::AnyIPv4,6677);参考自http://www.cnblogs.com/wurenzhong/p/8030220.html
//shareaddress 允许绑定在同一个端口
3 如果是组播还涉及的是否加入组播地址
Socket->joinMulticastGroup(QHostAddress("224.0.0.100"));
加入之后退出
Socket->leaveMulticastGroup(QHostAddress(“224.0.0.100”));
:报错 QNativeSocketEngine::joinMulticastGroup() was not called in QAbstractSocket::BoundState
原因:需要将端口号选择可以复用。修改代码如下

udpsocket->bind(QHostAddress::AnyIudp tcp
编写Qt udpsocket网络编程组播时候提示
:报错 QNativeSocketEngine::joinMulticastGroup() was not called in QAbstractSocket::BoundState
原因:需要将端口号选择可以复用。修改代码如下
udpsocket->bind(QHostAddress::AnyIPv4,8888);
1
改为
//绑定
udpsocket->bind(QHostAddress::AnyIPv4,8888,QUdpSocket::ShareAddress|QUdpSocket::ReuseAddressHint);
12
即可。
QXmlStreamReader接口说明
创建一个QXmlStreamReader的类对象
通过setDevice()设置好要处理的XML文件
通过readNext()挨个读入节点,
通过isStartElement()和isEndElement()判断是节点的开始和结束.
通过name()得到当前节点名字
通过readElementText()访问当前节点的内容
通过attributes()获取含有属性的节点的属性
在QT中,默认的编码格式是 Unicode,我们书写的代码文件被强制转换为utf-8,但是,简体中文版的windows操作系统中,默认编码是GBK
因此,在编译Qt程序时,如果代码中含有特定中文字符,Qt的编译器就会发生误判,报告“常量中有换行符”,有一些中文字符编码不含有这些特定字符,编译不报错,但程序运行时中文不能正常显示。
更改设置
打开qt creator 点击菜单 工具-选项-文本编辑器-行为选项卡中,把文件编码修改为 UTF-8,并且选择如果编码是UTF-8则添加
代码调整
在代码中用 QString.toLocal8Bit()将Unicode编码转换为本地系统编码
Qt中实现树形结构可以使用QTreeWidget类,也可以使用QTreeView类,QTreeWidget继承自QTreeView类。树形效果如下图所示:
1. 树形结构实现
QT GUI中有treeWidget部件,将该控件在Gui中布局好,假设其对象名为treeWidget。
QTreeWidget类官方文档:http://qt-project.org/doc/qt-4.8/qtreewidget.html
树形结构通过QTreeWidget类和QTreeWidgetItem类实现,QTreeWidgetItem类实现结点的添加。上图代码实现如下:
ui->treeWidget->setColumnCount(1); //设置列数
ui->treeWidget->setHeaderLabel(tr("图像选择")); //设置头的标题
QTreeWidgetItem *imageItem1 = new QTreeWidgetItem(ui->treeWidget,QStringList(QString("图像1")));
imageItem1->setIcon(0,QIcon("xxx.png"));
QTreeWidgetItem *imageItem1_1 = new QTreeWidgetItem(imageItem1,QStringList(QString("Band1"))); //子节点1
imageItem1->addChild(imageItem1_1); //添加子节点
QTreeWidgetItem *imageItem2 = new QTreeWidgetItem(ui->treeWidget,QStringList(QString("图像2")));
QTreeWidgetItem *imageItem2_1 = new QTreeWidgetItem(imageItem2,QStringList(QString("Band1"))); //子节点1
QTreeWidgetItem *imageItem2_2 = new QTreeWidgetItem(imageItem2,QStringList(QString("Band2"))); //子节点2
imageItem2->addChild(imageItem2_1); //添加子节点
imageItem2->addChild(imageItem2_2);
ui->treeWidget->expandAll(); //结点全部展开
当然,还有其他的一些方法用于设置,具体需要时查查帮助文档学习。
除了使用上面这种方法之外,还可以使用QList
//只写结点的实现
QList rootList;
QTreeWidgetItem *imageItem1 = new QTreeWidgetItem; //添加第一个父节点
imageItem1->setText(0,tr("图像1"));
rootList.append(imageItem1);
QTreeWidgetItem *imageItem1_1 = new QTreeWidgetItem(imageItem1,QStringList(QString("Band1"))); //添加子节点
imageItem1->addChild(imageItem1_1);
QTreeWidgetItem *imageItem2 = new QTreeWidgetItem; //添加第二个父节点
imageItem2->setText(0,tr("图像2"));
rootList.append(imageItem2);
QTreeWidgetItem *imageItem2_1 = new QTreeWidgetItem(imageItem2,QStringList(QString("Band1"))); //添加子节点
QTreeWidgetItem *imageItem2_2 = new QTreeWidgetItem(imageItem2,QStringList(QString("Band2")));
imageItem2->addChild(imageItem2_1);
imageItem2->addChild(imageItem2_2);
ui->treeWidget->insertTopLevelItems(0,rootList); //将结点插入部件中
ui->treeWidget->expandAll(); //全部展开
2. 点击节点的事件响应
首先想到有没有点击某个节点的信号,查看文档,有一个void itemClicked ( QTreeWidgetItem * item, int column )信号,是双击某个节点的信号,将该信号与某个自定义槽相连,当双击节点时触发槽函数。
看一下这个信号,第一个参数为点击的QTreeWidgetItem类对象,第二个参数为节点所在列号。
思路:根据点击的QTreeWidgetItem类对象可以通过parent()函数得到父节点,如果QTreeWidgetItem类对象就是最最顶端的节点时,parent()函数返回的就是NULL。通过insertChildren ( int index, const QList
目前只能解决只有一个最顶端父节点时的事件响应,当最顶端的父节点有多个(比如本文开头有2个),这时点击子节点时,无法判断子节点的父节点是哪一个(本人愚笨啊!),因此没法为其槽函数执行相应的操作。
这里就以一个分支为例。
private slots:
2 void showSelectedImage(QTreeWidgetItem * item, int column); //点击树节点事件
3
4 connect(ui->treeWidget,SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),this,SLOT(showSelectedImage(QTreeWidgetItem*,int)));
5
6 void MainWindow::showSelectedImage(QTreeWidgetItem *item, int column)
7 {
8 QTreeWidgetItem *parent = item->parent();
9 if(NULL==parent) //注意:最顶端项是没有父节点的,双击这些项时注意(陷阱)
10 return;
11 int col = parent->indexOfChild(item); //item在父项中的节点行号(从0开始)
12
13 if(0==col) //Band1
14 {
15 //执行对应操作
16 }
17 if(1==col) //Band2
18 {
19 //执行对应操作
20 }
21 }
1.头文件中不可以放变量的定义!一般头文件中只是放变量的声明,因为头文件要被其他文件包含#include,如果把定义放在头文件的话,就不能避免多次定义变量。C++不允许多次定义变量,一个程序中对指定变量的定义只有一次,声明可以无数次。
三个例外:1)值在编译时就已知的const变量的定义可放在头文件中,如:const int num=10;
2)类的定义可放在头文件中。
3)inline函数。
2.定义和声明是不同的。定义只能出现一次,而声明可以出现多次。
下面的语句是定义,不能放在头文件中:
extern int ival=10; //虽然ival声明为extern,但是它初始化了,代表这是个定义。
double fica_rate; //fica_rate虽然没有初始化,但是没有extern。所以仍是定义。
3.在多个C文件的程序中,想在头文件中声明定义几个变量,如果给声明的变量赋值,则提示重复定义,在声明的变量前加extern只对变量进行声明,可是在什么地方给变量赋值呢??
答:加了extern后,就可以在其他任何引用此文件的地方来进行赋值的。比如说,可在主文件中进行赋值。
\4. extern int x; //变量是声明,并未实际分配地址。
void print(); //函数声明,并未产生实际目标代码
如:int x; int x=3; void print() { }; //均为定义。
5。只有全局变量并且没有被static声明的变量才能声明为extern。所以,如果你不想自己源文件中全局变量被其他文件引用,你就可以给变量加上static声明。
定义也是声明。C++中有个关键字用来声明变量的,即extern。它声明一个变量,而不定义它。
注意:在C++中,变量有且只能有一次定义,但是可以声明多次。
举例说明:假设在文件1.cpp中定义了一个全局变量bufsize,文件1.cpp中当然能访问它,如果我想在文件2.cpp中访问这个全局变量,这时候需要在文件2.cpp中声明这个变量。
//1.cpp
int bufsize;
//2.cpp
extern int bufsize;
这里说的是非const的全局变量,如果是const的全局变量,如果想被其他文件访问,需要在定义时,加上extern关键字,表示它可被其他文件声明使用的。否则的话,这个变量只能在它被定义的文件里面被访问,其他文件不能访问。
//1.cpp
extern const int bufsize=10;
//2.cpp
extern const int bufsize;
为什么非const变量定义时候没有extern??答:非const变量默认为extern,const变量默认为文件的局部变量。而const变量如果想在其他文件里被访问,必须显示的指定它为extern。标题答案在这里
\6. C++标准并没有规定头文件中不能定义变量,只不过如果在头文件中定义变量,而该头文件又被多次包含的话,会造成变量的重新定义。
\7. 例如:在test.c文件中定义变量int global=0;
可以在头文件test.h中声明这个变量为extern int global;
要使用这个变量的其他文件,只要包含test.h就可以了。
\8. static全局变量是有文件作用域的。在a.c中用了,在其他文件中就不能使用了。static变量一般放在.cpp或者.c文件中。不放在.h文件中。
\9. 在某一个.c文件中定义:int num=0;
在.h文件中声明:extern int num;
\10. #ifndef、、、#define、、、#endif
可以保证在一个文件里只是定义一次。
比如:a.h引用b.h, c.cpp同时引用a.h和b.h。这样,#ifndef、、、#define、、、#endif可以保证c.cpp里的b.h只被引用一次。
注意:要把头文件的内容都放在#ifndef和#endif中。不管头文件会不会被多个文件引用,都要加上这个。
一般的格式:
#ifndef <标识>
#define <标识>
、、、、
、、、、
#endif
<标识>理论上说可以自由命名。但是每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中.也变成下划线。如:
stdio.h
\#ifndef _STDIO_H_
\#define _STDIO_H_
、、、
\#endif
程序示例:
//a.h
\#ifndef A
\#define A
bool AorB(bool a)
{
return a;
}
\#endif
//b.h
\#include "a.h"
bool CorD(bool a)
{
return AorB(a);
}
//a.cpp
\#include "a.h"
\#include "b.h"
int main()
{
bool a=0;
bool b=AorB(a);
bool c=CorD(b);
getchar();
return 0;
}
在程序运行中,函数代码是程序的算法指令部分,它们和数组一样也占用存储空间,都有相应的地址。可以使用指针变量指向数组的首地址,也可以使用指针变量指向函数代码的首地址,指向函数代码首地址的指针变量称为函数指针。
一、CONST的作用
1、保护被修饰的变量,防止程序中意外修改
2、为函数的重载提供一种区别方法
例如:void fun(int i) { …}
void fun(int i) const { … } // 跟上面的函数一起构成重载
二、CONST的使用
1、被修饰的指针本身是不能修改的
例如:void fun(char * const argv) {
char * const pVar = “hello world!”;
pVar = argv; // x }
2、被修饰的指针指向的内容是不能修改的
例如:const char * pVar = “Hello world”;
pVar[1] = “E”; // x
3、const修饰类成员变量时,表示该成员不能被修改,只能在初始化列表中赋值
例如:
class A {
const int nVar;
…
A(int x) : nVar(x) {}
};
4、修饰成员函数时,表示该函数不改变类的数据成员。
例如: class A {
public:
int getX() const { return pointX; }
}
注意:
A、const成员函数不允许修改其所在对象的任何一个数据成员;
B、const成员函数可以访问对象的const成员,而非const成员函数无法访问。
三、CONST转成非CONST类型
语法:const_cast (express)
作用:用来修改变量的const或volatile属性。
※用volatile声明的变量可以被某些编译器未知的因素改变。
换言之,编译器不会对volatile声明的变量相关代码做任何优化。
四、STATIC的作用
1、在函数中修饰成员变量
例如:
void fun(int i) {
static int index = 0;
…
index++;
}
在程序第一调用fun()的时候,index会被初始化成0;之后不再做初始化的动作,index的值为前一次调用之后的值保留。
2、在类中修饰类的数据成员
则可以看作该数据成员独立于对象,相当于一个全局性的数据,只是访问的时候必须加类名限定。
头文件只能声明文件,不能定义
有的时候能初始化变量, 应该是因为这个类 只使用了一次 没重复调用 重复定义和初始化
如果在头文件声明变量
则在cpp定义的时候 记得 new的时候 返回值是地址,所以你声明的时候最好也是指针,这样直接获得地址
调用其他类的方法的时候,类的参数需要定义为指针 不知道为什么
KMP数组解决的是字符串匹配问题:给定字符串p和t,问p在t中第一次出现的位置是哪里?
如果使用暴力匹配,设p的长度为m, t的长度为n,时间复杂度为O(m, n)
而KMP算法提供了一种新的思路:如果p的第0~j - 1位与t的第i ~ i + j - 1位已经匹配了,那么我们能不能利用这个信息呢?答案是可以。
于是给出了next数组的定义:next[j] = k,表示p的第0~k位与j - 1 - k ~ j - 1 位完全相等。那么我们发现p的第j位失配后,只需要将j = next[j],便能保证前j - 1位匹配。
这样,时间复杂度为O(m + n)
void find_next(string str, vector<int> &next)
{
int i = 0;
int k = -1;
int len = str.length();
next = vector<int> (len, 0);
next[0] = -1;
while (i < len - 1)
{
while (k >= 0 && str[i] != str[k])
{
k = next[k];
}
i++;
k++;
next[i] = k;
}
}
12345678910111213141516171819
是根据有限状态机的思路来写的
重点是:next数组怎么使用
int i = 0, k = -1;
int len = s.length();
vector<int> next(len + 1, 0);
next[0] = -1;
while (i < len)
{
while (k >= 0 && s[i] != s[k])
{
k = next[k];
}
i++;
k++;
next[i] = k;
}
int loop_len = len - next.back();
123456789101112131415
除了字符串匹配以外,KMP数组最常见的应用是:求一个字符串的循环。在求循环的时候,需要多求一位,使用len - next[len]作为循环节长度。
int kmp(string p, string t)
{
vector<int> next;
int i = 0, j = 0;
find_next(p, next);
int tLen = t.length(), pLen = p.length();
while (i < tLen && j < pLen)
{
if (j == -1 || t[i] == p[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if (j >= pLen)
{
return i - pLen;
}
return -1;
}
123456789101112131415161718192021222324123456789101112131415161718192021222324
这个函数求的则是字符串匹配
https://blog.csdn.net/pkuout/article/details/82952964
https://www.qcustomplot.com/index.php/introduction
//pro文件中 添加
QT +=printsupport
//可放大缩小
customPlot->axisRect()->setMarginGroup(QCP::msLeft|QCP::msRight, group);
//金融蜡烛图
customPlot->legend->setVisible(true);
// generate two sets of random walk data (one for candlestick and one for ohlc chart):
int n = 500;
QVector time(n), value1(n), value2(n);
QDateTime start = QDateTime(QDate(2014, 6, 11));
start.setTimeSpec(Qt::UTC);
double startTime = start.toTime_t();
double binSize = 3600*24; // bin data in 1 day intervals
time[0] = startTime;
value1[0] = 60;
value2[0] = 20;
qsrand(9);
for (int i=1; ixAxis, customPlot->yAxis);
candlesticks->setName("Candlestick");
candlesticks->setChartStyle(QCPFinancial::csCandlestick);
candlesticks->data()->set(QCPFinancial::timeSeriesToOhlc(time, value1, binSize, startTime));
candlesticks->setWidth(binSize*0.9);
candlesticks->setTwoColored(true);
candlesticks->setBrushPositive(QColor(245, 245, 245));
candlesticks->setBrushNegative(QColor(40, 40, 40));
candlesticks->setPenPositive(QPen(QColor(0, 0, 0)));
candlesticks->setPenNegative(QPen(QColor(0, 0, 0)));
// create ohlc chart:
QCPFinancial *ohlc = new QCPFinancial(customPlot->xAxis, customPlot->yAxis);
ohlc->setName("OHLC");
ohlc->setChartStyle(QCPFinancial::csOhlc);
ohlc->data()->set(QCPFinancial::timeSeriesToOhlc(time, value2, binSize/3.0, startTime)); // divide binSize by 3 just to make the ohlc bars a bit denser
ohlc->setWidth(binSize*0.2);
ohlc->setTwoColored(true);
// create bottom axis rect for volume bar chart:
QCPAxisRect *volumeAxisRect = new QCPAxisRect(customPlot);
customPlot->plotLayout()->addElement(1, 0, volumeAxisRect);
volumeAxisRect->setMaximumSize(QSize(QWIDGETSIZE_MAX, 100));
volumeAxisRect->axis(QCPAxis::atBottom)->setLayer("axes");
volumeAxisRect->axis(QCPAxis::atBottom)->grid()->setLayer("grid");
// bring bottom and main axis rect closer together:
customPlot->plotLayout()->setRowSpacing(0);
volumeAxisRect->setAutoMargins(QCP::msLeft|QCP::msRight|QCP::msBottom);
volumeAxisRect->setMargins(QMargins(0, 0, 0, 0));
// create two bar plottables, for positive (green) and negative (red) volume bars:
customPlot->setAutoAddPlottableToLegend(false);
QCPBars *volumePos = new QCPBars(volumeAxisRect->axis(QCPAxis::atBottom), volumeAxisRect->axis(QCPAxis::atLeft));
QCPBars *volumeNeg = new QCPBars(volumeAxisRect->axis(QCPAxis::atBottom), volumeAxisRect->axis(QCPAxis::atLeft));
for (int i=0; iaddData(startTime+3600*5.0*i, qAbs(v)); // add data to either volumeNeg or volumePos, depending on sign of v
}
volumePos->setWidth(3600*4);
volumePos->setPen(Qt::NoPen);
volumePos->setBrush(QColor(100, 180, 110));
volumeNeg->setWidth(3600*4);
volumeNeg->setPen(Qt::NoPen);
volumeNeg->setBrush(QColor(180, 90, 90));
// interconnect x axis ranges of main and bottom axis rects:
connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), volumeAxisRect->axis(QCPAxis::atBottom), SLOT(setRange(QCPRange)));
connect(volumeAxisRect->axis(QCPAxis::atBottom), SIGNAL(rangeChanged(QCPRange)), customPlot->xAxis, SLOT(setRange(QCPRange)));
// configure axes of both main and bottom axis rect:
QSharedPointer dateTimeTicker(new QCPAxisTickerDateTime);
dateTimeTicker->setDateTimeSpec(Qt::UTC);
dateTimeTicker->setDateTimeFormat("dd. MMMM");
volumeAxisRect->axis(QCPAxis::atBottom)->setTicker(dateTimeTicker);
volumeAxisRect->axis(QCPAxis::atBottom)->setTickLabelRotation(15);
customPlot->xAxis->setBasePen(Qt::NoPen);
customPlot->xAxis->setTickLabels(false);
customPlot->xAxis->setTicks(false); // only want vertical grid in main axis rect, so hide xAxis backbone, ticks, and labels
customPlot->xAxis->setTicker(dateTimeTicker);
customPlot->rescaleAxes();
customPlot->xAxis->scaleRange(1.025, customPlot->xAxis->range().center());
customPlot->yAxis->scaleRange(1.1, customPlot->yAxis->range().center());
// make axis rects' left side line up:
QCPMarginGroup *group = new QCPMarginGroup(customPlot);
customPlot->axisRect()->setMarginGroup(QCP::msLeft|QCP::msRight, group);
volumeAxisRect->setMarginGroup(QCP::msLeft|QCP::msRight, group);
// 初始化
m_CustomPlot = new QCustomPlot;
// CustomPlot的基础功能设置
m_CustomPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iMultiSelect | QCP::iSelectOther | QCP::iSelectItems);
// 基础功能共有以下几种,大体意思是:
// 1、轴可拖动 2、通过鼠标滚轮改变轴显示范围 3、用户可以选择多个对象,设定的修饰符(不是特别明白)
// 4、图形是可选的 5、轴是可选的 6、图例是可选的。。。
/*enum Interaction { iRangeDrag = 0x001 ///< 0x001 Axis ranges are draggable (see \ref QCPAxisRect::setRangeDrag, \ref QCPAxisRect::setRangeDragAxes)
,iRangeZoom = 0x002 ///< 0x002 Axis ranges are zoomable with the mouse wheel (see \ref QCPAxisRect::setRangeZoom, \ref QCPAxisRect::setRangeZoomAxes)
,iMultiSelect = 0x004 ///< 0x004 The user can select multiple objects by holding the modifier set by \ref QCustomPlot::setMultiSelectModifier while clicking
,iSelectPlottables = 0x008 ///< 0x008 Plottables are selectable (e.g. graphs, curves, bars,... see QCPAbstractPlottable)
,iSelectAxes = 0x010 ///< 0x010 Axes are selectable (or parts of them, see QCPAxis::setSelectableParts)
,iSelectLegend = 0x020 ///< 0x020 Legends are selectable (or their child items, see QCPLegend::setSelectableParts)
,iSelectItems = 0x040 ///< 0x040 Items are selectable (Rectangles, Arrows, Textitems, etc. see \ref QCPAbstractItem)
,iSelectOther = 0x080 ///< 0x080 All other objects are selectable (e.g. your own derived layerables, the plot title,...)
};*/
// 设置矩形边框
m_CustomPlot->axisRect()->setupFullAxesBox();
// 清空CustomPlot中的图形
m_CustomPlot->clearGraphs();
// 在CustomPlot中添加图形
m_CustomPlot->addGraph();
// 设置图形中的数据m_x和m_y是两个QVector容器
m_CustomPlot->graph(0)->setData(m_x, m_y);
// 这个是设置图形显示为合适范围(感觉设置的只是Y轴)
m_CustomPlot->graph(0)->rescaleValueAxis(true);
// 设置X轴的显示范围(这里是4条轴,x是下面那条,x2是上面那条,Y是先左后右)
m_CustomPlot->xAxis->setRange(m_x.at(0) - 1, m_x.at(m_x.size() - 1) + 1 );
// 刷新m_CustomPlot中数据
m_CustomPlot->replot();
可以自己新建一个 current 数据,然后读取day文件, ohlc->data()->set(); 加进去,读取
customPlot->legend->setVisible(true);
2
3 // 生成2种随机的蜡烛图数据,第一个是蜡烛图数据,第二个是美国线数据
4 int n = 500;
5 QVector time(n), value1(n), value2(n);
6 QDateTime start = QDateTime(QDate(2014, 6, 11));
7 start.setTimeSpec(Qt::UTC);
8 double startTime = start.toTime_t();
9 double binSize = 3600*24; // 1天的数据
10 time[0] = startTime;
11 value1[0] = 60;
12 value2[0] = 20;
13 qsrand(9);//生成随机数时给指定的种子,那么生成的随机数都是相同的,因此每次运行后得到的结果都是不变的
14 for (int i=1; ixAxis, customPlot->yAxis);
23 candlesticks->setName("Candlestick");
24 candlesticks->setChartStyle(QCPFinancial::csCandlestick);//设置图表类型为蜡烛图
25 candlesticks->data()->set(QCPFinancial::timeSeriesToOhlc(time, value1, binSize, startTime));//设置数据
26 candlesticks->setWidth(binSize*0.9);//设置每一个数据项的绘制宽度
27 candlesticks->setTwoColored(true);//设置是否显示两种颜色
28 candlesticks->setBrushPositive(QColor(245, 245, 245));//设置收>开画刷
29 candlesticks->setBrushNegative(QColor(40, 40, 40));//设置收<开画刷
30 candlesticks->setPenPositive(QPen(QColor(0, 0, 0)));//设置收>开画笔
31 candlesticks->setPenNegative(QPen(QColor(0, 0, 0)));//设置收>开画笔
32
33 // 初始化一个美国线图指针:
34 QCPFinancial *ohlc = new QCPFinancial(customPlot->xAxis, customPlot->yAxis);
35 ohlc->setName("OHLC");
36 ohlc->setChartStyle(QCPFinancial::csOhlc);//设置图表类型为美国线
37 ohlc->data()->set(QCPFinancial::timeSeriesToOhlc(time, value2, binSize/3.0, startTime)); //为了区分于蜡烛图显示,
38 ohlc->setWidth(binSize*0.2);
39 ohlc->setTwoColored(true);
40
41 // 创建一个坐标轴矩形
42 QCPAxisRect *volumeAxisRect = new QCPAxisRect(customPlot);
43 customPlot->plotLayout()->addElement(1, 0, volumeAxisRect);
44 volumeAxisRect->setMaximumSize(QSize(QWIDGETSIZE_MAX, 100));
45 volumeAxisRect->axis(QCPAxis::atBottom)->setLayer("axes");
46 volumeAxisRect->axis(QCPAxis::atBottom)->grid()->setLayer("grid");
47 // 设置自己构造的坐标轴矩形属性
48 customPlot->plotLayout()->setRowSpacing(0);
49 volumeAxisRect->setAutoMargins(QCP::msLeft|QCP::msRight|QCP::msBottom);
50 volumeAxisRect->setMargins(QMargins(0, 0, 0, 0));
51 // 生成两种颜色的柱状图
52 customPlot->setAutoAddPlottableToLegend(false);//是否自动生成图例
53 QCPBars *volumePos = new QCPBars(volumeAxisRect->axis(QCPAxis::atBottom), volumeAxisRect->axis(QCPAxis::atLeft));
54 QCPBars *volumeNeg = new QCPBars(volumeAxisRect->axis(QCPAxis::atBottom), volumeAxisRect->axis(QCPAxis::atLeft));
55 for (int i=0; iaddData(startTime+3600*5.0*i, qAbs(v)); //构造随机数据
59 }
60 volumePos->setWidth(3600*4);
61 volumePos->setPen(Qt::NoPen);
62 volumePos->setBrush(QColor(100, 180, 110));
63 volumeNeg->setWidth(3600*4);
64 volumeNeg->setPen(Qt::NoPen);
65 volumeNeg->setBrush(QColor(180, 90, 90));
66
67 // 设置自己构造的坐标轴矩形的x轴和QCustomPlot中的坐标轴矩形(默认的会生成一个)x轴同步,两个坐标轴永远显示的坐标范围是一样的
68 connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), volumeAxisRect->axis(QCPAxis::atBottom), SLOT(setRange(QCPRange)));
69 connect(volumeAxisRect->axis(QCPAxis::atBottom), SIGNAL(rangeChanged(QCPRange)), customPlot->xAxis, SLOT(setRange(QCPRange)));
70 // 构造一个新的坐标轴刻度计算类
71 QSharedPointer dateTimeTicker(new QCPAxisTickerDateTime);
72 dateTimeTicker->setDateTimeSpec(Qt::UTC);
73 dateTimeTicker->setDateTimeFormat("dd. MMMM");
74 volumeAxisRect->axis(QCPAxis::atBottom)->setTicker(dateTimeTicker);//赋予自己构造的坐标轴矩形的x轴一个新的刻度计算类
75 volumeAxisRect->axis(QCPAxis::atBottom)->setTickLabelRotation(15);
76 customPlot->xAxis->setBasePen(Qt::NoPen);
77 customPlot->xAxis->setTickLabels(false);//不显示坐标轴文本
78 customPlot->xAxis->setTicks(false); // 不显示坐标轴 (这个接口实现的不友好,后续文章我会具体说到)
79 customPlot->xAxis->setTicker(dateTimeTicker);//赋予默认的坐标轴矩形的x轴一个新的刻度计算类
80 customPlot->rescaleAxes();
81 customPlot->xAxis->scaleRange(1.025, customPlot->xAxis->range().center());
82 customPlot->yAxis->scaleRange(1.1, customPlot->yAxis->range().center());
83
84 // 设置两个坐标轴矩形左右对齐
85 QCPMarginGroup *group = new QCPMarginGroup(customPlot);
86 customPlot->axisRect()->setMarginGroup(QCP::msLeft|QCP::msRight, group);
87 volumeAxisRect->setMarginGroup(QCP::msLeft|QCP::msRight, group);
candlesticks->data()->set(QCPFinancial::timeSeriesToOhlc(time, value1, binSize, startTime));//设置数据
蜡烛图绘制,是读取各个时间节点的数据,然后收集24小时内的 0-23 自动读取其中的开盘价收盘价 最大值最小值 来绘制, 很省心省力。
QCPFinancialDataContainer QCPFinancial::timeSeriesToOhlc(const QVector &time, const QVector &value, double timeBinSize, double timeBinOffset) //定义变量
{
QCPFinancialDataContainer data;
int count = qMin(time.size(), value.size());
if (count == 0)
return QCPFinancialDataContainer();
QCPFinancialData currentBinData(0, value.first(), value.first(), value.first(), value.first());
int currentBinIndex = qFloor((time.first()-timeBinOffset)/timeBinSize+0.5);
for (int i=0; i currentBinData.high) currentBinData.high = value.at(i);
if (i == count-1) // last data point is in current bin, finalize bin:
{
currentBinData.close = value.at(i);
currentBinData.key = timeBinOffset+(index)*timeBinSize;
data.add(currentBinData);
}
} else // data point not anymore in current bin, set close of old and open of new bin, and add old to map:
{
// finalize current bin:
currentBinData.close = value.at(i-1);
currentBinData.key = timeBinOffset+(index-1)*timeBinSize;
data.add(currentBinData);
// start next bin:
currentBinIndex = index;
currentBinData.open = value.at(i);
currentBinData.high = value.at(i);
currentBinData.low = value.at(i);
}
}
return data;
}
保存应用程序设置(QSettings)
1. QSettings 类
QSettings 提供保存应用程序当前设置的接口,可以方便地保存程序的状态,例如窗口大小和位置,选项的选中状态等等。
在 Windows 系统中,程序程序的状态信息记录在注册表中;在 Mac OS X 系统上,这些信息记录在 XML 配置文件中;在 Unix 系统中,则使用 INI text 文件记录。QSettings 则是对这些技术的一个抽象,使得保存和取得应用程序的设置状态的只得独立于操作系统。
QSettings 的 API 是基于 QVariant 类,当创建一个 QSettings 对象时,必须传递公司或组织的名称(QString)和应用程序的名称(QString)用于构造一个 QSettings 对象。
2. 使用 QSettings
(1)构造一个 QSettings 对象
QSettings settings("MySoft", "Star Runner") ;
(2)添加一个设置到 settings 中
程序的设置是以“key-value”的形式,保存在 QSettings 对象中的。其中,key 由一个 QString 类型定义,value 是由 QVariant 类型定义:
settings.setValue( "editor/wrapMargin", 68 ) ;
/* wrapMargin 是一个子 key
/* 如果存在相同的 key,那么已存在的 key 所对应的值将由新值代替
(3)从 setttings 中取出设置
同时也可以通过 key 从 settings 中取出值:
int margin = settings.value( "editor/wrapMargin").toInt( ) ;
3. QSettings 的组织方式
(1)用“/”表示子 key
QSettings 存储状态信息的形式是 key-value,其中 key 与文件路径这个概念是类似的,subkey 可以用定义文件路径的形式定义,例如 findDialog/ matchCase,其中 matchCase 就是一个 subkey;
(2)使用 beginGroup( ) 和 endGroup( )
void QSettings : : beginGroup( const QString &prefix ) 的作用是在当前的 group 后面加上 prefix。当前的 group 自动加到一个 QSettings 对象的尾部:
settings.beginGroup("mainwindow") ;
settings.setValue("size", win->size( ) ) ;
settings.setValue("fullScreen", win->isFullScreen( ) ) ;
settings.endGroup( ) ;
settings.beginGroup("outputpanel") ;
settings.setValue("visible", panel->isVisible( ) ) ;
settings.endGroup( ) ;
这样设置后,当前的 settings 对象看上去应该是这样的层次结构:
mainwindow/ size
mainwindow/ fullScreen
outputpanel/ visible
(3)取得 key 与子 key
QStringList QSettings : : childKeys( ) const 函数返回所有顶层 keys,组成一个 QStringList 作为一个返回值。例如:
QSettings settings ;
settings.setValue("fridge/color", Qt::white) ;
settings.setValue("fridge/size", QSize(32, 96) ) ;
settings.setValue("sofa", true) ;
settings.setValue("tv", false) ;
QStringList keys = settings.childKeys( ) ;
那么这个 keys 中看上去应该是这样的:
keys: [ "sofa", "tv" ]
QStringList QSettings : : childGroups ( ) const 是返回所有包含有 key 的顶层 groups,组成一个 QStringList 作为返回值:
QSettings settings ;
settings.setValue("fridge/color",Qt::white);
settings.setValue("fridge/size",QSize(32,96));
settings.setValue("sofa",true);
settings.setValue("tv",false);
QStringList groups = settings.childGroups() ;
则 groups 看上去是:
groups : [ "fridge" ]
4. 保存和取得程序的设置
(1)在主窗口的构造函数中,readSettings( )
void MainWindow::readSettings()
{
QSettings settings("Software Inc.", "Spreadsheet"); // 写入与读取的 settings 要一致
restoreGeometry(settings.value("geometry").toByteArray());
recentFiles = settings.value("recentFiles").toStringList();
updateRecentFileActions();
bool showGrid = settings.value("showGrid", true).toBool();
showGridAction->setChecked(showGrid);
bool autoRecalc = settings.value("autoRecalc", true).toBool();
autoRecalcAction->setChecked(autoRecalc);
}
(2)在关闭主窗口时,writeSettings( )
void MainWindow::writeSettings()
{
QSettings settings("Software Inc.", "Spreadsheet");
settings.setValue("geometry", saveGeometry());
settings.setValue("recentFiles", recentFiles);
settings.setValue("showGrid", showGridAction->isChecked());
settings.setValue("autoRecalc", autoRecalcAction->isChecked());
}
/* 这里选择 4 个状态进行保存
Qt的应用程序在画图的时候,都会调用QWidget::paintEvent()事件函数。一般有两种方法来重回widget:
这种方法其实是将重绘的事件加入到Qt的事件列表中,不一定马上就会重绘得出你想要的效果。在同一时刻你要是多次调用update(),Qt会将它们自动合并为一个update()。这样的好处是不会产生闪烁。
这个方法是立即重绘!但是不建议经常使用,只需要在需要的特效区域调用即可。
无论是update()还是repaint(),都可以指定参数来说明要绘制的区域。
//头文件
#ifndef OVENTIMER_H
#define OVENTIMER_H
#include
#include
#include
class OvenTimer : public QWidget
{
Q_OBJECT
public:
OvenTimer(QWidget *parent = 0);
void setDuration(int secs);
int duration() const;
void draw(QPainter *painter);
signals:
void timeout();
protected:
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *event);
private:
QDateTime finishTime;
QTimer *updateTimer;
QTimer *finishTimer;
};
#endif
//cpp文件
#include
#include
#ifndef M_PI
#define M_PI 3.14159265359
#endif
#include "oventimer.h"
const double DegreesPerMinute = 7.0;
const double DegreesPerSecond = DegreesPerMinute / 60;
const int MaxMinutes = 45;
const int MaxSeconds = MaxMinutes * 60;
const int UpdateInterval = 5;
OvenTimer::OvenTimer(QWidget *parent)
: QWidget(parent)
{
finishTime = QDateTime::currentDateTime();
updateTimer = new QTimer(this);
connect(updateTimer, SIGNAL(timeout()), this, SLOT(update()));
finishTimer = new QTimer(this);
finishTimer->setSingleShot(true);
connect(finishTimer, SIGNAL(timeout()), this, SIGNAL(timeout()));
connect(finishTimer, SIGNAL(timeout()), updateTimer, SLOT(stop()));
QFont font;
font.setPointSize(8);
setFont(font);
}
void OvenTimer::setDuration(int secs)
{
secs = qBound(0, secs, MaxSeconds);
finishTime = QDateTime::currentDateTime().addSecs(secs);
if (secs > 0) {
updateTimer->start(UpdateInterval * 1000);
finishTimer->start(secs * 1000);
} else {
updateTimer->stop();
finishTimer->stop();
}
update();
}
int OvenTimer::duration() const
{
int secs = QDateTime::currentDateTime().secsTo(finishTime);
if (secs < 0)
secs = 0;
return secs;
}
void OvenTimer::mousePressEvent(QMouseEvent *event)
{
QPointF point = event->pos() - rect().center();
double theta = std::atan2(-point.x(), -point.y()) * 180.0 / M_PI;
setDuration(duration() + int(theta / DegreesPerSecond));
update();
}
void OvenTimer::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
int side = qMin(width(), height());
painter.setViewport((width() - side) / 2, (height() - side) / 2,
side, side);
painter.setWindow(-50, -50, 100, 100);
draw(&painter);
}
void OvenTimer::draw(QPainter *painter)
{
static const int triangle[3][2] = {
{ -2, -49 }, { +2, -49 }, { 0, -47 }
};
QPen thickPen(palette().foreground(), 1.5);
QPen thinPen(palette().foreground(), 0.5);
QColor niceBlue(150, 150, 200);
painter->setPen(thinPen);
painter->setBrush(palette().foreground());
painter->drawPolygon(QPolygon(3, &triangle[0][0]));
QConicalGradient coneGradient(0, 0, -90.0);
coneGradient.setColorAt(0.0, Qt::darkGray);
coneGradient.setColorAt(0.2, niceBlue);
coneGradient.setColorAt(0.5, Qt::white);
coneGradient.setColorAt(1.0, Qt::darkGray);
painter->setBrush(coneGradient);
painter->drawEllipse(-46, -46, 92, 92);
QRadialGradient haloGradient(0, 0, 20, 0, 0);
haloGradient.setColorAt(0.0, Qt::lightGray);
haloGradient.setColorAt(0.8, Qt::darkGray);
haloGradient.setColorAt(0.9, Qt::white);
haloGradient.setColorAt(1.0, Qt::black);
painter->setPen(Qt::NoPen);
painter->setBrush(haloGradient);
painter->drawEllipse(-20, -20, 40, 40);
QLinearGradient knobGradient(-7, -25, 7, -25);
knobGradient.setColorAt(0.0, Qt::black);
knobGradient.setColorAt(0.2, niceBlue);
knobGradient.setColorAt(0.3, Qt::lightGray);
knobGradient.setColorAt(0.8, Qt::white);
knobGradient.setColorAt(1.0, Qt::black);
painter->rotate(duration() * DegreesPerSecond);
painter->setBrush(knobGradient);
painter->setPen(thinPen);
painter->drawRoundRect(-7, -25, 14, 50, 99, 49);
for (int i = 0; i <= MaxMinutes; ++i) {
if (i % 5 == 0) {
painter->setPen(thickPen);
painter->drawLine(0, -41, 0, -44);
painter->drawText(-15, -41, 30, 30,
Qt::AlignHCenter | Qt::AlignTop,
QString::number(i));
} else {
painter->setPen(thinPen);
painter->drawLine(0, -42, 0, -44);
}
painter->rotate(-DegreesPerMinute);
}
}
tDateTime().addSecs(secs);
if (secs > 0) {
updateTimer->start(UpdateInterval * 1000);
finishTimer->start(secs * 1000);
} else {
updateTimer->stop();
finishTimer->stop();
}
update();
}
int OvenTimer::duration() const
{
int secs = QDateTime::currentDateTime().secsTo(finishTime);
if (secs < 0)
secs = 0;
return secs;
}
void OvenTimer::mousePressEvent(QMouseEvent *event)
{
QPointF point = event->pos() - rect().center();
double theta = std::atan2(-point.x(), -point.y()) * 180.0 / M_PI;
setDuration(duration() + int(theta / DegreesPerSecond));
update();
}
void OvenTimer::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
int side = qMin(width(), height());
painter.setViewport((width() - side) / 2, (height() - side) / 2,
side, side);
painter.setWindow(-50, -50, 100, 100);
draw(&painter);
}
void OvenTimer::draw(QPainter *painter)
{
static const int triangle[3][2] = {
{ -2, -49 }, { +2, -49 }, { 0, -47 }
};
QPen thickPen(palette().foreground(), 1.5);
QPen thinPen(palette().foreground(), 0.5);
QColor niceBlue(150, 150, 200);
painter->setPen(thinPen);
painter->setBrush(palette().foreground());
painter->drawPolygon(QPolygon(3, &triangle[0][0]));
QConicalGradient coneGradient(0, 0, -90.0);
coneGradient.setColorAt(0.0, Qt::darkGray);
coneGradient.setColorAt(0.2, niceBlue);
coneGradient.setColorAt(0.5, Qt::white);
coneGradient.setColorAt(1.0, Qt::darkGray);
painter->setBrush(coneGradient);
painter->drawEllipse(-46, -46, 92, 92);
QRadialGradient haloGradient(0, 0, 20, 0, 0);
haloGradient.setColorAt(0.0, Qt::lightGray);
haloGradient.setColorAt(0.8, Qt::darkGray);
haloGradient.setColorAt(0.9, Qt::white);
haloGradient.setColorAt(1.0, Qt::black);
painter->setPen(Qt::NoPen);
painter->setBrush(haloGradient);
painter->drawEllipse(-20, -20, 40, 40);
QLinearGradient knobGradient(-7, -25, 7, -25);
knobGradient.setColorAt(0.0, Qt::black);
knobGradient.setColorAt(0.2, niceBlue);
knobGradient.setColorAt(0.3, Qt::lightGray);
knobGradient.setColorAt(0.8, Qt::white);
knobGradient.setColorAt(1.0, Qt::black);
painter->rotate(duration() * DegreesPerSecond);
painter->setBrush(knobGradient);
painter->setPen(thinPen);
painter->drawRoundRect(-7, -25, 14, 50, 99, 49);
for (int i = 0; i <= MaxMinutes; ++i) {
if (i % 5 == 0) {
painter->setPen(thickPen);
painter->drawLine(0, -41, 0, -44);
painter->drawText(-15, -41, 30, 30,
Qt::AlignHCenter | Qt::AlignTop,
QString::number(i));
} else {
painter->setPen(thinPen);
painter->drawLine(0, -42, 0, -44);
}
painter->rotate(-DegreesPerMinute);
}
}