I want to create ListView which includes nested ListModel for expanding and collapsing submenu. (The topic that I use while I creating nested expandable listview in here.)
My problem is expandable menu is not responding when I create it's ListModel from Python/PyQt5. But if I create it in QML side, expanding operation is working smoothly. (But I don't want create it on QML side cause I have to manipulate it on backend.)
Here is the main.py
import sys
from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterType
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtCore import QTimer, QObject, pyqtSignal, pyqtSlot, QAbstractListModel, QModelIndex, Qt, pyqtProperty
class Backend(QObject):
modelChanged = pyqtSignal()
def __init__(self):
super().__init__()
self._model = MyListModel()
##~~Expose model as a property of our backend~~##
#pyqtProperty(QObject, constant=False, notify=modelChanged)
def model(self):
return self._model
class MyListModel(QAbstractListModel):
##~~My Custom UserRoles~~##
NameRole = Qt.UserRole + 1000
CollapsedRole = Qt.UserRole + 1001
SubItemsRole = Qt.UserRole + 1002
def __init__(self, parent=None):
super().__init__()
self.itemNames = []
def rowCount(self, parent=None, *args, **kwargs):
return len(self.itemNames)
def data(self, index, role=Qt.DisplayRole):
if 0 <= index.row() < self.rowCount() and index.isValid():
item = self.itemNames[index.row()]
if role == MyListModel.NameRole:
return item['assetName']
elif role == MyListModel.SubItemsRole:
return item['subItems']
elif role == MyListModel.CollapsedRole:
return item['isCollapsed']
def roleNames(self):
roles = dict()
roles[MyListModel.NameRole] = b'assetName'
roles[MyListModel.SubItemsRole] = b'subItems'
roles[MyListModel.CollapsedRole] = b'isCollapsed'
return roles
#pyqtSlot(str, bool)
def appendRow(self, name, isCollapsed):
self.subItem = MySubListModel()
self.subItem.addRow()
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.itemNames.append({'assetName': name, 'subItems': self.subItem, 'isCollapsed': isCollapsed})
self.endInsertRows()
print(self.itemNames)
print(self.subItem.subItemParams)
#pyqtSlot(int, str)
def collapseEditInputsMenu(self, index, modelIndexName):
self.itemNames[index][modelIndexName] = not self.itemNames[index][modelIndexName]
print(f"From Backend: {self.itemNames}")
class MySubListModel(QAbstractListModel):
##~~My Custom UserRole For SubItem ListModel~~##
CellSizeRole = Qt.UserRole + 1004
def __init__(self, parent=None):
super().__init__()
self.subItemParams = []
def rowCount(self, parent=None, *args, **kwargs):
return len(self.subItemParams)
def data(self, index, role=Qt.DisplayRole):
if 0 <= index.row() < self.rowCount() and index.isValid():
item = self.subItemParams[index.row()]
if role == MySubListModel.CellSizeRole:
return item['cellSize']
def roleNames(self):
roles = dict()
roles[MySubListModel.CellSizeRole] = b'cellSize'
return roles
def addRow(self):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.subItemParams.append({'cellSize': "888"})
self.endInsertRows()
class MainWindow():
def __init__(self):
app = QGuiApplication(sys.argv)
self.engine = QQmlApplicationEngine()
self.engine.quit.connect(app.quit)
self.engine.load("main.qml")
app_backend = Backend()
self.engine.rootObjects()[0].setProperty("backendObjectInQML", app_backend)
sys.exit(app.exec())
def main():
window = MainWindow()
if __name__ == '__main__':
main()
... and main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15
ApplicationWindow {
id: myApplicationWindow
title: "Expandable ListView App"
visible: true
height: 400
width: 400
property QtObject backendObjectInQML
Rectangle {
id: rootRectangle
color: "grey"
anchors.fill: parent
Item {
id: solutionFileListViewRoot
anchors.fill: parent
// ListModel {
// id: myNestedListModel
// ListElement {
// assetName: "Dummy Item"
// isCollapsed: true
// subItems: [ListElement {cellSize: "888"}]
// }
// }
ListView {
id: myNestedListView
anchors {
top: parent.top
left: parent.left
right: parent.right
bottom: parent.bottom
bottomMargin: 50
}
model: myApplicationWindow.backendObjectInQML.model
// model: myNestedListModel
delegate: myAppListElementDelegate
spacing: 6
clip: true
ScrollBar.vertical: ScrollBar {
active: true
}
}
}
Component {
id: myAppListElementDelegate
Column {
id: listElementColumn
width: myNestedListView.width
Rectangle {
id: listElementRectangle
height: 30
anchors {
left: parent.left
right: parent.right
rightMargin: 15
leftMargin: 15
}
color: "yellow"
radius: 3
Text {
height: 24
width: 100
text: assetName
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
color: "black"
}
Button {
id: expandButton
width: 70
height: 24
text: "Expand"
anchors {
right: parent.right
rightMargin: 20
verticalCenter: parent.verticalCenter
}
onClicked: {
myNestedListView.currentIndex = index
myApplicationWindow.backendObjectInQML.model.collapseEditInputsMenu(index, "isCollapsed")
// myNestedListModel.setProperty(index, "isCollapsed", !isCollapsed)
console.log("From QML isCollapsed:")
console.log(isCollapsed)
}
}
}
Loader {
id: subSolutionEditItemLoader
visible: !isCollapsed
property variant subEditItemModel: subItems
sourceComponent: isCollapsed ? null : subItemEditInputsDelegate
onStatusChanged: {
// console.log(subItems)
if(status == Loader.Ready) item.model = subEditItemModel
}
}
}
}
Component {
id: subItemEditInputsDelegate
Column {
property alias model: subItemRepeater.model
id: nestedListElementColumn
width: myNestedListView.width
anchors {
top: parent.top
topMargin: 3
}
spacing: 3
Repeater {
id: subItemRepeater
width: parent.width
delegate: Rectangle {
id: nestedListElementRectangle
color: "blue"
height: 40
anchors {
left: parent.left
leftMargin: 30
right: parent.right
rightMargin: 30
}
radius: 5
Rectangle {
id: cellSizeBackground
height: 20
width: cellSizeLabel.implicitWidth
color: "#00000000"
anchors {
left: parent.left
leftMargin: 25
top: parent.top
topMargin: 10
}
Label {
id: cellSizeLabel
text: "Cell Size: "
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
color: "#6e95bc"
}
}
Rectangle {
id: cellSizeTextInputBorder
height: 24
width: 120
color: "#00000000"
radius: 5
anchors {
left: cellSizeBackground.right
leftMargin: 10
verticalCenter: cellSizeBackground.verticalCenter
}
border.width: 1
border.color: "#12C56A"
TextInput {
id: cellSizeTextInput
text: cellSize
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
color: "#6e95bc"
selectByMouse: true
leftPadding: 5
rightPadding: 5
clip: true
onEditingFinished: {
console.log("cellSizeTextInput edited...")
}
}
}
}
}
}
}
Button {
id: addListElementButton
height: 24
width: 70
text: "Add"
anchors {
bottom: parent.bottom
right: parent.right
}
onClicked: {
myApplicationWindow.backendObjectInQML.model.appendRow("Dummy Item", false)
}
}
}
}
I suspect that when I update "isCollapsed" item of listModel from false to true, it is updated on listView but somehow it is not triggering the GUI. But I don't know why.
I also added Nested ListModel and relevant lines to QML as commented out for ones who want to try the code with ListModel created on QML side.
You can see application ScreenShot below:
You didn't emitted any signals while changing the collapsed state.
Therefore there was no way for properties that relay on that role to know that they need to synchronize with it.
Change:
#pyqtSlot(int, str)
def collapseEditInputsMenu(self, index, modelIndexName):
self.itemNames[index][modelIndexName] = not self.itemNames[index][modelIndexName]
print(f"From Backend: {self.itemNames}")
To:
#pyqtSlot(int, str)
def collapseEditInputsMenu(self, index, modelIndexName):
self.layoutAboutToBeChanged.emit()
self.itemNames[index][modelIndexName] = not self.itemNames[index][modelIndexName]
print(f"From Backend: {self.itemNames}")
self.layoutChanged.emit()
Fully working example
P.S: This is very messy code, I'll list you here a few severe mistakes:
First:
self.engine.rootObjects()[0].setProperty("backendObjectInQML", app_backend)
property QtObject backendObjectInQML
This is redundant you should use instead:
self.engine.rootContext().setContextProperty("backendObjectInQML", app_backend)
Second:
myApplicationWindow.backendObjectInQML.model
If you will use contextProperty as I said above you don't need to access it threw myApplicationWindow
Third:
The isCollapsed role from your model is probably redundant and you should have preferred creating a property for your myAppListElementDelegate component.
Something like this:
Component {
id: myAppListElementDelegate
Column {
id: listElementColumn
property bool isCollapsed: false // <<<---
width: myNestedListView.width
Rectangle {
id: listElementRectangle
height: 30
anchors {
left: parent.left
right: parent.right
rightMargin: 15
leftMargin: 15
}
color: "yellow"
radius: 3
Text {
height: 24
width: 100
text: assetName
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
color: "black"
}
Button {
id: expandButton
width: 70
height: 24
text: "Expand"
anchors {
right: parent.right
rightMargin: 20
verticalCenter: parent.verticalCenter
}
onClicked: isCollapsed = !isCollapsed
}
}
Loader {
id: subSolutionEditItemLoader
visible: !isCollapsed
property variant subEditItemModel: subItems
sourceComponent: isCollapsed ? null: subItemEditInputsDelegate
onStatusChanged: {
// console.log(subItems)
if(status == Loader.Ready) item.model = subEditItemModel
}
}
}
}
Related
I'm confused at some point in QML as I'm new to it. The simplified QML codes define a window that includes one ListView which includes my delegate Items. One button loads and unloads this ListView Element. When I load and unload the ListView Element, all text that I wrote inside the TextInput is reset from listModel as I expected. So I need to dynamically update listModel thus I will not lose the text that I wrote in TextInput. I added a Keys.onPressed in TextInput to achieve that. But it works with some logical error. When I type, let's say "aaaa", then I unload the ListView and load again via Button, what I get is "aaa" (the last letter is not passed to listModel). This is understandable but how can I update the list model roles dynamically in this example?
main.qml
ApplicationWindow {
id: applicationWindow
width: 300
height: 200
visible: true
title: qsTr("01_Change_Model_Data")
ListModel {
id: listModel1
ListElement {labelText: "Text Field 1:"; textInput_text : "This is text 1"}
ListElement {labelText: "Text Field 2:"; textInput_text : "This is text 2"}
}
Button {
id: loadUnloadBtn
height: 24
width: 50
text: "Load"
anchors {
right: parent.right
rightMargin: 20
top: parent.top
topMargin: 10
}
onClicked: {
if(listAreaLoader.source == "") {
listAreaLoader.source = "ListArea.qml"
}else {
listAreaLoader.source = ""
}
}
}
Loader {
id: listAreaLoader
anchors {
top: parent.top
topMargin: 10
bottom: parent.bottom
bottomMargin: 10
left: parent.left
leftMargin: 10
right: parent.right
rightMargin: 80
}
source: ""
}
}
ListArea.qml:
Item {
id: listViewDelegate
ListView {
id: listView1
anchors.fill: parent
model: listModel1
delegate: listElementDelegate
spacing: 6
}
Component {
id: listElementDelegate
Rectangle {
color: "#00000000"
height: 24
width: 50
Label {
id: label1
text: labelText
}
Rectangle {
color: "grey"
radius: 4
width: 100
height: 20
anchors {
verticalCenter: label1.verticalCenter
left: label1.right
leftMargin: 10
}
TextInput {
id: textInput1
anchors.fill: parent
leftPadding: 5
rightPadding: 5
clip: true
verticalAlignment: Text.AlignVCenter
text: textInput_text
Keys.onPressed: {
listView1.currentIndex = index
listModel1.setProperty(index, "textInput_text", textInput1.text)
}
}
}
}
}
}
I'm trying to create a ListView with different delegates and a drag'n'drop functionality. The delegates shall be loaded with a Loader.
The QML Documentation provides a working example for a ListView without a Loader:
http://doc.qt.io/qt-5/qtquick-tutorials-dynamicview-dynamicview3-example.html
However, using the Loader I get the error: Cannot read property 'DelegateModel' of undefined
I do not understand how I can access the DelegateModel from the Loader.
A hint to the solution is highly appreciated!
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Window 2.0
import QtQuick.Dialogs 1.1
import QtQml.Models 2.3
Window {
id: mainroot
visible: true
width: 640
height: 480
Rectangle{
id:viewContainer
anchors.fill: parent
DelegateModel {
id: visualModel
model: ListModel{
id:m_model
ListElement{
type:1
m_text :"Text1"
}
ListElement{
type:1
m_text :"Text2"
}
}
delegate: Loader{
id:idLoader
width: view.width
height: childrenRect.height
Component.onCompleted: {
switch(type){
case 1:
idLoader.setSource("TestDelegate.qml", {"m_text": m_text})
break;
}
}
}
}
ListView{
id: view
anchors.fill: parent
spacing: 5
model: visualModel
}
}
}
TestDelegate.qml:
import QtQuick 2.7
MouseArea {
id: dragArea
property bool held: false
property string m_text
anchors { left: parent.left; right: parent.right }
height: 50
width: view.width
drag.target: held ? content : undefined
drag.axis: Drag.YAxis
onPressAndHold: held = true
onReleased: held = false
Rectangle {
id: content
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
width: dragArea.width
height: textfield.implicitHeight
Drag.active: dragArea.held
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
border.width: 1
border.color: "lightsteelblue"
color: dragArea.held ? "lightsteelblue" : "white"
Behavior on color { ColorAnimation { duration: 100 } }
radius: 2
states: State {
when: dragArea.held
ParentChange { target: content; parent: viewContainer }
AnchorChanges {
target: content
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
Text{
id: textfield
anchors.centerIn: parent
text: m_text
}
}
DropArea {
anchors { fill: parent; margins: 10 }
onEntered: {
visualModel.items.move(
idLoader.item.drag.source.DelegateModel.itemsIndex,
idLoader.item.dragArea.DelegateModel.itemsIndex)
}
}
}
The items defined in the file loaded with Loader or in general with any other .qml file that is imported should not depend directly on the main file since the ids have a scope, it is better to expose properties, in your case:
╭------------------------------------------╮
| bool held ------┿--->
| TestDelegate string m_text ------┿--->
| ============ DelegateModel md ------┿--->
| int index ------┿--->
╰------------------------------------------╯
Considering the above, the solution is the following:
main.qml
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQml.Models 2.3
Window {
id: mainroot
visible: true
width: 640
height: 480
Rectangle{
id:viewContainer
anchors.fill: parent
DelegateModel {
id: visualModel
model: ListModel{
id:m_model
Component.onCompleted: {
for(var i=0; i< 40; i++){
m_model.append({"type": 1, "m_text": "Text" + i})
}
}
}
delegate:
Loader{
id: idLoader
width: view.width
height: childrenRect.height
property int index: DelegateModel.itemsIndex
onIndexChanged: if(status == Loader.Ready) idLoader.item.index = index
Component.onCompleted: {
switch(type){
case 1:
idLoader.setSource("TestDelegate.qml", {
"m_text": m_text,
"index": index,
"md": visualModel
})
break;
}
}
}
}
ListView{
id: view
anchors.fill: parent
spacing: 5
model: visualModel
}
}
}
TestDelegate.qml
import QtQuick 2.7
import QtQml.Models 2.3
MouseArea {
id: dragArea
property bool held: false
property string m_text
property DelegateModel md: null
property int index : -1;
anchors { left: parent.left; right: parent.right }
height: 50
width: view.width
drag.target: held ? content : undefined
drag.axis: Drag.YAxis
onPressAndHold: held = true
onReleased: held = false
Rectangle {
id: content
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
width: dragArea.width
height: textfield.implicitHeight
Drag.active: dragArea.held
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
border.width: 1
border.color: "lightsteelblue"
color: dragArea.held ? "lightsteelblue" : "white"
Behavior on color { ColorAnimation { duration: 100 } }
radius: 2
states: State {
when: dragArea.held
ParentChange { target: content; parent: viewContainer }
AnchorChanges {
target: content
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
Text{
id: textfield
anchors.centerIn: parent
text: m_text
}
}
DropArea {
anchors { fill: parent; margins: 10 }
onEntered: {
if(md !== null)
md.items.move(drag.source.index, dragArea.index)
}
}
}
This question already has an answer here:
How to create an animated, variable size accordion component in QtQuick / QML
(1 answer)
Closed 5 years ago.
Is there a QML type to build an unfolding menu like that:
state1:
state2:
Or do I have to somehow engineer this type by combining other types and logic?
Edit:
This is how I'vew done it. There are of course improvements possible, e.g.anchoring the ScrollView of the tables to the header row, using a Repeater intead of creating objects, modularize the components into seperate files, etc.:
import QtQuick 2.7
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
ApplicationWindow {
id: rootWindow
visible: true
width: 320
height: 320
Rectangle {
id:base
anchors.fill: parent
color: "#FFFFFFFF"
property var numOfInputs
property var inputcomp
signal currFullInputChanged();
function start() {
currFullInputChanged()
}
Window {
id: win
x: base.anchors.rightMargin
width: 200
height: 200
visible: true
MouseArea {
id: area
anchors.fill: parent
property int count: 0
onClicked: {
inputmod.append({"Key_1": count, "Key_2": count+1})
++count;
base.start()
}
}
}
Component.onCompleted: {
win.show()
}
onCurrFullInputChanged: {
if(typeof inputcomp !== "undefined")
inputcomp.destroy()
numOfInputs = inputmod.count
inputcomp = inputComponent.createObject(this, {"inputRows": numOfInputs})
}
ListModel {
id: inputmod
}
Component {
id: inputComponent
Column {
id: clTop
anchors.fill: parent
width: parent.width
height: parent.height
spacing: 1
property int inputRows
property int lastIndex: -1
property var objs: []
Component {
id: inputitem
Item {
id:item_
width: parent.parent.width
height: parent.parent.height/12
property string clickedColor: "#B4D0B4"
property int currIndex
Rectangle {
id: rectinfo
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: rectexpand.open === 0 ? parent.height : parent.height/4
border.width: 1
border.color: "#E4EEDA"
radius: 2
color: baseColor
property string baseColor: "#FDFFF9"
Text {
anchors.top: parent.top
anchors.left: parent.left
padding: 5
width: parent.width/2
height: parent.height/2
fontSizeMode: Text.Fit
text: "Input " + parseInt(parent.parent.currIndex + 1)
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignTop
wrapMode: Text.WordWrap
elide: Text.ElideRight
font.family: "Helvetica"
font.bold: true
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
rectexpand.open = rectexpand.open === 0 ? 1 : 0
item_.height = Qt.binding(function() { return rectexpand.open === 0 ? clTop.height*1/12 : clTop.height*4/12} )
var currIndex = parent.parent.currIndex
if(clTop.lastIndex > -1) {
var lastItem = clTop.objs[clTop.lastIndex];
lastItem.children[0].color = lastItem.children[0].baseColor;
lastItem.children[1].color = lastItem.children[1].baseColor;
lastItem.children[0].opacity = 1
lastItem.children[1].opacity = 1
}
var currItem = clTop.objs[currIndex];
currItem.children[0].color = parent.parent.clickedColor
currItem.children[1].color = parent.parent.clickedColor
currItem.children[0].opacity = 0.5
currItem.children[1].opacity = 0.5
clTop.lastIndex = parent.parent.currIndex
}
}
}
Rectangle {
id: rectexpand
anchors {top: rectinfo.bottom; left: parent.left; bottom: parent.bottom; right: parent.right}
border.width: 1
radius: 0
border.color: "grey"
property string baseColor: "white"
property int open: 0
onOpenChanged: {
if(open === 1)
tabloader.sourceComponent = view;
else
tabloader.sourceComponent = undefined;
}
Component {
id: del
Item {
anchors.left: parent !== null ? parent.left : undefined
anchors.right: parent !== null ? parent.right : undefined
TextInput {
id: textinput
anchors.margins: 4
anchors.left: parent !== null ? parent.left : undefined
anchors.verticalCenter: parent !== null ? parent.verticalCenter : undefined
text: styleData.value
color: styleData.textColor
}
}
}
Component {
id: view
TableView {
anchors.fill: parent
itemDelegate: del
model: inputmod
TableViewColumn {
id: col1
role: "Key_1"
title: "Key 1"
width: parent !== null ? parent.width/2 - 2 : 0
}
TableViewColumn {
id: col2
role: "Key_2"
title: "Key 2"
width: parent !== null ? parent.width/2 - 2 : 0
}
}
}
Loader {
id: tabloader
anchors.fill: parent
}
}
}
}
Component.onCompleted: {
for(var i=0; i<inputRows; ++i) {
objs.push(inputitem.createObject(this, {"currIndex": i}))
}
}
Component.onDestruction: {
for(var i=0; i<inputRows; ++i) {
objs[i].destroy()
}
}
}
}
}
}
Apperently QML still does not have enough usefull Control types like such kind of menus. But it has enought basic components to create your own ControlsWidgets/whatsoever.
To make such menu you can use
ListView QML Component with custom delegate for items. Here is an example/tutorial And here is Expanding Delegates in Qt docs
Use Menu Control from QtQuick.Controls 2 and customize MenuItem like described here
I created an example. I do not know why I get a crash when I try to resize the window horizontally. Crash happens only when iconVisible is true
here is the qml code from the main, paste this in a hello world qt quick controls 2 project to test:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtQuick.XmlListModel 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Page {
anchors.fill: parent
XmlListModel{
id : modelId
namespaceDeclarations: "declare namespace media = 'http://search.yahoo.com/mrss/';"
source: "http://api.flickr.com/services/feeds/photos_public.gne?format=rss2&tags="
query: "//item[title and media:thumbnail and media:content]"
}
ListView {
anchors.fill: parent
model: modelId
Layout.fillWidth: true
delegate: itemDelegateId
ScrollBar.vertical: ScrollBar {}
}
Component {
id: itemDelegateId
Rectangle {
property bool iconVisible: true
property string contentText: "contentaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
property double itemHeight: 100
property int titleTextSize: 32
property int columnLayoutLeftMargin: (iconVisible)? 20 : 0
property string borderColor: "black"
property int iconSize: (iconVisible)? 40 : 0
height : itemHeight
anchors {
left: parent.left
right: parent.right
}
border.color: borderColor
border.width: 1
RowLayout {
anchors {
fill: parent
}
Rectangle {
id: notificationIconId
visible: iconVisible
anchors {
top: parent.top
}
height: iconSize
width: iconSize
Image {
anchors.fill: parent
fillMode: Image.PreserveAspectFit
}
}
ColumnLayout {
id: columnLayoutId
anchors {
top: parent.top
bottom: parent.bottom
left: notificationIconId.right
right: parent.right
}
Text {
id: contentId
anchors {
top: parent.top
bottom: parent.bottom
}
Layout.fillWidth: true
font.pixelSize: 20
elide: Text.ElideRight
maximumLineCount: 3
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
color: "black"
text: contentText
}
}
}
}
}
}
}
There are a few issues here. The first is that Layout.fillWidth: true has no effect, as Page is not a layout:
ListView {
anchors.fill: parent
model: modelId
Layout.fillWidth: true // remove
delegate: itemDelegateId
ScrollBar.vertical: ScrollBar {}
}
The second is that you shouldn't specify width and height for items in a layout:
Rectangle {
id: notificationIconId
visible: iconVisible
anchors {
top: parent.top
}
height: iconSize // Should be Layout.preferredHeight: iconSize
width: iconSize // Should be Layout.preferredWidth: iconSize
Image {
anchors.fill: parent
fillMode: Image.PreserveAspectFit
}
}
You can't use horizontal anchors on an item managed by a horizontal layout:
ColumnLayout {
id: columnLayoutId
anchors {
top: parent.top
bottom: parent.bottom
// This ColumnLayout is managed by a RowLayout, so these two lines have to go
left: notificationIconId.right
right: parent.right
}
}
Same thing for the Text item:
Text {
id: contentId
// Can't use vertical anchors on an item that is managed by a ColumnLayout
anchors {
top: parent.top
bottom: parent.bottom
}
Layout.fillWidth: true
font.pixelSize: 20
elide: Text.ElideRight
maximumLineCount: 3
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
color: "black"
text: contentText
}
With all of those issues fixed, it seems to work as expected:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtQuick.XmlListModel 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Page {
anchors.fill: parent
XmlListModel{
id : modelId
namespaceDeclarations: "declare namespace media = 'http://search.yahoo.com/mrss/';"
source: "http://api.flickr.com/services/feeds/photos_public.gne?format=rss2&tags="
query: "//item[title and media:thumbnail and media:content]"
}
ListView {
anchors.fill: parent
model: modelId
delegate: itemDelegateId
ScrollBar.vertical: ScrollBar {}
}
Component {
id: itemDelegateId
Rectangle {
property bool iconVisible: true
property string contentText: "contentaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
property double itemHeight: 100
property int titleTextSize: 32
property int columnLayoutLeftMargin: (iconVisible)? 20 : 0
property string borderColor: "black"
property int iconSize: (iconVisible)? 40 : 0
height : itemHeight
anchors {
left: parent.left
right: parent.right
}
border.color: borderColor
border.width: 1
RowLayout {
anchors {
fill: parent
}
Rectangle {
id: notificationIconId
visible: iconVisible
anchors {
top: parent.top
}
Layout.preferredHeight: iconSize
Layout.preferredWidth: iconSize
Image {
anchors.fill: parent
fillMode: Image.PreserveAspectFit
}
}
ColumnLayout {
id: columnLayoutId
anchors {
top: parent.top
bottom: parent.bottom
}
Text {
id: contentId
Layout.fillWidth: true
font.pixelSize: 20
elide: Text.ElideRight
maximumLineCount: 3
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
color: "black"
text: contentText
}
}
}
}
}
}
}
I have a problem, which can be seen on the attached screenshot
There is ApplicationWindow, which has header and ListView which is used in horizontal layout. Each item of list should be one page of application. Unfortunatelly, the width of base page is not set correctly to fill width of its parent (white background, not the grey one).
Here is the code of main page, it should load Login page - it is shown on the image.
ApplicationWindow {
id: root_window
title: Style.applicationName
visible: true
color: "white"
width: Style.windowWidth
height: Style.windowHeight
ColumnLayout {
id: root_layout
spacing: 0
width: root_window.width
height: root_window.height
SmonHeader {
id: smon_user_app_header
height: Style.headerHeight
anchors.top: parent.top
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
}
Component.onCompleted: {
console.log("Main width: " + width);
}
ListView {
id: navigation
width: parent.width
height: parent.height
orientation: ListView.Horizontal
interactive: true // disable manual pageChange
snapMode: ListView.SnapOneItem // while moving to right, finish move
highlightRangeMode: ListView.StrictlyEnforceRange // mouse gesture make currentIndex change
highlightMoveDuration: 400 // speed up pages change (swap)
model: ObjectModel {
/* First page with login capabilities */
Login {
id: login_module
width: root_layout.width
height: root_layout.height
}
}
}
}
/* Private function definition*/
function init_database()
{
var database = Storage.LocalStorage.openDatabaseSync(Style.databaseName, Style.databaseVersion, Style.databaseDescr, Style.databaseSize);
smonDatabase.startDatabase(Style.databaseName);
}
Component.onCompleted: {
init_database();
}
}
Here is base of Login page
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import "../"
import "./common"
Rectangle {
id: login_page
// why parent.width is not set ?
anchors.fill: parent
//width: parent.width
//Layout.fillWidth: true
property string credentials_title: qsTr("Přístupové údaje")
property string available_devices: qsTr("Dostupné servery")
property string identity_title: qsTr("Identita")
property string password_title: qsTr("Heslo")
property string domain_title: qsTr("Doména")
property string infoLine_title: qsTr("Zapamatovat přihlašovací údaje")
property string username_title: qsTr("Jméno");
Component.onCompleted: {
console.log("Login width: " + login_page.width);
control.cancelEnabled = false
}
ColumnLayout{
id: navigation
spacing: Style.spacing
anchors.left: parent.left
anchors.leftMargin: Style.defaultAnchors
Layout.fillWidth: true
anchors.fill: parent
width: parent.width
Text {
id: title
//anchors.top: parent.top
//anchors.left: parent.left
font.pointSize: Style.fontSizeHeading
text: credentials_title
}
ColumnLayout{
id: navigationLogin
spacing: Style.spacing
anchors.left: parent.left
anchors.leftMargin: Style.defaultAnchors
Layout.fillWidth: true
Layout.bottomMargin: Style.bottomMargin
width: (parent.width - 4*Style.defaultAnchors)
GridLayout {
id: input_login
rowSpacing: Style.denseSpacing
columns: 2
columnSpacing: Style.spacing
anchors.left: parent.left
anchors.leftMargin: Style.defaultAnchors
width: 200
Text {
id: user_name
font.pointSize: Style.fontSizeSubHeading
text: username_title
}
SmonComboBox {
id: user
width: parent.width
value: smonRole.user
object: smonRole
prop: "user"
isEditable: true
dataModel: smonRole.userData
selectedIndex: smonRole.userNameSelected
}
Text {
id: password_name
font.pointSize: Style.fontSizeSubHeading
text: password_title
}
SmonTextField {
id: password
width: parent.width
type: "password"
object: smonRole
prop: "pass"
value: smonRole.pass
onEnterPressed: {
user.enabled = false
password.enabled = false
//control.okEnabled = false
control.okEnabled = false
control.cancelEnabled = true
smonRole.save();
smonCommunication.connect();
}
onValueChanged: {
if(password.value !== "")
{
control.okEnabled = true
}
else
{
control.okEnabled = false
}
}
}
}
ColumnLayout {
id: scanning
spacing: Style.denseSpacing
anchors.left: parent.left
//Layout.fillWidth: true
RowLayout {
id: slider_component
Text {
id: scanningHeader
font.pointSize: Style.fontSizeSubHeading
text: qsTr("Perioda vyhledávání zařízení");
}
Text {
id: value
font.pointSize: Style.fontSizeInfo
anchors.left: scanningHeader.right
anchors.leftMargin: Style.defaultAnchors
width: 30
text: slider.value
}
}
Slider {
id: slider
minimumValue: 2
maximumValue: 30
Layout.fillWidth: true
stepSize: 1
value: smonCommunication.scanPeriod
onValueChanged: {
smonCommunication.scanPeriod = slider.value;
}
}
}
SmonControlPanel {
id: control
width: parent.width
okEnabled: smonRole.user != "" && smonRole.pass != ""
okVisible: true
cancelVisible: true
onSignalOk: {
// hide content
user.enabled = false
password.enabled = false
control.okEnabled = false
control.cancelEnabled = true
smonRole.save();
smonCommunication.connect();
}
onSignalCancel: {
// show content again
password.enabled = true
user.enabled = true
//domain.enabled = true
control.cancelEnabled = false
control.okEnabled = true
//smonConnection.logout();
smonCommunication.disconnect();
smonRole.disconnected();
}
}
}
Text {
id: favourite
font.pointSize: Style.fontSizeHeading
text: available_devices
}
ListView{
id: servers
Layout.fillHeight: true
width: parent.width
model: smonCommunication.devicesList
delegate: Rectangle {
id: serverList
height: 80
width: parent.width
ColumnLayout{
Text {
id: serverName
text: modelData.bluetooth_name
}
Text {
id: rssi
text: modelData.bluetooth_rssi
}
}
MouseArea {
id: bt_device
anchors.fill: parent
onClicked: {
if(smonCommunication.btCanConnect === true)
smonCommunication.connect(index);
}
}
}
}
}
MessageDialog {
id: errorDialog
standardButtons: StandardButton.Cancel | StandardButton.OK
visible: false;
informativeText: smonCommunication.errorMessage
onInformativeTextChanged: {
errorDialog.visible = true;
}
}
}
Is there problem on the main page or on the page which is loaded ? Thanks for help...
Your problem lies with the anchors.fill: parent bit in your ObjectModel.
The parent here, is not the ListView, but the ListView's contentItem, which happens to have an implicit width of 100px.
In your minimal example, something like this should work:
model: ObjectModel {
/* First page with login capabilities */
Rectangle{
id: one
//anchors.fill: parent <- parent is not navigation
width: navigation.width
height: 50
color: "red"
}
}
Generally speaking, you should not use the parent property in your delegates.
So, after answers from ddriver and Kevin Krammer (thanks) I made a minimal working example.
I stopped using ColumnLayout, and set everything as best as I can.
Here is the code
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
import QtQml.Models 2.1
ApplicationWindow {
id: root_window
title: "Hello world"
visible: true
color: "white"
width: 480
height: 520
Rectangle {
id: smon_user_app_header
height: 50
color: "blue"
width: parent.width
}
ListView {
id: navigation
orientation: ListView.Horizontal
interactive: true // disable manual pageChange
snapMode: ListView.SnapOneItem // while moving to right, finish move
highlightRangeMode: ListView.StrictlyEnforceRange // mouse gesture make currentIndex change
highlightMoveDuration: 400 // speed up pages change (swap)
anchors.top: smon_user_app_header.bottom
anchors.bottom: root_window.bottom
width: parent.width
height: 400
model: ObjectModel {
/* First page with login capabilities */
Rectangle{
id: one
anchors.fill: parent
color: "red"
}
}
}
}
And here is how it looks