Qt嵌套模型视图

时间:2018-07-20 09:43:42

标签: c++ qt user-interface

我来自C#和WPF,在那里我们有一种很好的方式将集合绑定到视图,并通过当前的DataContext填充数据。

我一直在学习Qt,我知道有一种方法可以为视图提供模型(QAbstractItemModel),然后通过修改模型可以自动更新视图,这很不错,而且是我想要的。

我当前的问题是我想为以下类Definition

创建一个编辑器视图
class Definition
{
public:
    vector<Step*> Steps;

    bool SomeBoolMember1;
    bool SomeBoolMember2;
    int SomeIntMember1;
    int SomeIntMember2;
}

Step

class Step
{
public:
    vector<Requirement*> Requirements;

    bool SomeBoolMember1;
    bool SomeBoolMember2;
    int SomeIntMember1;
    int SomeIntMember2;
}

Requirement

class Requirement
{
public:
    RequirementType Type;
}

我的目标是建立一个视图,以能够修改Definition的集合。可能的视图可能是这样的:

enter image description here

当选择了不同的定义时,将更新选定的项目视图(以加载选定的定义的数据),然后我们将在滚动视图中列出步骤列表。每个步骤都列出了要求。

如您所见,这不是火箭科学,但我知道这可能很乏味。关于如何构造模型/视图模型以实现所需功能的任何提示?

1 个答案:

答案 0 :(得分:1)

您的问题很广泛。 Qt的模型非常笼统,有很多方法可以构造它们以实现相同的行为。我创建了一个简化的示例,说明如何构建它们。我只使用了DefinitionStep,将Requirement添加到Step类似于将Step添加到Definition ...

struct Step
{
    bool SomeBoolMember1 {false};
    int SomeIntMember1 {0};
};

struct Definition
{
    std::vector<std::shared_ptr<Step>> Steps;

    bool SomeBoolMember1 {false};
    int SomeIntMember1 {0};
};

struct Data
{
    std::vector<std::shared_ptr<Definition>> definitions;
};

模型非常简单,我为Definition创建了一个模型,为Step创建了一个模型。每个模型都有代表这些对象属性的列,模型的行代表对象的一个​​实例。 Definition的模型有一个Step的列,它返回给定Step的{​​{1}}的模型。

Definition

在此示例中,class StepModel : public QAbstractItemModel { Q_OBJECT using BaseClass = QAbstractItemModel; public: enum Columns { E_BOOL_1, E_INT_1, E_REQUIREMENT, _END }; StepModel(Definition* definition, QObject* parent) : BaseClass(parent), definition(definition) { } virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { return createIndex(row, column, nullptr); } virtual QModelIndex parent(const QModelIndex &child) const override { return QModelIndex(); } virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override { return definition->Steps.size(); } virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override { return _END; } virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (role != Qt::DisplayRole) return QVariant(); const auto& step = definition->Steps.at(index.row()); switch (index.column()) { case E_BOOL_1: return step->SomeBoolMember1; case E_INT_1: return step->SomeIntMember1; case E_REQUIREMENT: return QVariant(); } } virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override { if (role != Qt::EditRole) return false; auto& step = definition->Steps.at(index.row()); switch (index.column()) { case E_BOOL_1: step->SomeBoolMember1 = value.toBool(); return true; case E_INT_1: step->SomeIntMember1 = value.toInt(); return true; case E_REQUIREMENT: assert(false); return false; } } virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override { assert(count == 1); beginInsertRows(parent, row, row); definition->Steps.push_back(std::make_shared<Step>()); endInsertRows(); return true; } virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override { assert(count == 1); beginRemoveRows(parent, row, row); definition->Steps.erase(definition->Steps.begin() + row); endRemoveRows(); return true; } private: Definition* definition; }; Q_DECLARE_METATYPE(StepModel*) class DefinitionModel : public QAbstractItemModel { Q_OBJECT using BaseClass = QAbstractItemModel; public: enum Columns { E_NAME, E_BOOL_1, E_INT_1, E_STEPS, _END }; DefinitionModel(QObject* parent) : BaseClass(parent) { } virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { return createIndex(row, column, nullptr); } virtual QModelIndex parent(const QModelIndex &child) const override { return QModelIndex(); } virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override { return definitionData.definitions.size(); } virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override { return _END; } virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (role != Qt::DisplayRole) return QVariant(); const auto& definition = definitionData.definitions.at(index.row()); switch (index.column()) { case E_NAME: return QString("Definition %1").arg(index.row() + 1); case E_BOOL_1: return definition->SomeBoolMember1; case E_INT_1: return definition->SomeIntMember1; case E_STEPS: return QVariant::fromValue(new StepModel(definition.get(), nullptr)); } } virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override { if (role != Qt::EditRole) return false; auto& definition = definitionData.definitions.at(index.row()); switch (index.column()) { case E_BOOL_1: definition->SomeBoolMember1 = value.toBool(); return true; case E_INT_1: definition->SomeIntMember1 = value.toInt(); return true; default: assert(false); return false; } } virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override { assert(count == 1); beginInsertRows(parent, row, row); definitionData.definitions.push_back(std::make_shared<Definition>()); endInsertRows(); return true; } virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override { assert(count == 1); beginRemoveRows(parent, row, row); definitionData.definitions.pop_back(); endRemoveRows(); return true; } private: Data definitionData; }; 拥有DefinitionModel的实例,这不是您在实际应用中想要执行的操作,只是为了简单起见。

对话框有点复杂,因为在创建新实例时必须为Data动态创建窗口小部件。我为小部件创建了一个简单的映射,这些映射到其适当模型的列。每次选择新的定义时,都会通过此映射将数据从模型中加载回去。

Steps

Ui文件:

namespace Ui
{
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0)  :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);

        auto definitionModel = new DefinitionModel(this);
        ui->definitionView->setModel(definitionModel);

        connect(ui->definitionView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [=]
        {
            update();
        }, Qt::QueuedConnection);

        connect(ui->addDefinition, &QPushButton::pressed, this, [this]
        {
            auto* model = ui->definitionView->model();
            model->insertRow(model->rowCount());

            ui->definitionView->update();
            ui->definitionView->setCurrentIndex(model->index(model->rowCount() - 1, 0));
        });
        connect(ui->removeDefinition, &QPushButton::pressed, this, [this]
        {
            auto* model = ui->definitionView->model();
            if (model->rowCount() > 0)
            {
                const int row = model->rowCount() - 1;
                for (const auto& widget : steps[row])
                {
                    unmap(widget);
                }
                steps.erase(steps.find(row));
                model->removeRow(row);
            }
        });

        auto getCurrentDefinition = [this] { return ui->definitionView->currentIndex().row(); };
        map(ui->definitionInt, definitionModel, DefinitionModel::E_INT_1, getCurrentDefinition);
        map(ui->definitionBool, definitionModel, DefinitionModel::E_BOOL_1, getCurrentDefinition);

        connect(ui->addStep, &QPushButton::pressed, this, [=]
        {
            if (getCurrentDefinition() == -1) return;
            auto widget = new QWidget(this);
            auto layout = new QHBoxLayout(widget);
            auto checkBox = new QCheckBox(widget);
            layout->addWidget(checkBox);
            auto spinBox = new QSpinBox(widget);
            layout->addWidget(spinBox);
            auto removeButton = new QPushButton(widget);
            removeButton->setText("remove");
            layout->addWidget(removeButton);
            ui->rightLayout->addWidget(widget);

            const int currentDefinition = getCurrentDefinition();
            steps[currentDefinition].push_back(widget);

            auto model = definitionModel->data(definitionModel->index(currentDefinition, DefinitionModel::E_STEPS), Qt::DisplayRole).value<QAbstractItemModel*>();
            model->setParent(widget);
            const int rowCount = model->rowCount();
            model->insertRow(rowCount);

            auto getRow = [=]
            {
                auto it = std::find(steps[currentDefinition].begin(), steps[currentDefinition].end(), widget);
                return std::distance(steps[currentDefinition].begin(), it);
            };
            map(checkBox, model, StepModel::E_BOOL_1, getRow);
            map(spinBox, model, StepModel::E_INT_1, getRow);

            connect(ui->definitionView->selectionModel(), &QItemSelectionModel::currentRowChanged, widget, [=] (const QModelIndex& current)
            {
                widget->setVisible(current.row() == currentDefinition);
            });

            connect(removeButton, &QPushButton::pressed, widget, [=]
            {
                model->removeRow(rowCount);
                unmap(checkBox);
                unmap(spinBox);
                ui->rightLayout->removeWidget(widget);
                auto it = std::find(steps[getCurrentDefinition()].begin(), steps[getCurrentDefinition()].end(), widget);
                steps[currentDefinition].erase(it);
                delete widget;
            });

            update();
        });
    }

    ~MainWindow()
    {
        delete ui;
    }

private:

    void map(QCheckBox* checkBox, QAbstractItemModel* model, int column, std::function<int()> getRow)
    {
        connect(checkBox, &QCheckBox::toggled, this, [=] (bool value)
        {
            model->setData(model->index(getRow(), column), value, Qt::EditRole);
        });

        auto update = [=]
        {
            checkBox->setChecked(model->data(model->index(getRow(), column), Qt::DisplayRole).toBool());
        };

        mapping.emplace(checkBox, update);
    }

    void map(QSpinBox* spinBox, QAbstractItemModel* model, int column, std::function<int()> getRow)
    {
        connect(spinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [=] (int value)
        {
            model->setData(model->index(getRow(), column), value, Qt::EditRole);
        });

        auto update = [=]
        {
            spinBox->setValue(model->data(model->index(getRow(), column), Qt::DisplayRole).toInt());
        };

        mapping.emplace(spinBox, update);
    }

    void unmap(QWidget* widget)
    {
        mapping.erase(mapping.find(widget));
    }

    void update() const
    {
        for (const auto& pair : mapping)
        {
            pair.second();
        }
    }

    Ui::MainWindow *ui;
    std::map<QWidget*, std::function<void()>> mapping;
    std::map<int, std::vector<QWidget*>> steps;
};