32、qt中的文件操作+

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; }

验证码绘制:

注意:验证码中的每个字符必须分开绘制。

小结:

验证码用于有效避免恶意程序的破坏。

验证码的产生需要随机数的支持。

验证码必须附带有效噪点。

使用文本绘制的方式显示验证码。

每一个验证码的字符需要独立绘制。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值