Qt中如何结合自定义QVariant对象实现QDataWidgetMapper映射?
Great question! This is a common pain point when moving from a row-column model to object-based storage in Qt, and there's a clean way to pull this off using QVariant, custom roles, and QDataWidgetMapper's underutilized role parameter. Let's break this down step by step:
1. Prepare Your Custom Object with Meta-Type Support
First, make sure your Fruit class is compatible with QVariant and exposes its properties via Qt's property system (this makes data access much cleaner):
#include <QObject> class Fruit : public QObject { Q_OBJECT // Expose properties for easy access from Qt's model system Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(int quantity READ quantity WRITE setQuantity NOTIFY quantityChanged) Q_PROPERTY(bool isFresh READ isFresh WRITE setIsFresh NOTIFY isFreshChanged) public: explicit Fruit(QObject* parent = nullptr) : QObject(parent) {} // Getters QString name() const { return m_name; } int quantity() const { return m_quantity; } bool isFresh() const { return m_isFresh; } // Setters void setName(const QString& name) { if (m_name != name) { m_name = name; emit nameChanged(); } } void setQuantity(int quantity) { if (m_quantity != quantity) { m_quantity = quantity; emit quantityChanged(); } } void setIsFresh(bool isFresh) { if (m_isFresh != isFresh) { m_isFresh = isFresh; emit isFreshChanged(); } } signals: void nameChanged(); void quantityChanged(); void isFreshChanged(); private: QString m_name; int m_quantity = 0; bool m_isFresh = false; }; // Declare the type for QVariant support Q_DECLARE_METATYPE(Fruit*)
Don't forget to register the meta-type in your main() function to ensure Qt can handle it properly:
#include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); qRegisterMetaType<Fruit*>(); // Critical for QVariant to work with Fruit* // ... rest of your app setup return a.exec(); }
2. Create a Custom Model (or Extend QStandardItemModel)
We'll extend QStandardItemModel to handle custom roles that map to your Fruit object's properties. This lets the model return specific property values when requested, without splitting your object into multiple columns.
First, define custom roles to identify each property:
enum FruitRoles { NameRole = Qt::UserRole + 1, QuantityRole, FreshRole, FruitObjectRole // To store the full Fruit* object };
Then subclass QStandardItemModel to override data() and setData():
#include <QStandardItemModel> class FruitModel : public QStandardItemModel { Q_OBJECT public: explicit FruitModel(QObject* parent = nullptr) : QStandardItemModel(parent) {} QVariant data(const QModelIndex& index, int role) const override { if (!index.isValid()) return QVariant(); QStandardItem* item = itemFromIndex(index); Fruit* fruit = item->data(FruitObjectRole).value<Fruit*>(); if (!fruit) return QStandardItemModel::data(index, role); // Return the appropriate property based on the role switch (role) { case NameRole: return fruit->name(); case QuantityRole: return fruit->quantity(); case FreshRole: return fruit->isFresh(); default: return QStandardItemModel::data(index, role); } } bool setData(const QModelIndex& index, const QVariant& value, int role) override { if (!index.isValid()) return false; QStandardItem* item = itemFromIndex(index); Fruit* fruit = item->data(FruitObjectRole).value<Fruit*>(); if (!fruit) return QStandardItemModel::setData(index, value, role); // Update the Fruit object's property based on the role switch (role) { case NameRole: fruit->setName(value.toString()); break; case QuantityRole: fruit->setQuantity(value.toInt()); break; case FreshRole: fruit->setIsFresh(value.toBool()); break; default: return QStandardItemModel::setData(index, value, role); } emit dataChanged(index, index, {role}); return true; } };
3. Populate the Model with Your Objects
Add Fruit objects to the model as single-column rows. We store the full Fruit* in the FruitObjectRole for easy access:
// Create the model FruitModel* model = new FruitModel(this); // Add a sample Fruit Fruit* apple = new Fruit(this); apple->setName("Apple"); apple->setQuantity(5); apple->setIsFresh(true); QStandardItem* appleItem = new QStandardItem(); appleItem->setData(QVariant::fromValue(apple), FruitObjectRole); model->appendRow(appleItem); // Add more fruits as needed...
4. Map Widgets to Object Properties with QDataWidgetMapper
Here's the key part: instead of mapping widgets to different columns, we map all widgets to the same column (since our model only has one), but use custom roles to target specific Fruit properties.
#include <QDataWidgetMapper> #include <QLineEdit> #include <QSpinBox> #include <QCheckBox> // Create the mapper QDataWidgetMapper* mapper = new QDataWidgetMapper(this); mapper->setModel(model); mapper->setItemDelegate(new QStyledItemDelegate(this)); // Default delegate works for basic widgets // Map each widget to the same column (0) but with a custom role mapper->addMapping(ui->nameLineEdit, 0, NameRole); mapper->addMapping(ui->quantitySpinBox, 0, QuantityRole); mapper->addMapping(ui->freshCheckBox, 0, FreshRole); // Optional: Enable auto-submit to sync changes immediately mapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit); // Navigate between rows (objects) mapper->toFirst(); // Show the first fruit
Why This Works
- The model stores full
Fruitobjects in a single column, eliminating redundant row-column conversion. - Custom roles act as "virtual columns" that expose individual object properties to the mapper.
QDataWidgetMapperuses the role parameter inaddMapping()to fetch/update the correct property value, without needing multiple model columns.
When to Use a Custom Delegate
If you have complex custom widgets that don't work with the default QStyledItemDelegate, you can subclass QStyledItemDelegate and override setEditorData() and setModelData() to handle your widget's specific needs. For example, if you have a custom color picker for a Fruit::color property, you'd add logic to convert between the widget's value and the Fruit property.
内容的提问来源于stack exchange,提问作者Ryan Carmichael

