switching from QGraphicsView to QML
-
Hi,
I need some guideline, how to achieve in QML what I have done in QGraphicsView.
I have a QGraphicsView in which I have one big custom QGraphicsPixmapItem with around 100 smaller custom QGraphicsPixmapItem at specific x,y positions. At application start mainItem is scaled to fit the view, then user can stretch this item with pinch gesture (child items are stretched too), then user can move mainItem in view but with restrictions - top left corner of mainItem must not cross top left corner of the view, and bottom right corner must not cross bottom left corner of the view - classic case, picture in view is twice the size of the view itself.
So I need QML equivalent for QGrapvicsView signal that view is ready and has width and heightvoid showEvent(QShowEvent * event); void resizeEvent(QResizeEvent *event);
setting scale for mainView item (and its children), something like:
floorData->transform.setMatrix(floorData->scale/(double)100,0,0,0,floorData->scale/(double)100,0,0,0,1); floorData->mainviewItem->setTransform(floorData->transform);
and some way to control mainItem movement, which is done in subclassed item:
QVariant MainItem::itemChange(GraphicsItemChange change, const QVariant &value)
Is this doable in QML, well I'm sure it is, but what is the concept for that?
Best Regards
Marek -
Some basic way to achieve this:
Item { // Assuming this is your floor item onXChanged: if (x < 0) x = 0 onYChanged: if (y < 0) y = 0 }
Or if you use anchors/ layouts to position items, you kind of get this for free. Modifying position can then be achieved by specifying anchor margins.
-
Some basic way to achieve this:
Item { // Assuming this is your floor item onXChanged: if (x < 0) x = 0 onYChanged: if (y < 0) y = 0 }
Or if you use anchors/ layouts to position items, you kind of get this for free. Modifying position can then be achieved by specifying anchor margins.
@sierdzio thanks for hint. I'm quite new to QML although I'm writing widget apps for some years.
So now I have main.qml and SwipeView with two pages, second one is for QGraphicsView equivalent
Is there any equivalent in QML ?With anchor approach it would be something like ?:
MainItem { anchor.left: parent.left - (calculated size diff between MainItem width and screen width) |
this size should be calculated whenever user stretches MainItem, right?
-
There is no equivalent ot QGraphicsView in QML. They work on different principles. I strongly recommend doing some simple apps first, just to get the hang of it. QML is easy to learn, but you will need some time to get adjusted to different way of thinking than in imperative code.
Regarding anchors. You can't add or substract anything from an anchor. You can modify the margin, however.
MainItem { anchors.left: parent.left anchors.leftMargin: 20 }
Another possibility: consider using Flickable element. I think it might work out very well in your use case. It already supports zooming and flicking, just like you need. And you can get the exact behaviour you need using boundsBehaviour property. Exact position of content item (your floor) can be accessed with contentY and contentX properties, etc.
-
There is no equivalent ot QGraphicsView in QML. They work on different principles. I strongly recommend doing some simple apps first, just to get the hang of it. QML is easy to learn, but you will need some time to get adjusted to different way of thinking than in imperative code.
Regarding anchors. You can't add or substract anything from an anchor. You can modify the margin, however.
MainItem { anchors.left: parent.left anchors.leftMargin: 20 }
Another possibility: consider using Flickable element. I think it might work out very well in your use case. It already supports zooming and flicking, just like you need. And you can get the exact behaviour you need using boundsBehaviour property. Exact position of content item (your floor) can be accessed with contentY and contentX properties, etc.
@sierdzio Thanks for flickable example, there is also one in QtCreator which I will try to implement.
I have an impression that I have seen somewhere in examples that right part of the binding was an expression calculated from some function in javascript or from C++ bindings.Best Regards
Marek -
@Marek said in switching from QGraphicsView to QML:
I have an impression that I have seen somewhere in examples that right part of the binding was an expression calculated from some function in javascript or from C++ bindings.
Yes, that is correct. You can easily write stuff like:
x: 15 * someProperty y: 80 + SomeQObject.someFunction()
etc. However, in case of anchors property, it expects not a numerical value, but an actual anchor from another object. Hence in that case, margins are needed.
-
@Marek said in switching from QGraphicsView to QML:
I have an impression that I have seen somewhere in examples that right part of the binding was an expression calculated from some function in javascript or from C++ bindings.
Yes, that is correct. You can easily write stuff like:
x: 15 * someProperty y: 80 + SomeQObject.someFunction()
etc. However, in case of anchors property, it expects not a numerical value, but an actual anchor from another object. Hence in that case, margins are needed.
@sierdzio I have copied flick-resize into my app and played for a while, seems to be what I need.
Howver, I cant work out how to have two images parent and a children, where children is also affected by pinch gesture
For instance, child image is positioned in (100,100)px of the main mapImage, then pinch gesture resizes the mapImage but child image remains in the same scale and in fixed position over the scaled image.Flickable { id: flick anchors.fill: parent contentWidth: 4109 contentHeight: 824 PinchArea { id:pinch width: Math.max(flick.contentWidth, flick.width) height: Math.max(flick.contentHeight, flick.height) property real initialWidth property real initialHeight //![0] onPinchStarted: { initialWidth = flick.contentWidth initialHeight = flick.contentHeight } onPinchUpdated: { // adjust content pos due to drag flick.contentX += pinch.previousCenter.x - pinch.center.x flick.contentY += pinch.previousCenter.y - pinch.center.y // resize content if(initialWidth * pinch.scale>500 && initialHeight * pinch.scale>200) flick.resizeContent(initialWidth * pinch.scale, initialHeight * pinch.scale, pinch.center) } onPinchFinished: { // Move its content within bounds. flick.returnToBounds() console.log("pinch updated width:"+flick.contentWidth+" height:"+flick.contentHeight+" scale:"+pinch.scale) } //![0] Rectangle { width: flick.contentWidth height: flick.contentHeight color: "white" Image { id: mapImage anchors.fill: parent source: "images/mapa.jpg" MouseArea { anchors.fill: parent onDoubleClicked: { flick.contentWidth = 4109 flick.contentHeight = 824 } } onScaleChanged: { console.log("scale changed:"+scale) } Image { id: childImage x: 100 y: 100 source: "images/seat_icon.jpg" } } } } }
Is this a parent-child relation problem ? Scale of the mapImage does not change.
I'm looking for a similar behavior as with QGraphicsPixmapItem. When I have parent and child and I apply setScale to parent child is also scaled and his position is in parent coordinates so it seems it moves with parent image when it is resized.Best Regards
Marek -
@sierdzio I have copied flick-resize into my app and played for a while, seems to be what I need.
Howver, I cant work out how to have two images parent and a children, where children is also affected by pinch gesture
For instance, child image is positioned in (100,100)px of the main mapImage, then pinch gesture resizes the mapImage but child image remains in the same scale and in fixed position over the scaled image.Flickable { id: flick anchors.fill: parent contentWidth: 4109 contentHeight: 824 PinchArea { id:pinch width: Math.max(flick.contentWidth, flick.width) height: Math.max(flick.contentHeight, flick.height) property real initialWidth property real initialHeight //![0] onPinchStarted: { initialWidth = flick.contentWidth initialHeight = flick.contentHeight } onPinchUpdated: { // adjust content pos due to drag flick.contentX += pinch.previousCenter.x - pinch.center.x flick.contentY += pinch.previousCenter.y - pinch.center.y // resize content if(initialWidth * pinch.scale>500 && initialHeight * pinch.scale>200) flick.resizeContent(initialWidth * pinch.scale, initialHeight * pinch.scale, pinch.center) } onPinchFinished: { // Move its content within bounds. flick.returnToBounds() console.log("pinch updated width:"+flick.contentWidth+" height:"+flick.contentHeight+" scale:"+pinch.scale) } //![0] Rectangle { width: flick.contentWidth height: flick.contentHeight color: "white" Image { id: mapImage anchors.fill: parent source: "images/mapa.jpg" MouseArea { anchors.fill: parent onDoubleClicked: { flick.contentWidth = 4109 flick.contentHeight = 824 } } onScaleChanged: { console.log("scale changed:"+scale) } Image { id: childImage x: 100 y: 100 source: "images/seat_icon.jpg" } } } } }
Is this a parent-child relation problem ? Scale of the mapImage does not change.
I'm looking for a similar behavior as with QGraphicsPixmapItem. When I have parent and child and I apply setScale to parent child is also scaled and his position is in parent coordinates so it seems it moves with parent image when it is resized.Best Regards
MarekI have modified example to work with onWheel signal, easier to work with on desktop.
Child image item is repositioned, but there is slight error with position, due to setting "scale" for child item.
Child Image moves towards bottom right corner when I scale down main image.
Can someone advice me on how to keep this child image in fixed position when flickable element is resized ?Flickable { id: flick anchors.fill: parent contentWidth: 4109 contentHeight: 824 Image { id: mapImage anchors.fill: parent source: "images/mapa.jpg" property real itemScale: 1 MouseArea { anchors.fill: parent onWheel: { if(wheel.angleDelta.y>0) parent.itemScale+=0.1 else parent.itemScale+=-0.1 flick.resizeContent(mapImage.sourceSize.width*parent.itemScale,mapImage.sourceSize.height*parent.itemScale,Qt.point(mouseX,mouseY)) flick.returnToBounds() } } Image { id: childImage x: 100*parent.itemScale y: 100*parent.itemScale scale: parent.itemScale source: "images/seat_icon.jpg" } } }
Best Regards
Marek -
@Marek said in switching from QGraphicsView to QML:
anchors.fill: parent
I suspect that might be the issue: you are trying to anchor to an object that is designed for scrolling (that is: it is designed to handle components larger than itself).
So, my recommendations:
- remove anchors from mapImage
- give mapImage a fixed size
- when zooming/ pinching, modify scale of mapImage
Feel free to take a look at/clone/reuse my code from CCF it's old, very old code, but it used to work and scale contents of a Flickable correctly.
-
@Marek said in switching from QGraphicsView to QML:
anchors.fill: parent
I suspect that might be the issue: you are trying to anchor to an object that is designed for scrolling (that is: it is designed to handle components larger than itself).
So, my recommendations:
- remove anchors from mapImage
- give mapImage a fixed size
- when zooming/ pinching, modify scale of mapImage
Feel free to take a look at/clone/reuse my code from CCF it's old, very old code, but it used to work and scale contents of a Flickable correctly.
@sierdzio Thanks for hint and for Your help, I will look into your code.
I have just returned from the gym and during workout I have figured out the problem ;) Probably more oxygen for the brain.
Instead of child Image which need to be scaled (and then the problem appears) I have Rectangle which is scaled, and inside rectangle there is an image. Works very well.Rectangle { width:sourceImageWidth*parent.itemScale height:sourceImageHeight*parent.itemScale x: 100*parent.itemScale y: 100*parent.itemScale Image { id: childImage anchors.fill: parent source: "images/seat_icon.jpg" } }
Just need to check whether this will work well with 100 child Images on top the mapImage.
So partly solved ;) Now I need to load them dynamically so next questions will follow ;)Best Regards
Marek -
OK, good news. Happy coding :-)