How to create scrollbar in QtQuick 2.0? - qt

I am trying to create a scrollbar in QtQuick 2.0,
I found that Scrollbar component is available in QtQuick 1.0 but I can't find such component in QtQuick 2.0. How can I create scrollbar for ListView in QtQuick 2.0?
Any help? Thanks in advance.

ScrollBar/ScrollIndicator is easy to do, and the code would be identical in QQ1 or QQ2 (except the import) :
///////// ScrollBar.qml //////////////
import QtQuick 2.0;
Item {
id: scrollbar;
width: (handleSize + 2 * (backScrollbar.border.width +1));
visible: (flickable.visibleArea.heightRatio < 1.0);
anchors {
top: flickable.top;
right: flickable.right;
bottom: flickable.bottom;
margins: 1;
}
property Flickable flickable : null;
property int handleSize : 20;
function scrollDown () {
flickable.contentY = Math.min (flickable.contentY + (flickable.height / 4), flickable.contentHeight - flickable.height);
}
function scrollUp () {
flickable.contentY = Math.max (flickable.contentY - (flickable.height / 4), 0);
}
Binding {
target: handle;
property: "y";
value: (flickable.contentY * clicker.drag.maximumY / (flickable.contentHeight - flickable.height));
when: (!clicker.drag.active);
}
Binding {
target: flickable;
property: "contentY";
value: (handle.y * (flickable.contentHeight - flickable.height) / clicker.drag.maximumY);
when: (clicker.drag.active || clicker.pressed);
}
Rectangle {
id: backScrollbar;
radius: 2;
antialiasing: true;
color: Qt.rgba(0.5, 0.5, 0.5, 0.85);
border {
width: 1;
color: "darkgray";
}
anchors { fill: parent; }
MouseArea {
anchors.fill: parent;
onClicked: { }
}
}
MouseArea {
id: btnUp;
height: width;
anchors {
top: parent.top;
left: parent.left;
right: parent.right;
margins: (backScrollbar.border.width +1);
}
onClicked: { scrollUp (); }
Text {
text: "V";
color: (btnUp.pressed ? "blue" : "black");
rotation: -180;
anchors.centerIn: parent;
}
}
MouseArea {
id: btnDown;
height: width;
anchors {
left: parent.left;
right: parent.right;
bottom: parent.bottom;
margins: (backScrollbar.border.width +1);
}
onClicked: { scrollDown (); }
Text {
text: "V";
color: (btnDown.pressed ? "blue" : "black");
anchors.centerIn: parent;
}
}
Item {
id: groove;
clip: true;
anchors {
fill: parent;
topMargin: (backScrollbar.border.width +1 + btnUp.height +1);
leftMargin: (backScrollbar.border.width +1);
rightMargin: (backScrollbar.border.width +1);
bottomMargin: (backScrollbar.border.width +1 + btnDown.height +1);
}
MouseArea {
id: clicker;
drag {
target: handle;
minimumY: 0;
maximumY: (groove.height - handle.height);
axis: Drag.YAxis;
}
anchors { fill: parent; }
onClicked: { flickable.contentY = (mouse.y / groove.height * (flickable.contentHeight - flickable.height)); }
}
Item {
id: handle;
height: Math.max (20, (flickable.visibleArea.heightRatio * groove.height));
anchors {
left: parent.left;
right: parent.right;
}
Rectangle {
id: backHandle;
color: (clicker.pressed ? "blue" : "black");
opacity: (flickable.moving ? 0.65 : 0.35);
anchors { fill: parent; }
Behavior on opacity { NumberAnimation { duration: 150; } }
}
}
}
}
To use it :
import QtQuick 2.0;
Rectangle {
width: 400;
height: 300;
ListView {
id: list;
anchors.fill: parent;
model: 100;
delegate: Rectangle {
height: 50;
width: parent.width;
color: (model.index %2 === 0 ? "darkgray" : "lightgray");
}
}
ScrollBar {
flickable: list;
}
}
Enjoy !

Loved the solution by TheBootroo (+1 for him!) but found his solution only few days ago, by following a comment to a recent question.
Meanwhile, I've independently developed mine for a project I was working on and I'm going to share such a solution here. Hope it can be useful. :)
My scrollbar has a (sort of) "OS X feel" (intended) so e.g. it does not include scrolling arrows on the sides.
Here is the code:
import QtQuick 2.0
Item {
id: scrollbar
property Flickable flk : undefined
property int basicWidth: 10
property int expandedWidth: 20
property alias color : scrl.color
property alias radius : scrl.radius
width: basicWidth
anchors.right: flk.right;
anchors.top: flk.top
anchors.bottom: flk.bottom
clip: true
visible: flk.visible
z:1
Binding {
target: scrollbar
property: "width"
value: expandedWidth
when: ma.drag.active || ma.containsMouse
}
Behavior on width {NumberAnimation {duration: 150}}
Rectangle {
id: scrl
clip: true
anchors.left: parent.left
anchors.right: parent.right
height: flk.visibleArea.heightRatio * flk.height
visible: flk.visibleArea.heightRatio < 1.0
radius: 10
color: "gray"
opacity: ma.pressed ? 1 : ma.containsMouse ? 0.65 : 0.4
Behavior on opacity {NumberAnimation{duration: 150}}
Binding {
target: scrl
property: "y"
value: !isNaN(flk.visibleArea.heightRatio) ? (ma.drag.maximumY * flk.contentY) / (flk.contentHeight * (1 - flk.visibleArea.heightRatio)) : 0
when: !ma.drag.active
}
Binding {
target: flk
property: "contentY"
value: ((flk.contentHeight * (1 - flk.visibleArea.heightRatio)) * scrl.y) / ma.drag.maximumY
when: ma.drag.active && flk !== undefined
}
MouseArea {
id: ma
anchors.fill: parent
hoverEnabled: true
drag.target: parent
drag.axis: Drag.YAxis
drag.minimumY: 0
drag.maximumY: flk.height - scrl.height
preventStealing: true
}
}
}
And here is the code to use it. All the fields are optional expect for the flickable, obviously. Values set are the default ones:
ScrollBar {
flk: privacyFlick
radius: 10 // Optional
basicWidth: 10 // Optional
expandedWidth: 20 // Optional
color: "grey" // Optional
}

I think this will do the trick
http://qt-project.org/doc/qt-5.1/qtquickcontrols/qml-qtquick-controls1-scrollview.html
import QtQuick 2.0
import QtQuick.Controls 1.0
ScrollView{
ListView {
...
}
}

Qt 5.6 introduces new controls as the technical preview "Qt Labs Controls". Among other stuff, the controls introduce a built-in ScrollBar type (interactive) and ScrollIndicator type (not interactive).
In Qt 5.7 new controls exited technical preview and are now renamed "Quick Controls 2", to stress the fact that they superseed the previous controls.
If you are using Qt 5.6, which is an LTS version and will be around for quite sometime, ScrollBar can be used as follows:
import QtQuick 2.6
import Qt.labs.controls 1.0
import QtQuick.Window 2.2
ApplicationWindow {
visible: true
width: 400
height: 600
Flickable {
anchors.fill: parent
contentWidth: image.width
contentHeight: image.height
//ScrollIndicator.vertical: ScrollIndicator { } // uncomment to test
ScrollBar.vertical: ScrollBar { }
ScrollBar.horizontal: ScrollBar { }
Image {
id: image
source: "http://i.ytimg.com/vi/tntOCGkgt98/maxresdefault.jpg"
}
}
}
Whereas in Qt 5.7 and onward you can use ScrollBar or ScrollIndicator as follows:
import QtQuick 2.6
import QtQuick.Controls 2.0
import QtQuick.Window 2.2
ApplicationWindow {
visible: true
width: 600
height: 300
Flickable {
anchors.fill: parent
contentWidth: image.width
contentHeight: image.height
ScrollIndicator.vertical: ScrollIndicator { }
//ScrollBar.vertical: ScrollBar { } // uncomment to test
Image {
id: image
source: "http://s-media-cache-ak0.pinimg.com/736x/92/9d/3d/929d3d9f76f406b5ac6020323d2d32dc.jpg"
}
}
}
Usage syntax is pretty much the same whereas a major refactoring occured in the styling code as can be seen in e.g. Labs Controls ScrollIndicator customization page in comparison to Quick Controls 2 ScrollIndicator customization page.

Related

QML 'Custom Menu' option moving upwards

I am trying to create a "menu" in QML with custom data in each option
For requirements of my application, I need to show it loading the QML file dynamically (Qt.createComponent). What I need is to show some fixed options in the bottom part, and when clicked the top one, another options appear below this top option, which keeps in the top
A simple example. I have this menu:
Option 4
Option 2
Option 1
And when clicked in Option 4, the menu changes to
Option 4
Option 3
Option 2
Option 1
So Option 4 is moved upwards and Option 3 appears.
I would like to have a 'shadow' around all my menu (I added a DropShadow component for that purpose).
I have this simple test code, where I have a main Rectangle (to be surrounded by the shadow), and 2 Rectangles inside.
Rect1 for the fixed part (Option 1, Option 2), and Rect2 for the 'movable' part (Option 3, Option 4).
Rect2 is behind Rect1 (z: -1), and located to have only Option 4 visible, above Option 2. When clicked Option 4, Rect2 is moved upwards and all options are visible.
To achieve that, I have to update Rect2 visible height, and main window position (y value), since main window height depends on this Rect2 visible height.
I have it working, but it flicks a lot since 2 variables changes ('fixed panel' is moved upwards and back).
I have also tried with a ParallelAnimation for 2 values, but no success.
Any idea to have this menu with a smooth movement?
Main.qml:
import QtQuick 2.0
Rectangle
{
id: window
property variant win: undefined;
Component.onCompleted:
{
var component = Qt.createComponent("TestMenu.qml");
win = component.createObject(window, {"x": 500, "y": 500});
win.show();
}
}
TestMenu.qml:
import QtQuick 2.0
import QtQuick.Window 2.1
import QtGraphicalEffects 1.0
Window {
id: window
flags: Qt.Tool | Qt.FramelessWindowHint
height: panel.height
color: "transparent"
property int radiusShadow: 20
property int iOptionHeight: 30
Rectangle {
id: panel
anchors { centerIn: parent}
height: menu1.height + menu2.heightVisible + 2*radiusShadow
width: window.width - 2*radiusShadow
color: "transparent"
Rectangle {
id: menu1
anchors { bottom: parent.bottom; bottomMargin: radiusShadow }
width: parent.width
height: column1.children.length * iOptionHeight
Column {
id: column1
anchors.fill: parent
Rectangle {
color: "red";
Text { text: qsTr("option 2") }
height: iOptionHeight; width: parent.width
}
Rectangle {
color: "green";
Text { text: qsTr("option 1") }
height: iOptionHeight; width: parent.width
}
}
}
Rectangle {
id: menu2
property int heightVisible: iOptionHeight
anchors { top: parent.top; topMargin: radiusShadow; left: menu1.left }
width: parent.width
height: column2.children.length * iOptionHeight
z: -1
Column {
id: column2
anchors.fill: parent
Rectangle {
id: blue
property bool bOpen: false
color: "blue";
height: iOptionHeight; width: parent.width
Text { text: qsTr("option 4") }
MouseArea {
anchors.fill: parent
onClicked: {
blue.bOpen = !blue.bOpen;
panel.showHideMenu2(blue.bOpen);
}
}
}
Rectangle {
color: "pink";
Text { text: qsTr("option 3") }
height: iOptionHeight; width: parent.width
}
}
}
function showHideMenu2(bShow)
{
if (bShow)
{
window.y -= iOptionHeight
menu2.heightVisible += iOptionHeight;
}
else
{
window.y += iOptionHeight
menu2.heightVisible -= iOptionHeight;
}
}
}
DropShadow {
id: dropShadow
visible: true
anchors.fill: panel
radius: radiusShadow
samples: 24
color: "#40000000"
source: panel
}
}
As a quick answer for your question, you can get what you want using Behavior animation for a property change.
Here, Behavior animation will be used on y (position) change of your window, and for height change of respective rectangles.
Here is the patch for your code I recommend you to apply to see smooth movement:
## -10,6 +10,9 ##
property int radiusShadow: 20
property int iOptionHeight: 30
+ property int animationDuration: 500 // ms
+
+ Behavior on y { NumberAnimation { duration: window.animationDuration } }
Rectangle {
id: panel
## -18,6 +21,7 ##
height: menu1.height + menu2.heightVisible + 2*radiusShadow
width: window.width - 2*radiusShadow
color: "transparent"
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
Rectangle {
id: menu1
## -25,6 +29,7 ##
anchors { bottom: parent.bottom; bottomMargin: radiusShadow }
width: parent.width
height: column1.children.length * iOptionHeight
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
Column {
id: column1
## -52,6 +57,8 ##
width: parent.width
height: column2.children.length * iOptionHeight
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
+
z: -1
Column {
## -64,6 +71,7 ##
color: "blue";
height: iOptionHeight; width: parent.width
Text { text: qsTr("option 4") }
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
MouseArea {
anchors.fill: parent
## -77,6 +85,7 ##
color: "pink";
Text { text: qsTr("option 3") }
height: iOptionHeight; width: parent.width
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
}
}
}
## -105,4 +114,4 ##
color: "#40000000"
source: panel
}
-}+}
I have tried with a model and a ListView (code is simpler now), but I don't know where and how insert an 'Animation' or a 'Behaviour' to achieve my target, if it is possible to do it (I have tried several options with no success...)
The expected effect is that the 1st rectangle moves up when the 2nd appears, so the bottom one keeps in its position (at bottom). But the default behaviour when I add an element to the model is that the list increased the height downwards
Maybe anyone could help...
My code:
import QtQuick 2.0
import QtQuick.Controls 1.4
Rectangle {
id: rootItem
width: 400
height: list.height
ListModel {
id: modelRects
ListElement { rectColor: "green" }
ListElement { rectColor: "orange" }
}
ListView {
id: list
height: modelRects.count * 30
model: modelRects
delegate: Rectangle {
id: delegate
height: 30
width: rootItem.width
color: rectColor
MouseArea {
anchors.fill: parent
onClicked: onRectClicked(index);
}
}
}
function onRectClicked(index)
{
if (index == 0)
{
if (modelRects.count == 2)
modelRects.insert(1, {"idRect": 2, "rectColor": "red"});
else
modelRects.remove(1, 1);
}
}
}

QML: How to reject drop action

I have one DropArea and two elements. I want DropArea reject the drop event if the DropArea already got one element be dropped, the another element not allow drop into, unless the first one move out.
DropArea {
property bool dropped: false
onDropped: {
drop.accepted = !dropped;
dropped = true;
}
onExited: dropped = false
}
But looks like drop.accepted not work,
BTW anyway to get the objects was dropped in DropArea
You should control if the item must be dropped or not in onReleased, checking the dropped property.
Full example:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: win
visible: true
width: 800
height: 600
title: qsTr("Hello World")
Repeater {
model: 10
Rectangle {
id: rect
width: 50
height: 50
z: mouseArea.drag.active || mouseArea.pressed ? 2 : 1
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
x: Math.random() * (win.width / 2 - 100)
y: Math.random() * (win.height - 100)
property point beginDrag
property bool caught: false
border { width:2; color: "white" }
radius: 5
Drag.active: mouseArea.drag.active
Text {
anchors.centerIn: parent
text: index
color: "white"
}
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: parent
onPressed: {
rect.beginDrag = Qt.point(rect.x, rect.y);
}
onReleased: {
if(!rect.caught || dragTarget.dropped) {
backAnimX.from = rect.x;
backAnimX.to = beginDrag.x;
backAnimY.from = rect.y;
backAnimY.to = beginDrag.y;
backAnim.start()
}
parent.Drag.drop()
console.log("MouseArea - containsDrag " + dragTarget.dropped)
}
}
ParallelAnimation {
id: backAnim
SpringAnimation { id: backAnimX; target: rect; property: "x";
duration: 500; spring: 2; damping: 0.2 }
SpringAnimation { id: backAnimY; target: rect; property: "y";
duration: 500; spring: 2; damping: 0.2 }
}
}
}
Rectangle {
anchors {
top: parent.top
right: parent.right
bottom: parent.bottom
}
width: parent.width / 2
color: "gold"
DropArea {
id: dragTarget
anchors.fill: parent
property bool dropped: false
onEntered: {
console.log("onEntered " + containsDrag)
drag.source.caught = true;
}
onExited: {
console.log("onExited " + containsDrag)
dropped = false;
}
onDropped:
{
console.log("onDropped");
dropped = true;
}
}
}
}
Use drop.accept() instead. The above can be done as follows:
property bool containsItem: false
DropArea {
id: dropArea
anchors.fill: parent
onDropped: {
if(containsItem)
drop.accept(Qt.IgnoreAction)
else
drop.accept()
containsItem = true;
}
}
Also donot use dropped property as it is already an attached property inside onDropped event handler.
Edit:
If rect is the Item to be dragged and dropped then:
Rectangle {
id: rect
width: 40; height: 40
color: "red"
Drag.active: dragArea.drag.active
Drag.hotSpot.x: 20
Drag.hotSpot.y: 20
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
onReleased: if (rect.Drag.drop() !== Qt.IgnoreAction) {
console.log("Accepted!");
} else {
console.log("Rejected!");
}
}
}

How to add animation onPressed and onReleased in QML Slider?

http://doc.qt.io/qt-5/qml-qtquick-controls-styles-sliderstyle.html
Slider {
anchors.centerIn: parent
style: SliderStyle {
groove: Rectangle {
implicitWidth: 200
implicitHeight: 8
color: "gray"
radius: 8
}
handle: Rectangle {
anchors.centerIn: parent
color: control.pressed ? "white" : "lightgray"
border.color: "gray"
border.width: 2
implicitWidth: 34
implicitHeight: 34
radius: 12
}
}
How to access the onReleased and onPressed of the slider in order to start and stop some animation?
Here is what I tried:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls.Styles 1.4
import QtQuick.Controls 1.4
Window {
visible: true
Slider
{
id: head
property Rectangle thumb: thumb
anchors.centerIn: parent
style: SliderStyle {
groove: Rectangle {
implicitWidth: 200
implicitHeight: 8
color: "gray"
radius: 8
}
handle: Rectangle {
id: thumb
anchors.centerIn: parent
color: control.pressed ? "white" : "lightgray"
border.color: "gray"
border.width: 2
implicitWidth: 34
implicitHeight: 34
radius: 12
}
}
onPressedChanged:
{
if(pressed)
{
console.log("pressed")
returnAnimation.stop()
}
else
{
console.log("released")
returnAnimation.start()
}
}
ParallelAnimation {
id: returnAnimation
NumberAnimation { target: thumb.anchors; property: "horizontalCenterOffset";
to: 0; duration: 200; easing.type: Easing.OutSine }
NumberAnimation { target: thumb.anchors; property: "verticalCenterOffset";
to: 0; duration: 200; easing.type: Easing.OutSine }
}
}
}
Error:
ReferenceError: thumb is not defined
Here is a fully working example. You will have to create your own images referenced here since I can't attach them.
I have found scoping is tricky in QML with component objects. The ":style:handle" component in Slider can "see out" to the higher levels but the higher levels cannot "see in" to the ":style:handle" component.
General Strategy
Create a property in the Top Level Slider scope
Use the property inside the ":style:handle" component since it can "see out"
Use the higher level onPressedChanged handler and the pressed property to adjust the high level property which will be "seen" by the low level component.
Slider {
id: portVoltageSlider
width: 100; height: 27
maximumValue: 150; minimumValue: -150
value: 0.00
stepSize: 10
anchors { centerIn: parent }
// style:handle component will be able to see/access this property
// opacity value of style: SliderStyle:handle.sliderHover
property real hoverOpacity: 0
// adjust property on slider pressed
onPressedChanged: {
// show slider Hover when pressed, hide otherwise
if( pressed ) {
console.log("slider pressed. show hover.")
hoverShowAnimation.start()
}
else {
console.log("slider released. hide hover.")
hoverHideAnimation.start()
}
}
// gratuitous animation using opacity
PropertyAnimation {
id: hoverShowAnimation
target: portVoltageSlider; properties: "hoverOpacity"; from: portVoltageSlider.hoverOpacity; to: 1; duration: 500
}
PropertyAnimation {
id: hoverHideAnimation
target: portVoltageSlider; properties: "hoverOpacity"; from: portVoltageSlider.hoverOpacity; to: 0; duration: 500
}
style: SliderStyle {
id: sliderStyle
property bool hoverVisible: false
groove: Rectangle {
// x: slider1.leftPadding
y: portVoltageSlider.topPadding + portVoltageSlider.availableHeight / 2 - height / 2
implicitWidth: 200; implicitHeight: 4
width: portVoltageSlider.availableWidth; height: implicitHeight
radius: 2
color: "#bdbebf"
Rectangle {
width: portVoltageSlider.visualPosition * parent.width; height: parent.height
color: "yellow"
radius: 2
}
}
handle: Image {
id: sliderHandle
width: 22; height: 24
source: "sliderThumb.svg"
anchors { centerIn: parent }
Image {
id: sliderHover
width: 22; height: 24
source: "sliderValue.svg"
anchors { bottom: sliderHandle.top }
opacity: portVoltageSlider.hoverOpacity
Label {
id: check
anchors {centerIn: parent; verticalCenterOffset: -4 }
text: portVoltageSlider.value
font.pointSize: 6
font.bold: true
}
}
}
}
}
That what I meant in the comment above:
Slider {
...
onPressedChanged: {
if(pressed)
console.log("pressed")
else
console.log("released")
}
}
Would this work?
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls.Styles 1.4
import QtQuick.Controls 1.4
Window {
visible: true
Slider
{
id: head
property Rectangle thumb: thumb
//Added these signals:
signal startAnim
signal stopAnim
anchors.centerIn: parent
style: SliderStyle {
groove: Rectangle {
implicitWidth: 200
implicitHeight: 8
color: "gray"
radius: 8
}
handle: Rectangle {
id: thumb
anchors.centerIn: parent
color: control.pressed ? "white" : "lightgray"
border.color: "gray"
border.width: 2
implicitWidth: 34
implicitHeight: 34
radius: 12
//Moved animation within the confines of the object that it actually pertains to
ParallelAnimation {
id: returnAnimation
NumberAnimation { target: thumb.anchors; property: "horizontalCenterOffset";
to: 0; duration: 200; easing.type: Easing.OutSine }
NumberAnimation { target: thumb.anchors; property: "verticalCenterOffset";
to: 0; duration: 200; easing.type: Easing.OutSine }
}
//Signal connections done here:
Component.onCompleted: {
head.startAnim.connect(returnAnimation.start)
head.stopAnim.connect(returnAnimation.stop)
}
}
}
onPressedChanged:
{
if(pressed)
{
console.log("pressed")
startAnim()
}
else
{
console.log("released")
stopAnim()
}
}
}
}

Create a context menu with QML

The question is quite simple: How to create a context menu with QML and javascript?
I have found this manual about 'Menu' component on qt-project.org but quite unusable: http://qt-project.org/doc/qt-5.1/qtquickcontrols/qml-qtquick-controls1-menu.html#details
The Qt Creator IDE says: "Unknown component" on the word 'Menu' in my QML file. I'm using Qt 5.2.1 stable. And I'm coding with Qt Quick 2.
Need to import Qt Quick Controls along with Qt Quick:
import QtQuick 2.0
import QtQuick.Controls 1.1
For a context menu, you need to call popup() which opens the menu at the cursor position.
I have created one customized context menu for my desktop application. This is working perfect for me. I think this may be helpfull.
ContextMenu.qml
import QtQuick 1.1
Rectangle {
id: contextMenuItem
signal menuSelected(int index) // index{1: Select All, 2: Remove Selected}
property bool isOpen: false
width: 150
height: menuHolder.height + 20
visible: isOpen
focus: isOpen
border { width: 1; color: "#BEC1C6" }
Column {
id: menuHolder
spacing: 1
width: parent.width
height: children.height * children.length
anchors { verticalCenter: parent.verticalCenter; left: parent.left; leftMargin: 3 }
ContextButton {
id: selectedAll
button_text: "Select All"
index: 1
onButtonClicked: menuSelected(index);
}
ContextButton {
id: removeSelected
button_text: "Remove Selected"
index: 2
onButtonClicked: menuSelected(index);
}
}
}
ContextItem.qml
import QtQuick 1.1
Item {
id: contextButtonItem
property string button_text;
property bool clicked;
property int index;
property string target;
property bool enable: true;
signal buttonClicked;
height: 25
width: parent.width - 5
function viewButtonHovered() {
viewButton.state = "hovered";
outerArea.z= -10;
}
function viewButtonExited() {
outerArea.z= 1;
if(clicked == false) {
viewButton.state = "";
} else {
viewButton.state = "clicked"
}
}
Rectangle {
id: viewButton;
height: vButton.height + 4
width: parent.width
Text {
id: vButton
text: qsTr(button_text)
width: parent.width
anchors { verticalCenter: parent.verticalCenter; left: parent.left; leftMargin: 10 }
font { pixelSize: 14 }
}
MouseArea {
hoverEnabled: enable
anchors.fill: parent
enabled: enable
onClicked: buttonClicked();
onEntered: viewButtonHovered();
onExited: viewButtonExited();
}
states: [
State {
name: "clicked";
PropertyChanges { target: vButton; color: "#286E1E"; }
},
State {
name: "hovered";
PropertyChanges { target: vButton; color: "#BEA1C6"; }
},
State {
name: "normal";
PropertyChanges { target: vButton; color: "#232323"; }
}
]
}
}

Proper way to implement zoom/scale in Qt Quick (QML) Flickable

I'm using Qt 5.2 beta, Qt Quick 2.1.
I have straight ahead problem with Flickable component.
Here is minimal working code example:
import QtQuick 2.1
import QtQuick.Controls 1.0
ApplicationWindow {
width: 300
height: 200
Rectangle {
anchors.fill: parent
color: "green"
Flickable {
id: flickArea
anchors {fill: parent; margins: 10; }
contentWidth: rect.width;
contentHeight: rect.height
Rectangle {
id: rect
x: 0; y: 0;
width: 200; height: 300;
color: "lightgrey"
Rectangle {
anchors { fill: parent; margins: 10; }
color: "red"
}
}
}
}
Button {
anchors.bottom: parent.bottom;
anchors.horizontalCenter: parent.horizontalCenter;
text: "Scale flickArea"
onClicked: { flickArea.scale += 0.2; }
}
}
When I'm doing scaling, I expect that all child elements will stay visible as it was before and inner area to become bigger.
But instead, child elements move out of Flickable content.
Can someone propose a normal way to avoid this problem without the need for manual recalculation of all offsets?
I got it work as expected this way, but IMO this way is little bit tricky:
import QtQuick 2.1
import QtQuick.Controls 1.0
ApplicationWindow {
width: 300
height: 200
Rectangle {
anchors.fill: parent
color: "green"
Flickable {
id: flickArea
anchors {fill: parent; margins: 10; }
contentWidth: rect.width*rect.scale
contentHeight: rect.height*rect.scale
Rectangle {
id: rect
transformOrigin: Item.TopLeft
x: 0; y: 0;
width: 200; height: 300;
color: "lightgrey"
Rectangle {
id: inner
anchors { fill: parent; margins: 10; }
color: "red"
}
}
}
}
Button {
anchors.bottom: parent.bottom;
anchors.horizontalCenter: parent.horizontalCenter;
text: "Scale flickArea"
onClicked: {
rect.scale += 0.2;
}
}
}
Can someone propose something better?
UPD. I did not close this question for 1 year, but still didn't get any better solution or better way to doing things.
using Scale transform seems better solution:
import QtQml 2.11
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.11
import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.4
import QtCharts 2.2
import QtGraphicalEffects 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
property int scaleX: 1;
Rectangle {
anchors.fill: parent
color: "green"
Flickable {
id: flickArea
anchors {fill: parent; margins: 10; }
contentWidth: rect.width*rect.scale
contentHeight: rect.height*rect.scale
transform: Scale { origin.x: 0; origin.y: 240; xScale: scaleX}
Rectangle {
id: rect
transformOrigin: Item.TopLeft
x: 0; y: 0;
width: 200; height: 300;
color: "lightgrey"
Rectangle {
id: inner
anchors { fill: parent; margins: 10; }
color: "red"
}
}
}
}
Button {
anchors.bottom: parent.bottom;
anchors.horizontalCenter: parent.horizontalCenter;
text: "Scale flickArea"
onClicked: {
// flickArea.scale += 0.2;
scaleX += 1;
console.log(flickArea.contentWidth);
console.log(flickArea.scale)
}
}
}
A propar way to implement zoomable image in qml
import QtQuick 2.15
Flickable
{
id:flickable
property int imageWidth
property int imageHeight
property alias source: mImage.source
contentWidth: imageWidth
contentHeight:imageHeight
boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.StopAtBounds
states: [
State {
name: "state_StickToCenter" // state is used when content size is less than flickable size then content
// center should stick to flickable center
when : ( flickable.contentWidth < flickable.width || flickable.contentHeight< flickable.height )
PropertyChanges {
target: flickable.contentItem
anchors.horizontalCenter: width < flickable.width ? flickable.horizontalCenter : undefined
anchors.verticalCenter: height < flickable.height ? flickable.verticalCenter : undefined
}
}
]
onStateChanged: { cancelFlick(); returnToBounds(); }
Image
{
id:mImage
width:flickable.contentWidth;
height: flickable.contentHeight;
source: flickable.imageSource
fillMode: Image.PreserveAspectFit;
Component.onCompleted:
{
imageWidth = mImage.paintedWidth;
imageHeight = mImage.paintedHeight
}
autoTransform: true
PinchArea
{
id:pinchArea
anchors.fill: parent
property bool zoomTriggeredFromPinchArea:false
property point pinchCenter;
onPinchStarted: zoomTriggeredFromPinchArea=true;
onPinchUpdated:
{
var newZoomFactor = privateProperties.currentZoomFactor+ privateProperties.currentZoomFactor*(pinch.scale-1);
pinchCenter =pinch.center;
privateProperties.zoom(privateProperties.getBoundedScaleFactor(newZoomFactor))
}
onPinchFinished:
{
privateProperties.currentZoomFactor += privateProperties.currentZoomFactor*(pinch.scale-1);
privateProperties.currentZoomFactor = privateProperties.getBoundedScaleFactor(privateProperties.currentZoomFactor);
zoomTriggeredFromPinchArea=false;
}
MouseArea
{
id:mouseArea
anchors.fill: parent
propagateComposedEvents :true
scrollGestureEnabled: false
hoverEnabled: true
onDoubleClicked:
{
if(privateProperties.currentZoomFactor>1)resetScale();
else
{
var widthScale = (flickable.width+20)/mImage.width;
var heightScale = (flickable.height+20)/mImage.height;
var maxScale = Math.max(widthScale,heightScale);
if(maxScale>1)
{
privateProperties.pointOfDoubleClick = Qt.point(mouseX,mouseY);
privateProperties.useDoubleClickPoint = true;
privateProperties.currentZoomFactor = maxScale;
}
}
}
onWheel:
{
if(wheel.modifiers===Qt.ControlModifier)
{
wheel.accepted=true;
var newZoomFactor;
if(wheel.angleDelta.y>0)
newZoomFactor = privateProperties.currentZoomFactor + (privateProperties.currentZoomFactor*privateProperties.zoomStepFactor);
else newZoomFactor = privateProperties.currentZoomFactor - (privateProperties.currentZoomFactor*privateProperties.zoomStepFactor);
privateProperties.currentZoomFactor = privateProperties.getBoundedScaleFactor(newZoomFactor);
return;
}
wheel.accepted=false;
}
}
}
}
QtObject
{
id : privateProperties
property bool useDoubleClickPoint:false;
property point pointOfDoubleClick;
property real maxZoomFactor : 30.0
property real zoomStepFactor :0.3;
property real currentZoomFactor: 1
property real minZoomFactor :1;
property point scaleCenter : pinchArea.zoomTriggeredFromPinchArea
? pinchArea.pinchCenter : Qt.point(mouseArea.mouseX,mouseArea.mouseY);
Behavior on currentZoomFactor {
NumberAnimation { id:scaleNumberAnimation
duration: pinchArea.zoomTriggeredFromPinchArea ? 0 : privateProperties.useDoubleClickPoint ?
Math.min(200*privateProperties.currentZoomFactor,500) : 200 ;
onRunningChanged: if(!running) privateProperties.useDoubleClickPoint=false;
}
}
onCurrentZoomFactorChanged:
{
if(!pinchArea.zoomTriggeredFromPinchArea)
zoom(currentZoomFactor);
}
function zoom(scaleFactor)
{
var targetWidth = imageWidth*scaleFactor;
var targetHeight = imageHeight*scaleFactor;
if(useDoubleClickPoint) resizeContent(targetWidth,targetHeight,mapToItem(mImage,pointOfDoubleClick));
else resizeContent(targetWidth,targetHeight,scaleCenter);
returnToBounds();
}
function getBoundedScaleFactor(ScaleFactor)
{
if(ScaleFactor>maxZoomFactor)ScaleFactor = maxZoomFactor;
else if(ScaleFactor<minZoomFactor)ScaleFactor = minZoomFactor;
return ScaleFactor;
}
}
function resetScale()
{
privateProperties.pointOfDoubleClick = Qt.point(0,0);
privateProperties.useDoubleClickPoint = true;
privateProperties.currentZoomFactor = 1;
}
}

Resources