赞
踩
32qt中的文件操作+
qt中io操作的处理方式:(类似linux思想外部设备:一切皆文件)
qt通过统一的接口简化了文件与外部设备的操作方式,qt中的文件被看做一种特殊的外部设备,qt中的文件操作与外部设备的操作相同。 统一的IO操作方式
io操作中的关键函数接口:
打开设备:bool open(OpenMode mode)
读取数据:QByteArray read(qint64 maxSize)
写入数据:qint64 write(const QByteArray& byteArray)
关闭设备:void close()
io操作的本质:连续存储空间的数据读写。 打开读写关闭
Qt中IO设备的类型:
顺序存取设备:只能从头开始顺序的读写数据,不能指定数据读写位置。比如串口,串口也相当于io操作
随机存取设备:可以定位到任意的位置进行数据的读写(seek function)。
QBuffer指的是内存里边一片连续的空间,我们可以将内存空间也作为一种io设备来处理,直接对内存空间进行读写操作,
QAbstractSocket 网络编程,
QProcess指的是进程间通信的多进程编程。
QFile是qt中用于文件操作的类,QFile对象对应到计算机上的一个文件。QFile对象其实代表的是硬盘上的一个文件。
QFile file("c:/Users/hp/Desktop/test.txt"); //QFile对象对应硬盘上的那个文件
if( file.open ( QIODevice::WriteOnly | QIODevice:: Text )) { //以只写的方式打开,写入的内容是文本
file.write("w.s.software"); //写内容
file.close(); //关闭
}
if(file.open(QIODevice::ReadOnly | QIODevice::Text) ){ //以只读的方式打开,读取的是文本
QByteArray ba=file.readAll(); //将全部的文件内容读出来放到数组里边去。ba就是文件的所有内容,ba保存的就是文件中的每个字节
QString s(ba); //创建了一个字符串对象,用上边的字节数组初始化这个字符串对象,将字节数组里边的内容转换为字符串,并且将字符串输出到调试窗口中
qDebug()<<s;
file.close(); //关闭文件
}
QFileInfo类用于读取文件属性信息。
QFile file( "C:/Users/hp/Desktop/test.txt" );
QFileInfo info(file);
qDebug() <<info.exists();
qDebug() <<info.isFile();
qDebug() <<info.isReadable();
qDebug() <<info.isWritable();
qDebug() <<info.created(); 什么时候创建的
qDebug() <<info.lastRead(); 最后访问时间
qDebug() <<info.lastModified(); 最后修改时间
qDebug() <<info.path(); 文件路径
qDebug() <<info.fileName(); 文件名
qDebug()<<info.suffix(); 文件后缀
qDebug() <<info.size(); 文件大小
为什么 \n 打印不成换行啊?
qt中提供了临时文件操作类QTemporaryFile:
安全的创建一个全局唯一的临时文件,当对象销毁时对应的临时文件将被删除,临时文件的打开方式为QIODevice::ReadWrite
临时文件常用于大数据传递或者进程间通信的场合。
小结:qt通过统一的方式读写文件和外部设备,Qt中IO设备的类型分为顺序存取和随机存取两种。QFile提供了文件操作相关的方法,QFileInfo提供了读取文件属性相关的方法。Qt中提供了临时文件操作类QTemporaryFile。
33、文本流和数据流
辅助类:方便io设备的存取工作。
qt中文件类型分为2大类:
文本文件:内容是可读的文本字符。
数据文件:文本内容是直接的二进制数据。
QFile直接支持文本文件和数据文件的读写:
qint64 read(char* data,qint64 maxSize)
QByteArray read(qint64 maxSize)
qint64 write(const char* data,qint64 maxSize)
qint64 write(const QByteArray& byteArray)
思考:
如何将一个浮点数据写入文本文件和数据文件?
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QFile file("C:/Users/Bieber/Desktop/text.hex");
if( file.open(QIODevice::WriteOnly))
{
QString dt="D.t.software";
double value=3.14;
file.write(dt.toStdString().c_str());//转换为标准字符串类型,然后转换为直接二进制数据
file.write(reinterpret_cast<char*>(&value),sizeof(value));
file.close();
}
if(file.open(QIODevice::ReadOnly))
{
QString dt="";
double value=0;
dt=QString(file.read(12)); //先读取前12个字节 返回QByteArray
value=file.read(reinterpret_cast<char*>(&value),sizeof(value)); //读取数据3.14
file.close();
qDebug()<<dt;
qDebug()<<value;
}
return a.exec();
}
qt提供辅助类简化了文本文件/数据文件的读写:
QTextStream:
写入的数据根据类型转换为可读文本。
QDataStream:
写入的数据根据类型转换为二进制数据。
IO设备辅助类的使用方式:
//1、创建QFile文件对象file。
//2、使用file对象打开文件。
//3、将数据写入文件。 Q....Stream out(&file); out<<QString("d.t.slretest"); out<<QString("resule: ") <<3.14;
//4. 将数据从文件中读出。Q....Stream in(&file); in>>dt;; //QString dt;in>>result; //QString result;
in>>value; //double value
#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QDataStream>
#include <QDebug>
void text_stream_test(QString f)
{
QFile file(f);
if(file.open(QIODevice::WriteOnly|QIODevice::Text))
{
QTextStream out(&file);
out<<QString("t.stlsdtl")<<endl;
out<<QString("result: ")<<endl;
out<<5<<'*'<<6<<'='<<5*6<<endl;
file.close();
}
if(file.open(QIODevice::ReadOnly| QIODevice::Text))
{
QTextStream in(&file);
while(!in.atEnd()) //以行为单位来读入
{
QString line=in.readLine();
qDebug()<<line;
}
file.close();
}
}
void data_stream_data(QString f)
{
QFile file(f);
if(file.open(QIODevice::WriteOnly))
{
QDataStream out(&file);
out<<QString("t.stlsdtl");
out<<QString("result: ");
out<<3.14;
file.close();
}
if(file.open(QIODevice::ReadOnly))
{
QDataStream in(&file);
QString dt="";
QString result="";
double value=0;
in>>dt; //传输三个变量
in>>result;
in>>value;
file.close();
qDebug()<<dt;
qDebug()<<result;
qDebug()<<value;
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
text_stream_test("C:/Users/Bieber/Desktop/text.txt");
data_stream_data("C:/Users/Bieber/Desktop/text.dat");
return a.exec();
}
不同的qt版本的数据流文件格式可能不同:qdatastream
void setVersion(int v) //设置读写版本号
int version() const //获取读写版本号
当数据流文件可能在不同版本的qt程序间传递数据时,需要考虑版本问题。可以提高程序兼容性。
小结:
Qt中的文件辅助类用于方便读写操作。
QTextStream用于文本数据的快速读写。
QDataStream用于二进制数据的快速读写。
QDataStream的文件格式与Qt版本相关。
数据格式文件在程序间传递时,需要考虑版本问题。
34、缓冲区和目录
Qt中缓冲区的概念:
缓冲区的本质为一段连续的存储空间,分为内部缓冲区(连续的内存空间)和外部缓冲区。
QBuffer是qt中缓冲区相关的类,在qt中可以将缓冲区看做一种特殊的io设备。
文件流辅助类可以直接用于操作缓冲区。
QBuffer缓冲区的使用方式:
QByteArray array; //连续的内存空间
QBuffer buffer(&array); //通过初始化对象将缓冲区对象和内存空间关联在一起
if(buffer.open(QIODevice::WriteOnly))
{ QDataStream out(&buffer); //辅助类,操作的是buffer这个io对象,就是缓冲区
out<<QString("CD.DGEKG");
out<<3.14;
buffer.close();
}
QBuffer缓冲区的使用场合:
1、在线程间进行不同类型的数据传递。将不同的数据类型转换为同一个对象。
2、缓冲外部设备中的数据返回。
3、数据读取速度小于数据写入速度。
#include <QCoreApplication>
#include <QBuffer>
#include <QByteArray>
#include <QDataStream>
#include <QDebug>
void write_buffer(int type,QBuffer& buffer)
{
if(buffer.open(QIODevice::WriteOnly ))
{
QDataStream out(&buffer);
out<<type;
if(type == 0)
{
out<<QString("w.s.wngd");
out<<QString("3.149526");
}
else if(type==1)
{
out<<3;
out<<1413;
}
else if(type==2)
{
out<<3.1415;
}
buffer.close();
}
}
void read_buffer(QBuffer& buffer)
{
if(buffer.open(QIODevice::ReadOnly ))
{
int type=-1;
QDataStream in(&buffer);
in>>type;
if(type==0)
{
QString dt="";
QString pi="";
in >>dt;
in >>pi;
qDebug()<<dt;
qDebug()<<pi;
}
else if(type==1)
{
int a=0;
int b=0;
in>>a;
in>>b;
qDebug()<<a;
qDebug()<<b;
}
else if(type==2)
{
double pi=0;
in>>pi;
qDebug()<<pi;
}
buffer.close();
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QByteArray array;
QBuffer buffer(&array);
write_buffer(2,buffer);
read_buffer(buffer);
return a.exec();
}
QDir是qt中功能强大的目录操作类:
qt中的目录分隔符统一使用‘/’
QDir能够对目录进行任意操作(创建,删除,重命名)。
QDir能够获取指定目录中的所有条目(文件和文件夹)。
QDir能够使用过滤字符串获取指定条目。
QDir能够获取系统中的所有根目录。
目录操作基础示例:
const char* PATH="C:/Users/hp/DeskTop/QDir";
QDir dir;
if( ! dir.exists(PATH) )
{ dir.mkdir(PATH); }
if( dir.exists(PATH) )
{ dir.cd(PATH);
QStringList list =dir.entryList(); //得到当前文件夹下的所有条目,包含文件及目录
for(int i=0;i<list.count(); i++)
{ /* Dir Operation */ }
}
计算文件大小:
#include <QCoreApplication>
#include <QDir>
#include <QDebug>
#include <QFileInfo>
#include <QFileInfoList>
void test_dir()
{
const char* PATH="C:/Users/Bieber/DeskTop/QDir";
QDir dir;
if( ! dir.exists(PATH) )
{
dir.mkdir(PATH);
}
if( dir.exists(PATH) )
{
dir.cd(PATH);
QStringList list =dir.entryList(); //得到当前文件夹下的所有条目,包含文件及目录
for(int i=0;i<list.count(); i++)
{
qDebug()<<list[i];
}
}
}
//计算文件大小
unsigned int calculate_size(QString path)
{
QFileInfo info(path);
unsigned int ret=0;
if(info.isFile())
{
ret=info.size();
}
else if(info.isDir())
{
QDir dir(path); //生成一个文件夹,代表当前目录
// QStringList list =dir.entryList(); //得到当前文件夹下的所有条目,包含文件及目录
QFileInfoList list=dir.entryInfoList();//得到fileinfo
for(int i=0;i<list.count(); i++)
{
if((list[i].fileName() !=".")&& (list[i].fileName() != ".."))//如果文件名不为.和..那么就计算
{
qDebug()<<list[i].absoluteFilePath();
ret +=calculate_size(list[i].absoluteFilePath()); //得到文件名字调用函数是不可以的,因为参数要求是绝对路径
}
}
}
return ret;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//test_dir();
qDebug()<<calculate_size("C:/Users/Bieber/DeskTop/QDir");
return a.exec();
}
QFileSystemWatcher用于监控文件和目录的状态变化:
能够监控特定目录和文件的状态,能够同时对多个目录和文件进行监控,当目录或者文件发生改变时将触发信号,可以通过信号与槽的机制捕捉信号并作出响应。 内容 重命名,删除
文件监控示例:
//1.定义槽函数,监控状态变化
void Watcher::statusChanged(const QString& path)
{ // do something}
// 2,连接状态信号到槽函数
Watcher::Watcher(QObject* parent) :QObject(parent)
{ connect(&m_watcher,SIGNAL(fileChanged(const QString&)),...);
connect(&m_watcher,SIGNAL(directoryChanged(const QString&)),...);
}
// 3.加入受监控的文件或者目录
void Watcher::addPath(QString path)
{ m_watcher.addPath(path);
}
#include "Watcher.h"
#include <QDebug>
Watcher::Watcher(QObject *parent) : QObject(parent)
{
connect(&m_watcher,SIGNAL(fileChanged(const QString&)),this,SLOT(statusChanged(const QString&)));
connect(&m_watcher,SIGNAL(directoryChanged(const QString&)),this,SLOT(statusChanged(const QString&)));
}
void Watcher::statusChanged(const QString &path)
{
qDebug()<<path<<"is changed!";
}
void Watcher::addPath(QString path)
{
m_watcher.addPath(path);
}
小结:
缓冲区的本质为一段连续的内存空间。
在Qt中可以将缓冲区看作一种特殊的IO设备。
QDir提供了强大的目录操作支持。
QFireSyetemWatcher能够监控文件和目录的状态变化。
35、文本编辑器中数据存取
QAction被点击之后会产生一个trggered信号
通过信号与槽的机制能够捕捉对QAction对象的操作。
项目中可以将多个信号映射到同一个槽函数。
connect(action,SIGNAL(triggered()),this,SLOT(slot_function()));
小结:
qt项目中尽量将界面代码与功能代码分离开。
qt项目开发尽量复用平台中提供的相关组件。
qt项目中的多数情况都是编写相应的槽函数:用于相应用户操作,具体功能的触发点。
36、文本编辑器中的功能交互
如何判读未保存的数据?
QPlainTextEdit能够触发与编辑操作相关的信号。
viod textChanged()//字符发生变化时触发
void copyAvailable(bool) //可以复制的
void cursorPositionChanged() //光标变化触发
void redoAvailable(bool) //撤销操作触发
void undoAvailable(bool) //重新操作触发
解决方案:
1、定义槽函数void onTextChanged()。
2、映射textChanged()到槽函数。
3、定义成员变量bool m_isTextChanged=false
4、文本框中的字符发生变化时:m_isTextChanged=true;
5、当m_isTextChanged为真,则存在未保存的数据。
小结:
文本编辑组件能够触发与编辑操作相关的信号。
textChanged()信号能够用于检测数据变化。
文本编辑器项目中需要设置状态变量。
功能间的交互通过状态变量完成。
37、数据结构类QMap与QHash(哈希)
QMap是一个以升序键顺序存储键值对的数据结构。非线性:
QMap原型为class QMap<k,T>模板. k是键,T是值
QMap中的键值对根据Key进行了排序。
QMap中的Key类型必须重载operator<.
QMap使用示例一:
QMap<QString, int> map;
map.insert(“key 2”, 2);
map.insert("key 0",0);
map.insert(key 1",1);
for(int i=0;i<3;i++)
{ qDebug()<<map.value("key" + QString::number(i)) };
QList<QString> list=map.keys();
for(int i=0;i<list.count();i++)
{ qDebug()<<list[i] };
QMap使用示例二
QMap<QString,int> map;
map["key 2"]=2
map["key 0"]=0;
map["key 1"]=1;
for(int i=0;i<3;i++)
{ qDebug()<<map["key "+QString::number(i)] ; }
QMapIterator<QString,int> it(map); //迭代器,将指向map对象的每一个元素,开始指向第一个元素之前的元素
while(it.hasNext() ) //有下一个元素
{ it.next(); //指向第一个元素
qDebug()<<it.key()<<":"<<it.value(); //得到键值对
}
QMap的注意事项
通过Key获取Value时:
当Key存在:返回对应的Value。
当Key不存在:返回值类型所对应的“零”值。
插入键值对时:
当Key存在:更新Value的值。
当Key不存在:插入新的键值对。
#include <QCoreApplication>
#include <QMap>
#include <QDebug>
#include <QMapIterator>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QMap<QString,int> map;
map.insert("key 2",2);
map.insert("key 1",1);
map.insert("key 0",0);
QList<QString> klist=map.keys(); //自动排序
for(int i=0;i<klist.count();i++)
{
qDebug()<<klist[i];
}
//得到所有的值
QList<int> vlist=map.values();
for(int i=0;i<vlist.count();i++)//自动排序
{
qDebug()<<vlist[i];
}
QMapIterator<QString,int> it(map); //初始化,it指向第一个元素之前的位置
while(it.hasNext())
{
it.next();
qDebug()<<it.key()<<":"<<it.value();
}
return a.exec();
}
QHash是qt中的哈希数据结构:哈希表 类模板,存储键值对
QHash原型为class QHash<K,T>模板
QHash中的键值对在内部无序排列。
QHash中的Key类型必须重载operator==.:
QHash中的Key对象必须重载全局哈希函数qHash().:每个对象调用这个函数得到一个不同的id。查找效率最快
QHash使用示例:
QHash<QString,int> hash;
hash["key 2"] =2;
hash["key 0"]=0;
hash["key 1"]=1;
QHash<QString,int>::const_iterator i;
for(i=hash.constBegin(); i !=hash.constEnd(); ++i ) //从第一个数据到最后一个数据
{ qDebug()<<i.key()<<":" <<i.value(); }
#include <QCoreApplication>
#include <QHash>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QHash<QString,int> hash;
hash.insert("key 2",2);
hash.insert("key 1",1);
hash.insert("key 0",0);
QList<QString> hlist=hash.keys();
for(int i=0;i<hlist.count();i++)
{
qDebug()<<hlist[i];
}
QList<int> vlist=hash.values();
for(int i=0;i<vlist.count();i++)
{
qDebug()<<vlist[i];
}
hash["key 4"]=4;
QHash<QString,int>::const_iterator i;
for(i=hash.constBegin();i !=hash.constEnd(); ++i)
{
qDebug()<<i.key()<<":"<<i.value();
}
return a.exec();
}
QMap和QHash的接口相同,可直接替换使用。
1、QHash的查找速度明显快于QMap,QHash占用的存储空间明显多于QMap。
2、QHash以任意的方式存储元素,QMap以Key顺序存储元素。
3、QHash的键类型必须提供operator==()和qHash(key)函数。QMap的键类型必须提供operator<()函数。
QMap以二分查找的方式来查找定位元素,查找时间费时多。
文本编辑器用QMap加后缀,感觉没什么用
小结:
Qt中提供了用于存储键值对的类模板。
QHash和QMap遵循相同的使用接口。
QHash的查找速度快于QMap。
QMap需要的内存空间低于QHash。
QHash对于Key类型的要求高于QMap。
38、qt中的事件处理
图形界面应用程序的消息处理模型。
用户操作->系统内核 系统消息->消息处理函数
思考:操作系统发送的消息如何转换成qt信号?
qt平台将系统产生的消息转换成qt事件:
qt事件是一个QEvent的对象,qt事件用于描述程序内部或外部发生的动作,任意的QObject对象都具备事件处理的能力。
输入事件,拖拽事件,绘制事件,关闭事件,计时器事件:代表操作系统消息
GUI应用程序的事件处理方式:当qt gui程序被用户操作的时候,就会产生系统消息,系统消息被发送到qt应用程序上去,应用程序接收到消息后就会产生qt事件。
1、qt事件产生后立即被分发到QWidget对象
2、QWidget中的event(QEvent*)进行事件处理:事件处理入口
3、event()根据事件类型调用不同的事件处理函数。
4、在事件处理函数中发送qt中预定义的信号。
5、调用信号关联的槽函数。
点击按钮,操作系统发送系统消息到应用程序,qt内部转换为QEvent,qt事件分发到用户点击的按钮上去,按钮对象接收到事件后调用event函数,这个函数内部又会调用click子函数,子函数发送qt信号到其它qt对象上去,槽函数被调用。
QPushButton事件处理分析:
1、接收到鼠标事件
2、调用event(QEnent*)成员函数
3、调用mouseReleaseEvent(QMouseEvent*)成员函数
4、调用click()成员函数
5、触发信号SIGNAL(clicked())
事件(QEvent)和信号(SIGNAL)不同:事件是用来描述操作系统所发出来的消息的。
事件由具体对象进行处理,信号由具体对象(在事件处理函数被调用的时候产生的)主动产生,可以重写事件处理函数不产生信号。改写事件处理函数可能导致程序行为发生改变,信号是否存在对应的槽函数不会改变程序行为,一般而言,信号在具体的事件处理函数中产生。
小结:
Qt中的事件(QEvent)和信号(SIGNAL)不同。事件用来映射操作系统发送过来的消息的,每一个系统发送过来的消息都会被映射成一个qt中的事件对象。事件对象需要被处理,处理时就需要发送qt信号了。事件被发送出来需要被对象处理,而信号是被对象发送出去的。
事件由QObject的对象进行处理。
信号由QObject对象触发。
重写事件处理函数可能改变程序行为。
信号的触发不会对程序行为造成影响。
事件处理是在实际工程开发中的应用非常普遍。工程中首选映射信号到槽函数,而不是重写事件处理函数。
39、事件处理(下)
操作系统检测到用户动作时,产生一条系统消息,然后系统消息被发送到用户使用的qt应用程序中去,然后qt应用程序把系统消息翻译成一个对应的qt事件对象,并且将qt事件对象分发到当前用户操作的窗口部件上去,窗口部件是一个QWidget子类对象,QWidget子类对象收到事件后,调用event事件处理函数,event函数又会调用子函数来进行事件的具体处理,当窗口部件处理完事件后,就会顺着箭头将当前事件传送到他的父组件上面去,但是传到父组件也不是绝对的。有些不会传递。
QEvent中的关键成员函数:QEvent类是所有事件类的父类。
void ignore(); 接收者忽略当前事件,事件可能传递给父组件。
void accept(); 接收者期望处理当前事件。把当前事件处理好,自己处理事件
bool isAccepted(); 判断当前事件是否被处理。
qt中的事件过滤器:事件过滤器可以对其他组件接收到的事件进行监控,任意的QObject对象都可以作为事件过滤器使用,事件过滤器对象需要重写eventFilter()函数。
组件通过installEventFilter()函数安装事件过滤器:事件过滤器在组件之前接收到事件,能够决定是否将事件转发到组件对象。
应用程序对象-QEvent->过滤器对象-QEvevt->组件对象
事件过滤器的典型实现:
// 返回true表示事件已经处理,无需传递给obj
// 返回false则正常传递到obj
bool Widget::eventFilter(QObject* obj,QEvent* e)
{ if(/* 根据obj判断对象 */) {
if(/* 根据e->type()判断事件*/)} { /*事件处理逻辑*/}
}
/*调用父类中的同名函数*/
return QWidget ::eventFilter(obj,e);
bool Widget::eventFilter(QObject* obj,QEvent * e)
{
bool ret=true;
if((obj==&myLineEdit)&&(e->type()==QEvent::KeyPress)) //如果是文本框和按键事件
{
qDebug()<<"Widget::eventFilter";
QKeyEvent* evt=dynamic_cast<QKeyEvent*>(e);
switch(evt->key()) //只能接收到数字
{
case Qt::Key_0:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
ret=false;
break;
default:
break;
}
}
else
{
ret=QWidget::eventFilter(obj,e);
}
return ret;
}
小结:
Qt应用程序有严格的事件处理顺序
Qt事件在处理后可能传递给父组件对象。
可以通过installEventFilter()函数安装事件过滤器。
事件过滤器可以对其他组件接收到的事件进行监控。
事件过滤器能够决定是否将事件转发到组件对象。
40、拖放事件深度剖析
拖放一个文件进入窗口时将触发拖放事件,每一个QWidget对象都能够处理拖放事件,拖放事件的处理函数为:
void dragEnterEvent(QDragEnterEvent* e);
void dropEvent(QDropEvent* e);
拖放事件中的QMimeData:
QMimeData是qt中的多媒体数据类,拖放事件通过QMimeData对象传递数据,QMimeData支持多种不同类型的多媒体数据。
自定义拖放事件的步骤:
1、对接收拖放事件的对象调用setAcceptDrops成员函数。
2、重写dragEnterEvent函数并判断MIME类型。 期望数据:e->acceptProposedaAction();其它数据:e->ignore();
3、重写dropEvent函数并判断MIME类型。期望数据:熊从事件对象中获取MIME数据并处理,其它数据:e->ignore();
#include "Widget.h"
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QDebug>
#include <QList>
#include <QUrl>
#include <QMimeData> //老师的没有这个
Widget::Widget(QWidget *parent) : QWidget(parent)
{
setAcceptDrops(true);
}
void Widget::dragEnterEvent(QDragEnterEvent* e)
{
if( e->mimeData()->hasUrls() )
{
e->acceptProposedAction();
}
else
{
e->ignore();
}
}
void Widget::dropEvent(QDropEvent* e)
{
if( e->mimeData()->hasUrls())
{
QList<QUrl> list = e->mimeData()->urls();
for(int i=0; i<list.count(); i++)
{
qDebug() << list[i].toLocalFile();
}
}
else
{
e->ignore();
}
}
//得到路径
文本编辑器中拖放操作:
解决方案:
1、调用主窗口对象的setAcceptDrops成员函数,使主窗口支持拖放操作。
2、重写dragEnterEvent函数并判断MIME类型
3、重写dropEvent函数判断MIME类型后打开文件。
小结:
QWidget对象都能够处拖放事件。
41、编辑交互功能实现
常规编辑交互功能:
复制copy,粘贴paste,剪切cut,撤销undo,重做redo,删除delete
QPlainTextEdit提供了丰富的交互功能接口:
左边公有槽函数,右边编写槽函数接受的信号。
将mainwidow action的信号连接到qplaintextedit的槽函数上面去,也就是说将action对象的copy信号连接到文本框对象的copy槽函数上面去就可以了。点击copy action就可以将选择的文本拷贝到系统的剪贴板上面去。不用编写槽函数。
文本编辑器中的界面状态
不是任何时候都能够进行:复制,粘贴,撤销,重做。
如何维护文本编辑器的界面状态?
QPlainTextEdit能够发送与界面相关的信号:
void copyAvailable(bool yes)
void copyAvailable(bool available)
void undoAvailable(bool available)
void cursorPositionChanged()
实现步骤:
1、连接界面状态信号到自定义槽函数。
2、通过文本信息找到对应的QAction对象。比如通过copy字符串找到与copy相关的action对象。
3、根据信号标志设置QAction对象的界面状态。
有一个bug,如果在这里QMenu* menu = new QMenu("Edit(&E)",mb);,没有指定父组件mb的话,在后边设置状态的时候,就找不到菜单栏的孩子,也就完成不了通过字符串找对象的操作,如果通过字符串Copy找对应的图标或者菜单栏中的复制时,就会导致找不到而出现一个空指针,因为这个空指针,程序出错了。
没有dynamic_cast,这个实现就要非常复杂了。
小结:
QPlainTextEdit封装了常用的文本编辑器动能。
可以将信号直接连接到QPlainTextEdit的公有槽函数。
界面状态是GUI开发的重点和难点。
Qt中组件的状态信号能够简化界面状态的维护。
主窗口中的组件可以通过遍历的方式找回。
42、文本打印与光标定位
QPlainTextEdit内部的文档结构
QPlainTextEdit通过QTextDocument对象存储文本数据。
QPlainTextEdit本身只负责界面形态的显示。
遵循mvc思想,界面和数据分离的思想。
QTextCursor:光标行为。里边有个指针指向光标对象。
QTextDocument是表示文本以及文本属性的数据类:
设置文本的属性:排版,字体,标题,等
获取文本参数:行数,文本宽度,文本信息,等
实现标准操作:撤销,重做,查找,打印,等
打印功能的实现步骤:
1、连接QAction打印对象的信号到槽函数。
2、在槽函数中定义QPrintDialog对象。
3、根据用户选择获取QPrienter对象。
4、通过QTextDocument对象进行打印。
问题:
如何计算编辑框中光标的位置?
思路:
文本框对象的内部包含了QTextCursor对象
通过position()成员函数获取当前光标的字符位置。
根据光标的字符位置计算横纵坐标。
当光标位置发生变化时进行计算。
算法流程描述:
思想:
1、通过 ‘ \n ' 字符的个数计算所在行。
2、通过最后一个 ' \n ' 字符的下边计算所在列。
小结:
QPlainTextEdit将数据和界面分开设计。
QTextDocument用于存储数据信息。
QTextCursor用于提供光标相关的信息。
可以通过光标的字符位置信息计算坐标。
43、发送自定义事件上
发送预定义事件。事件是qt平台描述用户操作的对象。
Qt中可以在程序中自主发送事件:
阻塞型事件发送:事件发送后需要等待事件处理完成。
非阻塞型事件发送:事件发送后立即返回,事件被发送到事件队列中等待处理。
QApplication类提供了支持事件发送的静态成员函数:
阻塞型发送函数:
bool sendEvent(QObject* receiver,QEvent* event);
非阻塞型发送函数:
void postEvent(QObject* receiver,QEvent* event);
注意事项:
sendEvent中事件对象的生命期由Qt程序管理:同时支持栈事件对象和堆事件对象的发送。
postEvent中事件对象的生命期由Qt平台管理:只能发送堆事件对象。事件被处理后由Qt平台销毁。
先发到事件队列。
#include "Widget.h"
#include <QMouseEvent>
#include <QApplication>
#include <QDebug>
Widget::Widget(QWidget *parent): QWidget(parent)
{
m_pushButton.setParent(this);
m_pushButton.setText("Test");
connect(&m_pushButton,SIGNAL(clicked()),this,SLOT(onButtonClicked()));
}
void Widget::onButtonClicked()
{
testpostEvent();
//testSendEvent();
}
void Widget::testSendEvent()
{
QMouseEvent dbcEvt(QEvent::MouseButtonDblClick,QPoint(0,0),Qt::LeftButton,Qt::NoButton,Qt::NoModifier);//定义事件对象,2双击的坐标。鼠标左键,没有按键盘
qDebug()<<"before sendEvent()";
QApplication::sendEvent(this,&dbcEvt);//接收这个事件对象的Qt对象,给当前Widget对象发送鼠标双击事件
qDebug()<<"after sendevent()";
}
void Widget::testpostEvent()
{
QMouseEvent* dbcEvt=new QMouseEvent(QEvent::MouseButtonDblClick,QPoint(0,0),Qt::LeftButton,Qt::NoButton,Qt::NoModifier);//定义事件对象,2双击的坐标。鼠标左键,没有按键盘
qDebug()<<"before postEvent()";
QApplication::postEvent(this,dbcEvt);//接收这个事件对象的Qt对象,给当前Widget对象发送鼠标双击事件
qDebug()<<"after postevent()";
}
bool Widget::event(QEvent *evt)
{
if(evt->type() == QEvent::MouseButtonDblClick)
{
qDebug()<<"event();"<<evt;
}
return QWidget::event(evt);//调用父类事件处理函数
}
Widget::~Widget()
{
}
菜单栏中删除功能的实现:
1、定义事件对象KeyPress
2、定义事件对象KeyRelease
3、发送事件KeyPress
4、发送事件KeyRelease
小结:
Qt程序中能够自主的发送系统事件。
QApplication类提供了支持事件发送的成员函数。
sendEvent()发送事件后需要等待事件处理完成。
postEvent()发送事件后立即返回。
44、发送自定义事件下
上一课是系统预定义的,
那么自定义呢?
Qt可以自定义新的事件类:
自定义的事件类必须继承自QEvent。
自定义的事件类必须拥有全局唯一的Type值。id值。
程序中必须提供处理自定义事件对象的方法。
自定义事件类:
1、将QEvent作为父类继承。
2、指定全局唯一的Type值。
class StringEvent:public QEvent
{ public:
static const Type TYPE=static_cast<Type>(QEvent::User+0xFF);//User之后的id值才能用,之前的qt用了
//。。。
}
Qt中事件的Type值:
每个事件类都拥有全局唯一的Type值。id,作为标识
自定义事件类的Type值也需要自定义。
自定义事件类使用QEvent::User之后的值作为Type值。
程序中保证QEvent::User+VALUE(常量) 全局唯一即可。
处理自定义事件对象的方法:
1、将事件过滤器安装到目标对象。
在eventFilter()函数中编写自定义事件的处理逻辑。
2、也可以在目标对象的类中重写事件处理函数:
在event()函数中编写自定义事件的处理逻辑。
我们想要捕获的是双击事件,双击事件为什么不用槽函数? widget这个类没有提供双击这个信号,没有提供信号就没有办法将信号映射到槽,如果想捕捉,就只能重写event成员函数了。
为什么要自定义事件类?
需要扩展一个已有组件类的功能。
需要开发一个全新功能的组件类。
需要向一个第三方的组件类发送信息。
小结:
自定义事件类必须继承自QEvent。
自定义事件类使用QEvent::User之后的值作为Type值。
自定义事件类的Type值必须全局唯一。
程序中需要提供自定义事件类的处理方法。
45、查找对话框
查找对话框是应用程序中的常用部件。
目标:开发一个可以在不同项目间复用的查找对话框。
查找对话框需求分析:
可复用软件部件。查找文本框中的指定字符串。能够指定查找方向。支持大小写敏感查找。
附加需求:点击关闭按钮后隐藏。
查找对话框的架构与设计:
查找对话框得劲界面布局:
小结:查找对话框可以作为一个可复用的软件部件进行开发。
查找对话框继承自QDialog。
查找对话框的界面通过布局管理器相互嵌套完成。
查找对话框的设计与实现时GUI学习中的经典范例。
46、查找对话框功能实现
文本查找功能的核心思想:
1、获取当前光标的位置并作为起始点。
2、向后(向前)查找目标第一次出现的位置。
3、通过目标位置以及目标长度在文本框中进行标记。
QString类中提供了子串查找的相关函数:
indexOf:从指定位置向后查找目标子串的下标位置。
lastIndexOf:从指定位置向前查找目标子串的下标位置。
QString类中查找函数所使用的下标位置:
Qt中的光标信息类QTextCursor:
文本框中的光标是一个QTextCursor对象。
所有与光标相关的信息都通过QTextCursor描述。
如:光标位置,文本选择,等等
keepAnchor就是选择文本。高亮起来。选择1-4的文本,第三部文本框中的字还不会发生变化,因为c只是一个复制品。真正设置到文本框还需要调用文本框的settextCursor类,将当前设置的光标信息设置到文本框对象中,这样我们选择的文本就可以显示出来了。
查找算法流程图:
FindDialog是可复用的,内部没有文本框,所以采用松耦合的设计,FindDialog和QPlainTextEdit是独立存在的,FindDialog内部有一个指针指向外部的QPainTextEdit对象。
小结:
QString中提供了不同的子串查找方式。
QTextCursor对象保存了文本框中光标的相关信息。
QString对象和QTextCurosr对象协作实现查找功能。
查找对话框与文本框的弱耦合关系满足了可复用的需求。弱耦合关系==就是=面向对象中的聚合关系
47、Qt中的调色板
QPalette类包含了组件状态的颜色组。
QPalette对象包含3个状态的颜色描述:
激活颜色组(Active):组件获得焦点使用的颜色搭配方案。
非激活颜色组(Inactive):组件失去焦点使用的颜色方案。
失效颜色组(Disabled):组件处于不可用状态使用的颜色方案。
QPalette中的颜色组定义了组细节的颜色值。
QPalette::ColorRole中的常量用于标识组件细节。
理解Qt中的调色板:
理解:
1、调色板是存储组件颜色信息的数据结构。
2、组件外观所使用的颜色都定于调色板中。
调色版的使用方式:
QPalette p=widget.palette();
p.setColor(QPalette::ACtive,QPalette::WindowText,Qt::blue); 激活态下为蓝色
p.setColor(QPalette::Inactive,QPalette::Window Text,Qt::blue); 失去焦点为蓝色
widget.setPalette(p);将调色版设置到想要改变颜色的窗口组件上。
小结:
QPalette是Qt中标识颜色信息的数据结构。
窗口组件内部都拥有QPalette。
重新设置组件调色板的值能够改变特定区域的颜色。
QPalette对象时定制组件外观的重要角色。
48、替换对话框的设计与实现
替换对话框需求分析:
可复用软件部件。
查找文本框中的指定字符串。
替换单个指定字符串。
替换所有指定字符串。
附加需求:
点击关闭按钮后隐藏。
替换对话框的设计与实现:
替换算法流程图:
MainWindow与ReplaceDialog之间的关系图:
主窗口与其它对话框是整体与局部的关系,是组合关系。
查找,替换对话框与文本编辑框是弱耦合关系,是聚合关系。
小结:
替换对话框的功能涵盖了查找对话框的功能。
替换对话框可以继承自查找对话框。
替换功能的实现是基于查找算法完成的。
替换对话框是一个可复用的软件部件。
49、文本编辑器项目持续开发
开发目标一:指定目标行号并执行跳转动作。
用户需求:
提供输入对话框。
用户可输入目标行号。
确定后光标跳转到指定行。
行间跳转算法设计:
1、通过输入对话框获取目标行号
2、查找换行符的位置计算目标行第一个字符的下标。
3、通过QTextCursor定位到目标行。
开发目标二:
设置工具栏和状态栏的可见性
实现思路:
通过setVisible()设置可见性。
更新界面上QAction对象的状态。
菜单中的QAction对象的状态,工具栏中QAction对象是否按下。
小结:
通过输入对话框获取目标行并实现行间跳转。
根据用户操作控制状态栏和工具栏的可见性。
菜单和工具栏中的QAction对象反映可见性状态。
50、关于对话框(About)
标准的桌面应用程序软件都有一个关于对话框。
关于对话框用于标识软件自身的信息。
软件logo,项目名,版本号。开发者信息,版权信息,联系方式。
经典设计方案:
开发目标:
自定义文本编辑框中的字体和大小。
设置文本编辑框是否自动换行。
打开外部帮助文件。
1实现思路:
通过QFontDialog选择字体以及大小。
将QFont对象设置到文本编辑框。
2实现思路:
获取当前文本编辑框的换行模式。
将模式进行反转并进行设置。
更新对应QAction对象的状态。
3QDesktopServices提供了一系列桌面开发相关的服务接口
通过QDesktopServices中的成员函数打开帮助文件:QDesktopServices::openUrl(QURL("path"))
小结:
关于对话框用于标识软件自身的信息。
使用QFontDialog设置文本编辑框的字体。
设置文本编辑框的自动换行属性。
通过QDesktopServices使用桌面环境的系统服务。
51、程序中的配置文件
应用程序在运行后都有一个初始化的状态。
一般而言:程序的初始状态是最近一次运行退出前的状态。
问题:如何保存和恢复程序状态?
解决思路:
程序退出前保存状态参数到文件(数据库)。
程序再次启动时读出状态参数并恢复。
状态参数的存储方式:
文本文件格式(XML,JSON(不安全,有密码),等)。
轻量级数据库(Access,SQLite,等)
私有二进制文件格式。
Qt中的解决方案:
通过二进制数据流将状态参数直接存储于文件中。
优势:
参数的存储和读取简单高效,易于编码实现。
最终文件为二进制格式,不易被恶意修改。
设计与实现:
小结:
应用程序中在退出时保持程序状态(用户配置)。
应用程序启动时恢复最近一次的程序状态。
可以通过二进制数据流将状态参数直接存储于文件中。
二进制数据流的方式非常的安全,简单,高效。
主窗口的状态参数:
应用程序必须保持和恢复主窗口的状态参数:位置,大小。。。
问题:什么时候保存主窗口的状态数据?
应用程序退出的过程:
1、收到关闭事件。
2、执行关闭事件处理函数。在这里保存状态数据
3、主窗口从屏幕上消失。
4、主窗口的析构函数执行。
5、。。。
一般而言:应用程序收到关闭事件时进行状态参数的保存。
Qt中的解决方案:
1、重写关闭事件处理函数。
2、在关闭事件处理函数中保存状态参数。
每一个应用程序都能够接收命令行参数。
问题:GUI系统中命令行参数如何传递到应用程序?
命令行参数的应用一:
传统应用方式:在命令行启动GUI程序时传递参数。
命令行参数的应用二:
操作系统关联方式:
在文件被双击时,操作系统根据文件后缀选择应用程序。
将文件路径作为命令行参数启动应用程序。notepad test.txt
第一个参数是程序名,第二个参数是绝对路径。
小结:
GUI程序主窗口的相关参数必须保存。
GUI程序在收到关闭事件时保存状态参数。
GUI程序依然能够接收命令行参数。
操作系统将文件的路径作为命令行参数传递。
操作系统就是利用命令行参数将双击的文件路径传给GUI程序,这样双击文件就用GUI程序打开了。
背后发生了命令行参数的传递。
53、应用程序的打包与发布
发布应用程序时的候选者:
调试版(debug):开发阶段生成的可执行程序。
发布版(release):最终产品的可执行程序。
调试板的可执行程序:包含与调试相关的各种信息,体积巨大。执行速度慢,支持断点调试。
发布版的可执行程序:无任何冗余信息,体积小巧。执行速度快,无法映射到源码调试。
可执行程序的正常运行需要外部库的支持:
因此发布程序时必须保证所有的依赖库都存在。
Windows中可以使用Depends工具查看程序的依赖库。
Depends官网地址:
http://www.dependencywalker.com
Linux中可以使用ldd命令查看程序的库依赖:
ldd是Linux系统中一个脚本程序(Shell).
文件路径:/usr/bin/ldd.
linux高手必须学Shell脚本
程序的环境依赖:
应用程序对于执行环境还可能存在依赖关系:
可能的依赖:
环境变量,驱动程序,数据库引擎
java虚拟机,.net Framework
...
问题:如何在客户机器上部署程序的执行环境?
方案一:用户根据提供的使用手册自定部署执行环境。XXXX
方案二:开发一个专用的部署(安装)程序。部署(安装)程序运行后自动部署执行环境。
部署(安装)程序的开发:
通用的软件开发方式:
Visual Studio
Qt SDK+Qt Creater
...
专用的部署开发方式:
InstallShield
Inno Setup
NSIS
.....
InstallShield 简介 贵
商业级应用软件,功能强大,应有尽有。
用于解决Windows软件安装包开发的制作工具。
官网地址:http://www.installshield.com
Inno Setup:
免费应用软件,小巧,简便,精美。
以Pascal语言(object 面向对象)作为脚本开发Windows部署程序。
官网地址:http://www.jrsoftware.org/jsinfo.php
NSIS简介:
开源应用软件,超级量级,无任何冗余功能。
以专用脚本语言开发Windows安装程序。
官网地址:http://nsis.sourceforge.net
Inno Setup免费但没开源,提供了一个IDE,在IDE里边编写代码,然后编译,最后生成一个安装程序。
NSIS超轻量级:没有任何的开发环境提供出来,就只有一个编译工具提供出来,写好程序脚本直接编译,在任何地方编写脚本。需要要学习专业脚本语言。
Linux下Qt程序发布简介:
方法一:
1、通过ldd命令确定程序的库依赖。
2、通过Shell脚本开发部署程序。拷贝出库依赖,通过shell完成环境依赖,在shell脚本中进行描述,一起将shell脚本,库依赖,可执行程序发送非用户,然后用户通过管理员权限执行Shell脚本就可以了。
方法二:根据具体发行版开发专用部署程序(deb格式安装包 , rpm格式安装包)
小结:
程序开发时能够生成debug和release版。
打包时首选要解决程序的库依赖。
程序执行需要特殊环境依赖时必须开发部署程序。
Windows中有丰富的部署程序开发工具可供选择。
Linux中可以采用Shell脚本开发部署程序。
54、Qt中的多页面切换组件QTabWidget
能够在同一个窗口中自由切换不同页面的内容。
是一个容器类型的组件,同时提供友好的页面切换方式。
QTabWidget的使用方式:
在应用程序中创建QTabWidget的对象:
将其他QWidget对象加入该对象中。
小贴士:
1、在QTabWidget对象中加入一个组件将生成一个新的页面。
2、QTabWidget对象每次只能加入一个QWidget对象。
问题:如何将多个组件加入到一个QTabWidget页面中?
解决方案:
1、创建容器类型的组件对象。
2、将多个子组件在容器对象中布局。
3、将容器对象加入QTabWidget中生成新的页面。
QTabWidget组件的高级用法:
设置Tab标签的位置(North,South,West,East)
设置Tab的外观(Rounded,Triangular)
设置Tab的可关闭模式。
QTabWidget组件中预定义的信号:
void currentChanged(int index) 当前显示的页面发生变化,index为新页面下标。第一个页面下标为0
void tabCloseRequested(int index) 位置为index页面的关闭按钮被点击发出关闭请求。
下一步能做什么?
当前的文本编辑器项目只支持单文档操作。
如果推出新版本,可以考虑支持多文档。
QTabWidget组件是实现多文档编辑器的关键。
小结:
Qt平台提供了功能强大的多页面组件。
QTabWidget组件每次只能加入一个组件。
加入多个组件时通过容器组件和布局管理器完成。
QTabWidget能够定制页面标签的外观和位置。
QTabWidget的预定义信号能够实现程序中的高级功能。
55、模型和视图设计模式
模型视图设计模式的核心思想:数据组织与数据呈现分开
模型(数据相关的模块)与视图(显示模块)相分离
模型对外提供标准接口存取数据(不关心数据如何显示)
视图自定义数据的显示方式(不关心数据如何组织存储)
模型视图模式的直观理解:
模型视图模式的工作机制:
当数据发生改变时:模型发出信号通知视图。
当用户与视图进行交互时:视图发出信号提供交互信息。
Qt中的模型类层次结构:
列表方式组织数据--表格
Qt中视图类的层次结构:
关键技术问题:
模型如何为数据提供统一的访问方式?
深入理解:
在Qt中,不管模型以什么结构组织数据,都必须为每一个数据提供第一无二的索引。视图通过索引访问模型中的具体数据。
模型视图编程示例:
QFileSystemModel fsMosel; /* 定义文件系统模型 */ 和目录,文件相关
QTreeView treeView; /*定义树形显示视图*/
QString path=QDir::currentPath(); /* 当前工作目录 */
fsModel.setRootPath(path); /* 从当前工作目录中取数据 */
treeView.setModel(&fsModel); /* 连接模型与视图 */
/*设置树形视图的数据索引 */
/* 从树形视图的根部开始显示工作目录中的内容 */
treeView.setRootIndex(fsModel.index(path)); 设置到视图根部从模型取得当前工作目录的索引
小结:
Qt中内置的支持了模型视图的开发方式。
模型用于组织数据源,不关心数据的显示方式。
视图用于定义数据的显示方式,不关心数据的组织方式、
Qt中的模型必须为每一个数据提供第一无二的索引。
Qt中的视图通过索引访问模型中的数据。
55、模型视图设计中
模型定义标准接口(成员函数)对数据进行访问。
视图通过标准接口获取数据并定义显示方式。
模型使用信号与槽的机制通知视图数据变化。
模型中的数据都是以层次结构表示的。
模型索引是数据与视图分离的重要机制。
模型中的数据使用唯一的索引来访问。
QModelIndex是Qt中的模型索引类:包含具体数据的访问途径,包含一个指向模型的指针。
索引中的行和列:
线性模型可以使用(row,column)作为数据索引:
问题:只用行和列描述数据索引是否足够通用?
思考:如何索引以树形结构组织的数据?
模型中的通用树形结构:
解决方案:
Root为虚拟节点,用于统一所有数据到同一树中。
同一节点的子节点以递增的方式编号。
通过(index,parent)的方式确定节点。
A:(0,Root) B:(1,Root)
C:(2,Root)D:(0,A)
E:(0,C) F:(1,C)
模型中数据索引的通用方式:
三元组:(row,column,parent)
//模型触发信号,视图触发槽函数做出变化
小结:
索引是访问模型中具体数据的约定方式。
获取索引的通用方式为三元组(row,colum,parent)。
索引在需要时由模型实时创建。
使用空索引作为父节点表示顶层数据元素。统一了线性和非线性的表示方式。
特殊的模型可以自定义特殊的索引获取方式。
索引是模型创建时实时创建的。
57、模型视图设计模式下
问题:不同的视图如何显示同一个模型中的数据?
通过(0,0,root)取itemA的数据,那么要显示那个数据呢?三个都显示么?
数据角色的概念:添加辅助属性
模型中的数据在视图中的用途(显示方式)可能不同。
模型必须为数据设置特定数据角色(数据属性)
数据角色用于提示视图数据的作用。
数据角色是不同视图以统一风格显示数据的标准。
Qt中的数据角色定义:
数据角色的意义:
定义了数据在特定系统下的标准用途;不同的视图可以通过相同标准显示数据。
注意:数据角色只是一个附加的属性,这个属性代表推荐的数据显示方式。不同的视图完全可以自由解析或者忽略数据的角色信息。
小结:
模型中的数据有附加的角色属性。
数据角色定义了数据显示的标准方式。
数据角色用于提示视图数据的作用。
视图可以自由解析或者忽略数据的角色信息。
58、自定义模型类上
QStandardItemModel是一个通用的模型类:
能够以任意的方式组织数据(线性,非线性)。
数据组织的基本单位为数据项(QStandardItem)
每一个数据项能够存储多个具体数据(附加数据角色)
每一个数据项能够对数据状态进行控制(可编辑,可选)
因为通用模型类可以包含字符串,数字等,所以返回的数据类型不确定,所以QVariant的类型是可变的。
Qt中的变体类型QVariant:
QVariant是一个用于封装的类型。
QVariant能够表示大多数常见的值类型。
QVariant每次只能封装(保存)单一类型的值。
QVariant的意义在于能够设计“返回类型可变的函数”
变体来下QVariant中的常用成员函数:
bool isNull()
bool isValid()
boid setValue(const T&) 将具体的值存储到变体类型中
Type type() 具体类型
const char* typeName() 名字
T value()
工程中的常用模型设计:
解析数据源中的数据(数据库,网络,串口,等)。
将解析后的数据存入QStandardItem对象中。
根据数据间的关系在QStandardItemModel对象中组织数据项。
选择合适的视图显示数据值。
工程中的常用模型设计:
实例分析:
在文件中以行的形式存储了考试成绩信息(ID,Name,Score)
开发GUI程序显示文件中的信息。
计算平均成绩。查找最好成绩和最差成绩。可刷新显示的内容和删除内容。
系统架构图:
数据层:读取,解析数据。
具体表示,模型-放到具体数据项中,选择视图显示
系统核心类图:
小结:
QStandardItemModel是一个通用的模型类。
QStandardItemModel能够以任意的方式组织数据。
使用QVariant能够设计“返回类型可变的函数”。
工程中常用数据应用架构为4层结构:
数据层,数据表示层,数据组织层,数据显示层。
59、自定义模型类中
DataSource类的设计与实现:
设置数据源并读取数据。
对数据进行解析后生成数据对象。
ScoreInfo类的设计与实现
封装数据源中的一组完整数据。
提供返回具体数据值的接口函数。
ScoreInfoModel类的设计与实现:
使用标准模型类QStandardItemModel作为成员。
以ScoreInfo类对象为最小单位进行数据组织。
交互图:
小结:
工程中的架构图用于定义模块功能。
工程找那个的类图用于定义具体功能的接口。
工程中的流程图用于定义类对象间的交互。
模块实现结束后需要进行单元测试。
60、自定义模型类下
界面设计:
右键上下文菜单的实现:
定义菜单对象(QMenu)
连接菜单中的QAction对象到槽函数。
定义事件过滤器,并处理ContextMenu事件。
在当前鼠标的位置打开菜单对象。
答疑解惑:
为什么DataSoure类中获取数据的方式是fetchData而不是getData?
这样的四层架构有什么好处?
如何数据源发生变化,仅仅改变数据层就好,耦合性低,其它层次不用改动。
小结:
数据源类(DataSource)用于抽象表示数据的来源。获取解析数据
模型类(Model)用于从数据源获取数据并组织。
视图类(View)用于显示模型中的数据。
数据应用4层架构设计非常易于扩展和维护。
61、模型视图中的委托上
问题:模型负责组织数据,视图负责显示数据,如何编辑修改数据?
传统的MVC设计模式:
Qt中的模型视图设计模式如何处理用户输入?
视图中集成了处理用户输入的功能。
视图将用户输入作为内部独立的子功能而实现。
模型视图中的委托:
委托(Delegate)是视图中处理用户输入的部件。
视图可以设置委托对象用于处理用户输入。
委托对象负责创建和显示用户输入上下文
如:编辑框的创建和显示。
模型视图中委托:
委托中的编辑器:
委托能够提供编辑时需要的上下文环境(编辑器)
不同委托提供的编辑器类型不同(文本框,单选框,等)
编辑器需要从模型获得数据,并将编辑结果返回模型。
委托中的关键函数:
createEditor:需要编辑数据时,创建编辑器组件。
updateEditorGeometry:更新编辑器组件的大小。
setEditorData:通过索引从模型中获取数据。
setModelData:将编辑后的新数据返回模型。
委托中的关键信号:
void closeEditor(QWidget* editor): 编辑器组件关闭信号。
void commitData(QWidget* editor): 新数据提交信号。
小结:
委托(Delegate)是视图中处理用户输入的部件。
视图可以设置委托对象用于处理用户输入。
委托能够提供编辑时需要的上下文环境(编辑器)
不同委托提供的编辑器类型不同(文本框,单选框,等)
编辑器需要从模型获取数据,并将编辑结果返回模型。
62、模型中的委托下
委托的本质:
为视图提供数据编辑的上下文环境。
产生界面元素的工厂类。
能够使用和设置模型中的数据。
问题:如何自定义一个委托类?
自定义委托时需要重写的函数:
1、createEditor
2、updateEditorGeometry
3、setEditorData //从模型取数据放到编辑器
4、setModelData //编辑器数据设置到模型
5、paint ( 可 选 ) //绘制委托
1、重写creatorEditor成员函数
根据索引中的值的类型创建编辑器组件。
2、重写updateEditorGeometry成员函数
根据参数中数据项的信息设置编辑器的位置和大小。
3、重写setEditorData成员函数
根据参数中的数据索引设置编辑器中的初始数据。
4、重写setModelData成员函数
根据参数中的数据索引更改模型中的数据。
5、重写paint成员函数(可选)
根据参数中信息绘制编辑器。
问题:自定义委托时重写的函数由谁调用?
视图调用
小结:
自定义委托类时需要重写相应的成员函数。
根据需要创建编辑组件并设置组件中的数据。
编辑结束后将数据返回模型。
成员函数的参数携带了数据存取时需要的信息。
63、深入理解解析视图与委托 63,64工程中用的多
分析:Qt中的委托作为视图的内部组件而存在,因此,委托是视图的一部分,必然,委托需要承担数据显示的部分工作。
试验结论:
视图负责确定数据项的组织显示方式(列表,树形,表格)
委托负责具体数据项的显示和编辑(数据值,编辑器)
视图和委托共同完成数据显示功能和数据编辑功能。
拓展思考:
如何改变视图默认的数据显示方式?
自定义委托的默认的数据显示方式:
1、重写paint成员函数。
2、在paint重自定义数据显示方式。
3、重写editorEvent成员函数。
4、在editorEvent中处理交互事件。
QApplication::style()类对象保存了当前系统应用程序的分格的信息。drawControl函数就是具体的以当前操作系统风格绘制具体组件外观。ceheckBox表示勾选框,第二个绘制参数,painter绘制参数传进去。
取数据,根据数据值设置绘制参数,具体绘制外观
在editorEvent中处理交互事件:
小结:
委托是视图的重要构造部分。
视图负责数据项的组织显示方式。
委托负责具体数据项中数值的显示方式。
重写委托中paint函数自定义数据项显示方式。
重写委托的editorEvent函数处理交互事件。
64、深入解析视图与委托下
一个实例的分析与改进:
思考:如何改进程序界面使其拥有更好的用户体验? 进度条
改进思路:
将Progress从纯文本的显示方式改变为进度条+文本显示的方式;可以直观的让用户感受到当前的任务进度。
解决方案:
1、自定义新的委托类。
2、在paint成员函数中绘制进度条显示方式。
3、在editorEvent成员函数中禁止数据编辑操作。
在paint成员函数中绘制进度条显示:
在editorEvent成员函数中禁止数据编辑操作:
委托中编辑器的双击事件将触发委托进入数据编辑状态。
任务进度模拟:
1、定义计时器用于模拟任务进度。
2、定义计时器槽函数void timerTimeout().
3、在槽函数中修改模型中的数据。
示例扩展:
在实际工程项目中,可以使用后台线程根据实际的任务情况更新模型中的数据,从而更新数据的界面显示。
65、深入浅出信号与槽
一个事实:
在实际的项目开发中,大多数时候是直接将组件中预定义的信号连接到槽函数,信号发射时,槽函数被调用。
深度的思考?
信号是怎么来的? 又是如何发射的?
Qt中信号(SIGNAL)的本质:
信号只是一个特殊的成员函数声明:函数的返回值是void类型,函数只能声明不能定义。
信号必须使用signals关键字进行声明:函数的访问属性被设置为protected,只能通过emit关键字调用函数(发射信号)。
信号是自定义的。
信号定义示例:
信号与槽的对应关系:
信号与槽的对应关系:
一个信号可以连接到多个槽(一对多)
多个信号可以连接到一个槽(多对一)
一个信号可以连接到另一个信号(转嫁)
连接可以被disconnect函数删除(移除)
不可忽视的军规:
1、Qt类(继承自QObject)只能在头文件中声明
2、信号与槽的原型应该完全相同。
3、信号参数多于槽参数时,多于的参数被忽略。
4、槽函数的返回值必须是void类型。
5、槽函数有声明有定义可以像普通成员函数一样被调用,需要显式说明访问级别
6、信号与槽的访问属性对于connect/disconnect无效。信号是protected,槽也是protected,但是可以在类外部直接访问调用。
信号与槽的意义:
最大限度的弱化了类之间的耦合关系。
在设计阶段,可以减少不必要的接口类(抽象类)。
在开发阶段,对象间的交互通过信号与槽动态绑定。
想要就connect,不想要就disconnect。
小结:
信号只是一个特殊的成员函数声明。
信号必须使用signals关键字进行声明。
信号与槽可以存在多种对应关系。
信号与槽机制使得类间关系松散,提高类的可复用性。
66、基础图形绘制1
Qt图形系统中关键角色:
QPainter:
Qt中的画家,能够绘制各种基础图形。
拥有绘图所需的画笔(QPen),画刷(QBrush),字体(QFont)
QPaintDevice:
Qt中的画布,画家(QPainter)的绘图板。
所有的QWidget类都继承自QPaintDevice。
Qt图形系统中的关键角色:
小贴士:
1、QPainter中的所有绘制参数都可以自定义。
2、任意的QWidget对象都能够作为画布绘制图形。
画家(QPainter)所使用的工具角色:
QPen:用户绘制几何图形的边缘,由颜色,宽度,线风格等参数组成。
QBrush:用于填充几何图形的调色板,由颜色和填充风格组成。
QFont:用于文本绘制,由字体属性组成。
重要规则:只能在QWidget::paintEvent中绘制图形: 子类 paintEvent事件处理函数
void paintEvent(QPaintEvent* event)
{ QPainter painter(this);
painter.drawLine(QPoint(30,30),QPoint(100,100));
}
问题:
如何动态绘制图形:
工程中的额解决方案:
1、根据需要确定参数对象(绘图类型,点坐标,角度,等)
2、将参数对象存入数据集合中(如:链表)
3、在paintEvent函数中遍历数据集合。
4、根据参数对象绘制图形(update()).
小结:
QPainter是Qt中的关键绘图类。
QPainter只能在QPaintDevice上绘图。
paintEvent()是Qt中的绘图上下文。
先确定绘图参数,放到数据结构中,在paintEvent事件处理函数中遍历数据结构,进行动态绘图。
67、基础图形绘制2
坐标变换。
Qt图形系统中的坐标系:
物理坐标系(设备坐标系):屏幕坐标系
原点(0,0)在左上角的位置,单位:像素(点)
x坐标向右增长,与坐标向下增长。
逻辑坐标系:
数学模型中的抽象坐标系,单位由具体问题决定。
坐标轴的增长方向由具体问题决定。
一些事实:
QPainter使用逻辑坐标系绘制图形。
逻辑坐标系中图形的大小和位置经由转换后绘制于具体设备。
默认情况下的逻辑坐标系与物理坐标系完全一致。
小例子:A4纸的大小是固定的,但可以用于绘制任意类型的曲线图。
视口与窗口:
视口(view port):物理坐标系中一个任意指定的矩形。
窗口(window):逻辑坐标系下对应到物理坐标系中相同矩形。
站在不同的角度看同一个矩形,就有了视口和窗口。
深入理解视口与窗口:
视口与窗口是不同坐标系中的同一个矩形。
视口与窗口中的坐标点存在一一映射的关系。
视口与窗口能够通过坐标变换而相互转换。
视图与窗口的变换方法:
定义视口(setViewport):左上角坐标,右下角坐标,计算宽度和高度。
定义窗口(setWindow):左上角坐标,右下角坐标,计算宽度和高度。
正弦波形绘图实例:
解决方案:
1、定义视口矩形和逻辑坐标系。
2、定义画笔并填充窗口底色。
3、根据实际问题中的波形函数绘图(drawPoint()).
解决方案:
小结:
QPainter使用逻辑坐标系进行绘图。
逻辑坐标系能够变换到物理坐标系。
视口与窗口指不同坐标系下的一个矩形。
窗口用于逻辑坐标系下的图形绘制。
视口用于实际物理设备上的图形显示。
68、基础图形绘制3
综合实例开发:简易绘图程序
功能需求:
自由图形绘制
基本图形绘制(直线,矩形,椭圆)
能够选择图形绘制颜色
简易绘图程序运行截图:略
界面解决方案:
1、以QWidget为基类创建绘图主窗口。
2、使用QGroupBox创建图形设置区域。
3、使用单选按钮QRadioBox实现目标图形的选择。
4、使用组合框QCombox实现绘图颜色的选择。
问题:如何实现自由绘图?
分析:
自由绘图的本质是跟踪鼠标的移动轨迹;因此,必须考虑什么时候开始?什么时候结束?如何记录鼠标移动?
提示一:
从绘图参数的角度,可以将已经绘制结束的图形与正在绘制的图形进行分开处理。
提示二:
自由绘图必须记录鼠标移动时经过的所有点坐标,因此,绘图参数必须有能力保存多个坐标值。
自由绘图解决方案:
1、以鼠标按下为开始,记录开始坐标。mousePressEvent
2、记录鼠标移动时经过的像素坐标。mouseReleaseEvent
3、以鼠标释放为结束,记录结束坐标。mouseMoveEvent
4、按照记录顺序在两两坐标之间绘制直线。paintEvent
问题:如何实现基础图形动态绘制?
分析:
基础图形的目标是固定的,但是开始点与结束点的不同会导致最终形状的差异。因此,鼠标移动时根据当前坐标实时绘制,鼠标松开时确定最终图形。
提示三:
基本图形绘制需要在鼠标按下并移动时进行动态绘制图,但是,无论何时都只需要记录两个坐标值。
基础图形绘制解决方案:
1、以鼠标按下为开始,记录开始坐标。mousePressEvent
2、将鼠标移动时经过的每个坐标作为临时结束坐标。mouseReleaseEvent
3、以鼠标释放为结束,确定最终结束坐标。mouseMoveEvent
4、在开始坐标和结束坐标之间绘制目标图形。paintEvent
小结:
绘图程序需要重写鼠标事件处理函数。
模型视图的思想适用于绘图程序。
所有图形的绘制由paintEvent函数完成。
工程中需要避免在绘制进行浮点运算。
69、图像处理与绘制
设备无关的图形类-QImage
独立于具体硬件的图像类。
主要用于读写图像文件,针对IO访问而设计。
能够直接在像素级对图像进行处理。
设备相关图形类-QPixmap
依赖于具体硬件的图像类
主要是用于绘图,针对屏幕显示而设计。
显示效果依赖于所在平台的绘图引擎(不可移植)
都是绘图设备,Qt图像类都继承自QPaintDevice,QPainter 能够直接在图像上绘制图形,QImage和QPixmap能够相互转换。
图像处理------图像显示
特殊技能:
QImage:
读取图像文件,直接进行像素级操作。
内置简易图像处理相关算法。
opencv 库(算法)--集成到Qt中使用--利用QImage,不关心显示不显示
QPixmap:
最大限度利用硬件(gpu显卡)加速,增强图像显示效果。
屏幕截图,窗口截图,组件截图。。。。
能够拿到显存里边的东西。
功能:从彩色图像变成灰度图像。
数字图像可以说是像素的矩阵,二维的,矩阵的每个元素是RGB(0-255)。
得到桌面id,抓取,截屏
小结:
QImage使用于直接进行图像处理的场合。
QPixmap适用于在界面上显示图像的场合。
QPixmap能够对QImage图像进行转换。
QPainter能够直接在图像对象上进行绘图。
70、文本绘制技巧
QPainter拥有绘制文本的能力。
drawText(拥有多个重载形式)
常见调用方式:
p.drawText(10,10,"w.s.software");在坐标(10,10)处绘制文本。
p.drawText(0,0,100,30, Qt::AlignCenter, "w.s.software");
在矩形范围(0,0,100,30)中以居中对齐的方式绘制文本。
文本绘制参数:
字体(QFont),颜色(QColor):控制文本大小,风格,颜色等
坐标(QPoint),角度(rotate):文本绘制的位置(对齐该坐标),以绘制坐标为圆心顺时针旋转。
文本绘制示例:
实例分析:
解决方案分析:
1、在主窗口中绘制文本(QWidget)
2、将文本中心绘制于窗口中心(width()/2, height()/2)
3、动画效果通过连续控制字体参数完成(QFont)
4、通过计时器强行更新文本绘制(QTimer)。
小技巧:通过QFontMetrics获取字符串在指定字体下的宽度和高度。
/* 指定字体 */
QFontMetrics metrics(font); 参数是字体对象
/* 获取指定字体下相应字符串宽度 */
int w=metrics.width(text);
/* int h=metrics.height(); */
坐标计算:
小结:
QPainter能够根据需要任意绘制文本。
QPainter可以自定义文本颜色,位置,字体等参数。
QPainter绘制文本时可以通过参数控制实现动画效果。
QPainter能够将文本绘制于图片(图片水印)。
71、登录对话框的改进
第一个版本的登录对话框:
问题:没有实现验证码功能,容易被恶意程序攻击,盗取用户名和密码。
改进思路-验证码机制:
1、随机产生验证码。
2、用户识别后填写。
3、判断用户识别的正确性。
需求:验证码必须能够有效避开恶意程序的识别。
关于验证码和恶意程序:
自动测试原理:利用一些特殊的系统函数能够通过代码控制程序,从而模拟用户操作。
恶意程序:使用自动测试原理对目标程序进行控制,从而盗取信息或进行攻击。
验证码:随机产生,用户容易识别,程序难以识别,从而有效避免恶意攻击。
需要注意的问题:
验证码必须动态随机产生。
验证码的显示避开使用标准组件(标签,文本框等)。因为他们可以能直接得到内容
验证码应该附带足够多的障碍增加程序识别难度。
解决方案:
1、随机产生目标验证码。
2、将验证码直接绘制于登录对话框。
3、验证码中的字符颜色随机改变。
4、在验证码区域随机绘制噪点。
关于随机数:
计算机无法产生真正意义上的随机数。
计算机只能模拟随机数序列(伪随机数)。
随机种子决定每次产生的随机序列是否相同。
qsrand(seed); //设置随机数种子。
int n=qrand()%100; //生成[ 0,99 ]之间的随机数。
随机产生验证码:
QString getCaptcha()
{ QString ret="" ;
for( int i=0 ; i<4 ; i++ )
{ int c=(qrand()%2)? 'a': 'A';
ret +=static_cast<QChar>(c+qrand()%26)
}
return ret; }
验证码绘制:
注意:验证码中的每个字符必须分开绘制。
小结:
验证码用于有效避免恶意程序的破坏。
验证码的产生需要随机数的支持。
验证码必须附带有效噪点。
使用文本绘制的方式显示验证码。
每一个验证码的字符需要独立绘制。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。