【问题标题】:QAbstractItemModel with QtQuick: Column is always 0 in the index使用 QtQuick 的 QAbstractItemModel:索引中的列始终为 0
【发布时间】:2017-07-05 10:42:58
【问题描述】:

我对 QML 感到很困惑。几周以来,我尝试使用 QML 为视频中的注释内容实现时间线,但我无法真正实现它,因为我对 QML 还很陌生。

我会尽力帮助您解决我的问题。这是一个示例,时间线应如下所示: Timeline example 我得到了不同的轨道,我在其中存储了不同的注释,这些注释简单地表示,从开始到结束,视频包含给定轨道的注释。例如,如果我对包含阳光图像的视频中的所有场景进行注释,则每个注释框都会标记视频中包含阳光图像的场景。

例如,我计划通过 XML 文件保存和获取这些信息。一个可能的例子是:

<root length="800" filename="tralala.mp4">
  <track name="sunny">
    <annotation start="20" end="50"/>
    <annotation start="70" end="120"/>
    ...
  </track>
  <track name="cloudy">
    ...
  </track>
</root>

为了将数据放入我以后可以使用的模型中,我使用如下方法解析文件:

readModelFromXML():

QFile xmlFile(_filename);
xmlFile.open(QIODevice::ReadOnly);
xml.setDevice(&xmlFile);

TrackItem* root;
while(!xml.atEnd() && !xml.hasError())
{
    QXmlStreamReader::TokenType token = xml.readNext();
    if(token == QXmlStreamReader::StartDocument)
            continue;

    if(token == QXmlStreamReader::StartElement)
    {
       if(xml.name() == "root")
       {
           QMap<QString, QVariant> itemData;
           itemData["length"] = xml.attributes().value("length").toInt();
           itemData["filename"] = xml.attributes().value("filename").toString();
           root = new TrackItem(itemData);
       }
       else if(xml.name() == "track")
       {
           QMap<QString, QVariant> itemData;
           itemData["name"] = xml.attributes().value("name").toString();
           TrackItem* track = new TrackItem(itemData, root);
           root->insertChildren(root->childCount(), track);
       }
       else if(xml.name() == "annotation")
       {
           QMap<QString, QVariant> itemData;
           itemData["start"] = xml.attributes().value("start").toInt();
           itemData["end"] = xml.attributes().value("end").toInt();
           TrackItem* parent = root->child(root->childCount() - 1);
           TrackItem* annotation = new TrackItem(itemData, parent);
           parent->insertChildren(parent->childCount(), annotation);
       }
   }
}

其中一个 TrackItem 包含一个 QList 及其子项,一个带有存储数据的 QMap 以及可能的父表单类型 TrackItem。所以我的数据看起来很像一棵树,它有一个没有父对象的根 TrackItem 对象,因为它存储了长度和文件名的数据,并且作为它的子对象,它具有不同轨道的 TrackItems。 轨道 TrackItems 将根对象作为其父对象,并且仅存储轨道的名称。每个轨道都具有作为其子项存储为 itemData 的起点和终点的注释。

TrackItem.h:

public:
explicit TrackItem(QMap<QString, QVariant> &data, TrackItem *parent = 0);
~TrackItem();

some functions for getting childs, inserting childs and so on

private:
QList<TrackItem*> childItems;
QMap<QString, QVariant> itemData;
TrackItem *parentItem;

所以现在我们越来越接近我的问题了。我制作了自己的 QAbstractItemModel 实现来与我的 QtQuick 视图进行通信。我自己的 QAbstractItemModel 目前有以下角色。

角色名称():

QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[StartFrameRole] = "startFrame";
roles[EndFrameRole] = "endFrame";
return roles;

数据函数如下所示。

data(const QModelIndex &index, int role):

if (!index.isValid())
    return QVariant();

TrackItem *item = getItem(index);

if (role == NameRole)
    return item->data("name");
else if (role == StartFrameRole)
    return item->data("start");
else if (role == EndFrameRole)
    return item->data("end");

return QVariant();

使用 getItem(const QModelIndex &index):

if (index.isValid()) {
    TrackItem *item = static_cast<TrackItem*>(index.internalPointer());
    if (item)
        return item;
}
return rootItem;

和 TrackItem::data(QString key) 返回存储在 TrackItem 的 QMap itemData 中的数据。

index(int row, int column, const QModelIndex &parent):

if (parent.isValid() && parent.column() != 0)
    return QModelIndex();

TrackItem *parentItem = getItem(parent);
TrackItem *childItem = parentItem->child(row);

if (childItem)
    return createIndex(row, column, childItem);
else
    return QModelIndex();

所以在索引中我尝试创建 TrackItems 的索引。

C++ 方面就这么多了。现在我将介绍一下我的 QML 代码并解决我的问题。所以在程序的 QML 端,我有一个名为timeline 的QML 文件,我在自己的QWidget 的构造函数中设置了它,它代表了上面示例的外观。

从QWidget派生的TimelineWidget构造函数:

sharedEngine_ = new QQmlEngine(this);
quickWidget_ = new QQuickWidget(sharedEngine_, this);

QQmlContext *context = quickWidget_->rootContext();
context->setContextProperty("timeline", this);

model_ = new TrackModel(":/resources/example.txt", context);

context->setContextProperty("trackmodel", model_);

quickWidget_->setSource(QUrl::fromLocalFile("qml/timeline.qml"));
ui->layout->addWidget(quickWidget_);

如您所见,此时我还创建了 QAbstractItemModel 并将其设置为 QML 上下文的 contect 属性,因此我可以在 QML 中使用我的模型。

所以基本上我的时间线 QML 文件是一个包含两列的矩形。在第一个中,我通过中继器在上面的上下文属性“trackmodel”上创建具有轨道名称的轨道头。

Repeater {
  id: headerRepeater
   model: trackmodel
    TrackHead {
       label: model.name
        width: headerWidth
        height: 50
        selected: false
    }
}

在第二列中,我基本上比在滚动视图中创建每个轨道:

Item {
  width: tracksContainer.width + headerWidth
  height: headers.height + 30
  Column {
    id: tracksContainer
    Repeater {
      id: tracksRepeater
      model: trackDelegateModel
    }
  }
}

在这里,我使用了一个 DelegateModel,我尝试在其中构建各个轨道。

DelegateModel {
  id: trackDelegateModel
  model: trackmodel
  Track {
    model: trackmodel
    trackId: index
    height: 50
    width: timelineLength
    ...
    also here are some "slots"  
  }
}

现在转到 Track QML 文件。每个轨道也只是一个矩形,我尝试在其中创建新项目,它应该代表注释。在这里,我也尝试使用委托。

Item {
  Repeater { id: annotationRepeater; model: trackModel }
}

使用这个 DelegateModel:

DelegateModel {
  id: trackModel
  Annotation {
    myModel: model
    trackIndex: trackId
    height: 15
    width: model.endFrame - model.startFrame
    x: model.startFrame
    y: 17.5
    ...
    like before here are also some "slots"
  }
}

所以此时我尝试通过 startFrame 和 endFrame 角色从 QAbstractItemModel 中获取信息,以计算每个注释的长度。 Annotation 不仅仅是另一个 Rectangle,它具有一些操作可能性,可以将它们移动到轨道中的另一个帧或整个其他轨道,修剪它们和其他一些东西。

现在终于解决我的问题了。我可以像上面的例子一样构建时间线。示例中的黄色框在 gimp 中绘制。我无法显示注释,因为我不明白 QModelIndex 是如何创建的。每次进入QAbstractItemModel的数据函数,只能从根之后的层获取TrackItems,所以只有track层。 QtQuick 以什么方式与 QAbstractItemModel 通信?我认为我在索引函数中获得了一行和一列,并且可以为每个 TrackItem 创建唯一的索引,这样我就可以在我的数据函数中使用 getItem 函数获取正确的 TrackItem。不知何故,索引函数中的列始终为 0。如何告诉我的模型我在哪一层(根、轨道或注释),以便在 QML 中我可以在代表中获取正确的数据? 我希望我的问题足够清楚。这是我在这里的第一篇文章,所以如果它太长或不合时宜,我可能会道歉。我真的希望,有人可以帮助我解决这个问题。

【问题讨论】:

  • 你的问题格式很好,信息量很大,但标题不好。如果您的标题更多地与您的问题有关,它使人们更容易回答和查找您的帖子,例如:“索引函数中的列始终为 0”这只是一个建议,无论如何您的帖子都很好。希望你能得到答案
  • 是的,你是对的:QML 只使用模型的第一列。但是,您可以为每个轨道提供一个角色,其中包含一个包含数据的对象。另一个想法是,通过 ProxyModel 对列进行重新排序,让您的模型始终排在第一位。
  • 即使是 QML 的 TableModel 也只使用模型的一列。它在每个视图列的第一个模型列中使用一个 role
  • 所以我可以正确地假设,不可能像 editable tree model example 那样使用带有 QML 的树形模式?我的模型基于此示例,因为我认为将整个时间线组织在树中是一个好主意,其中根的子节点是轨道,轨道的子节点是注释。
  • 如果您查看Simple Tree Model Example,您可以看到,该列在索引函数中并不总是 0。所以必须有某种方式来访问我模型中的其他列,还是我完全错了? TreeView QML 类型是如何做到这一点的?

标签: c++ qml timeline qabstractitemmodel


【解决方案1】:

在重新实现示例“可编辑树模型”时,我偶然发现了同样的问题。我的模型中有 2 个列。当我点击第二列 selection.currentIndex.column 总是返回 0 时,styleData.index.column 属性也是如此。但是当我点击第二列时,styleData.column 给了我 1。所以我在 QML 中构建了我需要的索引。

var currentIndex = myTreeModel.index(styleData.index.row, styleData.column, styleData.index.parent)
var success = myTreeModel.setData( currentIndex , loaderEditor.item.text, 2 )

之后,我让我的 setData 函数执行它应该执行的操作 - 更改模型第二列中的值。 这是一种 hack,但它仍然有效。我希望它对某人有用。

附:可编辑模型示例之间的唯一显着区别 我的实现是他们正在使用 C++ 中的模型

  view->setModel(model)

我通过 qmlRegisterType 使用我的模型。这是我能想到的唯一区别。

【讨论】:

    猜你喜欢
    • 2014-01-29
    • 1970-01-01
    • 1970-01-01
    • 2016-03-13
    • 2016-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-08
    相关资源
    最近更新 更多