I've got class that inherits from QQuickItem and inside I'm operating on its width height properties. I'm also exposing my own properties.
class MyQQuickItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QUrl source MEMBER m_source NOTIFY sourceChanged)
public:
explicit MyQQuickItem(QQuickItem *a_parent = Q_NULLPTR);
~PDFPage();
signals:
void sourceChanged(const QUrl a_source);
private:
QUrl m_source;
};
In qml:
...
MyQQuickItem
{
width: parent.width / 2
height: parent.height
source: "someSource"
Component.onCompleted
{
console.log("component onCompleted");
}
}
When sourceChanged is emmited I'm working on width height properties, but with the first emit they are not initialized yet, and Component.onCompleted is called after my NOTIFY signal.
I can check if component is ready before getting width and height by calling isComponentComplete and execute my code when it is, but I can't find out when it happens and the initial value of source is skipped when it is not.
Of course I could create slot 'itemReady' and call it from Component.onCompleted, but I have many QMLs using my class and I will create more in the future, so I don't want to make copy-paste job.
I also do not want to set the source in Component.onCompleted.
Connecting to widthChanged, and heightChanged signals isn't also good idea, because I'm resizing my item very frequently, and I don't want to execute my code then.
Is there any way to get completed signal from C++?
EDIT:
I did as #Mitch said - overriten componentComplete method and inside, called it's base class equavilent. isComponentComplete() returns true, but I'm still getting 0 from width() and height() methods:
void MyQQuickItem::componentComplete()
{
QQuickItem::componentComplete();
if(isComponentComplete())
{
qDebug() << "c++: oncompleted, width,height: " << width() << height(); //gives 0,0
}
}
Actually I figured out that Component.onCompleted in QML prints 0's too:
...
MyQQuickItem
{
width: parent.width / 2
height: parent.height
source: "someSource"
Component.onCompleted
{
console.log("component onCompleted width, height: ", width, height); //it gives 0 too
}
}
#derM Component.onCompleted is not emmited by QQuickItem::componentComplete() directly, because it prints its log after the qDebug in c++ above.
So, although it is ready, properties are not initialized.
EDIT2:
Finally I've solved it. The Window was guilty, because it uses contentItem as the parent of his childrens, and in MyQQuickItem Component.onCompleted width and height of contentItem(his parent) are 0.
When I do so:
Window
{
id: wnd
width: 640
height: 480
Component.onCompleted:
{
console.log("Window completed: ", this, width, height);
console.log("windowContentItem: ", contentItem, width, height);
}
MyQQuickItem
{
width: parent.width
height: parent.height
Component.onCompleted:
{
console.log("MyQQuickItem completed: ", this, width, height);
console.log("MyQQuickItem parent: ", parent, parent.width, parent.height);
}
}
}
It prints:
qml: Window completed: QQuickWindowQmlImpl(0x345b5eb4d0) 640 480
qml: windowContentItem: QQuickRootItem(0x345e2cdec0) 640 480
qml: MyQQuickItem completed: MyQQuickItem(0x345b5b99e0) 0 0
qml: MyQQuickItem parent: QQuickRootItem(0x345e2cdec0) 0 0
So MyQQuickItem parent is Window's contentItem. When I replace
width: parent.width
height: parent.height
to:
width: wnd.width
height: wnd.height
It works perfectly, and in my void MyQQuickItem::componentComplete() I've got width and height initialized as expected.
One thing that I do not understand is why in Window onCompleted, contentItem size is correct, but in MyQQuickItem onCompleted the size is 0,0. If anyone could explain it to me, I would be grateful :P
Qt code uses componentComplete() like so:
void QQuickControl::componentComplete()
{
Q_D(QQuickControl);
QQuickItem::componentComplete();
d->resizeContent();
// ...
}
You can do something similar, replacing resizeContent() with your function that works on the width and height. Assuming that function is also called whenever source changes, you'd need the isComponentComplete() check. With this approach, you shouldn't have any issues.
You can inherit QQmlParserStatus and implement componentComplete().
class MyQQuickItem : public QQuickItem, public QQmlParserStatus
{
Q_OBJECT
Q_PROPERTY(QUrl source MEMBER m_source NOTIFY sourceChanged)
public:
explicit MyQQuickItem(QQuickItem *a_parent = Q_NULLPTR);
~PDFPage();
void classBegin();
void componentComplete();
signals:
void sourceChanged(const QUrl a_source);
private:
QUrl m_source;
};
componentComplete() will be called automatically by the QML-engine.
Related
I am connecting a ProgressBar to two Buttons. The ProgressBar will do a computation of a loop as soon as I push the button. The computation will be erased if I will push the other button. I still haven't implemented the pause button.
The print screen of what I am trying to achieve is below and in case needed the whole code of the minimal verifiable example is available here:
The problem is that as soon as I connect the ProgressBar with my main.cpp file I have a bunch of errors all that look like the following:
class ProgressDialog has no member named...
code is below:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include "progressbardialog.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQuickView view;
ProgressBarDialog progressDialog;
// One way but it does not work
// engine.rootContext()->setContextProperty("control", QVariant::fromValue(progressDialog.refModel()));
// Another way but this also does not work
view.setSource(QUrl::fromLocalFile("main.qml"));
QQmlApplicationEngine engine;
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();
}
progressbardialog.h
#ifndef PROGRESSBARDIALOG_H
#define PROGRESSBARDIALOG_H
#include <QObject>
#include <QFutureWatcher>
class ProgressBarDialog : public QObject
{
Q_OBJECT
Q_PROPERTY(float progress READ progress WRITE setProgress NOTIFY progressChanged)
public:
ProgressBarDialog();
float progress(int &iterator);
// I will use this as a reference to pass to main.cpp using setContextProperty()
QObject &refModel()
{
return m_Model;
}
public Q_SLOTS:
void startComputation();
void cancelComputation();
signals:
void progressChanged();
private:
int m_progressValue;
QObject m_Model;
QFutureWatcher<void> m_futureWatcher;
};
#endif // PROGRESSBARDIALOG_H
progressbardialog.cpp
#include "progressbardialog.h"
#include <QtConcurrent/QtConcurrentMap>
ProgressBarDialog::ProgressBarDialog()
{}
void spin(int &iteration)
{
const int work = 1000 * 1000 * 40;
volatile int v = 0;
for(int j = 0; j < work; ++j)
++v;
}
float ProgressBarDialog::progress(int &iterator)
{
(void) iterator;
const int work = 1000 * 1000 * 40;
volatile int v = 0;
for(int j = 0; j < work; ++j)
++v;
emit progressChanged();
}
void ProgressBarDialog::startComputation()
{
// Prepare the vector
QVector<int> vector;
for(int i = 0; i < 40; ++i)
vector.append(i);
const QFuture<void> future = QtConcurrent::map(vector, spin);
m_futureWatcher.setFuture(future);
}
void ProgressBarDialog::cancelComputation()
{
m_futureWatcher.cancel();
}
and finally the main.qml
import QtQuick 2.12 // for the Item
import QtQuick.Controls 2.12 // for ApplicationWindow
import QtQuick.Layouts 1.12
ApplicationWindow {
visible: true
width: 440
height: 480
title: qsTr("Progress Bar")
ColumnLayout {
spacing: 10
width: parent.width
GroupBox {
id: box1
title: "Start - Stop"
font.pointSize: 20
Layout.alignment: parent.width
spacing: 10
GridLayout {
width: parent.width
columns: 1
RowLayout {
spacing: 200
Layout.fillWidth: true
Layout.fillHeight: false
Button {
id: buttonStart
text: "Start"
font.pointSize: 15
enabled: !progressDialog.active
onClicked: progressDialog.startComputation()
}
Button {
id: buttonFinish
text: "Finish"
font.pointSize: 15
enabled: progressDialog.cancelComputation()
}
}
}
}
GroupBox {
id: boxprogress
title: "Progressbar"
font.pointSize: 20
Layout.alignment: parent.width
spacing: 10
GridLayout {
width: parent.width
columns: 1
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: false
ProgressBar {
id: progressbar_id
Layout.fillWidth: true
Layout.fillHeight: true
width: parent.width
// These are hard-coded values to confirm it is working
from: 0
to: 100
value: 5
onValueChanged: {
console.log("Progressbar value changed: ", progressbar_id.value)
}
onVisibleChanged: {
console.log("Progressbar visibility changed: ", progressbar_id.visible)
}
}
Connections {
target: progressDialog
onProgressChanged: progressbar_id.value = progress;
}
// This is working if clicking on the progressbar
MouseArea {
anchors.fill: parent
onClicked: progressbar_id.value += 5;
}
}
}
}
}
}
What I tried so far :
1) In order to prove myself that the ProgressBarDialog is correctly working I tried it before as a stand alone main.qml using hard-coded values (which I left in the code so you can see what I have done). But as soon as I started implementing it in the .cpp file and setting the Q_PROPERTY I received all the errors I posted on the second screenshot.
2) I am sure about the procedure taken so far because: a) According to the official documentation and from this post because I will need to check the process of a long running operations; b) According to QtConcurrent the process has a better possibility to be checked, and that is exactly what I did using QtConcurrent::map()
3) I am using the following statement in the progressbardialog.h to make sure that the object is correctly connected and listens to the QML file:
QObject &refModel()
{
return m_Model;
}
But this did n't show and substancial improvements. Therefore digging more into the process I was thinking that it should have been the case to use setContextProperty(), whih cled me to the next point.
4) According to the official documentation and according to this post here I thought I would have fix the error, but it still remains.
I am runnign out of ideas and anyone who may have had this problem, please point to the right direction for a possible solution.
Q_PROPERTY is designed to easier read/write/react to variable change (C++ side) on the QML side. To complete your example with variable read/write/notify add correct getter/setter (signal is already ok).
float progress();
void setProgress(float);
If you want to call a function from QML mark it Q_INVOKABLE instead.
Also what is the point of volatile in your code?
So to sum up, mark your iteration function as a Q_INVOKABLE. It will increment some internal progress value and then emit progressChanged(). This should cause QML side to update.
I have created QQuickPaintedItem class that is responsible for painting UI stuff with overridable paint() method . The component is placed in Qml but i am not able to see it in designer.But i am able to see the result on runtime.
I am following https://doc.qt.io/qt-5/qtquick-customitems-painteditem-example.html
Nothing works , i really need to see the output in qml designer not on runtime.
As i am following https://doc.qt.io/qt-5/qtquick-customitems-painteditem-example.html
here is the source code for header file
class TextBalloon : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(bool rightAligned READ isRightAligned WRITE setRightAligned NOTIFY rightAlignedChanged)
public:
TextBalloon(QQuickItem *parent = 0);
void paint(QPainter *painter);
bool isRightAligned();
void setRightAligned(bool rightAligned);
private:
bool rightAligned;
signals:
void rightAlignedChanged();
};
and definition
TextBalloon::TextBalloon(QQuickItem *parent)
: QQuickPaintedItem(parent)
, rightAligned(false){}
void TextBalloon::paint(QPainter *painter){
QBrush brush(QColor("#007430"));
painter->setBrush(brush);
painter->setPen(Qt::NoPen);
painter->setRenderHint(QPainter::Antialiasing);
QSizeF itemSize = size();
painter->drawRoundedRect(0, 0, itemSize.width(), itemSize.height() - 10, 10, 10);
if (rightAligned)
{
const QPointF points[3] = {
QPointF(itemSize.width() - 10.0, itemSize.height() - 10.0),
QPointF(itemSize.width() - 20.0, itemSize.height()),
QPointF(itemSize.width() - 30.0, itemSize.height() - 10.0),
};
painter->drawConvexPolygon(points, 3);
}
else
{
const QPointF points[3] = {
QPointF(10.0, itemSize.height() - 10.0),
QPointF(20.0, itemSize.height()),
QPointF(30.0, itemSize.height() - 10.0),
};
painter->drawConvexPolygon(points, 3);
}
}
and here is qml file
ListModel {
id: balloonModel
ListElement {
balloonWidth: 200
}
ListElement {
balloonWidth: 120
}
}
ListView {
anchors.bottom: controls.top
anchors.bottomMargin: 2
anchors.top: parent.top
id: balloonView
delegate: TextBalloon {
anchors.right: index % 2 == 0 ? undefined : parent.right
height: 60
rightAligned: index % 2 == 0 ? false : true
width: balloonWidth
}
model: balloonModel
spacing: 5
width: parent.width
}
Please help with this i really need to see the painted output in qml designer
or please suggest another painting way by which i can see the output in qml designer.
thanks:)
I have a QML QuickControls 2 application with a Video component/control. I want to create a C++ callback to handle each frame in the video. The C++ callback function will process each frame, ie, find the edges in the image/frame and return that edge image for the UI to display.
How can I hook all this up? Ie, somehow tell the QML to call a c++ callback on each frame?
Video {
id: video
fillMode: VideoOutput.PreserveAspectFit
anchors.fill : parent
source: "file:///D:/cards.mp4"
muted: true
focus: true
Keys.onSpacePressed: video.playbackState == MediaPlayer.PlayingState ? video.pause() : video.play()
Keys.onLeftPressed: video.seek(video.position - 5000)
Keys.onRightPressed: video.seek(video.position + 5000)
}
My callback class, not sure if correct:
class ImageProcessor : public QObject
{
Q_OBJECT
public:
explicit ImageProcessor(QObject *parent = nullptr);
Q_INVOKABLE void processImage(QString va);
signals:
public slots:
};
You can create a VideoOutput from the video:
Rectangle {
width: 800
height: 600
color: "black"
MediaPlayer {
id: player
source: "file://video.webm"
autoPlay: true
}
VideoOutput {
id: videoOutput
source: player
anchors.fill: parent
}
}
You can add filters to the VideoOutput. For example here a faceRecognitionFilter:
VideoOutput {
...
filters: [ faceRecognitionFilter ]
}
In the C++ implemtation of the filter you can reach the frame:
QVideoFrame FaceRecogFilterRunnable::run(QVideoFrame *input, const QVideoSurfaceFormat &surfaceFormat, RunFlags flags)
{
// Convert the input into a suitable OpenCV image format, then run e.g. cv::CascadeClassifier,
// and finally store the list of rectangles into a QObject exposing a 'rects' property.
...
return *input;
}
You can collect some info here:
http://doc.qt.io/qt-5/qml-qtmultimedia-videooutput.html
http://blog.qt.io/blog/2015/03/20/introducing-video-filters-in-qt-multimedia/
I came across QFontMetrics?
http://doc.qt.io/qt-5/qfontmetrics.html
This gives the height and width of the present font.
I need to run my application with full screen mode on on different monitors for which I am using Scale class. http://doc.qt.io/qt-5/qml-qtquick-scale.html
That returns the height and width of the current screen.
Is there a way to use QFontMetrics or anything else to change the font size according to the monitor size?
ApplicationWindow
{
id: head
visible: true
width: Screen.width
height: Screen.height
title: "Novus Pilot"
property var id: 0;
Draw_on_qimage
{
id: draw_on_qimage
anchors.fill: parent
parent: image
scaleX: head.width / 640
scaleY: head.height / 480
}
}
Draw_on_qimage is a cpp class.
The easiest way is to set QFont as Q_PROPERTY of your item so you can set it from QML:
#ifndef DRAWITEM_H
#define DRAWITEM_H
#include <QPainter>
#include <QQuickPaintedItem>
class DrawItem : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
public:
DrawItem(QQuickItem *parent = Q_NULLPTR):QQuickPaintedItem(parent){}
void paint(QPainter *painter){
painter->setFont(mFont);
painter->drawText(boundingRect(), "Hello");
}
QFont font() const{
return mFont;
}
void setFont(const QFont &font){
if(mFont == font)
return;
mFont = font;
emit fontChanged();
update();
}
signals:
void fontChanged();
private:
QFont mFont;
};
#endif // DRAWITEM_H
To set its size we use the pointSize property of QFont:
DrawItem
{
id: draw_on_qimage
anchors.fill: parent
font.pointSize: some_function(head.width, head.height)
transform: Scale {
xScale: head.width / 640
yScale: head.height / 480
}
}
Where some_function is the function that establishes the relationship between the font size and the size of the window.
I have a problem with QQuickView instantiating components from C++... Here is my code:
Class definition (vviewerqml.h):
class VViewerQml : public QObject
{
Q_OBJECT
public:
explicit VViewerQml(QSettings &systemSettings, QObject *parent = 0);
~VViewerQml();
protected slots:
void onViewStatusChanged(QQuickView::Status status);
protected:
QString _qmlFolder;
QQuickView _view;
};
Class implementation (vviewerqml.cpp):
#include "vviewerqml.h"
VViewerQml::VViewerQml(QSettings &systemSettings, QObject *parent) :
QObject(parent)
{
// Initialize viewer reading from settings file
_qmlFolder = "/my/path/to/qml/files";
// Initialize the source
connect(&_view, SIGNAL(statusChanged(QQuickView::Status)),
this, SLOT(onViewStatusChanged(QQuickView::Status)));
_view.setSource(QUrl::fromLocalFile(QDir(_qmlFolder).filePath("Main.qml")));
// Show the viewer
_view.show();
}
VViewerQml::~VViewerQml()
{
// Close the viewer
_view.close();
}
void VViewerQml::onViewStatusChanged(QQuickView::Status status)
{
if(status == QQuickView::Ready)
{
QQmlComponent *c =
new QQmlComponent(_view.engine(),
QDir(_qmlFolder).filePath("TextLegacy.qml"));
QQuickItem *i = qobject_cast<QQuickItem*>(c->create());
QQmlEngine::setObjectOwnership(i, QQmlEngine::CppOwnership);
i->setParent(_view.rootObject());
i->setVisible(true);
}
}
Here is my Main.qml:
import QtQuick 2.0
Rectangle {
width: 1024
height: 768
color: "#000000"
Text {
x: 0
y: 0
color: "#ffffff"
text: "Main page"
}
}
And here is my TextLegacy.qml:
import QtQuick 2.0
Item {
Rectangle {
x: 0
y: 0
width: 100
height: 50
color: "#ff0000"
}
}
My code works fine until the load of Main.qml: the QML viewer opens on the screen and I can read the text "Main page" (white on black) on my screen... But unluckily I am not able to load the TextLegacy.qml... if I put a breakpoint in onViewStatusChanged function, the execution reaches that point... no visible errors are shown in the debug console... but I am not able to see on the screen the red rectangle provided by TextLegacy.qml...
What am I missing? Can somebody give some help?
Ok, I found the solution on my own: I have confused setParent with setParentItem... The correct code is:
void VViewerQml::onViewStatusChanged(QQuickView::Status status)
{
if(status == QQuickView::Ready)
{
QQmlComponent *c = new QQmlComponent(_view.engine(),
QUrl::fromLocalFile(QDir(_qmlFolder).filePath("TextLegacy.qml")));
QQuickItem *i = qobject_cast<QQuickItem*>(c->create());
QQmlEngine::setObjectOwnership(i, QQmlEngine::CppOwnership);
i->setParent(this);
i->setVisible(true);
i->setParentItem(_view.rootObject());
}
}
Actually i->setParent(this); defines the parent of i as a QObject (for deleting purposes, for example) while i->setParentItem(_view.rootObject()); actually adds the object to the scene, as a child of the scene root object.