QVariant
QVariant行为类似于C/C++的union, 但在Qt中比union强大很多, QVariant内置支持所有QMetaType::Type里的类型如:int,QString,QFont等甚至QList<QVariant>,
QMap<QString, QVariant>等组成的任意复杂类型. 主要应该在Qt项中附带额外的数据,如setData(),setItemData()等,和数据序列化中.简单的说QVariant可存储任意类型.
1.基本用法
(1)构造
QVariant v(1);
QVariant v1;
v1.setValue("boy");
(2)使用
if (v.canConvert<int>())
{
bool ok;
int i = v.toInt(&ok);
//int i = v.value<int>();
}
需要注意canConvert返回true不一定转化就能成功, 如QVariant存储的是QString类型,QString是可以转化为int的所以必定会返回true,但如果QString不是一个纯数字
那v.toInt(&ok), ok就是false; 上面中v的ok返回true,而v1的返回false.如不能转换铡返回相应值的默认值.
2.自定义可存储QVariant中类型
要使用一个自定义类型可用于QVariant中只需要在类声明的后面加上:Q_DECLARE_METATYPE(),
struct MyClass
{
QString name;
int age;
}
Q_DECLARE_METATYPE(MyClass)
这样我们的类就可以像QMetaType::Type类一样使用没什么不同,有点不同的是使用方法上面只能这样使用.
MyClass myClass;
QVariant v3 = QVairant::fromValue(myClass);
//
v2.canConvert<MyClass>();
MyClass myClass2 = v2.value<MyClass>();
应用场景1
Qt一些类可以附带额外数据, 最常用的有QTreeWidgetItem.setData(int column, int role, QVariant &value). 我们常会在不同列上或者不同的role上增加一个额外的数据,
当额外数据多一点不会显示杂乱且有时不知存放在哪一列上.这样我们可以定义一个类将所有额外数据放在类中,只需将数据放在一个地方,不仅数据统一还好扩展.
item.setData(0, Qt::UserRole+1, QVariant::fromValue(myClass));
应用场景2
对于QMap<QString, QVariant>结构, 可以很优雅的实现全局配置存储, QString作为键, 而QVariant可存储配置值.
应用场景3
使用Qt的postEvent()
VxCommonLib的IMsgObserver中有封装了一个PostMsg, 相信大家都有用过, 但它有个不好的地方是:每次我要PostMsg一个新的数据时必须做两件事(1)定义一个转换函数
(2)定义一个转换的QEvent类.一个简单的例子如下:
// 一个QEvent类
class CAudioDataEvent:public QEvent
{
public:
CAudioDataEvent(QEvent::Type type);
AUDIOUVMETER m_audioData;
};
// 转换函数
static QEvent *_createAudioDataEvent(void *theParam,int type)
{
CAudioDataEvent *pAudioEvent = new CAudioDataEvent(QEvent::Type(type));
memcpy(&pAudioEvent->m_audioData, theParam, sizeof(AUDIOUVMETER));
return pAudioEvent;
}
//事件发出
void CVxPlayViewController_X::__audiovu(AUDIOUVMETER* theParam)
{
IMsgObserver *pMsg = GetIMsgObserver();
pMsg->PostMsg(m_pParent, _createAudioDataEvent, TURBO_EDIT_PLAYVIEW_AUDIODATA,theParam ,m_pParent);
}
//事件接收
CAudioDataEvent *vxDataEvent = static_cast<CAudioDataEvent *>(event);
AUDIOUVMETER audioData = vxDataEvent->m_audioData;
出现以上问题主要原因是找不到一个可以储存任意类型的结构导致每次都要重定义事件, 没错你想到了,根据上面的知识找到了解决方案.
我们先定义好数据
//事件类
class CDsVariantDataEvent : public QEvent
{
public:
CDsVariantDataEvent(Type type, QVariant variant)
:QEvent(type)
{
m_variant = variant;
}
inline QVariant data() const { return m_variant; }
protected:
QVariant m_variant;
};
// 发送类
void CDsMsgEngine::PostMsg(int eventType, QVariant variant/* = QVariant()*/)
{
QObject *anObject = NULL;
MSGMAPITER it = m_msgMapTable.find(eventType);
if(it != m_msgMapTable.end())
{
ObserverList *pObserverList = (*it).second;
for(int i = 0; i < pObserverList->size(); ++i)
{
//csj 2010-7-3
if((*pObserverList)[i].anObject==NULL ||
(*pObserverList)[i].observer==anObject)
{
CDsVariantDataEvent *pEvent = new CDsVariantDataEvent(static_cast<QEvent::Type>(eventType),variant);
QApplication::postEvent((*pObserverList)[i].observer,pEvent);
}
}
}
}
这样以后每次使用异步事件就可以:
//发送
GetIMsgObserver()->PostMsg(TURBO_EDIT_PLAYVIEW_AUDIODATA,QVariant::fromValue(MyClass)));
//接收
CDsVariantDataEvent *pEvent = static_cast<CDsVariantDataEvent *>(event);
MyClass myClass = pEvent->data().value<MyClass>();
应用场景4
自定义类型要能在队列信号槽中使用,使用Q_DECLARE_METATYPE()后还要使用qRegisterMetaType()注册成QMetaType类型, 使用队列信号槽其实是很强大的,但我们常会
使用其它的方式解决问题, 这个下个专题会介绍.
3.序列化使用
要能序列化自定义类,还需要qRegisterMetaTypeStreamOperators()注册,还需要实现两个函数:
QDataStream &operator<<(QDataStream &out, const MyClass &myObj);
QDataStream &operator>>(QDataStream &in, MyClass &myObj);
这样就可以将数据序列化, 到底这样有什么用呢, 广告后再回来.
应用场景1
正如上面实现的全局配置QMap<QString, QVariant>, 你是否想保存成配置文件等下次程序启动的时候再回复呢, 是的可以而且和你想的一样简单.
(1)保存配置
QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file); // we will serialize the data into the file
out << map; //
(2)读取配置
QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file); // read the data serialized from the file
in >> map;
唯一需要注意的是所有的qRegisterMetaTypeStreamOperators()都要在读取配置文件之前, 否则会因为无法识别自定义类取序列化失败.
需要说明的是当你保存的配置是QFont,QRect,QKeySequence, QList<>等你才是知道他的强大之处, 可惜的是我们现在的程序往往用不着.
应用场景2
只要是继承QIODevice都可以序列化写和读, 如果我告诉你QAbstractSocket, QLocalSocket, QNetworkReply, QProcess都是继承QIODevice,
那么你是否会想到, 应用场景1中的功能都可以应用到网络数据交换和进程间通信. 实现过程其它和场景1一样,只不过是将QFile换成你想要
的类而已, 可惜的是我们也少用到.
4.QVariant源码实现
内部使用一个union管理所有类型, 对于复杂的类主要使用o, prt, shared管理. shared中存储的会使用隐式共享, 其它会进行深拷贝.
(1)内部存储结构
union Data
{
char c;
int i;
uint u;
bool b;
double d;
float f;
qreal real;
qlonglong ll;
qulonglong ull;
QObject *o;
void *ptr;
PrivateShared *shared;
} data;
当是几个常用基本类型, 刚直接将数据存储在char, int, bool等等上面, 继续QObject类将会放到o上,其它Qt内置类型,则存储在shared上, 而用户自定义的数据存储在ptr上.在构造QVariant
时有个大的switch负责存储责任.
static void construct(QVariant::Private *x, const void *copy)
{
x->is_shared = false;
switch (x->type) {
case QVariant::String:
v_construct<QString>(x, copy);
break;
case QVariant::Char:
v_construct<QChar>(x, copy);
break;
case QVariant::StringList:
v_construct<QStringList>(x, copy);
break;
case QVariant::Map:
v_construct<QVariantMap>(x, copy);
break;
case QVariant::Hash:
v_construct<QVariantHash>(x, copy);
break;
case QVariant::List:
v_construct<QVariantList>(x, copy);
break;
case QVariant::Date:
v_construct<QDate>(x, copy);
break;
case QVariant::Time:
v_construct<QTime>(x, copy);
break;
case QVariant::DateTime:
v_construct<QDateTime>(x, copy);
break;
case QVariant::ByteArray:
v_construct<QByteArray>(x, copy);
break;
case QVariant::BitArray:
v_construct<QBitArray>(x, copy);
break;
#ifndef QT_NO_GEOM_VARIANT
case QVariant::Size:
v_construct<QSize>(x, copy);
break;
case QVariant::SizeF:
v_construct<QSizeF>(x, copy);
break;
case QVariant::Rect:
v_construct<QRect>(x, copy);
break;
case QVariant::LineF:
v_construct<QLineF>(x, copy);
break;
case QVariant::Line:
v_construct<QLine>(x, copy);
break;
case QVariant::RectF:
v_construct<QRectF>(x, copy);
break;
case QVariant::Point:
v_construct<QPoint>(x, copy);
break;
.............................. // 省略
default:
void *ptr = QMetaType::construct(x->type, copy);
if (!ptr) {
x->type = QVariant::Invalid;
} else {
x->is_shared = true;
x->data.shared = new QVariant::PrivateShared(ptr);
}
break;
}
x->is_null = !copy;
}
//调用v.value()
inline T value() const
{ return qvariant_cast<T>(*this); }
//
template<typename T> inline T qvariant_cast(const QVariant &v)
{
const int vid = qMetaTypeId<T>(static_cast<T *>(0));
if (vid == v.userType())
return *reinterpret_cast<const T *>(v.constData()); // 自定义数据使用reinterpret_cast<const T *>返回
if (vid < int(QMetaType::User)) {
T t;
if (qvariant_cast_helper(v, QVariant::Type(vid), &t)) // Qt内置,使用一个模版返回相对应数据, switch太大就不放出来了.
return t;
}
return T();
}
QVariant行为类似于C/C++的union, 但在Qt中比union强大很多, QVariant内置支持所有QMetaType::Type里的类型如:int,QString,QFont等甚至QList<QVariant>,
QMap<QString, QVariant>等组成的任意复杂类型. 主要应该在Qt项中附带额外的数据,如setData(),setItemData()等,和数据序列化中.简单的说QVariant可存储任意类型.
1.基本用法
(1)构造
QVariant v(1);
QVariant v1;
v1.setValue("boy");
(2)使用
if (v.canConvert<int>())
{
bool ok;
int i = v.toInt(&ok);
//int i = v.value<int>();
}
需要注意canConvert返回true不一定转化就能成功, 如QVariant存储的是QString类型,QString是可以转化为int的所以必定会返回true,但如果QString不是一个纯数字
那v.toInt(&ok), ok就是false; 上面中v的ok返回true,而v1的返回false.如不能转换铡返回相应值的默认值.
2.自定义可存储QVariant中类型
要使用一个自定义类型可用于QVariant中只需要在类声明的后面加上:Q_DECLARE_METATYPE(),
struct MyClass
{
QString name;
int age;
}
Q_DECLARE_METATYPE(MyClass)
这样我们的类就可以像QMetaType::Type类一样使用没什么不同,有点不同的是使用方法上面只能这样使用.
MyClass myClass;
QVariant v3 = QVairant::fromValue(myClass);
//
v2.canConvert<MyClass>();
MyClass myClass2 = v2.value<MyClass>();
应用场景1
Qt一些类可以附带额外数据, 最常用的有QTreeWidgetItem.setData(int column, int role, QVariant &value). 我们常会在不同列上或者不同的role上增加一个额外的数据,
当额外数据多一点不会显示杂乱且有时不知存放在哪一列上.这样我们可以定义一个类将所有额外数据放在类中,只需将数据放在一个地方,不仅数据统一还好扩展.
item.setData(0, Qt::UserRole+1, QVariant::fromValue(myClass));
应用场景2
对于QMap<QString, QVariant>结构, 可以很优雅的实现全局配置存储, QString作为键, 而QVariant可存储配置值.
应用场景3
使用Qt的postEvent()
VxCommonLib的IMsgObserver中有封装了一个PostMsg, 相信大家都有用过, 但它有个不好的地方是:每次我要PostMsg一个新的数据时必须做两件事(1)定义一个转换函数
(2)定义一个转换的QEvent类.一个简单的例子如下:
// 一个QEvent类
class CAudioDataEvent:public QEvent
{
public:
CAudioDataEvent(QEvent::Type type);
AUDIOUVMETER m_audioData;
};
// 转换函数
static QEvent *_createAudioDataEvent(void *theParam,int type)
{
CAudioDataEvent *pAudioEvent = new CAudioDataEvent(QEvent::Type(type));
memcpy(&pAudioEvent->m_audioData, theParam, sizeof(AUDIOUVMETER));
return pAudioEvent;
}
//事件发出
void CVxPlayViewController_X::__audiovu(AUDIOUVMETER* theParam)
{
IMsgObserver *pMsg = GetIMsgObserver();
pMsg->PostMsg(m_pParent, _createAudioDataEvent, TURBO_EDIT_PLAYVIEW_AUDIODATA,theParam ,m_pParent);
}
//事件接收
CAudioDataEvent *vxDataEvent = static_cast<CAudioDataEvent *>(event);
AUDIOUVMETER audioData = vxDataEvent->m_audioData;
出现以上问题主要原因是找不到一个可以储存任意类型的结构导致每次都要重定义事件, 没错你想到了,根据上面的知识找到了解决方案.
我们先定义好数据
//事件类
class CDsVariantDataEvent : public QEvent
{
public:
CDsVariantDataEvent(Type type, QVariant variant)
:QEvent(type)
{
m_variant = variant;
}
inline QVariant data() const { return m_variant; }
protected:
QVariant m_variant;
};
// 发送类
void CDsMsgEngine::PostMsg(int eventType, QVariant variant/* = QVariant()*/)
{
QObject *anObject = NULL;
MSGMAPITER it = m_msgMapTable.find(eventType);
if(it != m_msgMapTable.end())
{
ObserverList *pObserverList = (*it).second;
for(int i = 0; i < pObserverList->size(); ++i)
{
//csj 2010-7-3
if((*pObserverList)[i].anObject==NULL ||
(*pObserverList)[i].observer==anObject)
{
CDsVariantDataEvent *pEvent = new CDsVariantDataEvent(static_cast<QEvent::Type>(eventType),variant);
QApplication::postEvent((*pObserverList)[i].observer,pEvent);
}
}
}
}
这样以后每次使用异步事件就可以:
//发送
GetIMsgObserver()->PostMsg(TURBO_EDIT_PLAYVIEW_AUDIODATA,QVariant::fromValue(MyClass)));
//接收
CDsVariantDataEvent *pEvent = static_cast<CDsVariantDataEvent *>(event);
MyClass myClass = pEvent->data().value<MyClass>();
应用场景4
自定义类型要能在队列信号槽中使用,使用Q_DECLARE_METATYPE()后还要使用qRegisterMetaType()注册成QMetaType类型, 使用队列信号槽其实是很强大的,但我们常会
使用其它的方式解决问题, 这个下个专题会介绍.
3.序列化使用
要能序列化自定义类,还需要qRegisterMetaTypeStreamOperators()注册,还需要实现两个函数:
QDataStream &operator<<(QDataStream &out, const MyClass &myObj);
QDataStream &operator>>(QDataStream &in, MyClass &myObj);
这样就可以将数据序列化, 到底这样有什么用呢, 广告后再回来.
应用场景1
正如上面实现的全局配置QMap<QString, QVariant>, 你是否想保存成配置文件等下次程序启动的时候再回复呢, 是的可以而且和你想的一样简单.
(1)保存配置
QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file); // we will serialize the data into the file
out << map; //
(2)读取配置
QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file); // read the data serialized from the file
in >> map;
唯一需要注意的是所有的qRegisterMetaTypeStreamOperators()都要在读取配置文件之前, 否则会因为无法识别自定义类取序列化失败.
需要说明的是当你保存的配置是QFont,QRect,QKeySequence, QList<>等你才是知道他的强大之处, 可惜的是我们现在的程序往往用不着.
应用场景2
只要是继承QIODevice都可以序列化写和读, 如果我告诉你QAbstractSocket, QLocalSocket, QNetworkReply, QProcess都是继承QIODevice,
那么你是否会想到, 应用场景1中的功能都可以应用到网络数据交换和进程间通信. 实现过程其它和场景1一样,只不过是将QFile换成你想要
的类而已, 可惜的是我们也少用到.
4.QVariant源码实现
内部使用一个union管理所有类型, 对于复杂的类主要使用o, prt, shared管理. shared中存储的会使用隐式共享, 其它会进行深拷贝.
(1)内部存储结构
union Data
{
char c;
int i;
uint u;
bool b;
double d;
float f;
qreal real;
qlonglong ll;
qulonglong ull;
QObject *o;
void *ptr;
PrivateShared *shared;
} data;
当是几个常用基本类型, 刚直接将数据存储在char, int, bool等等上面, 继续QObject类将会放到o上,其它Qt内置类型,则存储在shared上, 而用户自定义的数据存储在ptr上.在构造QVariant
时有个大的switch负责存储责任.
static void construct(QVariant::Private *x, const void *copy)
{
x->is_shared = false;
switch (x->type) {
case QVariant::String:
v_construct<QString>(x, copy);
break;
case QVariant::Char:
v_construct<QChar>(x, copy);
break;
case QVariant::StringList:
v_construct<QStringList>(x, copy);
break;
case QVariant::Map:
v_construct<QVariantMap>(x, copy);
break;
case QVariant::Hash:
v_construct<QVariantHash>(x, copy);
break;
case QVariant::List:
v_construct<QVariantList>(x, copy);
break;
case QVariant::Date:
v_construct<QDate>(x, copy);
break;
case QVariant::Time:
v_construct<QTime>(x, copy);
break;
case QVariant::DateTime:
v_construct<QDateTime>(x, copy);
break;
case QVariant::ByteArray:
v_construct<QByteArray>(x, copy);
break;
case QVariant::BitArray:
v_construct<QBitArray>(x, copy);
break;
#ifndef QT_NO_GEOM_VARIANT
case QVariant::Size:
v_construct<QSize>(x, copy);
break;
case QVariant::SizeF:
v_construct<QSizeF>(x, copy);
break;
case QVariant::Rect:
v_construct<QRect>(x, copy);
break;
case QVariant::LineF:
v_construct<QLineF>(x, copy);
break;
case QVariant::Line:
v_construct<QLine>(x, copy);
break;
case QVariant::RectF:
v_construct<QRectF>(x, copy);
break;
case QVariant::Point:
v_construct<QPoint>(x, copy);
break;
.............................. // 省略
default:
void *ptr = QMetaType::construct(x->type, copy);
if (!ptr) {
x->type = QVariant::Invalid;
} else {
x->is_shared = true;
x->data.shared = new QVariant::PrivateShared(ptr);
}
break;
}
x->is_null = !copy;
}
//调用v.value()
inline T value() const
{ return qvariant_cast<T>(*this); }
//
template<typename T> inline T qvariant_cast(const QVariant &v)
{
const int vid = qMetaTypeId<T>(static_cast<T *>(0));
if (vid == v.userType())
return *reinterpret_cast<const T *>(v.constData()); // 自定义数据使用reinterpret_cast<const T *>返回
if (vid < int(QMetaType::User)) {
T t;
if (qvariant_cast_helper(v, QVariant::Type(vid), &t)) // Qt内置,使用一个模版返回相对应数据, switch太大就不放出来了.
return t;
}
return T();
}