1. 程式人生 > >如何在Qt中使用自定義資料型別

如何在Qt中使用自定義資料型別

這裡我們使用下面這個struct來做說明(這裡不管是struct還是class都一樣):

  1. struct Player
  2. {
  3.     int number;
  4. QString firstName;
  5.     QString lastName;
  6. };

複製程式碼

QVariant 
為了能在QVariant中使用自定義資料型別做,需要使用Q_DECLARE_METATYPE()來向Qt的元系統宣告這個自定義型別。如下列所示:

  1. struct Player
  2. {
  3.     ...
  4. };
  5. Q_DECLARE_METATYPE(Player);

複製程式碼

在作為QVariant傳遞自定義資料型別時,需要使用QVariant::fromValue()或者qVariantFromValue:

  1. Player player;
  2. object->setProperty("property", QVariant::fromValue(player));

複製程式碼

為了更方便一點,你可以在自定義型別中定義一個QVariant() 型別轉換符:

  1. struct Player
  2. {
  3.     ...
  4.     operator QVariant() const
  5.     {
  6.         return QVariant::fromValue(*this);
  7.     }
  8. };

複製程式碼

這樣我們便可以像下面這樣使用了:

  1. Player player;
  2. object->setProperty("property", player);

複製程式碼

訊號和槽 
對於直接連線型別(預設情況下就是直接連線)而言,使用自定義資料型別做訊號引數不需要做其他其他處理,就像內建資料型別一樣:

  1. connect(sender, SIGNAL(playerCreated(const Player&)), receiver, SLOT(addPlayer(const Player&)));

複製程式碼

但在跨執行緒時如果你還這麼做,編譯器就會給出警告了:

  1. QObject::connect: Cannot queue arguments of type 'Player'
  2. (Make sure 'Player' is registered using qRegisterMetaType().)

複製程式碼

這時我們需要先註冊Player:

qRegisterMetaType<Player>("Player");  

qRegisterMetaType<Player>( );   (上面那個是錯誤的,除非名字剛好和類名一樣)

connect(sender, SIGNAL(playerCreated(const Player&)), receiver, SLOT(addPlayer(const Player&)));

複製程式碼

QDebug 
最好是能這樣:

  1. qDebug() << player;

複製程式碼

而不是這樣:

  1. qDebug() << "Player(" << player.number << "," << player.firstName << "," << player.lastName << ")";

複製程式碼

怎麼做呢?我們需要對QDebug<<操作符過載一下:

  1. inline QDebug operator<<(QDebug debug, const Player& player)
  2. {
  3.         debug.nospace() << "Player("
  4.                 << player.number << ","
  5.                 << player.firstName << ","
  6.                 << player.lastName << ")";
  7.         return debug.space();
  8. }

複製程式碼

QDataStream 
跟上面的QDebug很像,我們也需要過載一下<<操作符:

  1. inline QDataStream& operator<<(QDataStream& out, const Player& player)
  2. {
  3.     out << player.number;
  4.     out << player.firstName;
  5.     out << player.lastName;
  6.     return out;
  7. }
  8. inline QDataStream& operator>>(QDataStream& in, Player& player)
  9. {
  10.     in >> player.number;
  11.     in >> player.firstName;
  12.     in >> player.lastName;
  13.     return in;
  14. }

複製程式碼

QSettings

QSettings 用QVariant儲存鍵值,用QDataStream序列化自定義資料。(參考後面的variantToString函式) 
為了能在QSettings中使用自定義資料型別,需要讓Qt的元系統知道有此型別,就像上面介紹QVariant部分一樣,另外還要提供相應的QDataStream操作符,還必須註冊這個流操作符:

  1. qRegisterMetaTypeStreamOperators<Player>("Player");

複製程式碼

如此處理之後我們就可以像下面這樣使用了:

  1. QSettings settings;
  2. Player player;
  3. settings.setValue("key", player);

複製程式碼

  1. QSettings settings;
  2. Player player = value("key").value<Player>();

複製程式碼

 

參考:

QString QSettingsPrivate::variantToString(const QVariant &v) 

    QString result;

    switch (v.type()) { 
        case QVariant::Invalid: 
            result = QLatin1String("@Invalid()"); 
            break;

        case QVariant::ByteArray: { 
            QByteArray a = v.toByteArray(); 
            result = QLatin1String("@ByteArray("); 
            result += QString::fromLatin1(a.constData(), a.size()); 
            result += QLatin1Char(')'); 
            break; 
        }

        case QVariant::String: 
        case QVariant::LongLong: 
        case QVariant::ULongLong: 
        case QVariant::Int: 
        case QVariant::UInt: 
        case QVariant::Bool: 
        case QVariant::Double: 
        case QVariant::KeySequence: { 
            result = v.toString(); 
            if (result.startsWith(QLatin1Char('@'))) 
                result.prepend(QLatin1Char('@')); 
            break; 
        } 
#ifndef QT_NO_GEOM_VARIANT 
        case QVariant::Rect: { 
            QRect r = qvariant_cast<QRect>(v); 
            result += QLatin1String("@Rect("); 
            result += QString::number(r.x()); 
            result += QLatin1Char(' '); 
            result += QString::number(r.y()); 
            result += QLatin1Char(' '); 
            result += QString::number(r.width()); 
            result += QLatin1Char(' '); 
            result += QString::number(r.height()); 
            result += QLatin1Char(')'); 
            break; 
        } 
        case QVariant::Size: { 
            QSize s = qvariant_cast<QSize>(v); 
            result += QLatin1String("@Size("); 
            result += QString::number(s.width()); 
            result += QLatin1Char(' '); 
            result += QString::number(s.height()); 
            result += QLatin1Char(')'); 
            break; 
        } 
        case QVariant::Point: { 
            QPoint p = qvariant_cast<QPoint>(v); 
            result += QLatin1String("@Point("); 
            result += QString::number(p.x()); 
            result += QLatin1Char(' '); 
            result += QString::number(p.y()); 
            result += QLatin1Char(')'); 
            break; 
        } 
#endif // !QT_NO_GEOM_VARIANT

        default: { 
#ifndef QT_NO_DATASTREAM 
            QByteArray a; 
            { 
                QDataStream s(&a, QIODevice::WriteOnly); 
                s.setVersion(QDataStream::Qt_4_0); 
                s << v; 
            }

            result = QLatin1String("@Variant("); 
            result += QString::fromLatin1(a.constData(), a.size()); 
            result += QLatin1Char(')'); 
#else 
            Q_ASSERT(!"QSettings: Cannot save custom types without QDataStream support"); 
#endif 
            break; 
        } 
    }

    return result; 
}

 

qsetting為了讓儲存的ini檔案能和ascii相容,所以

  • 我們將ini檔案中的鍵值讀入到 QVariant 中,需要兩個步驟:
    • 因為檔案內的一些字元被轉義了,比如 "\x1234\t\0"等,所以需要 unescape

    • 從 unescape 後的字串構造出 QVariant
  • 當將QVariant寫入檔案時:
    • 將 QVariant 轉換成字串
    • 處理字串中的特殊字元,即 escape

 

 

If you store types that QVariant can't convert to QString (e.g., QPoint, QRect, and QSize), Qt uses an @-based syntax to encode the type. For example: 
pos = @Point(100 100) 
To minimize compatibility issues, any @ that doesn't appear at the first position in the value or that isn't followed by a Qt type (Point, Rect, Size, etc.) is treated as a normal character.

不能轉化為 QString 的QVariant 會以@-based 編碼,base為(Point, Rect, Size, etc.)

Although backslash is a special character in INI files, most Windows applications don't escape backslashes (\) in file paths: 
windir = C:\Windows 
QSettings always treats backslash as a special character and provides no API for reading or writing such entries.

反斜槓不能用在鍵值中

The INI file format has severe restrictions on the syntax of a key. Qt works around this by using % as an escape character in keys. In addition, if you save a top-level setting (a key with no slashes in it, e.g., "someKey"), it will appear in the INI file's "General" section. To avoid overwriting other keys, if you save something using the a key such as "General/someKey", the key will be located in the "%General" section, not in the "General" section.

key用%轉義。頂層的 key會在General段中,General/someKey會在"%General"段中。


Following the philosophy that we should be liberal in what we accept and conservative in what we generate, QSettings will accept Latin-1 encoded INI files, but generate pure ASCII files, where non-ASCII values are encoded using standard INI escape sequences. To make the INI files more readable (but potentially less compatible), call setIniCodec().

意思是生成的ini是ascii的,non-ASCII全部被轉移,latin-1也不例外