Why does my QML ListView overlap my Text in a Column positioner? - qml

A Column positioner is supposed to position its children in a column. But when one of the children is a ListView, I find it overlaps the other children.
When the children are just Text's there's no overlapping and everything is fine.
import QtQuick 2.11
import QtQuick.Window 2.11
Window {
visible: true
width: 640
height: 480
Column {
Text {
text: "Hello World"
}
// This works - appears under "Hello World"
Text {
text: "Hi Again"
}
// This doesn't - overlaps "Hello World" and "Hi Again"
ListModel {
id: theModel
ListElement { display: "one" }
ListElement { display: "two" }
ListElement { display: "three" }
}
ListView {
height:100
model: theModel
delegate: Text {
text: display
}
}
}
}
Expected result: Listview appears under "Hi Again"
Actual result: ListView overlaps "Hello World" and "Hi Again"
I often come a cropper with overlapping items in QML. Am I missing something fundamental that someone can explain to me? I'm having trouble finding a good reference book for QML positioning.

The Column (and other positioners for that matter) will not position items that are deemed invisible, the definition being height and width above zero and visible = false. This means you can easily solve it by anchoring the ListView to the left and right of it's parent:
ListView {
height:100
anchors.left: parent.left
anchors.right: parent.right
Component.onCompleted: console.log(implicitHeight, implicitWidth)
model: theModel
delegate: Text {
text: display
}
}

Related

Reusable confirmation prompt in QML

I need a confirmation or alert dialog when user presses a button. Based on if they choose 'yes' or 'no', different actions are triggered. The challenge is that I have two buttons which pops such a dialog and it's not quite straightforward how to do that in QML. Here is the code (my demo application):
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
visible: true
function areYouSure()
{
prompt.visible = true
}
MainForm {
anchors.fill: parent
Button {
id: buttonA
anchors.left: parent.left
anchors.top: parent.top
text: "Button A"
onClicked: areYouSure() // based on yes or no, different actions but how to tell what was pressed?
}
Button {
id: buttonB
anchors.right: parent.right
anchors.top: parent.top
text: "Button B"
onClicked: areYouSure() // based on yes or no, different actions but how to tell what was pressed?
}
}
Prompt {
anchors.fill: parent
id: prompt
visible: false
onCancelled: {
console.log("Cancel was pressed")
// but how can I tell which button's cancel as pressed?
}
onAccepted: {
console.log("Accept was pressed")
// same for which button's Ok?
}
}
}
Prompt.qml
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Rectangle
{
id: root
width: parent.width
property string message: "Are you Sure?"
signal cancelled;
signal accepted;
Text{
id: messagetxt
text:root.message
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: cancelButton
anchors.bottom: parent.bottom
anchors.left: parent.left
width: 50
height: 40
Text {
anchors.centerIn: parent
text: "Cancel"
}
color: "red"
MouseArea {
anchors.fill: parent
onClicked: {
root.visible = false
cancelled()
}
}
}
Rectangle {
id: okButton
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 50
height: 40
Text {
anchors.centerIn: parent
text: "Ok"
}
color: "blue"
MouseArea {
anchors.fill: parent
onClicked: {
root.visible = false
accepted()
}
}
}
}
In traditional programming, an individual dialog pops up which respond exactly to that question and than we respond to its cancelled() or accepted() signals. In QML we can't really do that, right? What is the best way to know which button was pressed? The irony is that even the right signals are emitted, we just can't seem to act on them.
Well, first and foremost you should really have a look at Dialogs module since it provides what would be a ready made solution for you, i.e. MessageDialog.
That said, you can achieve a customisation in different ways, including redefining the handlers or passing the ids. If the action to perform are simple (e.g. a function call) you can dynamically create even the dialog and bind the signals with the desired behaviour. Customisation can obviously go further, changing title and text.
Here is a simple example which follows the last approach and prints different texts depending on the pressed button. Once the dialog is set to not visible it is destroyed via the destroy function.
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.0
ApplicationWindow {
id: win
title: qsTr("MultiDialog")
visible: true
RowLayout {
anchors.fill: parent
Button {
text: "Button 1"
onClicked: {
var d1 = compDialog.createObject(win)
// change "title" & "text"?
d1.accepted.connect(function(){
console.info("accepted: " + text)
})
d1.rejected.connect(function(){
console.info("rejected: " + text)
})
d1.visible = true
}
}
Button {
text: "Button 2"
onClicked: {
var d2 = compDialog.createObject(win)
// change "title" & "text"?
d2.accepted.connect(function(){
console.info("accepted: " + text)
})
d2.rejected.connect(function(){
console.info("rejected: " + text)
})
d2.visible = true
}
}
}
Component {
id: compDialog
MessageDialog {
title: "May I have your attention please"
text: "It's so cool that you are using Qt Quick."
onVisibleChanged: if(!visible) destroy(1)
standardButtons: StandardButton.Cancel | StandardButton.Ok
}
}
}
If you want to use Rectangle or are forced to use it, then you can still use this approach. Dynamic creation of objects is NOT related to the usage of MessageDialog and can be used (and should be used) to reduce the number of objects kept instanced throughout application lifetime. Have a look here for more details about that.
The following example uses the very same dialog component you defined (with some small modifications. As you can see the code is almost identical. I've just moved the destruction of the object at the end of the signal handlers. In this case I've also changed the value of the unique property defined in the component, i.e. message, to show you complete customization.
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
Window {
id: win
visible: true
RowLayout {
anchors.fill: parent
Button {
text: "Button 1"
Layout.alignment: Qt.AlignCenter
onClicked: {
var d1 = prompt.createObject(win)
d1.message = text + " - Are you Sure?"
d1.accepted.connect(function(){
console.info("accepted: " + text)
d1.destroy()
})
d1.rejected.connect(function(){
console.info("rejected: " + text)
d1.destroy()
})
}
}
Button {
text: "Button 2"
Layout.alignment: Qt.AlignCenter
onClicked: {
var d2 = prompt.createObject(win)
d2.message = text + " - Are you Sure?"
d2.accepted.connect(function(){
console.info("accepted: " + text)
d2.destroy()
})
d2.rejected.connect(function(){
console.info("rejected: " + text)
d2.destroy()
})
}
}
}
Component {
id: prompt
Rectangle {
id: root
anchors.fill: parent
property string message: "Are you Sure?"
signal rejected()
signal accepted()
Text{
id: messagetxt
text:root.message
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: cancelButton
anchors.bottom: parent.bottom
anchors.left: parent.left
width: 50
height: 40
Text {
anchors.centerIn: parent
text: "Cancel"
}
color: "red"
MouseArea {
anchors.fill: parent
onClicked: rejected()
}
}
Rectangle {
id: okButton
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 50
height: 40
Text {
anchors.centerIn: parent
text: "Ok"
}
color: "blue"
MouseArea {
anchors.fill: parent
onClicked: accepted()
}
}
}
}
}
If your component is not inlined as I did with Component but it's kept in another file you can use createComponent as depicted in the link provided above. The code of your main window would look like this:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
Window {
id: win
visible: true
property var prompt
RowLayout {
anchors.fill: parent
Button {
text: "Button 1"
Layout.alignment: Qt.AlignCenter
onClicked: {
var d1 = prompt.createObject(win)
d1.message = text + " - Are you Sure?"
d1.accepted.connect(function(){
console.info("accepted: " + text)
d1.destroy()
})
d1.rejected.connect(function(){
console.info("rejected: " + text)
d1.destroy()
})
}
}
Button {
text: "Button 2"
Layout.alignment: Qt.AlignCenter
onClicked: {
var d2 = prompt.createObject(win)
d2.message = text + " - Are you Sure?"
d2.accepted.connect(function(){
console.info("accepted: " + text)
d2.destroy()
})
d2.rejected.connect(function(){
console.info("rejected: " + text)
d2.destroy()
})
}
}
}
Component.onCompleted: prompt = Qt.createComponent("Prompt.qml");
}
You should always check that component creation is correcly carried out (I didn't do it for the sake of brevity). That said, the code is identical to the previous one.
Last but not least, I've noticed an error in your code: signals must always be declared with parenthesis, even when no parameter is emitted. It should be signal accepted(), not signal accepted, same goes for the other signal and any other signal declaration.

QML TabView in ColumnLayout

I am trying to modify Gallery example. I want to add Button under TabView. So, I put TabView and Button into ColumnLayout, here is code:
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Window 2.0
Window {
visible: true
title: "settings"
width: 600
height: 400
ColumnLayout{
anchors.fill: parent
TabView {
anchors.right: parent.right
anchors.left: parent.left
Tab {
title: "Controls"
Controls { }
}
Tab {
title: "Itemviews"
Controls { }
}
Tab {
title: "Styles"
Controls { }
}
Tab {
title: "Layouts"
Controls { }
}
}
RowLayout{
anchors.right: parent.right
anchors.left: parent.left
Button{
text: "ok"
}
}
}
}
However, when I resize window okButton stands under tab controls. How should I fix code?
When you have defined a Layout, each element added has access to specific properties related to the layout itself. These properties are useful to position the element inside the space covered from the layout. Confront what is described here.
Hence, you should modify the ColumnLayout like this:
ColumnLayout {
anchors.fill: parent
TabView {
id:frame
enabled: enabledCheck.checked
tabPosition: controlPage.item ? controlPage.item.tabPosition : Qt.TopEdge
Layout.fillHeight: true // fill the available space vertically
Layout.fillWidth: true // fill the available space horizontally
Layout.row: 0 // item in the first row of the column
anchors.margins: Qt.platform.os === "osx" ? 12 : 2
Tab {
id: controlPage
title: "Controls"
Controls { }
}
Tab {
title: "Itemviews"
ModelView { }
}
Tab {
title: "Styles"
Styles { anchors.fill: parent }
}
Tab {
title: "Layouts"
Layouts { anchors.fill:parent }
}
}
Button {
text: "ok"
Layout.row: 1 // item in the second row of the column
Layout.alignment: Qt.AlignCenter // simple center the button in its spatial slot
}
}
You don't need a RowLayout for the button. It should be placed in the second row of the ColumnLayout you have defined, since it is a simple component. A sub-layout could be useful in case of multiple elements on the same row, e.g. two or more buttons.
Note also that anchoring is just used for the ColumnLayout to "stretch" and fit the window. All the other operations are executed via the layout properties. For general rules take a look at this other article.

Prevent QML MouseArea from taking focus when pressed

In my application, I have a TextArea, and under it a toolbar with Button items. Whenever a Button gets pressed, the TextArea loses focus, and hides the virtual keyboard (which is right). But what if I want the TextArea to keep its focus, even if a Button has been pressed?
In SailfishOS, Button is just a MouseArea, not a QML Button. This means there is no property Button.activeFocusOnPress that I could set to false. Is there a way to replicate this behaviour for the MouseArea?
Here's some code:
TextArea {
id: editor
width: parent.width
height: 200
anchors.top: pageHeader.bottom
anchors.bottom: toolbar.top
}
ListModel {
id: textNavButtons
ListElement {buttonText: "left"}
ListElement {buttonText: "right"}
ListElement {buttonText: "tab"}
ListElement {buttonText: "home"}
ListElement {buttonText: "end"}
}
Row {
id: toolbar
anchors.bottom: parent.bottom
width: parent.width
height: 80
Repeater {
id: toolbarRepeater
model: textNavButtons
property int modelCount: model.count
delegate: Button {
text: buttonText
width: parent.width / toolbarRepeater.modelCount
onClicked: {
console.log("I was clicked: " + text);
}
}
}
}
EDIT: My goal is to use these buttons to insert tabs into the text, or to navigate with Home, End, Left, Right etc. I'm not sure if using these buttons will achieve the goal, but I had no other idea. I will have to simulate the keypress too.
Now you can use
delegate: Button {
text: buttonText
width: parent.width / toolbarRepeater.modelCount
focusPolicy: Qt.NoFocus
onClicked: {
console.log("I was clicked: " + text);
}
}

How to make TableView Items draggable

I’ve a TableView and have implemented my own item delegate for it. I want to be able to drag it out and place it somewhere else. How do I do it
itemDelegate: Text {
id: objMainText
anchors.horizontalCenter: parent.horizontalCenter
elide: styleData.elideMode
color: "yellow"
text: styleData.value.get(0).content //assume this is correct
MouseArea {
id: objDragArea
anchors.fill: parent
drag.target: objDragableText
drag.axis: Drag.XAndYAxis
hoverEnabled: true
onEntered: {
console.log("Hover Captured") //This gets printed
}
onClicked: {
console.log("Detected") //This NEVER gets printed
}
Text {
id: objDragableText
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
Drag.active: objDragArea.drag.active
opacity: Drag.active / 2
text: objMainText.text
color: objMainText.color
states: State {
when: { objDragArea.drag.active }
AnchorChanges {
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
}
}
}
Don’t mind if there are a few brackets less here and there. Those I’m taking care of ofcourse. But why can’t I drag objDragableText and how do i do it?
Also, onClicked is captured as in the comments above but not onPressed. How do I also do this?
{Qt 5.1 RC 1 - Win7 - MinGw 4.8}

Hide the highlight of a ListView while scrolling

I am building a Qt5 application based on Qt-Quick 2 for the UI. I have an issue while displaying a ListView with an highlight component. When I scroll the ListView the highlight rectangle is visible outside of the ListView and I can't find a way to avoid it.
Here is an example of the issue with a minimal QML file:
import QtQuick 2.0
Rectangle {
width: 360; height: 600
ListView {
width: 350; height: 200
anchors.centerIn: parent
id: myList
model: myModel
highlight: highlightBar
delegate: Item {
width: 400; height: 20
Text { text: name }
MouseArea {
id: mArea
anchors.fill: parent
onClicked: { myList.currentIndex = index; }
}
}
}
Component {
id: highlightBar
Rectangle {
width: parent.width; height: 20
color: "#FFFF88"
}
}
ListModel {
id: myModel
}
/* Fill the model with default values on startup */
Component.onCompleted: {
for(var i = 0; i < 100; i++) {
myModel.append({ name: "Big Animal : " + i});
}
}
}
Is there a way to "limit" a component to its parent borders or to hide the highlight component while scrolling?
As reported by the documentation:
Note: Views do not enable clip automatically. If the view is not clipped by another item or the screen, it will be necessary to set clip: true in order to have the out of view items clipped nicely.
Hence, what you are experiencing is a common behaviour and you should either 1) clip the view via other Items (e.g. a header Rectangle and a footer Rectangle with z:infinite or simply set the clip property to true, i.e.
ListView{
//...
clip:true
//...
}
Clipping has some perfomance disavantages which can greatly affect the application as it grows. Hence, its usage, especially outside the views scenario, should be evaluated carefully.