Qml 2.0 TableView与QAbstractItemModel和上下文菜单 [英] Qml 2.0 TableView with QAbstractItemModel and Context Menu
问题描述
我有来自连接到 QAbstractItemModel
的Qml 2.0控件的 TableView
。我想创建一个上下文菜单,可以更改属性或从模型中调用具体对象的方法。
示例: p>
QAbstractItemModel
有一个 std :: vector< Person>
。 Person
有一个方法 alter()
,它会做一些更改(任何更改, ,点是我们能够调用该方法)。
当右键点击该行时,菜单出现一个修改
。
我能找到的是如何进行菜单。
rowDelegate:Item {
Menu {
id:myContextMenu
MenuItem {text:Alter; onTriggered:{}}
}
MouseArea {
id:longPressArea
anchors.fill:parent
acceptedButtons:Qt.LeftButton | Qt.RightButton
onClicked:{
if(mouse.button == Qt.RightButton)
myContextMenu.popup()
}
}
}
但是我还是不知道如何连接菜单与行的确切对象。 p>
在代理中,您可以使用 role.property
约定。
默认角色为 display
。当然, Person
必须从 QObject
派生,并且必须向QML引擎注册。
下面的代码演示了如何:
-
创建一个明智的行为
ObjectListModel
用于存储可从QML使用的QObjects
。 -
创建保存您的数据的
QObject
- 派生类。 -
访问属性和可调用方法
模型可以设置为自动通知变更到包含的QObject的属性。此类通知(如果由批量更改(例如循环完成)导致)合并并作为单个 dataChanged
事件发送。
$ b $不幸的是,
QObject
的用户属性没有获得任何特殊含义 - 您仍然需要使用 .property
选择器来访问它。 可以直接观察模型的正常行为,因为有两个列表挂钩到同一个模型 - 他们更好地显示相同的事情。 / p>
ObjectListModel
也可以实现角色和属性之间的映射。目前,显示和编辑角色选择整个对象,而不是它的任何特定属性。
如果存储 QObject
s是太高的开销,一个替代模型实现可以创建 QObject
适配器到POD类型。
main.cpp
#include< QGuiApplication>
#include< QQmlApplicationEngine>
#include< QQuickWindow>
#include< QAbstractListModel>
#include< QQmlContext>
#include< QtQml>
#include< QSet>
#include& lt; QBasicTimer>
#include< functional>
class Person:public QObject {
Q_OBJECT
Q_PROPERTY(QString name NOTIFY nameChanged MEMBER m_name)
QString m_name;
public:
Q_INVOKABLE Person(QObject * parent = 0):QObject(parent){setRandomName(); }
Q_INVOKABLE Person(QString name,QObject * parent = 0):
QObject(parent),m_name(name){}
Q_SIGNAL void nameChanged(const QString&);
Q_INVOKABLE void setRandomName(){
static const QString names =Badger,Shopkeeper,Pepperpots,Gumbys,Colonel;
static const QStringList nameList = names.split(',');
QString newName = nameList.at(qrand()%nameList.length());
if(newName!= m_name){
m_name = newName;
emit nameChanged(m_name);
}
}
};
class ObjectListModel:public QAbstractListModel {
Q_OBJECT
Q_DISABLE_COPY(ObjectListModel)
//!是否通过`dataChanged`信号暴露底层对象的变化
Q_PROPERTY(bool elementChangeTracking
READ elementChangeTracking WRITE setElementChangeTracking
NOTIFY elementChangeTrackingChanged)
QObjectList m_data;
std :: function< QObject *()> m_factory;
bool m_tracking;
QBasicTimer m_notifyTimer;
QMap< int,char> m_notifyIndexes;
//!更新给定对象上的属性跟踪连接。
void updateTracking(QObject * obj){
const int nIndex = metaObject() - > indexOfSlot(propertyNotification());
QMetaMethod const nSlot = metaObject() - > method(nIndex);
const int props = obj-> metaObject() - > propertyCount();
if(m_tracking)for(int i = 0; i const QMetaProperty prop = obj-> metaObject()
if(prop.hasNotifySignal())connect(obj,prop.notifySignal(),this,nSlot);
} else {
disconnect(obj,0,this,0);
}
}
//!接收属性通知更改
Q_SLOT void propertyNotification(){
int i = m_data.indexOf(sender());
if(i> = 0)m_notifyIndexes.insert(i,0);
//所有通知将作为单个信号从事件循环发送。
if(!m_notifyTimer.isActive())m_notifyTimer.start(0,this);
}
protected:
//!发出对基础QObject属性所做的更改的通知
void timerEvent(QTimerEvent * ev){
if(ev-> timerId()!= m_notifyTimer.timerId())return;
emit dataChanged(index(m_notifyIndexes.begin()。key()),
index((m_notifyIndexes.end() - 1).key()),
QVector< int> 1,Qt :: DisplayRole));
m_notifyTimer.stop();
m_notifyIndexes.clear();
}
public:
//!通过给定的元对象创建实例的模型
ObjectListModel(const QMetaObject * mo,QObject * parent = 0):
QAbstractListModel(parent),
m_factory([mo,this]
return mo-> newInstance(Q_ARG(QObject *,this));
}),
m_tracking(false)
{}
//!使用工厂函数创建实例的模型
ObjectListModel(const std :: function< QObject *()&& factory,
QObject * parent = 0):
QAbstractListModel ,m_factory(factory),m_tracking(false)
{}
〜ObjectListModel(){
qDeleteAll(m_data);
}
bool elementChangeTracking()const {return m_tracking; }
void setElementChangeTracking(bool tracking){
if(m_tracking == tracking)return;
for(QObject * obj:m_data)updateTracking(obj);
emit elementChangeTrackingChanged(m_tracking = tracking);
}
QSIGNAL void elementChangeTrackingChanged(bool);
int rowCount(const QModelIndex&)const Q_DECL_OVERRIDE {
return m_data.count();
}
QVariant数据(const QModelIndex& index,int role)const Q_DECL_OVERRIDE {
if(role == Qt :: DisplayRole || role == Qt :: EditRole){
return QVariant :: fromValue(m_data.at(index.row()));
}
return QVariant();
}
bool setData(const QModelIndex& index,const QVariant& value,int role)
Q_DECL_OVERRIDE {
Q_UNUSED
QObject * object = value.value< QObject *>();
if(!object)return false;
if(object == m_data.at(index.row()))return true;
delete m_data.at(index.row());
m_data [index.row()] = object;
emit dataChanged(index,index,QVector< int>(1,role)
return true;
}
Q_INVOKABLE bool insertRows(int row,int count,
const QModelIndex& parent = QModelIndex())
Q_DECL_OVERRIDE {
Q_UNUSED
beginInsertRows(QModelIndex(),row,row + count - 1);
for(int i = row; i< row + count; ++ i){
QObject * object = m_factory
Q_ASSERT(object);
m_data.insert(i,object);
updateTracking(object);
QQmlEngine :: setObjectOwnership(object,QQmlEngine :: CppOwnership);
}
endInsertRows();
return true;
}
Q_INVOKABLE bool removeRows(int row,int count,
const QModelIndex& parent = QModelIndex())
Q_DECL_OVERRIDE {
Q_UNUSED
beginRemoveRows(QModelIndex(),row,row + count - 1);
while(count--)delete m_data.takeAt(row);
endRemoveRows();
return true;
}
};
int main(int argc,char * argv [])
{
QGuiApplication app(argc,argv);
QQmlApplicationEngine engine;
qmlRegisterType< Person>();
ObjectListModel model1(& Person :: staticMetaObject);
model1.setElementChangeTracking(true);
model1.insertRows(0,1);
engine.rootContext() - > setContextProperty(model1,& model1);
engine.load(QUrl(qrc:/main.qml));
QObject * topLevel = engine.rootObjects()。value(0);
QQuickWindow * window = qobject_cast window-> show();
return app.exec();
}
#includemain.moc
strong> main.qrc
< RCC>
< qresource prefix =/>
< file> main.qml< / file>
< / qresource>
< / RCC>
main.qml
import QtQuick 2.0
import QtQml.Models 2.1
import QtQuick.Controls 1.0
ApplicationWindow {
width: 300; height:300
Row {
width:parent.width
anchors.top:parent.top
anchors.bottom:row2.top
组件{
id:commonDelegate
Rectangle {
width:view.width
implicitHeight:editor.implicitHeight + 10
border.color:red
border.width:2
radius:5
TextInput {
id:editor
anchors.margins:1.5 * parent.border.width
anchors.fill:parent
text:edit .name //edit模型的角色,打破绑定循环
onTextChanged:{
display.name = text; //设置数据对象的名称属性
}
}
菜单{
id:myContextMenu
MenuItem {text:Randomize; onTriggered:display.setRandomName()}
MenuItem {text:Remove; onTriggered:model1.removeRows(index,1)}
}
MouseArea {
id:longPressArea
anchors.fill:parent
acceptedButtons:Qt.RightButton
onClicked:myContextMenu.popup()
}
}
}
spacing:2
ListView {
id:view
width: parent.width - parent.spacing)/ 2
height:parent.height
model:DelegateModel {
id:delegateModel1
model:model1
delegate:commonDelegate
}
spacing:2
}
ListView {
width:(parent.width - parent.spacing)/ 2
height:parent.height
model:DelegateModel {
model:model1
delegate:commonDelegate
}
spacing:2
}
}
Row {
id:row2
anchors.bottom:parent.bottom
Button {
text:Add Page;
onClicked:model1.insertRows(delegateModel1.count,1)
}
}
}
I have TableView
from Qml 2.0 controls that is connected to QAbstractItemModel
. I want to make a context menu that can change the properties or simply call methods of a concrete object from the model.
Example:
QAbstractItemModel
has an std::vector<Person>
. Person
has a method alter()
which makes some changes (any changes, it does not matter which ones exactly are changes, the point is that the we are able to call the method).
When there is a right click on the row, the menu appears with an item Alter
.
All I was able to find is how to make the menu.
rowDelegate: Item {
Menu {
id: myContextMenu
MenuItem {text: "Alter"; onTriggered: {} }
}
MouseArea {
id: longPressArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button == Qt.RightButton)
myContextMenu.popup()
}
}
}
But I still don't know how to connect the menu with an exact object of the row.
Within the delegate, you can refer to the item by using the role.property
convention.
The default role would be display
. Of course the Person
has to derive from QObject
, and must be registered with the QML Engine.
The code below demonstrates how to:
Create a sensibly behaving
ObjectListModel
for storage ofQObjects
, usable from QML.Create a
QObject
-derived class that keeps your data.Access the properties and invokable methods of the data objects from a pop-up menu shown on the delegate.
The model can be set to automatically notify about changes to the properties of the contained QObjects. Such notifications, if resulting from bulk changes (say done in a loop) are coalesced and sent off as a single dataChanged
event.
Unfortunately, the user property of a QObject
acquires no special meaning - you still need to use the .property
selector to access it.
Proper behavior of the model can be observed directly, since there are two lists hooked to the same model - they better showed the same thing.
The ObjectListModel
could also implement a mapping between the roles and the properties. Currently, both the display and edit roles select the entire object, not any particular property of it.
If the storage of QObject
s is too high of an overhead, an alternative model implementation could create QObject
adapters to POD types on-the-fly.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QAbstractListModel>
#include <QQmlContext>
#include <QtQml>
#include <QSet>
#include <QBasicTimer>
#include <functional>
class Person : public QObject {
Q_OBJECT
Q_PROPERTY(QString name NOTIFY nameChanged MEMBER m_name)
QString m_name;
public:
Q_INVOKABLE Person(QObject * parent = 0) : QObject(parent) { setRandomName(); }
Q_INVOKABLE Person(QString name, QObject * parent = 0) :
QObject(parent), m_name(name) {}
Q_SIGNAL void nameChanged(const QString &);
Q_INVOKABLE void setRandomName() {
static const QString names = "Badger,Shopkeeper,Pepperpots,Gumbys,Colonel";
static const QStringList nameList = names.split(',');
QString newName = nameList.at(qrand() % nameList.length());
if (newName != m_name) {
m_name = newName;
emit nameChanged(m_name);
}
}
};
class ObjectListModel : public QAbstractListModel {
Q_OBJECT
Q_DISABLE_COPY(ObjectListModel)
//! Whether changes to underlying objects are exposed via `dataChanged` signals
Q_PROPERTY(bool elementChangeTracking
READ elementChangeTracking WRITE setElementChangeTracking
NOTIFY elementChangeTrackingChanged)
QObjectList m_data;
std::function<QObject*()> m_factory;
bool m_tracking;
QBasicTimer m_notifyTimer;
QMap<int, char> m_notifyIndexes;
//! Updates the property tracking connections on given object.
void updateTracking(QObject* obj) {
const int nIndex = metaObject()->indexOfSlot("propertyNotification()");
QMetaMethod const nSlot = metaObject()->method(nIndex);
const int props = obj->metaObject()->propertyCount();
if (m_tracking) for (int i = 0; i < props; ++i) {
const QMetaProperty prop = obj->metaObject()->property(i);
if (prop.hasNotifySignal()) connect(obj, prop.notifySignal(), this, nSlot);
} else {
disconnect(obj, 0, this, 0);
}
}
//! Receives property notification changes
Q_SLOT void propertyNotification() {
int i = m_data.indexOf(sender());
if (i >= 0) m_notifyIndexes.insert(i, 0);
// All of the notifications will be sent as a single signal from the event loop.
if (!m_notifyTimer.isActive()) m_notifyTimer.start(0, this);
}
protected:
//! Emits the notifications of changes done on the underlying QObject properties
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_notifyTimer.timerId()) return;
emit dataChanged(index(m_notifyIndexes.begin().key()),
index((m_notifyIndexes.end()-1).key()),
QVector<int>(1, Qt::DisplayRole));
m_notifyTimer.stop();
m_notifyIndexes.clear();
}
public:
//! A model that creates instances via a given metaobject
ObjectListModel(const QMetaObject * mo, QObject * parent = 0) :
QAbstractListModel(parent),
m_factory([mo, this](){
return mo->newInstance(Q_ARG(QObject*, this));
}),
m_tracking(false)
{}
//! A model that creates instances using a factory function
ObjectListModel(const std::function<QObject*()> & factory,
QObject * parent = 0) :
QAbstractListModel(parent), m_factory(factory), m_tracking(false)
{}
~ObjectListModel() {
qDeleteAll(m_data);
}
bool elementChangeTracking() const { return m_tracking; }
void setElementChangeTracking(bool tracking) {
if (m_tracking == tracking) return;
for (QObject* obj : m_data) updateTracking(obj);
emit elementChangeTrackingChanged(m_tracking = tracking);
}
Q_SIGNAL void elementChangeTrackingChanged(bool);
int rowCount(const QModelIndex &) const Q_DECL_OVERRIDE {
return m_data.count();
}
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE {
if (role == Qt::DisplayRole || role == Qt::EditRole) {
return QVariant::fromValue(m_data.at(index.row()));
}
return QVariant();
}
bool setData(const QModelIndex &index, const QVariant &value, int role)
Q_DECL_OVERRIDE {
Q_UNUSED(role);
QObject* object = value.value<QObject*>();
if (!object) return false;
if (object == m_data.at(index.row())) return true;
delete m_data.at(index.row());
m_data[index.row()] = object;
emit dataChanged(index, index, QVector<int>(1, role));
return true;
}
Q_INVOKABLE bool insertRows(int row, int count,
const QModelIndex &parent = QModelIndex())
Q_DECL_OVERRIDE {
Q_UNUSED(parent);
beginInsertRows(QModelIndex(), row, row + count - 1);
for (int i = row; i < row + count; ++ i) {
QObject * object = m_factory();
Q_ASSERT(object);
m_data.insert(i, object);
updateTracking(object);
QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
}
endInsertRows();
return true;
}
Q_INVOKABLE bool removeRows(int row, int count,
const QModelIndex &parent = QModelIndex())
Q_DECL_OVERRIDE {
Q_UNUSED(parent);
beginRemoveRows(QModelIndex(), row, row + count - 1);
while (count--) delete m_data.takeAt(row);
endRemoveRows();
return true;
}
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<Person>();
ObjectListModel model1(&Person::staticMetaObject);
model1.setElementChangeTracking(true);
model1.insertRows(0, 1);
engine.rootContext()->setContextProperty("model1", &model1);
engine.load(QUrl("qrc:/main.qml"));
QObject *topLevel = engine.rootObjects().value(0);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
window->show();
return app.exec();
}
#include "main.moc"
main.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>
main.qml
import QtQuick 2.0
import QtQml.Models 2.1
import QtQuick.Controls 1.0
ApplicationWindow {
width: 300; height: 300
Row {
width: parent.width
anchors.top: parent.top
anchors.bottom: row2.top
Component {
id: commonDelegate
Rectangle {
width: view.width
implicitHeight: editor.implicitHeight + 10
border.color: "red"
border.width: 2
radius: 5
TextInput {
id: editor
anchors.margins: 1.5 * parent.border.width
anchors.fill: parent
text: edit.name // "edit" role of the model, to break the binding loop
onTextChanged: {
display.name = text; // set the name property of the data object
}
}
Menu {
id: myContextMenu
MenuItem { text: "Randomize"; onTriggered: display.setRandomName() }
MenuItem { text: "Remove"; onTriggered: model1.removeRows(index, 1) }
}
MouseArea {
id: longPressArea
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: myContextMenu.popup()
}
}
}
spacing: 2
ListView {
id: view
width: (parent.width - parent.spacing)/2
height: parent.height
model: DelegateModel {
id: delegateModel1
model: model1
delegate: commonDelegate
}
spacing: 2
}
ListView {
width: (parent.width - parent.spacing)/2
height: parent.height
model: DelegateModel {
model: model1
delegate: commonDelegate
}
spacing: 2
}
}
Row {
id: row2
anchors.bottom: parent.bottom
Button {
text: "Add Page";
onClicked: model1.insertRows(delegateModel1.count, 1)
}
}
}
这篇关于Qml 2.0 TableView与QAbstractItemModel和上下文菜单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!