qml chartview becomes nullptr when sent to C++ - c++

I have a data source containing multiple series of data (not known upfront).
I want to add a LineSeries to a qml ChartView, so I tried to 'hook' the C++ side to the qml like so:
// data source
...
Q_PROPERTY(QtCharts::QChartView *chart READ chart WRITE setChart NOTIFY chartChanged)
public slots:
void setChart(QtCharts::QChartView *newChart) {
qDebug() << "received new chart to draw on:" << newChart;
}
And in the qml, I send the chart to C++ more or less like:
...
ChartView { id: chart
...
}
Component.onCompleted: { backend.setChart(chart) }
Now the setChart is called allright, but the type does not appear to match: the incoming chart pointer is null:
> received new chart to draw on: QWidget(0x0)
Relaxing the input type to plain QObject* has shown me that the actual type of the incoming object is QtQuick::DeclarativeChart.
How should I send the chart item to my C++ model? (Or should I use a totally different approach?)

If you need to update chart axis and series you should send them into c++ and update them i wrote an example you can take a look i think it dose what you want .
On this example i converted c++ voice example from QWidget charts to QML chart but info in series are updating in c++ :
QML chart updating in c++ Example

I ran into a similar problem (I needed access to the chart in C++ to hide some markers on the chart legend).
I was able to get access to the chart by passing a pointer to an existing series on the chart to my C++ code.
void GraphHelper::hideLegends(QtCharts::QScatterSeries *series) {
QtCharts::QChart *chart = series->chart();
// do something with the chart...
}

Related

Why is my QStyledItemDelegate not visible?

I'm trying to create a custom item delegate for a list of objects, but when I run my app the list view is blank. I know my model is populated, and it displays as expected with the standard default delegate. I can also tell that my custom delegate is being rendered in some sense, since space is taken up in the list, and the tooltips and statustips that I set work, but the contents are not visible (the listview appears to be blank white).
My code is based on this example: https://doc.qt.io/archives/qt-5.12/qtwidgets-itemviews-stardelegate-example.html
Qt 5.12 is the version that I'm stuck with, and I'm also stuck using visual studio without the normal Qt debugging tools, do to factors outside of my control.
Anyway, I create the widgets that should be displayed in the constructor, with default values for testing:
EntityListDelegate::EntityListDelegate(QWidget* parent /*= nullptr*/) : QStyledItemDelegate(parent)
{
ui = new QWidget(parent);
ui->setMinimumSize(QSize(200, 40));
QHBoxLayout* hLayout = new QHBoxLayout(ui);
iconLabel = new QLabel(ui);
iconLabel->setMinimumSize(QSize(20, 20));
iconLabel->setMaximumSize(QSize(40, 40));
iconLabel->setPixmap(QPixmap(":/icons/Placeholder.png"));
hLayout->addWidget(iconLabel);
QVBoxLayout* vLayout = new QVBoxLayout(ui);
nameLabel = new QLabel(ui);
nameLabel->setMinimumSize(QSize(150, 20));
nameLabel->setText("Unknown entity");
vLayout->addWidget(nameLabel);
typeLabel = new QLabel(ui);
typeLabel->setMinimumSize(QSize(150, 16));
typeLabel->setText("Unknown type");
vLayout->addWidget(typeLabel);
hLayout->addLayout(vLayout);
ui->setLayout(hLayout);
}
Note that each item should have a thick black border, which is not being rendered. (Edit: I changed the QFrame back to a QWidget)
I also implemented paint, where the real values should get set based on info in the model:
void EntityListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
nameLabel->setText(index.data(Qt::DisplayRole).toString());
switch (qvariant_cast<int>(index.data(UserRoles::Type)))
{
case EntityType::Foo:
{
iconLabel->setPixmap(QPixmap(":/icons/foo.png"));
typeLabel->setText("Foo");
break;
}
case EntityType::Bar:
{
iconLabel->setPixmap(QPixmap(":/icons/bar.png"));
typeLabel->setText("Bar");
break;
}
case EntityType::Baz:
{
iconLabel->setPixmap(QPixmap(":/icons/baz.png"));
typeLabel->setText("Baz");
break;
}
default:
break;
}
}
The other functions are just stubs, since the user shouldn't be able to edit this data.
Am I missing some step in my setup?
Update: I tried making the pointer to the delegate that gets passed into setItemDelegate() a member of the class that owns the the ListView. I now get one item on the list that renders correctly, in the top position in the list. I can sometimes get it to display another item by clicking where that item should be, but it's still in the same location.
The answer to the question as asked is that if you're trying to alter the default display, not the editor, you have to reimplement paint and basically draw the whole thing manually. This is such a massive PITA as to make QStyledItemDelegate functionally unusable for this use case, IMO. Use QtQuick if you can, but that's not an option in my case.
My actual solution, for the curious, was to use QSortFilterProxyModel and override data() to return "EntityName\nEntityType". That gets me ~80% of what I was trying to achieve, and hopefully I can get a little closer with styling when I get to that part.

Accessing Qt / QML objects with C++

I'm working on a C++ Qt project that will eventually communicate with the serial port. One part of this is accessing QML objects in the C++ portion. I have code that can set properties of the QML, but accessing those features that are methods now has me stumped. View the following code:
object = view.rootObject();
rect = object->findChild<QObject *>("box");
rect->setProperty("color", "red"); // Verifies the object tree is accessible
viewer = object->findChild<QObject *>("viewer"); // Access the viewer text box
viewer->append("dummy text"); // OOPS! This doesn't compile!!!
Now, the type as a method setProperty(..), but how do you access methods of an object. "viewer" is a TextArea and I want to first do a selectAll(), then a cut() to clear the box.
The question here is how is this coded? Thanks all.
Of course it would not compile, QObject doesn't have an append() method.
If it is a C++ function, you will have to qobject_cast to the appropriate type that has it. This however is not always readily available for many of the stock QML types that are implemented in C++, and as C++ types they are not part of the public API and not generally intended for direct use by an end user.
If it is a JS function, you will have to use QMetaObject::invokeMethod. That will also work for C++ functions for which meta data has been generated. Which is also how setProperty() works, whereas setColor() would not work with a QObject* much like append() doesn't.
Last but not least, there is absolutely no good reason for you to be doing those kinds of things from C++. Using QML objects from C++ is poor design and an anti-pattern. You will only develop bad habits trying to do that. Such interactions must be limited to a clearly defined interface using signals, slots and properties. Generally speaking, it is OK for QML to reach into C++, because that only happens through an exposed interface, but the opposite way, even if possible, should not utilized.
Think of it like this - a car uses the engine, the engine doesn't use the car. And the engine control is interfaced through the starter key and the gas pedal, it is not used directly. The C++ stuff should be reserved to the application engine - the high performance or efficiency core logic, or the back-end, whereas the QML part is for the GUI/front-end.
The author's QML part may expose alias property to operate with desired text field content:
import QtQuick 2.0
import QtQuick.Controls 1.2
Item {
property alias viewerText: viewer.text // added
width: 350
height: 450
TextArea {
id: viewer
x: 8
y: 8
width: 223
height: 415
text: "text"
font.pixelSize: 12
objectName: "viewer"
}
Button {
id: open
x: 251
y: 8
text: "Open"
}
}
And then the author's C++ part can easily do:
auto* object = view.rootObject();
viewer = object->findChild<QObject *>("viewer");
viewer->setProperty("viewerText", "dummy text"); // using the new property added
Using the posted answer here using the invoke method, here's the solution that works:
// C++ Code to call function reset()
QMetaObject::invokeMethod(object, "reset");
// QML code to select all text the delete it
function reset() {
viewer.selectAll()
viewer.cut()
}

Invalid/undefined mediaobject property of QML Camera

I'm trying to create a QML item, defined in C++, that would intercept frames from a QML Camera before they are displayed by a VideoOutput. Something like:
Window {
Camera {
id: camera
}
MyFrameinterceptor {
id: myprocessing
source: camera.mediaObject
}
VideoOutput {
id: feedback
source: myprocessing
}
}
According to this comment, the mediaObject property of a Camera item can be used to access the C++ part of the Camera.
However, when I try to access the mediaObject from QML, e.g. with
Text {
text: qsTr(camera.mediaObject.objectName)
}
I get a TypeError: Cannot read property 'objectName' of undefined
When I try to use the camera.mediaObject property from C++, I get similar messages letting me think that mediaObject is undefined, uninitialized or not existing.
I'm new to Qt, so I may miss something really stupid, like starting the camera or what not... But I have the same problem with a MediaPlayer item
How can I access the mediaObject of a QML Camera from C++?
I tripped into this a couple of times as well, I resolved it like so:
QObject * obj = rootview->rootObject()->findChild<QObject *>("camera");
QVariant mediaObject = obj->property("mediaObject");
QCamera * camera = qvariant_cast<QCamera *>(mediaObject);
I then use a QVideoRendererControl to assign a subclass of QAbstractVideoSurface to process frames.

C++/QML: How to define and handle multiple contexts for dynamically created components?

Basically my situation is like this:
I've got a class that extends QQuickView and that exposes certain objects from C++ to QML by setting context properties. The views that are shown are created from QML and are all different istances of the same custom made component; new views are created when certain events occur, and when that happens the existing views should show the objects that were initially assigned to them in the C++ side, and the new ones should show the things assigned to them.
So, in the C++ side I've got something like this:
WindowManager::WindowManager(QQuickView *parent) :
QQuickView(parent)
{
// Setting the source file to use
this->setSource(QUrl("qrc:/qml/main.qml"));
// Exposing the istance of this class to QML for later use
this->rootContext()->setContextProperty("qquickView", this);
// Calling the method that will create dynamically a new view that will be child of main.qml; the parameter is not important, just a random number to start with
this->prepareNewView(3)
this->showFullScreen();
}
WindowManager::prepareNewView(int stuffId)
{
MyDatabase db;
// Getting something to show in QML from somewhere based on the parameter received
SomeStuff stuff = db.getStuff(stuffId)
// Exposing the object I need to show in QML
this->rootContext()->setContextProperty("someStuff", stuff);
QObject *object = this->rootObject();
// Here I'm invoking a function from main.qml that will add a new view dynamically
QMetaObject::invokeMethod(object, "addView");
}
Now, in the QML side I've got a main file like this:
// main.qml
Rectangle {
id: mainWindow
width: 1000
height: 1000
// This function adds a component to mainWindow
function addView()
{
// Creating the component from my custom made component
var component = Qt.createComponent("MyComponent.qml");
// Creating an istance of that component as a child of mainWindow
var newView = component.createObject(mainWindow);
// ... Now I would be doing something with this new view, like connecting signals to slots and such
}
}
Then I've got my custom component, which is the view that will be created dynamically:
// MyComponent.qml
Rectangle {
id: customComponent
// Here I would be using the object I exposed from the C++ side
x: someStuff.x
y: someStuff.y
width: someStuff.width
height: someStuff.height
// Here I'm creating a MouseArea so that clicking this component will cause the creation of another view, that will have to show diffrent things since the parameter I'm passing should be different from the starting parameter passed in the constructor of WindowManager
MouseArea {
anchors.fill: parent
onClicked: qquickView.prepareNewView(Math.random())
}
}
Now, with everything as it is, at first it will show "the stuff" with id 3, that was exposed as a context property of the main context.
But then, if I click on the MouseArea, assuming that an id other than 3 will be passed, a new context property with the same name will be exposed, causing the override of the old property. This means that the first view will now show "the stuff" just exposed, not "the stuff" based from the stuffId equals to 3, while what I need is the first view to keep showing what it was supposed to show ("the stuff" with id = 3), and any other view that will come later the things corresponding to their ids.
This happens because I'm defining a property in the context that is common to every component, while I should be defining a property that is visible ONLY by the new istance of the component that is being created dynamically. But how do I do that?
In the documentation I read that it's possibile to create a component directly from C++ and defining the context that it should use... something like this (snippet taken from here):
QQmlEngine engine;
QStringListModel modelData;
QQmlContext *context = new QQmlContext(engine.rootContext());
context->setContextProperty("myModel", &modelData);
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\nListView { model: myModel }", QUrl());
QObject *window = component.create(context);
I think that this would work for what I intend to do. Whenever I create a new view from C++ (caused by the click on the MouseArea) I create a new context with "someStuff" as its property, so that each view has its own "stuff"... but I need to have access to the newly created view from QML, because after I create it in the addView() function inside main.qml I access the view in order to do certain thins (not important what exactly), and if I create the istance of the component from C++ I don't know how to access it from QML... is there a way maybe to pass the component from C++ to QML in order to access it?
I'm out of ideas on how to resolve this or to find another way to have dynamically created views with custom contents all visible at the same time... any suggestions are appreciated.
I actually found out that it is possible (and easy) to directly pass a component created in C++ to QML.
So right now, I modified the code pretty much like this:
WindowManager::prepareNewView(int stuffId)
{
MyDatabase db;
// Getting something to show in QML from somewhere based on the parameter received
SomeStuff stuff = db.getStuff(stuffId)
// Creating the new context, based on the global one
QQmlContext *context = new QQmlContext(this->rootContext());
// Exposing the object I need to show in QML to the new context
context ->setContextProperty("someStuff", stuff);
// Creating the component
QQmlComponent component(this->engine(), QUrl("qrc:/qml/MyComponent.qml"));
// Creating the istance of the new component using the new context
QQuickItem *newView = qobject_cast<QQuickItem*>(component.create(context));
// Getting the root component (the Rectangle with it mainWindow)
QObject *object = this->rootObject();
// Manually setting the new component as a child of mainWIndow
newView->setParentItem(qobject_cast<QQuickItem*>(object));
// Invoking the QML that will connect the events of the new window, while passing the component created above as QVariant
QMetaObject::invokeMethod(object, "addView", Q_ARG(QVariant, QVariant::fromValue(newView)));
}
In QML the function in the main.qml is now like this:
// Function called from C++; the param "newView" is the last component added
function addView(newView)
{
// ... Here I would use the new view to connect signals to slots and such as if I created "newView" directly in QML
}
So I managed not to change the code too much after all.
I think you can pass your component instance (QObject) by setting an object as a context property as you did in your code.
class ViewInstance : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE QObject* getCurrentViewInstance() {
...
QObject *window = component.create(context);
return window;
}
};
int main(int argc, char *argv[]) {
...
QQuickView view;
ViewInstance data;
view.rootContext()->setContextProperty("viewInstance", &data);
}
Then, in your qml, you can get the component instance by calling viewInstance.getCurrentViewInstance(). Hope this helps.

QML two-way C++ binding doesn't receive changes anymore

I'm facing a big problem that it's taking a lot of time to be fixed because I don't know the cause and how to fix it. The problem is really simple: I have an example QML component defined as:
Rectangle {
id: rect
property bool test: myclass.testproperty
Rectangle {
width: 50
height: 50
visible: parent.test
}
}
and I connected a MouseArea onClicked signal to do this:
test = !test
so I switch the value of the boolean variable. To push the value from C++ to QML and from QML to C++ Q_PROPERTY with READ, WRITE and NOTIFY signals, I used this
Binding {
target: myclass
property: "testproperty"
value: rect.test
}
everything works fine until I click on the mouseArea and so I push the changes via the binding. After that, every time I try to set a new property value from C++ I don't see any change in QML, like if the binding is destroyed. But if I try to click on the MouseArea I still call the setTestProperty method of the C++ class. Basically, it goes out of sync the C++ -> QML way. Why? I can't find the problem, the signal is emitted because QSignalSpy gives me 1 as count of emitted times after using
emit this->testPropertyChanged(newvalue)
EDIT
here an example: basically here we're using a QString property with the same exact signals. The only thing that changes is that instead of using a Rectangle QML element and binding to a own property, I'm using a TextInput element
TextInput {
id: mytext
text: myclass.testproperty
}
Binding {
target: myclass
property: "testproperty"
value: mytext.text
}
There is no problem here. It is a standard QML bindings behaviour. When you change some QML Widget's property in JavaScript code, then all declarative bindings to it will be terminated. It is your choice to use declarative binding or update values manually in JS code of event handlers.
EDIT
Rectangle {
id: rect
// Establishing initial value for 'test' property via QML binding
property bool test: myclass.testproperty
// Manual update of 'test' property when binding will be broken
Connections {
target: myclass
onTestpropertyChanged: {
rect.test = xxx
}
}
}