Edit a QstandardItem model exposed to treeview - c++

I want to edit the model contents when clicked.
I am able to see the contents but when clicked i want to edit it.
I also want to view the clicked index.
Is there any way ?
Is there any way ?
Or is there any other method to introduce a tree model and make it's content editable?
h file
==========
class TreeView : public QStandardItemModel
{
Q_OBJECT
public:
TreeView(QObject *parent = nullptr);
Q_INVOKABLE void add_header_row();
};
cpp file
===========
QStandardItem *rootItem = new QStandardItem;
QStandardItem *group1 = new QStandardItem;
QStandardItem *group2 = new QStandardItem;
setColumnCount(1);
rootItem = invisibleRootItem();
group1->setText("Heading");
group2->setText("Sub Heading");
group1->appendRow(group2);
rootItem->appendRow(group1);
main cpp file
=========
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
TreeView myModel;
engine.rootContext()->setContextProperty("myModel", &myModel);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
qml file
==========
TreeView{
id: application_view
width: mainWindow.width / 2
height: mainWindow.height
model: myModel
backgroundVisible: true
headerVisible: false
selectionMode: SelectionMode.SingleSelection
TableViewColumn{
role: "display"
}
}

Related

Connect qml object property changes to signal/slot mechanism

I got this basic project:
main.qml
Rectangle{
Text {
id: text1
objectName: "showStr"
property string _textField: "passive"
text: _textField
}
Button{
id: _button
signal buttClick(string str)
anchors.top: text1.bottom
text: "Button"
onClicked:
{
_button.buttClick("state: active")
}
}
}
myClass.cpp
class myClass : public QObject{
Q_OBJECT
public:
explicit myClass(QObject *parent = nullptr): QObject(parent) {}
public slots:
void setTextProperty(const QString& s) {this->str = s;}
void getStrToQObj()
{
//TODO: set updated str into qml
}
signals:
void strChanged(const QString& time);
private:
QString str;
};
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlComponent component(&engine,
QUrl(QLatin1String("../myProj/qml/main.qml")));
QObject* item = component.create();
QObject* showTime = item->findChild<QObject*>("showTime");
QObject* butt = item->findChild<QObject*>("start");
myClass mc(item);
QObject::connect(butt, SIGNAL(buttClick(QString)),
&mc, SLOT(setTextProperty(QString)));
return app.exec();
}
Connection of Qml signal buttClick to setTextProperty(const QString& s) works fine and myClass::str is changed.The question is how to connect Text property _textField to update every time, when myClass::str is changed?
Do not export objects from QML to C++, instead do the opposite: export the C ++ objects to QML since this way the connection is simple and there is no need to use the old connection syntax:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
myClass mc;
engine.rootContext()->setContextProperty("mc", &mc);
QQmlComponent component(&engine,
QUrl(QLatin1String("../myProj/qml/main.qml")));
QObject* item = component.create();
return app.exec();
}
Rectangle {
Text {
id: text1
text: "passive"
}
Button {
id: _button
anchors.top: text1.bottom
text: "Button"
onClicked: {
mc.setTextProperty("state: active");
}
}
Connections {
function onTimeChanged(text) {
text1.text = text;
}
target: mc
}
}
It is old syntax of connection, but according to separations logiv from GUI, and for ones who do not know JS well,turns out you can call parent() method to you class because it is inherit QObject class. This return to you Qobject* you put in constructor of your class, and then you can findChild and work with it:
void myClass::f(){
//do fancy stuff with your myClass::str
emit activityChanged(this->parent())
}
void getStrToQObj(QObject* qo)
{
auto tmp = qo->findChild<QObject*>("showTime");
tmp->setProperty("_textField", this->str);
}
And connect this in main.cpp like:
QObject::connect(&mc, SIGNAL(strChanged(QObject*)),
&mc, SLOT(getStrToQObj(QObject*)));

Best way to create Elements dynamically in QML based on C++ QMap

This seemed like a simple task at first, but honestly I am struggling. I have a Map of profiles I load at startup, and profiles can be added and deleted at runtime. Those profiles need to be listed as RadioButtons or some other element for selection/editing in my UI.
I've tried using createQMLObject() while looping trough a QVariantMap getter:
profile.h
#ifndef PROFILE_H
#define PROFILE_H
#include <QObject>
class Profile final : public QObject
{
Q_OBJECT
public:
Profile(QObject *parent = nullptr);
~Profile(){}
Profile(QString profileName = "default", QObject *parent = nullptr);
QString getProfileName() const;
void setProfileName(const QString &value);
private:
QString profileName;
};
#endif // PROFILE_H
profile.cpp
#include "profile.h"
Profile::Profile(QString profileName, QObject *parent)
: QObject(parent), profileName{ profileName }{}
QString Profile::getProfileName() const
{
return profileName;
}
void Profile::setProfileName(const QString &value)
{
profileName = value;
}
controller.h
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include "profile.h"
#include <QObject>
#include <QQmlEngine>
class Controller : public QObject
{
Q_OBJECT
public:
Controller(QObject *parent = nullptr);
~Controller(){}
static QObject* controllerSingletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
static Controller* instance();
Q_INVOKABLE void addProfile(QString profileName);
Q_INVOKABLE QVariantList getLoadedProfiles(){
QVariantList rval;
foreach (QString key, loadedProfiles.keys()) {
rval.append(QVariant::fromValue<Profile*>(loadedProfiles[key].get()));
}
return rval;
};
private:
QMap<QString, std::shared_ptr<Profile>> loadedProfiles;
signals:
};
#endif // CONTROLLER_H
controller.cpp
#include "controller.h"
Controller::Controller(QObject *parent) : QObject(parent){}
void Controller::addProfile(QString profileName)
{
std::shared_ptr<Profile> profile{std::make_shared<Profile>(profileName)};
loadedProfiles[profile->getProfileName()] = profile;
}
QObject* Controller::controllerSingletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return Controller::instance();
}
Controller* Controller::instance(){
static Controller* controller = new Controller();
return controller;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <controller.h>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterSingletonType<Controller>("com.controller", 1, 0, "Controller", Controller::controllerSingletonProvider);
Controller::instance()->addProfile("Profile1");
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import com.controller 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Column {
id: radioButtonColumn
Component.onCompleted: {
for(var profile in Controller.getLoadedProfiles()){
var obj = Qt.createQmlObject('import QtQuick 2.12; RadioButton { id: '+ profile.getProfileName() +';
text: '+ profile.getProfileName() +'; }', radioButtonColumn, "dynamicSnippet1");
}
}
}
}
I've tried using a signal sent from my Controller Singleton using Connections, but the parameter of the signal does not arrive. I can however call my Controllers c++ functions from qml. I'll only post the changes to the previous code sample:
main.qml changes:
Connections {
target: Controller
onProfileAdded: {
var obj = Qt.createQmlObject('import QtQuick 2.12; RadioButton { id: '+ profileName +';
text: '+ profileName +'; }', radioButtonColumn, "dynamicSnippet1");
console.log("Value changed")
}
}
controller.h changes:
signals:
void profileAdded(QString &profileName);
};
controller.cpp changes:
void Controller::addProfile(QString profileName)
{
std::shared_ptr<Profile> profile{std::make_shared<Profile>(profileName)};
loadedProfiles[profile->getProfileName()] = profile;
emit profileAdded(profileName);
}
main.cpp changes:
engine.load(url);
Controller::instance()->addProfile("Profile2");
return app.exec();
Edit: Curious, that I had to change my onProfileAdded in controller.h to profileAdded for the signal to work. However, I still do not receive the parameter profileName in my qml.
I would like to ask what is the best way to handle a requirement like this. Maybe there is another, simpler way?
Im new to both c++ and QT, so please bear with my perhaps trivial question, but my research didnt yield me anything, that I could understand at least.

Create Model for QML TreeView

I'm trying to use QML TreeView Model. The example from Qt doesn't include how to create the model. I read this post and tried to use the code from #Tarod but the result is not what I expected.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "animalmodel.h"
#include <qqmlcontext.h>
#include <qqml.h>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal("wolf", "Medium");
model.addAnimal("Bear", "Large");
QQmlApplicationEngine engine;
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
animalmodel.h
#ifndef ANIMALMODEL_H
#define ANIMALMODEL_H
#include <QStandardItemModel>
class AnimalModel : public QStandardItemModel
{
Q_OBJECT //The Q_OBJECT macro must appear in the private section of a class definition that declares its own signals and slots or that uses other services provided by Qt's meta-object system.
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
Q_INVOKABLE void addAnimal(const QString &type, const QString &size);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
};
#endif // ANIMALMODEL_H
animalmodel.cpp
#include "animalmodel.h"
AnimalModel::AnimalModel(QObject *parent)
: QStandardItemModel(parent)
{
}
void AnimalModel::addAnimal(const QString &type, const QString &size)
{
QStandardItem* entry = new QStandardItem();
entry->setData(type, TypeRole);
auto childEntry = new QStandardItem();
childEntry->setData(size, SizeRole);
entry->appendRow(childEntry);
appendRow(entry);
}
QVariant AnimalModel::data(const QModelIndex & index, int role) const {
QStandardItem *myItem = itemFromIndex(index);
if (role == TypeRole)
return myItem->data(TypeRole);
else if (role == SizeRole) {
if (myItem->child(0) != 0)
{
return myItem->child(0)->data(SizeRole);
}
}
return QVariant();
}
QHash<int, QByteArray> AnimalModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
menuBar: MenuBar {
Menu {
title: qsTr("&File")
MenuItem {
text: qsTr("&Open")
onTriggered: messageDialog.show(qsTr("Open Action Triggered"));
}
MenuItem {
text: qsTr("&Exit")
onTriggered: Qt.quit();
}
}
}
TreeView {
anchors.fill: parent
model: myModel
TableViewColumn {
title: "Name"
role: "type"
width: 300
}
TableViewColumn {
title: "Size"
role: "size"
width: 300
}
}
}
What I got is something like this:
Result
What I want to have is the animal size as a child of animal type.
Model sub-classing is one of the worst minefields in Qt. The advice is always to have it go through the model test (https://wiki.qt.io/Model_Test) to see if everything was implemented correctly.
On the other hand, in 90% of the cases you do not need to subclass a model at all as the default models provided by Qt work quite well. What I'd do is just use QStandardItemModel using, on the C++ side, only the QAbstractItemModel interface (i.e. force yourself to use QAbstractItemModel* model = new QStandardItemModel(/*parent*/);) this way, if in the future you feel like you really need to reimplement the model (for efficiency) you'll just need to change 1 line in your existing code.
In your case:
void AnimalModel::addAnimal(const QString &type, const QString &size)
{
if(columnCount()==0) insertColumn(0); // make sure there is at least 1 column
insertRow(rowCount()); // add a row to the root
const QModelIndex addedIdx = index(rowCount()-1,0);
setData(addedIdx, type, TypeRole); // set the root data
insertRow(rowCount(addedIdx),addedIdx ); // add 1 row ...
insertColumn(0,addedIdx ); // ... and 1 column to the added root row
setData(index(0,0,addedIdx), size, SizeRole); // set the data to the child
}

QtToolBar with underlined shortcut key in button text

I have a simple Qt toolbar with text only button Action:
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
{
QToolBar* toolBar = new QToolBar(this);
QAction* action = toolBar->addAction("&Action");
QObject::connect(action, SIGNAL(triggered()), this, SLOT(onAction()));
action->setShortcut(QKeySequence("ctrl+a"));
addToolBar(toolBar);
}
I would like to have A in Action underlined to reflect its role as a shortcut key. How to accomplish that?
Standard QAction widget (it is a QToolButton actually) uses stripped version of its text for display: "&Menu Option..." becomes "Menu Option".
You can create a custom QAction widget which does not use stripped text by subclassing QWidgetAction:
MyAction::MyAction(QObject *parent) :
QWidgetAction(parent)
{
}
QWidget* MyAction::createWidget(QWidget *parent)
{
QToolButton *tb = new QToolButton(parent);
tb->setDefaultAction(this);
tb->setText(this->text());// override text stripping
tb->setFocusPolicy(Qt::NoFocus);
return tb;
}
In your MainWindow constructor use it as follows:
MainWindow(QWidget* parent=0) : QMainWindow(parent)
{
QToolBar* toolBar = new QToolBar(this);
MyAction* action = new MyAction();
action->setText("&Action");
action->setShortcut(QKeySequence(tr("ctrl+a","Action")));
toolBar->addAction(action);
QObject::connect(action, SIGNAL(triggered()), this, SLOT(onAction()));
addToolBar(toolBar);
}
Appearence of underline shortcut letters depends on your application style.
Here is an example of a custom style that will force shortcut underline display:
class MyStyle : public QProxyStyle
{
public:
MyStyle();
int styleHint(StyleHint hint,
const QStyleOption *option,
const QWidget *widget,
QStyleHintReturn *returnData) const;
};
int MyStyle::styleHint(QStyle::StyleHint hint,
const QStyleOption *option,
const QWidget *widget,
QStyleHintReturn *returnData) const
{
if (hint == QStyle::SH_UnderlineShortcut)
{
return 1;
}
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
Then you should set that style to your application:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setStyle(new MyStyle);
Widget w;
w.show();
return a.exec();
}

Add qml object to QGraphicsScene

I try to add some QML object to my QGraphcisScene but they don't display in the scene. Here is the code.
QList<QObject*> dataList;
dataList.append(new DataObject("Item 1", "red"));
dataList.append(new DataObject("Item 2", "green"));
QDeclarativeEngine engine ;
QDeclarativeContext *context = engine.rootContext();
context->setContextProperty("myModel", QVariant::fromValue(dataList));
QUrl url("qrc:view.qml") ;
QDeclarativeComponent component(&engine,url ) ;
QDeclarativeItem *item = qobject_cast <QDeclarativeItem *>(component.create());
item->setFlag(QGraphicsItem::ItemHasNoContents, false);
myScene->addItem(item);
And here is my qml file:
ListView {
width: 100; height: 100
model: myModel
delegate: Rectangle {
height: 25
width: 100
color: model.modelData.color
Text { text: name }
}
}
You can add a QML in a QDeclarativeView to your scene using addWidget:
QDeclarativeView view;
view.setSource( QUrl("qrc:view.qml"));
view.setStyleSheet("background-color:transparent");
QGraphicsProxyWidget * item = myScene->addWidget((QWidget *)view);
For QtQuick 2.0 you can embed QQuickView in a widget using createWindowContainer :
QQuickView *view = new QQuickView();
...
QWidget *container = QWidget::createWindowContainer(view);
container->setMinimumSize(...);
container->setMaximumSize(...);
container->setFocusPolicy(Qt::TabFocus);
QGraphicsProxyWidget * item = myScene->addWidget((QWidget *)container);