Home » Projekte » Qt-MVG

Model-View-Dialog generator for Qt

Repository:download here
Mailing list:http://groups.google.com/group/qt-ham

Qt's Model/View Programming might be extremely flexibly.

This flexibility comes at the price of tedious programming, even for simple case. What could be simple than having a struct filed with data, an array of such struct (either C++ array, or QList, or QVector, whatever), and a widget where you can see all those items and edit them.

A simple task, needed in many programs, no ?

Yet is is quite tedious to implement this in Qt 4.x:

mvg.py is a Model-View Generator and is a tool to solve this.

But first, we need some

Data representation 

Suppose you want to store locations with some text. You could store them in a struct:

    struct Locations {
        QString name;
        double longitude;
        double latitude;
    };

Field definition 

Now is the time to download mvg.py.

Then create a file, say locations.yaml. In this file we describe this structure. It should be easy to parse for computers, and easy to read for humans. Sorry, no XML. Better use YAML.

locations.yaml could look like this:

    Location:
        fields:
            - {
            name: name,
            type: QString,
            }

            - {
            name: longitude,
            type: double,
            }

            - {
            name: latitude,
            type: double,
            }

mvg.py reads all of this and generate a file locations.h which now consists of

    class Location {
    public:
            QString name;
            double longitude;
            double latitude;
    };

You might know that struct and class in C++ are equivalent, It's just that by default all members of a class are private, while all members of a struct are public. And, by convention, structs normally don't have constructors and destructors. But they could.

Oh, what I wanted really say is that this is equivalent to struct Locations { ... }. It's just nicer for lazy people like my, because now I can write Location l;, otherwise I'd have been forced to write struct Location l;.

So far we haven't really saved work. But this will soon change, when we start with automatic

Field attributes 

Dialog code generation 

A dialog doesn't just need the fields, but also labels for it. We can add "dia_labels" attributes to the fields, or we only add "head" attributes, which will be used for both the dialog and the view.

        fields:
            - {
            name: name,
            type: QString,
            head: "&Name",
            }

            - {
            name: longitude,
            type: double,
            head: "L&ongitude",
            }

            - {
            name: latitude,
            type: double,
            head: "L&atitude",
            }

Dialog definition 

And then we add the description of some simple dialog:

      dialog:
          name: LocationDialog
          head: "Location"

Now we run this new locations.yaml file through mvg.py. It will quickly create two files, locations.h and Locations.cpp. The dialog will look like this:

Dialog created by mvg.py

Let's first look at the header file:

    #define LOCATIONS_H

    // automatically generated from locations.yaml

    #include <QString>
    #include <QDialog>

    class QLineEdit;
    class QDoubleSpinBox;
    class QFormLayout;
    class QDialogButtonBox;

    class Location {
    public:
            QString name;
            double longitude;
            double latitude;
    };


    class LocationDialog : public QDialog
    {
            Q_OBJECT
    public:
            LocationDialog(Location *record, QWidget *parent=0, Qt::WindowFlags f=0);
            virtual void accept();

            Location *m;
            QLineEdit *editName;
            QDoubleSpinBox *editLongitude;
            QDoubleSpinBox *editLatitude;
            QDialogButtonBox *okCancel;
            QFormLayout *formLayout;
    };

    #endif

That's a simple dialog, with a field for every record and a pointer to the data-source.

In the generated locations.cpp seems also quite simple:

    #include "locations.h"

    #include <QLineEdit>
    #include <QDoubleSpinBox>
    #include <QFormLayout>
    #include <QDialogButtonBox>

    // automatically generated from locations.yaml

    LocationDialog::LocationDialog(Location *record, QWidget *parent, Qt::WindowFlags f)
            : QDialog(parent, f)
            , m(record)
    {
            setWindowTitle(tr("Location"));
            resize(500, 250);

            editName = new QLineEdit(this);
            editLongitude = new QDoubleSpinBox(this);
            editLatitude = new QDoubleSpinBox(this);

Here we create three edit widgets for our members.

            formLayout = new QFormLayout;
            formLayout->addRow(tr("&Name"), editName);
            formLayout->addRow(tr("L&ongitude"), editLongitude);
            formLayout->addRow(tr("L&atitude"), editLatitude);

We put them into a formLayout.

            editName->setText(m->name);
            editLongitude->setValue(m->longitude);
            editLatitude->setValue(m->latitude);

Now we populate the edit widgets with data from our record.

And the rest is just the usual dialog boilerplate:

            okCancel = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this)
            connect(okCancel, SIGNAL(accepted()), this, SLOT(accept()));
            connect(okCancel, SIGNAL(rejected()), this, SLOT(reject()));

            QVBoxLayout *mainLayout = new QVBoxLayout(this);
            mainLayout->addLayout(formLayout);
            mainLayout->addWidget(okCancel);
            setLayout(mainLayout);
    }

Now the user can already ::exec() the dialog. If he presses "Ok", the following method will be called:

    void LocationDialog::accept()
    {
            m->name = editName->text();
            m->longitude = editLongitude->value();
            m->latitude = editLatitude->value();
            QDialog::accept();
    }

... this method just stores the (eventually modified) data from the edit widgets back into the record.

Nothing surprising.

In fact, quite dumb --- once you know how to program this stuff.

And yet, mvg.py already saved me tons of typing. But it comes better. The next step is an extremely small step: let's have a container for records.

Dialog attributes 

Container code generation 

We now have a record, and we have a dialog to change the contents of the record. Now we want to store hundreds of those record somewhere. So we need a

Container definition 

We add this to our locations.yaml file:

      container:
          name: locations
          type: QList

This just adds the following line to locations.h:

    extern QList<Location> locations;

That's not really a big achievement, but please consider that mvg.py also can generate code to load or save data into this container:

      container:
          name: locations
          type: QList
          load: true
          save: true

... but this I won't cover here.

Container attributes 

Model code generation 

Writing a model is sometimes quite subtle. If you google for modeltest.cpp, you'll even find test code that checks if your model behaves sane and doesn't crash.

mvg.py can save us from this tedious task. We need a

Model definition 

We add another section to our locations.yaml file:

      model:
          name: LocationsModel
          type: QAbstractTableModel
          # This disables the automatically generated sort code of the model
          sort: false
          # This disabled editing directly inside the form:
          edit_table: false

Now locations.h gained:

    class LocationsModel : public QAbstractTableModel
    {
            Q_OBJECT
    public:
            LocationsModel(QObject *parent=0);
            int rowCount(const QModelIndex &parent = QModelIndex()) const;
            int columnCount(const QModelIndex &parent = QModelIndex() ) const;
            QVariant headerData(int section, Qt::Orientation orientation,
                                int role = Qt::DisplayRole) const;
            QVariant data(const QModelIndex &index, int role) const;
    };

If we turn on sort and edit_table, it would even gain more:

             // Sort support:
            virtual void sort(int column, Qt::SortOrder order=Qt::AscendingOrder);

            // In-Table edit support:
            void store(const QString &sign, const QString &code);
            Qt::ItemFlags flags(const QModelIndex &index) const;
            bool setData(const QModelIndex &index, const QVariant &value,
                         int role = Qt::EditRole);

I'm sparing me to replicate here the contents of locations.cpp. Let me just say that it now now contains 161 lines. Most of them, if not all, aren't really breathtaking. But I now have made mgv.py generate 161 lines that I didn't had to code (and debug) by myself. And that is breathtaking :-)

You could already use this model with some other view, but mvg.py can do a view for you as well:

Model attributes 

View code generation 

Now only the last piece is missing: conveniently generating a QTableView for us.

View definition 

First we add some data to locations.yaml:

    view:
        name: LocationsView
        type: QTableView
        # This would disable the automatically generated sort code of the view:
        #sort: false
        delete: true
        insert: true

This generates this header:

    class LocationsView : public QTableView
    {
            Q_OBJECT
    public:
            LocationsView(QWidget *parent=0);
    public slots:
            virtual void keyPressEvent(QKeyEvent *event);
    public slots:
            void slotEdit(const QModelIndex &index);
    };

And now you can use this like this:

    model = new LocationsModel(this);
    QLocationView view = new LocationView(this);
    view->setModel(model);
    view->show();

This is all you need to get this widget:

Table view generated by mvg.py

View attributes