Search code examples
c++qt6

Qt6 - Draw custom metatype in table/tree from QAbstractTableModel


If I have an overridden QAbstractTableModel that supplies a non-qt type, it's my understanding that supplying overloads for the << and >> operators will allow Qt to natively represent those types.

I have prepared an example with std::u16string in an attempt to create the most minimal test case, but can't seem to render anything.

Here's how I register the type with Qt:

#include <QtCore>

Q_DECLARE_METATYPE(std::u16string);

QDataStream& operator<<(QDataStream& out, const std::u16string& myObj)
{
    return out << QString::fromStdU16String(myObj);
}
QDataStream& operator>>(QDataStream& in, std::u16string& myObj)
{
    QString tmp;
    in >> tmp;
    myObj = tmp.toStdU16String();
    return in;
}

My trivial main.cpp which connects the type to the appropriate widget:

#include <QItemEditorFactory>
#include <QLineEdit>

int main()
{
    // not sure if this is required.
    // this blogpost (https://www.qt.io/blog/whats-new-in-qmetatype-qvariant) suggests it's
    // needed for name-to-type conversions, but no idea if that is still needed internally.
    qRegisterMetaType<std::u16string>();

    // tell qt that we would like to visualise std::u16string with the default text editor.
    QItemEditorFactory* factory = new QItemEditorFactory;
    factory->registerEditor(QMetaType::fromType<std::u16string>().id(), new QStandardItemEditorCreator<QLineEdit>());
    QItemEditorFactory::setDefaultFactory(factory);

    // kick off ui, etc
    return doApp();
}

And my trivial model, which supplies the external type via a variant:

#include <QAbstractTableModel>

class simple_model : public QAbstractTableModel
{
public:
    explicit simple_model(QObject* parent = nullptr) : QAbstractTableModel(parent) {}

    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
    {
        return QVariant::fromValue<std::u16string>(u"Hello, World!");
    }
};

Now, when I create QTableView like so:

QTableView* tableView = new QTableView;
tableView->setModel(new simple_model);

I would expect every column and row to print "Hello, World!". However, I just get a blank text box instead. Attaching my debugger to my overloaded << and >> operators shows they don't get run at all.

I feel like I'm missing a link here, but I'm not quite sure what. Some ideas about what could possibly be wrong:

  • Do I need to create a custom delegate and set it for each row and column which I'm returning this value? Ideally, I'd like my types to be interpreted as automatically and naturally as Qt would allow; I feel like it could create a lot of boilerplate code in my (actual, non-trivial) application.
  • Does QLineEdit not actually invoke any data conversions to display custom data? Perhaps there's a more appropriate text editor I should be using? I was hoping that QLineEdit would automatically convert them because it's the default QString editor; would be nice to have the exact same behaviour.

Solution

  • In my case, it turns out that the << and >> operators weren't needed at all. Instead, providing an appropriate meta type converter allowed for what I wanted.

    Sample code for converter:

    struct string_converter
    {
        static QString toQString(const std::u16string& value)
        {
            return QString::fromStdU16String(value);
        }
        static std::u16string tou16String(const QString& value)
        {
            return value.toStdU16String();
        }
    };
    

    and to register these converters:

    QMetaType::registerConverter<std::u16string, QString>(&string_converter::toQString);
    QMetaType::registerConverter<QString, std::u16string>(&string_converter::tou16String);
    

    Once these are provided, no other registration code appears to be needed. The simple_model seems to be able to construct a QVariant from the std::u16string and represent it with a QLineEdit without any extra boilerplate code.

    However, we'll need to explicitly convert the modified data back to std::u16string in simple_model, because they get tweaked and returned as a QString from QLineEdit. Eg:

    QVariant simple_model::setData(const QModelIndex& index, const QVariant& value, int role) override
    {
        std::u16string newData = value.value<std::u16string>();
        // do something with newData
    }
    

    While this has unblocked me, I'm not sure this is preferred Qt approach as it seems to be quite different to what the documentation says (as I cited originally in the question). Any extra feedback or tips would be appreciated.