I am kidding around with a quite simple QML sample that should end up being some kind of chessboard, but for some reason I can't properly add cells at runtime. A cell is defined using a C++ Class (BasicCell which extends QQuickItem) and can be styled using Qml (cell.qml):
BasicCell {
width: 32
height: 32
Rectangle {
anchors.fill : parent
color : "green"
}
}
I use QQmlComponent to construct instances of this "styled" BasicCell at runtime:
QQmlComponent cellComponent(qmlEngine(), cellUrl, this);
// Make sure we could actually load that QML component
if (cellComponent.status() != QQmlComponent::Ready)
{
std::cerr << "Error loading cell.qml:" << std::endl;
for (const auto& err : cellComponent.errors())
{
std::cerr << " " << err.toString().toStdString() << std::endl;
}
}
for (int x = 0; x < mNumTiles.width(); ++x)
{
for (int y = 0; y < mNumTiles.height(); y++)
{
BasicCell* cell = qobject_cast<BasicCell*>(cellComponent.create());
cell->setParent(this);
cell->setSize(QSize(tileSize(), tileSize()));
cell->setGridPos(QPoint(x, y));
childItems().append(cell);
mCells.insert(cell->gridPos(), cell);
}
}
When using the QML Debugger I can see that I have ended up with the "correct" hierarchy:
Game
BasicCell
Rectangle
BasicCell
Rectangle
...
But I can't see a thing ... I double and triple checked: All of those rectangles and basic cells do have the appropriate sizes set.
Getting more and more frustrated, I finally copied the code from the cell.qml and pasted it as a direct child into the Board.qml. To my astonishment, this renders the cell exactly as I would have expected it.
What am I missing in my use of QQmlComponent that differs from this kind of instantionation in QML?
Game
{
// Should be created at runtime using QQmlComponent
BasicCell {
width: 32
height: 32
Rectangle {
anchors.fill: parent
color : "green"
}
gridPos: "0,0"
}
}
cell->setParent(this);
should be
cell->setParentItem(this);
The concept of the visual parent differs from that of the QObject
parent. An item's visual parent may not necessarily be the same as its
object parent. See Concepts - Visual Parent in Qt Quick for more
details.
That was taken from:
http://qt-project.org/doc/qt-5/qquickitem.html#parent-prop
Related
I have Qt Quick Controls 2 Application. In main.qml I have besides other things canvas in scroll view:
Rectangle {
id: graph
width: mainArea.width / 3 - 14;
height: mainArea.height - 20;
ScrollView{
anchors.fill: parent;
Canvas {
id:canvasGraph;
width: graph.width;
height: graph.height;
property bool paintB: false;
property string colorRect: "#FFFF40";
property string name: "ELF header";
property int paintX: 0;
property int paintY: 0;
property int widthP: 160;
property int heightP: 30;
property int textX: (paintX + (widthP / 2)) - 15/*func return int length of text*/;
property int textY: (paintY + (heightP / 2)) + 3;
onPaint:{
if (paintB){
var ctx = canvasGraph.getContext('2d');
ctx.beginPath();
ctx.font = "normal 12px serif";
ctx.fillStyle = colorRect;
ctx.strokeRect(paintX, paintY, widthP, heightP);
ctx.fillRect(paintX, paintY, widthP, heightP);
ctx.strokeText("ELF header", textX, textY);
ctx.closePath();
ctx.save();
}
}
MouseArea{
id: canvasArea;
anchors.fill: parent;
onPressed: {
paint(mouseX,mouseY,"aaa",1);
}
}
}
}
}
At first I tried draw into canvas by js function, here:
function paint(x, y, name, type) {
canvasGraph.paintB = true;
canvasGraph.paintX = x;
canvasGraph.paintY = y;
canvasGraph.requestPaint();
}
This function was called by pressing mouse on canvas. It works good, it draw rectangles, one by one. But only one problem was, that after resizing app window, all rectangles except last one get lost. But it's not primary problem, because it works and this promblem I could resolve later.
For drawing chart I need C++ library (ELFIO, for reading ELF files). So in main.cpp I have two object. First allows me call from main.qml functions of some C++ class. Second allows me calling js functions from C++. Here is main.cpp:
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QScopedPointer<elfFile> elfFileObj(new elfFile);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
engine.rootContext()->setContextProperty("elfFileObj", elfFileObj.data()); //this is for calling c++ from qml
QObject *rof = engine.rootObjects().first();
elfFileObj.data()->rofS = rof; //this one is for calling js func from c++
return app.exec();
How you can see, reading ELF files is manage from object elfFileObj, where is public variable which holds loaded ELF file and variable rofS which hold object for access to main.qml to js functions.
In elfFileObj is Q_INVOKABLE int loadELF(QString fileName); where Q_INVOKABLE is macro, which ensure, that this function is possible call from qml file. Function:
int elfFile::loadELF(QString fileName)
{
string fileNameReal = (fileName).toStdString().substr(7);
if (!reader.load(fileNameReal.c_str())){
return -1;
}
QVariant x(30);
QVariant y(10);
QVariant name("ELF header");
QVariant type(1);
QMetaObject::invokeMethod(rofS, "paint", Q_ARG(QVariant,x), Q_ARG(QVariant,y), Q_ARG(QVariant,name), Q_ARG(QVariant,type));
y = QVariant(40);
QMetaObject::invokeMethod(rofS, "paint", Q_ARG(QVariant,x), Q_ARG(QVariant,y), Q_ARG(QVariant,name), Q_ARG(QVariant,type));
}
I try draw two rectangles, one by one. QMetaObject::invokeMethod should call js functions, which draw rectangle on (x,y). Other args are in this moment unusable.
Main problem: It draw rectangles on canvas, but after every call by invokeMethod is canvas cleared. So on the canvas always stay only last one rectangle.
Have somebody any idea, how to save actual state of canvas? Thanks for any help.
It isn't pretty code, but it's my first experience with qml.
The canvas, being an imperative drawing API, just has a dumb buffer of pixel data. It has no concept of objects like rectangles or anything else once the draw call has finished. As such, you are responsible for everything that it displays via your onPaint handler. It does not clear the canvas content from one frame to another (as an optimization), but it will (by necessity) clear it when you resize the window, as it has to allocate a differently sized buffer.
You can see this behaviour here:
import QtQuick 2.6
Canvas {
id: canvasGraph;
width: 500
height: 500
property int paintY: 10;
onPaint:{
var ctx = canvasGraph.getContext('2d');
ctx.beginPath();
ctx.font = "normal 12px serif";
ctx.fillStyle = "#ff0000";
ctx.strokeRect(10, paintY, 160, 30);
ctx.fillRect(10, paintY, 160, 30);
ctx.closePath();
ctx.save();
}
Timer {
interval: 16
running: true
repeat: true
onTriggered: {
canvasGraph.requestPaint();
canvasGraph.paintY += 10
if (canvasGraph.paintY > canvasGraph.height)
canvasGraph.paintY = 10
}
}
}
Try run this example with qmlscene and resize the window. You'll notice that all content is cleared on resize, except that one single rectangle that it draws.
So, if you want all your rectangles to be retained, then you need to paint them in the onPaint handler each time (and make use of clearRect or some other method to fill the background to get rid of stuff that doesn't belong there anymore, if you are moving stuff around or making them invisible).
On the other hand, I can't directly explain why invokeMethod would be causing it to clear, as you haven't really presented enough code. It may be that it's resizing the canvas (causing the buffer to reallocate, and be cleared). Either way, given the above, I'd say that it isn't all that relevant.
After all this, while I don't have full background over what you are making, I'd suggest that perhaps Canvas might not be the best tool to do what you want. You might want to look into QQuickPaintedItem instead, or (better still) composing your scene using a custom QQuickItem which positions other QQuickItems (or QSGNodes). Canvas (and QQuickPaintedItem) while easy to use, are not especially performant.
I didn't solved this problem. I just stop using QML and return to use just Qt. It helps me.
I am trying to display network image using qml and then save this image using c++ code,
Here is the qml code,
import QtQuick 2.3
import QtQuick.Window 2.2
import com.login 1.0
Window {
visible: true
width : 500
height: 500
Login{id: login}
MouseArea {
anchors.fill: parent
onClicked: {
// Qt.quit();
login.save(image);
}
}
Image {
id: image
source: "http://www.test.com/webp/gallery/4.jpg"
}
}
And inside my login class saving image like,
void Login::save( QQuickItem *item)
{
qDebug()<<"width: "<<item->width();
qDebug()<<"height: "<<item->height();
QQuickWindow *window = item->window();
QImage image = window->grabWindow();
QPixmap pix = QPixmap::fromImage(image);
pix.save("C:/Users/haris/Desktop/output.png");
}
I am getting the correct width and height of the image inside c++ class, but the problem is I cannot find a way to save the image item from QQuickItem.
Right now I am saving the image by grabing the window, which actually not giving the actual image size on output file, instead giving output file with current qml window size.
Basically I am following the code here saving QML image but it seems QDeclarativeItem is deprecated in Qt5, so I choose QQuickItem where as there is no paint option in QQuickItem.
Fortunately QQuickItem has a convenient grabToImage function which does that.
void Login::save( QQuickItem *item)
{
QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();
connect(grabResult.data(), &QQuickItemGrabResult::ready, [=]() {
grabResult->saveToFile("C:/Users/haris/Desktop/output.png");
//grabResult->image() gives the QImage associated if you want to use it directly in the program
});
}
Alternate solution without using lambdas:
void Login::save( QQuickItem *item)
{
QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();
/* Need to store grabResult somewhere persistent to avoid the SharedPointer mechanism from deleting it */
...
connect(grabResult.data(), SIGNAL(ready()), this, SLOT(onAsynchroneousImageLoaded()));
}
void Login::onAsynchroneousImageLoaded() {
auto grabResult = qobject_cast<const QQuickItemGrabResult*>(sender());
if (grabResult) {
grabResult->saveToFile("C:/Users/haris/Desktop/output.png");
} else {
//something went wrong
}
});
In a QObject-derived class (ImageSaver) register it as you would. It needs one member:
bool ImageSaver::saveImage(const QUrl &imageProviderUrl, const QString &filename){
qDebug() << Q_FUNC_INFO <<imageProviderUrl << filename;
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQmlImageProviderBase *imageProviderBase = engine->imageProvider(imageProviderUrl.host());
QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);
QSize imageActualSize;
QSize imageRequestedSize;
QString imageId = imageProviderUrl.path().remove(0,1);
QImage image = imageProvider->requestImage(imageId, &imageActualSize, imageRequestedSize);
qDebug() << Q_FUNC_INFO << imageId << imageActualSize;
return image.save(filename);
}
then in QML:
ImageSaver { id: imageSaver}
...
imageSaver.saveImage(image.source, "my.png");
...
Whereas grabToImage will grab the item using the items's size, this can preserve the actual size of the image.
I work in a C++/QML environment and I use Qt 4.8 with QtQuick 1.0.
I have a QWidget derivated class, QCustomPlot, and I encapsulated it in a custom QDeclarativeItem derived class. I use a QGraphicsProxyWidget to embed the QWidget, and it appears nicely upon creation. I would like to update the chart periodically, but I simply cannot, no matter what I do it stays however I initiated it in the constructor.
Here is the code (somewhat simplified) I have:
flowgrafik.h:
class FlowGrafik : public QDeclarativeItem
{
Q_OBJECT
public:
explicit FlowGrafik(QDeclarativeItem *parent = 0);
~FlowGrafik();
void addFlow(double flow);
signals:
public slots:
private:
QCustomPlot * customPlot;
QGraphicsProxyWidget * proxy;
QVector<double> x, y;
};
flowgrafik.cpp:
FlowGrafik::FlowGrafik(QDeclarativeItem *parent) : QDeclarativeItem(parent)
{
customPlot = new QCustomPlot();
proxy = new QGraphicsProxyWidget(this);
proxy->setWidget(customPlot);
this->setFlag(QGraphicsItem::ItemHasNoContents, false);
customPlot->setGeometry(0,0,200,200);
/* WHAT I WRITE HERE WILL BE DISPLAYED */
// pass data points to graph:
customPlot->graph(0)->setData(x, y);
customPlot->replot();
}
FlowGrafik::~FlowGrafik()
{
delete customPlot;
}
void FlowGrafik::addFlow(double flow)
{
//THIS PART DOES NOT GET DISPLAYED
for (int i=0; i<99; ++i)
{
y[i] = y[i+1];
}
y[99] = flow;
customPlot->graph(0)->setData(x, y);
customPlot->replot();
this->update();
}
mainview.qml:
Rectangle {
id: flowGrafik
objectName: "flowGrafik"
x: 400
y: 40
width: 200
height: 200
radius: 10
FlowGrafik {
id: flowGrafikItem
}
}
I would really appreciate if anyone could tell me why my QCustomPlot QWidget does not replot.
Eventually the solution was to create a pointer in the C++ code that points at the QML item.
I accidentally created an other instance in the C++, modified that one, and expected the QML instance to change.
QDeclarativeView mainView;
mainView.setSource(QUrl("qrc:///qml/qml/mainview.qml"));
Flowgrafik * flowGrafik = mainView.rootObject()->findChild<QObject*>(QString("flowGrafikItem"));
//or if Flowgrafik was the main element: Flowgrafik * flowGrafik = mainView.rootObject();
flowGrafik->addFlow(x);
I have to make a mechanical counter controlled from C++. I did it from an image wich contains the digits (0,1,2,3,4,5,6,7,8,9,0). Only one digit is visible at a time. I want this counter to change only in one direction (up), and I came up with this theory: if the new digit is smaller than the old one, I first go to the last zero, then disable the animation, go to the first zero, enable animations and finally go to the wanted number. But this wont work.
It moves instantly to the first zero, and then go with animation to the wanted number. Here is the code:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
id: mainWindow
visible: true
visibility: "Maximized"
property int digit0Y: 0
property bool anim0Enabled: true
Item {
id: root
visible: true
anchors.fill: parent
Rectangle {
id: container
width: 940; height:172
anchors.centerIn: parent
clip: true
color: "black"
NumberElement {
id: digit0
y: mainWindow.digit0Y; x: 0
animationEnabled: anim0Enabled
}
}
}
}
The NumberElement.qml:
import QtQuick 2.0
Rectangle {
id: root
property bool animationEnabled: true
width: 130; height: 1892
color: "transparent"
Behavior on y {
enabled: root.animationEnabled
SmoothedAnimation { velocity: 200; duration: 1500; alwaysRunToEnd: true }
}
Image {
id: digits
source: "http://s30.postimg.org/6mmsfxdb5/cifre_global.png"
}
}
EDIT:
#include <QQmlComponent>
#include <QGuiApplication>
#include <QThread>
#include <QQmlApplicationEngine>
#include <QQmlProperty>
#include <QDebug>
int number = 0;
int oldDigit = 0;
void set(QObject *object, int number) {
int newDigit = number%10;
if (newDigit < oldDigit) {
QQmlProperty::write(object, "digit0Y", -1720);
QQmlProperty::write(object, "anim0Enabled", false);
QQmlProperty::write(object, "digit0Y", 0);
QQmlProperty::write(object, "anim0Enabled", true);
}
QQmlProperty::write(object, "digit0Y",newDigit*(-172));
oldDigit = newDigit;
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlEngine engine;
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
if (component.status() == QQmlComponent::Error) {
qWarning() << component.errorString();
return 1;
}
QObject *object = component.create();
set(object, 9);
//QThread::msleep(1000);
set(object, 1);
return app.exec();
}
Normally a separate class take care of setting the digits, related to some events, but i tried to simplify to demonstrate my problem. In the example above, the digit goes to 1, and dont care about the set(object, 9). That is my problem.
You need to wait for the first animation to finish before you start the second one. You might be thinking, "but I set alwaysRunToEnd to true...", but that doesn't help here:
This property holds whether the animation should run to completion when it is stopped.
If this true the animation will complete its current iteration when it is stopped - either by setting the running property to false, or by calling the stop() method.
What's currently happening is that you set 9 as the digit, which tells the animation that it should start animating, but then the next instruction you give is to set 1 as the digit, which tells the animation to stop what it was doing and animate this new change.
In general, handling Qt Quick animations is easier from QML.
I'd also recommend PathView for this particular use case, as it might make it easier to achieve what you're after.
I am using VTK as qvtkwidget to display 3D objects in QT.
I know I can change its interaction style by making my own modified interaction style.
I wanna rotate and move an object by right click and holding left click respectively.
And tried to make the modified interaction style class in header file, but there are some difficulties using functions declared in other classes in the modified interaction style. This should be simple.
Does anyone know about this?
boxWidget.at(boxWidget.count()-1)->SetInput(reader->GetOutput());
boxWidget.at(boxWidget.count()-1)->SetScalingEnabled(0); //turn this on to be able to resize the model
boxWidget.at(boxWidget.count()-1)->SetRotationEnabled(0);
boxWidget.at(boxWidget.count()-1)->SetInteractor(this->ui->qvtkWidget->GetInteractor());
boxWidget.at(boxWidget.count()-1)->GetInteractor()->SetInteractorStyle(MyStyle::New()); /
BoxWidget is the object I am trying to apply 'MyStyle'
And the following is the MyStyle class
class MyStyle : public vtkInteractorStyleTrackballCamera , public MainWindow
{
private:
unsigned int NumberOfClicks;
int PreviousPosition[2];
int ResetPixelDistance;
public:
static MyStyle *New();
vtkTypeMacro(MyStyle, vtkInteractorStyleTrackballCamera);
PeterStyle() : NumberOfClicks(0) , ResetPixelDistance(5)
{
this->PreviousPosition[0] = 0;
this->PreviousPosition[1] = 0;
}
virtual void OnLeftButtonDown()
{
qDebug() << "My Style Mouse Left Clicked";
//std::cout << "Pressed left mouse button." << std::endl;
this->NumberOfClicks++;
//std::cout << "NumberOfClicks = " << this->NumberOfClicks << std::endl;
int pickPosition[2];
this->GetInteractor()->GetEventPosition(pickPosition);
int xdist = pickPosition[0] - this->PreviousPosition[0];
int ydist = pickPosition[1] - this->PreviousPosition[1];
this->PreviousPosition[0] = pickPosition[0];
this->PreviousPosition[1] = pickPosition[1];
int moveDistance = (int)sqrt((double)(xdist*xdist + ydist*ydist));
// Reset numClicks - If mouse moved further than resetPixelDistance
if(moveDistance > this->ResetPixelDistance)
{
this->NumberOfClicks = 1;
}
if(this->NumberOfClicks == 2)
{
vtkSmartPointer<vtkCamera> camera =
vtkSmartPointer<vtkCamera>::New();
camera->SetPosition(140.0, 155.0, 590.0);
camera->SetFocalPoint(140.0, 155.0, 0.0);
camera->SetClippingRange(590.0, 600.0);
this->GetCurrentRenderer()->SetActiveCamera(camera);
qDebug() << "Double clicked.";
this->NumberOfClicks = 0;
}
// forward events
vtkInteractorStyleTrackballCamera::OnLeftButtonDown();
}
I don't know what to change to move objects with leftclick.
If you're looking only to move objects with left buttons, you don't need to subclass your own interaction style, vtk already implements vtkInteractorStyleTrackballActor style that allows controlling actors by mouse, without interacting with camera. You can then override it to do whatever you want.