I have a ListView that contains a list of rectangles with text. Sometimes the text in the Rectangles is too long to fit so I would like the text to scrollable horizontally. I'm using a ScrollView surrounding the text to make it horizontally scrollable.
The problem is, I can't scroll through the list (vertically) because the scrolling gets intercepted by the horizontal ScrollViews.
Here is what my qml looks like:
ListView { //vertically scrolling list
model: modelName
anchors.fill: parent
id: vListView
delegate: Column {
width: parent.width
spacing: 3
bottomPadding: 2
Rectangle { // rectangles containing the text
width: parent.width
height: 40
radius: 5
id: buttonName
ScrollView {//horizontally scrolling list
contentHeight: -1
anchors.fill: parent
anchors.leftMargin: 18
id: hScrollView
Text {// text that is sometimes too long
width: parent.width
rightPadding: 15
bottomPadding: 5
text: model.data
topPadding: 9
horizontalAlignment: Text.AlignLeft
anchors.centerIn: parent
}
}
}
}
}
How can I get the list to scroll vertically and have the individual text boxes also scroll horizontally?
edit:
I've tried to use the following code to "catch" the scroll-wheel input and scroll the appropriate elements. But I hasn't been working out.
(code is placed in a mouse area that fills the whole thing)
onWheel: {
if (wheel.angleDelta.y > 0) {
vListView.flickableItem.contentX -= vListView.scrollSpeed;
if (vListView.flickableItem.contentX < 0) {
vListView.flickableItem.contentX = 0;
}
} else {
hScrollView.flickableItem.contentX += hScrollView.scrollSpeed;
if (individualCommandScrollView.flickableItem.contentX + hScrollView.flickableItem.width > hScrollView.flickableItem.contentWidth) {
hScrollView.flickableItem.contentX = hScrollView.flickableItem.contentWidth - hScrollView.flickableItem.width;
}
}
}
onClicked: mouse.accepted = false;
onPressed: mouse.accepted =
onReleased: mouse.accepted = false;
onDoubleClicked: mouse.accepted = false;
onPositionChanged: mouse.accepted = false;
onPressAndHold: mouse.accepted = false;
See the following solution using a Flickable instead of a ScrollView and a MouseArea to manually propagate the scroll to the horizontal Flickable when required.
The Flickable will not steal the mouse event from the parent ListView if the horizontal scroll on the text is not necessary.
Note that if all the texts are too long, you will never be able to scroll vertically on the main ListView...
I would recommend to imagine another design to handle long texts (text elide, wrap, multiline, ...).
ListView {
id: vListView
// ...
delegate: Rectangle {
width: parent.width
height: 40
radius: 5
Flickable {
id: hScrollView
contentHeight: -1
contentWidth: text.paintedWidth // <===== to allow horizontal scroll
interactive: contentWidth > width // <===== but only if text is too long
anchors.fill: parent
anchors.leftMargin: 18
Text {
id: text
width: parent.width
rightPadding: 15
bottomPadding: 5
text: model.data
topPadding: 9
horizontalAlignment: Text.AlignLeft
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
enabled: hScrollView.interactive
// manually scroll in Flickable on mouse wheel
onWheel: {
let velocity = vListView.highlightMoveVelocity // use same velocity as the vertical ListView
let scrollValue = wheel.angleDelta.y >= 0 ? velocity : -velocity // move left or right according to wheel value
hScrollView.flick(scrollValue, 0)
}
}
}
}
}
Try to add this to ListView:
ScrollBar.vertical: ScrollBar {}
Or add some margin to Rectangle and add borders or color to see where it is. So you will be able to point outside it and scroll ListView
Related
I'm trying to place Rectangle items inside a listview in qml. I need to show a cursor or image which resembles cursor between two items in the listview. The cursor should be able to switch positions between spaces of different items in listview.
Please help me with ideas to achieve this. Thank you.
Not pretty and can be improved especially when taking the center of the delegate item and calculate if the mouse is closer to the left or right side of the delegate, but it shows how to show such a cursor.
import QtQuick
Window {
id: root
width: 640
height: 240
visible: true
ListView {
id: listView
x: 40
y: 40
width: 400
height: 50
spacing: 10
orientation: ListView.Horizontal
model: ["Item 0", "Item 1", "Item 2", "Item 3"]
delegate: Rectangle {
width: 100
height: 50
border.width: 1
Text {
anchors.centerIn: parent
text: modelData
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: function(mouse) {
let item = listView.itemAt(mouse.x, mouse.y)
if (item)
cursor.x = item.x - listView.spacing
}
}
Rectangle {
id: cursor
width: listView.spacing
height: listView.height
color: "red"
}
}
}
I have a page with a ListView in a layout. The ListView consists of several rows represented with Rectangles and a RowLayout in it. The RowLayout itself consists of Text elements. The problem which I'm facing is that I can't properly set the width of these Text elements. E.g. I want the first Text element to have a 1/3 width of the whole row. The way I'm setting it now makes the Text element width to be cca 3/4 of the row width, at least visually it looks like that. What would be the proper way of setting the Text element width?
Item {
ColumnLayout {
anchors.fill: parent
// some components here ...
Rectangle {
width: parent.width
Layout.fillWidth: true
Layout.fillWidth: true
Rectangle {
anchors.fill: parent
anchors.margins: 0.1 * parent.height
ListView {
id: listView
anchors.fill: parent
model: SomeModel {}
delegate: Rectangle {
height: 40
width: listView.width
RowLayout {
width: listView.width
Text {
Layout.preferredWidth: 0.3 * listView.width
text: "text1"
}
Text {
text: "text2"
}
// some other Text elements ...
}
}
}
}
}
}
}
I have a TextEdit inside a ScrollView. How can I implement logic so that the ScrollView moves when the user presses the up or down arrow key and it moves the text beyond the ScrollView bounds?
//qml
ScrollView {
id: palGenTextScrollView
property int scrollBarWidth: 15
anchors.fill: parent
MouseArea {
id: mouseArea
anchors.fill: parent
onWheel: {
if (wheel.modifiers & Qt.ControlModifier){
if (wheel.angleDelta.y > 0)
{
mainTextEdit.font.pixelSize++
}
else
{
mainTextEdit.font.pixelSize--
}
}
else{
wheel.accepted=false
}
}
}
TextEdit {
id: mainTextEdit
text: fileio.palFileText
font.family: "Courier"
wrapMode: TextEdit.Wrap
selectByMouse: true
//when going out of upward bounds: palGenTextScrollView.flickableItem.contentY--
//when going out of downward bounds: palGenTextScrollView.flickableItem.contentY++
}
}
You can use TextArea which does exactly that for you. If you want to bake your own, take a look at the flickable example in TextEdit's docs in the detailed description.
Note that the TextEdit does not implement scrolling, following the cursor, or other behaviors specific to a look-and-feel. For example, to add flickable scrolling that follows the cursor:
Flickable {
id: flick
width: 300; height: 200;
contentWidth: edit.paintedWidth
contentHeight: edit.paintedHeight
clip: true
function ensureVisible(r)
{
if (contentX >= r.x)
contentX = r.x;
else if (contentX+width <= r.x+r.width)
contentX = r.x+r.width-width;
if (contentY >= r.y)
contentY = r.y;
else if (contentY+height <= r.y+r.height)
contentY = r.y+r.height-height;
}
TextEdit {
id: edit
width: flick.width
height: flick.height
focus: true
wrapMode: TextEdit.Wrap
onCursorRectangleChanged: flick.ensureVisible(cursorRectangle)
}
}
Let us suppose I have a card made using Rectangle and I want to show buttons on top of it when clicked. I'm calling showMenu() function to do that and for buttons I'm using an ListView with dynamic ListModel. The problem with such is that the button gets added bellow the Rectangle instead of the top of it. The anchor is not updating after appending an item to the model. Here is my code
Item {
width: 120
height: 120
Rectangle {
id: card
width: 50
height: 100
color: "pink"
anchors.bottom: parent.bottom
Item {
id: rec
width: 50
anchors.bottom: parent.top // This anchor is not updating after appending an item to the list.
ListModel {
id: menuListModel
}
Component {
id: delegate
Rectangle {
width: 120
height: 20
color: "blue"
Text {
anchors.fill: parent
anchors.centerIn: parent
text: commandText
}
}
}
ListView {
anchors.fill: parent
model:menuListModel
delegate: delegate
interactive: false
}
}
MouseArea {
anchors.fill: parent
onClicked: menuListModel.append({"commandText" : "Normal Summon"});
}
}
}
This is more or less a duplicate of this question. The Item needs a height. As mentioned in the answer to that question, you can add debug statements to the code when things like this happen. In this situation, you can also add a Rectangle as a child of the Item and make sure that it's visible:
Rectangle {
anchors.fill: parent
color: "transparent"
border.color: "darkorange"
}
If it's not visible, you know that the problem lies with that (parent) item.
I want to have a single (vertical) ListView with (horizontal) ListView delegates.
The horizontal delegates should scroll synchronously. To do so, I put a Flickable on top of the ListViews and bind the contentX of the horizontal ListView to the contentX of the Flickable (and the same for the contentY of the vertical ListView) (Note: Here a different approach was described for the synchronous ListView scrolling but this seems to have performance issues on mobile devices)
The code below kind of works but still has the following issues
I don't get the onClicked in the Rectangle (I do get it when I remove the top Flickable)
I want either horizontal flicking or vertical flicking but not both at the same time. I can restrict the flicking of the top Flickable by setting flickableDirection: Flickable.HorizontalFlick but then I can't flick vertically anymore (I was hoping that the Flickable would pass on unused mouse events to the vertical ListView but this doesn't seem to happen)
Suggestions on how to fix these issues?
Any help appreciated!
import QtQuick 2.0
Item {
id: main
visible: true
width: 600
height: 600
ListView {
id: verticalList
width: parent.width;
height: parent.height;
contentY : flickable.contentY
anchors.fill: parent
spacing: 10
orientation: ListView.Vertical
model: 100
delegate:
ListView {
id: horizontalList
width: parent.width;
height: 100;
contentX : flickable.contentX
spacing: 10
orientation: ListView.Horizontal
model: 20
property var verticalIndex : index
delegate:
Rectangle
{
property var colors : ['red', 'green', 'blue']
property var widths : [100, 200, 300]
height: 100
width: widths[(verticalIndex + model.index) % widths.length]
color: colors[model.index % colors.length]
MouseArea {
anchors.fill: parent
onClicked: {
console.log("Rectangle.onClicked")
}
}
}
}
}
//on top a Flickable
Flickable {
id: flickable
height: parent.height
width: parent.width
contentHeight: 100*100 //nrOfRows * rowHeight
contentWidth: 20*300 //nrOfEvent * max/averageEventWidth
}
}
I'm not giving you a perfect solution, but it's working. When you are using Flickable on the top of the ListView, you are not able to interact with it. So, I've used Flickable bellow the ListView and bounded the contentX of Flickable and ListView, but this is causing a loop and I'm getting the following output, but we're getting the desired behavior.
QML Binding: Binding loop detected for property "value"
EDIT
So, instead of using ListView for vertical list, I just used a Repeater and Column and used property binding. It's working well now.
Following is the updated version.
import QtQuick 2.0
Item {
id: main
visible: true
width: 600
height: 600
property bool virticalFlick: false //To get either vertical or horizontal flicking
Flickable {
anchors.fill: parent
contentWidth: contentItem.childrenRect.width
contentHeight: contentItem.childrenRect.height
flickableDirection: Flickable.VerticalFlick
interactive: (virticalFlick === true)?true:false
Column {
id: column
spacing: 10
Repeater {
id: repeater
model: 20
ListView {
id: horizontalList
width: 600;
height: 100;
clip: true
interactive: (virticalFlick === true)?false:true
spacing: 10
orientation: ListView.Horizontal
model: 20
property var verticalIndex : index
onMovingChanged: {
if(moving == true) {
for(var i=0; i<repeater.count ; i++) {
/* If the property is later assigned a static value from a JavaScript statement,
this will remove the binding.
However if the intention is to create a new binding then the property
must be assigned a Qt.binding() value instead. This is done by passing a function to
Qt.binding() that returns the desired result */
if (i !== index)
repeater.itemAt(i).contentX = Qt.binding(function() { return contentX });
}
}
else {
for(var i=0; i<repeater.count ; i++) {
if (i !== index)
repeater.itemAt(i).contentX = contentX; // This will remove binding
}
}
}
delegate: Rectangle {
property var colors : ['red', 'green', 'blue']
property var widths : [100, 200, 300]
height: 100
width: widths[(ListView.view.verticalIndex + model.index) % widths.length]
color: colors[model.index % colors.length]
MouseArea {
anchors.fill: parent
onClicked: {
console.log("Rectangle.onClicked")
}
}
}
}
}
}
}
}
The following does work, however the initial attempt seemed more elegant.
I still need to compare the performance (fps) when flicking, especially on a mobile device. I also get "Binding loop" warnings but I think they are false positives.
import QtQuick 2.0
Item {
id: main
visible: true
width: 600
height: 600
ListView {
id: verticalList
width: parent.width;
height: parent.height;
anchors.fill: parent
spacing: 10
cacheBuffer: 500 // in pixels
orientation: ListView.Vertical
model: 100
property var activeIndex : 0
property var lastContentX : 0
delegate:
ListView {
id: horizontalList
width: parent.width;
height: 100;
spacing: 10
cacheBuffer: 500 // in pixels
orientation: ListView.Horizontal
model: 20
property var verticalIndex : index
delegate:
Rectangle
{
property var colors : ['red', 'green', 'blue']
color: colors[model.index % colors.length]
height: 100
property var widths : [100, 200, 300]
width: widths[(verticalIndex + model.index ) % widths.length]
MouseArea {
z:10
anchors.fill: parent
onClicked: {
console.log("Rectangle.onClicked")
}
onPressed: {
console.log("Rectangle.onPressed")
}
onReleased: {
console.log("Rectangle.onReleased")
}
}
}
onContentXChanged: {
if(model.index === verticalList.activeIndex)
{
verticalList.lastContentX = contentX
}
}
onMovementStarted: {
verticalList.activeIndex = model.index
unbind();
}
onMovementEnded: {
bind();
}
Component.onCompleted: {
bind();
}
function bind()
{
contentX = Qt.binding(function() { return verticalList.lastContentX })
}
function unbind()
{
contentX = contentX ;
}
}
}
}
The following modifications were needed to my initial attempt
limit the Flickable to flickableDirection : Flickable.HorizontalFlick and remove the contentY : flickable.contentY on the verticalList
by doing so, there is no vertical scrolling anymore. This can be fixed by moving the Flickable inside the ListView
onClicked events are received by adding the following MouseArea to the Flickable
eg.
MouseArea {
anchors.fill: parent
//see http://stackoverflow.com/questions/29236762/mousearea-inside-flickable-is-preventing-it-from-flicking
onReleased: {
if (!propagateComposedEvents) {
propagateComposedEvents = true
}
}
}