我对 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 View 进行通信。我自己的 QAbstractItemModel 目前具有以下角色。
角色名称():
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[StartFrameRole] = "startFrame";
roles[EndFrameRole] = "endFrame";
return roles;
数据函数如下所示。
数据(const QModelIndex &index, int 角色):
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 中的数据。
索引(整数行,整数列,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
}
}
在第二列中,我基本上是在 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 文件。每个轨道也只是一个矩形,我尝试在其中创建新项目,它应该代表注释。在这里,我也尝试使用委托(delegate)。
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 中获取信息来计算每个注释的长度。
注释不仅仅是另一个矩形,具有一些操作可能性,可以将它们移动到轨道中的另一个帧或整个其他轨道,修剪它们和其他一些东西。
现在终于解决了我的问题。我可以像上面的例子一样构建时间线。示例中的黄色框在 gimp 中绘制。我无法显示注释,因为我不明白 QModelIndex 是如何创建的。每次进入QAbstractItemModel的data函数,只能从root之后的层获取TrackItems,所以就只有track层。 QtQuick 以什么方式与 QAbstractItemModel 通信?我以为我在 index 函数中得到了一行和一列,并且可以为每个 TrackItem 创建唯一的索引,这样我就可以在我的 data 函数中使用 getItem 函数获取正确的 TrackItem。不知何故,索引函数中的列始终为 0。我如何告诉我的模型在哪一层(根、轨道或注释),以便在 QML 中我可以在代表中获取正确的数据?
我希望我的问题足够清楚。这是我在这里的第一篇文章,所以如果它太长或不合时宜,我可能会道歉。我真的希望有人能帮助我解决这个问题。
最佳答案
在重新实现示例“可编辑树模型”时,我偶然发现了同样的问题。我的模型中有 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 函数做它应该做的事情 - 更改模型第二列中的值。
这是一种黑客行为,但仍然有效。我希望它对某人有用。
附言可编辑模型示例的唯一显着区别
我的实现是他们正在使用 C++ 中的模型
view->setModel(model)
我正在通过 qmlRegisterType 使用我的模型。这是我能想到的唯一区别。
关于c++ - QAbstractItemModel 与 QtQuick : Column is always 0 in the index,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42271955/