How to use MouseArea in ScrollView and Flow - qml

I do want to use an MouseArea to show a clickable Button within a Flow with in a ScrollView. Reason for this structure is that the Objects are created dynamically from the user, so I want to place them in a Flow to ensure vertical placement and in a ScrollView to ensure scrolling if the become to many objects.
ScrollView {
clip: true
id: shiftsScrollView
wheelEnabled: true
//ScrollBar.vertical.policy: ScrollBar.AlwaysOn
height: 100
width: 300
anchors.left: shiftsTitle.left
anchors.top: shiftsTitle.bottom
//spacing: 0
anchors.leftMargin: 0
anchors.topMargin: 20
Flow {
id: scrollableFlow
layoutDirection: Qt.RightToLeft
flow: Flow.TopToBottom
// Objects will be filled in here at runtime
}
}
This is how the Objects will be created within a js function:
for (let i=0; i<amountOfShifts; i++) {
var myShift = Qt.createQmlObject(
'ShiftListElement {
width: 300;
height: 25;
shiftText: "'+splittedShifts[i]+'";
shiftName: "'+shiftNames[i]+'";
}',
scrollableFlow,
"shiftElement");
shiftListObjects.push(myShift);
shiftsScrollView.contentHeight = amountOfShifts * 25
}
Note that the ID 'scrollableFlow' is the one in the ScrollView and you can see visually that it works. The Objects get placed vertically in order without knowing each other before.
QML Object which contains the non-working MouseArea:
Item {
id: item1
property string shiftText: "shift";
property string shiftName: "name";
width: 200
height: 100
Text {
id: text1
text: item1.shiftText
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 12
anchors.horizontalCenterOffset: -30
anchors.horizontalCenter: parent.horizontalCenter
}
Button {
id: button
width: 20
height: 20
text: qsTr("X")
anchors.verticalCenter: text1.verticalCenter
anchors.left: text1.right
anchors.leftMargin: 20
MouseArea {
id: buttonCommitShiftMouseArea
anchors.fill: parent
preventStealing: true
onClicked:
{
console.log("I work!");
}
}
}
}
I found some answers recommending the property "preventStealing", but it doesn't seem to have an effect in this case. I also tried removing Flow, which doesn't seem to make a difference either. It did work before outside of a ScrollView in a regular Item, which makes me think that the ScrollView steals all MouseEvents within. The ScrollView is scrollable as wished and appears in wanted size.
Did I miss something? Thank you for your help.

Related

QML property - read-only, assignment, binding

If I had a rectangle with a property width, there are three options to set a value that I confuse:
read-only property int widthReadOnly: 200
Rectangle{
width: 200 //first
width: widthReadOnly //second
Component.onCompleted: {width = 200} //third
}
Could you tell me when to use each of them?
Thank you.
at first all of your examples do quite the same.
First and second examle create a binding to the values, but beacause they are an int (first), or a read-only property (second) they will never change. Because there will never be a ...Changed() Signal, they are also like your third example whitch is only an assignment (if the assigned value changes, the change will not change the assignee).
The intendet use of the Bindings is when you bind to some value that is Changeable, maybe the width of the parent item. So if the parent width changes it will be propagated to the child item:
import QtQuick 2.0
import QtQuick.Controls 1.4
Item {
Rectangle {
id: papa
width: 100
height: 100
color: "red"
Rectangle {
id: child
anchors.centerIn: parent
width: parent.width / 2
height: parent.height / 2
color: "lightsteelblue"
}
}
Button {
id: button
onClicked: {
papa.width = 100 + (Math.random() * 100)
}
}
}
As you see, if the papa width is updated, it also changes the width of the child item.

Why does adding a MouseArea with anchors.fill: parent cause the rows to stack up on one another?

Given that this ListView works fine:
ListView {
id: myListView
model: myListModel
anchors.fill: parent
delegate: Row {
id: row
spacing: 5
Text {
text: id
width: 25
horizontalAlignment: Text.AlignHCenter
}
Text {
text: description
}
}
}
Why does adding a MouseArea with anchors.fill: parent cause the rows to stack up on one another? How do I get back the automatic vertical spacing that I had before adding MouseArea? I have already tried putting the Row in a Rectangle and also in a Component.
ListView {
id: myListView
model: myListModel
anchors.fill: parent
delegate: Row {
MouseArea {
anchors.fill: parent
onClicked: myListView.currentIndex = index
}
id: row
spacing: 5
Text {
text: id
width: 25
horizontalAlignment: Text.AlignHCenter
}
Text {
text: description
}
}
}
The items stack up for a simple reason: their height is not set. A delegate must always have an height set. Since you did not specify one, delegate height is zero and the enclosing text is rendered over the same y (zero), stacking up.
However, that's not the only problem here. You defined the MouseArea to be anchored. Rows, as well as Columns, force a specific arrangement for items inside themselves. Adding anchors can interfer with this automatic mechanism.
Also docs are clear about this. You can read that...
Row is a type that positions its child items along a single row. It can be used as a convenient way to horizontally position a series of items without using anchors.
...and also that...
[...]since a Row automatically positions its children horizontally, a
child item within a Row should not set its x position or horizontally
anchor itself using the left, right, anchors.horizontalCenter, fill or
centerIn anchors.
Probably, the anchoring error generates an inconsistent state such that Row does not inherit height from the enclosing text, as it did without anchoring items. This in turn results in the zero height and the stacking.
In this particular case, you can include the Row inside an Item and apply the filling MouseArea to the latter. The resulting code, with also the delegate height and width correctly set, would look similar to the following (mind you, I've removed roles and model in your code since the latter was not available in the provided code snippet):
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
ApplicationWindow {
visible: true
width: 200
height: 300
ListView {
id: myListView
model: 20
anchors.fill: parent
delegate: Item {
width: myListView.width
height: text1.height // set the height!
Row {
id: row
anchors.fill: parent
spacing: 5
Text {
id: text1
text: "id"
width: 25
horizontalAlignment: Text.AlignHCenter
}
Text {
text: "description"
}
}
MouseArea { // fills the delegate Item, not the Row!
anchors.fill: parent
onClicked: {
myListView.currentIndex = index
console.info("Area clicked! Index: " + index)
}
}
}
}
}

How are implicit dimensions of QtQuick items propagated?

I am trying to implement a component which should by default (if no width was explicitly set) take up as much space as it needs (i.e. depending on its implicitWidth). And if width was set at definition it should shrink its contents to fit in the provided area.
Here's an example:
import QtQuick 2.2
import QtQuick.Window 2.0
Window {
width: 200
height: 100
visible: true
property bool restricted: false
Component {
id: external
FocusScope {
implicitWidth: column.implicitWidth
implicitHeight: column.implicitHeight
focus: true
Column {
id: column
width: parent.width > 0 ? parent.width : undefined
Text {
id: label
width: parent.width > 0 ? parent.width : undefined
elide: Text.ElideRight
font.pixelSize: 24
text: "1234567890"
}
}
Keys.onRightPressed: label.text += label.text
}
}
Loader {
width: restricted ? 100 : undefined
sourceComponent: external
focus: true
Keys.onReturnPressed: restricted = !restricted
}
}
In this sample two modes are controlled by auxiliary bool property, and I want it to support two forms of declaration:
Explicit width. Text should elide.
Loader {
width: 100
sourceComponent: external
focus: true
}
Loader width should be enough to fit the whole text without eliding.
Loader {
sourceComponent: external
focus: true
}
Motivation is that such a component will be defined in a separate file and is being designed to be placed in different parts of UI with both behaviors desired depending on current needs. This sample with inline component declaration is only for demonstration purpose.
UPDATE:
The following trick parent.width > 0 ? parent.width : undefined works, but only for initial setup. If component contents change and implicitWidth is updated (in an unrestricted mode) the width of the component does not change (i.e. Text remains elided).
For example, press right key just right after launching example. You should see that Text has become elided, but its width did not increased
twice regardless the fact that string was duplicated.

Implement component supporting implicit and explicit width

I'd like to have a row of two one-line text items. The first one should be always fully visible. The second one should add to main item's implicitWidth by the value of own width if the root item has not its width property set explicitly. In other case, the second text item should be elided so that it fits into remaining space.
Here's an example of what I am trying to do. The variant 1 is the first thing that came to my mind, but it does not work. The second variant shows how I expect it to be. Alas, I don't like since it requires to sum up all the child items of the Row. Fortunately, I have only two of them. What if I want to add more?
import QtQuick 2.2
Item {
// variant 1
// implicitWidth: row.implicitWidth
// variant 2
implicitWidth: Math.ceil(name.implicitWidth + value.implicitWidth)
implicitHeight: row.implicitHeight
Row {
id: row
anchors {
left: parent.left
right: parent.right
}
Text {
id: name
text: "Label"
}
Text {
id: value
width: parent.width - x
text: "Value"
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
}
}
}

How to add items into a calendar over more than one cell

I want to try out the new calendar object of Qml in Qt5.3. Therefor I want to add some items (e.g. rectangles) into the cells at specific coordinates of the cell. To add a rectangle into one cell I use dayDelegate:
Calendar{
id: calendar
width: parent.width * 0.5
height: parent.height * 0.5
style: CalendarStyle {
dayDelegate:
Rectangle { //the background of the cell
id: cellRect
color: "#505"
Rectangle{ //a rectangle in the cell
height: parent.height * 0.5
width: parent.width
}
Now I have a model in my C++-code which stores several items belonging to specific dates. That means you can have for one date more than one item. I want to show these items as rectangles like a list in the cell of the given date. So what is the best way to do that in QML? One item stores its date and a group id to which this item belongs to. I pass the items as a QQmlPropertyList to QML. I already realized a function which is telling me, whether for the current date an item is available:
function apAvailable(d) {
for (var i in ApModel.items) {
var item = ApModel.items[i];
if (item.startDate.getTime() === d.getTime()) {
return true;
}
}
return false;
}
I can call this function in the dayDelegate passing the current processed day of the delegate.
Rectangle {
id: cellRect
color: "#505"
Rectangle{
visible: apAvailable(styleData.date)
height: parent.height * 0.5
width: parent.width
}
}
Now my question: I want to show now all items for this date. So I have to draw one rectangle for each item available. How can I process this in the for-iteration of the function? Can I create a QML-Component and add for each item a rectangle into this component and then return it to the cellRect? Do you know a better way to realize that?
Another further problem will be that an item belongs to a group of items but each with another date(e.g. 3 days, one after another = 3 items in one group). I want to combine these items visually. In fact, there shall be one big rectangle starting at the first date and ending at the last date. In case of three days, this rectangle shall be displayed over these three cells. My first thoughts were that I draw for each item an individual rect but pay attention to the same y-coordinate within the cells. Is there a good way to group these single rectangles to have for instances only one big component?
I hope you can give me some advices how to do all this in a nice way.
How can I process this in the for-iteration of the function? Can I
create a QML-Component and add for each item a rectangle into this
component and then return it to the cellRect? Do you know a better way
to realize that?
Did you try it? It really helps if you have an existing attempt at it that you can share with us, so that you can point out why you think it may not be satisfactory.
Is there a good way to group these single rectangles to have for
instances only one big component?
You can use ListView, amongst other things. The Calendar Example already provides a similar backend to what you're describing, so let's use that as a base:
ListView {
id: rectView
anchors.left: parent.left
anchors.right: parent.right
anchors.top: dayDelegateText.bottom
anchors.bottom: parent.bottom
anchors.margins: -1
clip: true
interactive: false
model: eventModel.eventsForDate(styleData.date)
delegate: Rectangle {
color: eventColours[index % eventColours.length]
width: parent.width
height: 10
}
}
This doesn't take care of each colour belonging to the same group ID, however, because it relies on the assumption that events that overlap several days will be at a certain index in the model. However, until you share your attempt at the problem, this should give you some idea. The changes I made to the example, as a diff:
http://pastebin.com/U14iKUeQ
Or you can just replace the Calendar in the example with this one:
Calendar {
id: calendar
width: parent.width * 0.6 - row.spacing / 2
height: parent.height
selectedDate: new Date(2014, 0, 1)
focus: true
style: CalendarStyle {
readonly property var eventColours: ["lightblue", "darkorange", "purple"]
dayDelegate: Item {
readonly property color sameMonthDateTextColor: "#444"
readonly property color selectedDateColor: Qt.platform.os === "osx" ? "#3778d0" : __syspal.highlight
readonly property color selectedDateTextColor: "white"
readonly property color differentMonthDateTextColor: "#bbb"
readonly property color invalidDatecolor: "#dddddd"
Rectangle {
id: selectionRect
anchors.fill: parent
border.color: "transparent"
color: styleData.date !== undefined && styleData.selected ? selectedDateColor : "transparent"
anchors.margins: styleData.selected ? -1 : 0
}
Label {
id: dayDelegateText
text: styleData.date.getDate()
font.pixelSize: 14
anchors.left: parent.left
anchors.leftMargin: 2
anchors.top: parent.top
anchors.topMargin: 2
color: {
var color = invalidDatecolor;
if (styleData.valid) {
// Date is within the valid range.
color = styleData.visibleMonth ? sameMonthDateTextColor : differentMonthDateTextColor;
if (styleData.selected) {
color = selectedDateTextColor;
}
}
color;
}
}
ListView {
id: rectView
anchors.left: parent.left
anchors.right: parent.right
anchors.top: dayDelegateText.bottom
anchors.bottom: parent.bottom
anchors.margins: -1
clip: true
interactive: false
model: eventModel.eventsForDate(styleData.date)
delegate: Rectangle {
color: eventColours[index % eventColours.length]
width: parent.width
height: 10
Label {
text: modelData.name
anchors.fill: parent
font.pixelSize: 8
fontSizeMode: Text.Fit
}
}
}
}
}
}