带QtQuick的QAbstractItemModel:索引

时间:2017-02-16 10:58:11

标签: c++ qml timeline qabstractitemmodel

我对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&amp; 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&amp; 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&amp; 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的构造函数中设置,它代表了上面例子的外观。

TimelineWidget构造函数派生自QWidget:

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
    }
}

在第二列中,我主要是在scrollView中创建每个曲目:

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,所以只有轨道图层。 QtQuick以什么方式与QAbstractItemModel通信?我认为我在index函数中得到了一个行和列,并且可以为每个TrackItem创建唯一索引,这样我就可以在我的数据函数中使用getItem函数获取正确的TrackItem。不知何故,列在索引函数中始终为0。如何告诉我的模型我在哪个层(根,轨道或注释),以便在QML中我可以在代理中获取正确的数据? 我希望我的问题很清楚。这是我在这里发表的第一篇文章,所以如果是长期或不合格,我可能会道歉。我真的希望,有人可以帮我解决这个问题。

1 个答案:

答案 0 :(得分:1)

在重新实现示例&#34;可编辑树模型&#34;时,我偶然发现了同样的问题。我的模型中有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函数来做它应该做的事情 - 更改模型第二列中的值。 这是一种黑客攻击,但仍然有效。我希望它对某些人有用。

P.S。可编辑模型示例之间唯一的显着差异 我的实现是他们正在使用C ++方面的模型

  view->setModel(model)

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