How propagate TableViewColumn list to child TableView? - qt

I'm trying to make custom component for editable tables in QML, like this:
// BaseTableView.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
Item {
signal addActionPerformed()
signal editActionPerformed(int id)
signal deleteActionPerformed(int id)
property var model
ToolBar {
id: toolBar
anchors.left: parent.left
anchors.right: parent.right
Row {
ToolButton {
id: addButton
iconSource: "qrc:/icons/actions/add.png"
onClicked: addActionPerformed()
}
ToolButton {
id: editButton
enabled: false
iconSource: "qrc:/icons/actions/edit.png"
}
ToolButton {
id: deleteButton
enabled: false
iconSource: "qrc:/icons/actions/delete.png"
}
}
}
TableView {
id: tableView
model: parent.model
anchors.left: parent.left
anchors.right: parent.right
anchors.top: toolBar.bottom
anchors.bottom: parent.bottom
onCurrentRowChanged: {
editButton.enabled = currentRow !== null
deleteButton.enabled = currentRow !== null
}
}
}
and use this component in another file like this:
// Another.qml file
import QtQuick 2.3
import QtQuick.Controls 1.2
import "../common" // Here is BaseTableView.qml
BaseTableView {
TableViewColumn {
role: "id"
title: qsTr("Id")
}
TableViewColumn {
role: "object_expression"
title: qsTr("Expression")
}
}
So, problem is how i can pass table view columns from usage to underlying TableView?
I've tried to make property list in BaseTableView and assign a list of objects to this property in Aother.qml? but unsuccessfully.

Use default properties:
An object definition can have a single default property. A default property is the property to which a value is assigned if an object is declared within another object's definition without declaring it as a value for a particular property.
More relevant for your scenario:
You will notice that child objects can be added to any Item-based type without explicitly adding them to the children property. This is because the default property of Item is its data property, and any items added to this list for an Item are automatically added to its list of children.
Default properties can be useful for reassigning the children of an item. See the TabWidget Example, which uses a default property to automatically reassign children of the TabWidget as children of an inner ListView.
If you take a look at the TabWidget example that the last paragraph refers to, you should have all you need:
import QtQuick 2.0
Item {
id: tabWidget
// Setting the default property to stack.children means any child items
// of the TabWidget are actually added to the 'stack' item's children.
// See the "Property Binding"
// documentation for details on default properties.
default property alias content: stack.children
property int current: 0
onCurrentChanged: setOpacities()
Component.onCompleted: setOpacities()
function setOpacities() {
for (var i = 0; i < stack.children.length; ++i) {
stack.children[i].opacity = (i == current ? 1 : 0)
}
}
Row {
id: header
Repeater {
model: stack.children.length
delegate: Rectangle {
width: tabWidget.width / stack.children.length; height: 36
Rectangle {
width: parent.width; height: 1
anchors { bottom: parent.bottom; bottomMargin: 1 }
color: "#acb2c2"
}
BorderImage {
anchors { fill: parent; leftMargin: 2; topMargin: 5; rightMargin: 1 }
border { left: 7; right: 7 }
source: "tab.png"
visible: tabWidget.current == index
}
Text {
horizontalAlignment: Qt.AlignHCenter; verticalAlignment: Qt.AlignVCenter
anchors.fill: parent
text: stack.children[index].title
elide: Text.ElideRight
font.bold: tabWidget.current == index
}
MouseArea {
anchors.fill: parent
onClicked: tabWidget.current = index
}
}
}
}
Item {
id: stack
width: tabWidget.width
anchors.top: header.bottom; anchors.bottom: tabWidget.bottom
}
}
In cases like this, where you want to replicate something that is done by an item offered by Qt, it can also be helpful to take a look at the source code of what you're trying to replicate. However, the documentation is a bit easier to read. :)

Related

How can I access and change a item in Component in QML

In a Qml file ,the code like:
StackView {
id: stackView
anchors.right: parent.right
width: parent.width/2-20
initialItem:patientdetail
Component{
id:patientdetail
Column {
id:detailcolumn
spacing: 2
anchors.right: parent.right
width: parent.width/2-20
Label {
id: label
color: "#ffffff"
text: qsTr("User ID")
}
TextField {
id: textField_id
readOnly: true
placeholderText: qsTr("")
}
}
}
Component{
id:component2
//...other component will add to stackview
}
}
And I want to change the text of TextField by a JS function(in same file) like:
function updatedetail(clear,rowindex){
if(clear){
textField_id.text="";
}
else{
textField_id.text=jsonModel1.model.get(rowindex).id;
}
}
But there is a error:
ReferenceError: textField_id is not defined
Where the error occurred?
As you try to change an Object that is not yet instantiated, it will fail. But even when it is instantiated, its id will be in a different scope, that can't be reached like that.
This is necessary, as the same Component might be instantiated multiple times (e.g. as a delegate in a ListView) so it would not be unique in the context anymore.
Uppon instantiation of your StackView, your Component will be instantiated an pushed on the StackView. Now you have an instance and might alter exposed properties by using:
currentItem.textFieldText = newValue
in your function. For this to work you need to expose the property:
Component{
id:patientdetail
Column {
id:detailcolumn
spacing: 2
anchors.right: parent.right
width: parent.width/2-20
property alias textFieldText: textField_id.text // in this context, you have access to the id. Expose it, to change it from the outside.
Label {
id: label
color: "#ffffff"
text: qsTr("User ID")
}
TextField {
id: textField_id
readOnly: true
placeholderText: qsTr("")
}
}
}
However, as the instance might be destroyed and recreated later, this change would not be permanent, so it would be better to bind the TextField.text to a property of an object, that will survive as long as necessary. This could be a contextProperty exposed from C++ or a QtObject passed as a model, or just a property, e.g. in the StackView.
StackView {
id: stackView
anchors.right: parent.right
width: parent.width/2-20
initialItem:patientdetail
// Change this property. The textField_id.text will be bound to it.
property string textFieldText
Component{
id:patientdetail
Column {
id:detailcolumn
spacing: 2
anchors.right: parent.right
width: parent.width/2-20
Label {
id: label
color: "#ffffff"
text: qsTr("User ID")
}
TextField {
id: textField_id
readOnly: true
placeholderText: qsTr("")
text: stackView.textFieldText
}
}
}
}

Warnings in QML: Delegate in separate file and access on model item properties

The following code works and shows my items correctly, but I get the warning
qrc:/TableDelegate.qml:24: ReferenceError: name is not defined
I think it is because the ListView tries to access the model when it is empty and can not reference the item properties. I assume I am not doing to it correctly but I do not know how to do it better.
So my question is: how to get rid of the warning by doing it the right way?
TableDelegate.qml:
import QtQuick 2.0
import QtQuick.Layouts 1.1
Item {
property color bgcolor: 'transparent'
property alias box: rowBox
height: 40
width: parent.width
Rectangle {
id: rowBox
anchors.fill: parent
color: bgcolor
RowLayout {
anchors.fill: parent
Rectangle {
id: tableNameColumn
color: 'transparent'
Layout.fillHeight: true
Layout.fillWidth: true
Text {
anchors.centerIn: parent
color: textcolor
text: name // <--- here is `name`
}
}
// More Columns ...
}
}
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = index
}
}
}
And I use it like this
TableView.qml:
// ...
ListModel {
id: model
}
ListView {
id: view
model: model
anchors.fill: parent
highlight: delegate_highlighted
highlightFollowsCurrentItem: true
delegate: delegate
}
Component {
id: delegate
TableDelegate {
bgcolor: 'transparent';
}
}
Component {
id: delegate_highlighted
TableDelegate {
bgcolor: 'lightsteelblue'
box.border.color: 'black'
box.radius: 3
}
}
// ...
You use a TableDelegate for the highlight. That is wrong.
The ListView creates 1 instance of the highlight item, that will be drawn as a background for the currently selected item, It may also move between items as transition when the current item changes. It should only be a rectangle or whatever you want to use.
In your example, the highlight item is a full delegate, that wants to access model data, which it cannot.

Move items between ListModels

Assume a QML application with two list models, each holding, say 3, items. Graphically the two lists are displayed in two containers. I'd like to implement a drag-and-drop functionality to reorder the items inside their list and to swap two items between different lists.
Reordering in one list poses no problem due to the move method of the ListModel. For swapping items between the two list models, however, I think I have to use the remove and insert or set operations of ListModel. First experiments using set didn't work.
What is the canonical way to achieve this that also works with transitions? (E.g. just swapping the items' roles is no solution, I will have to swap the items themselves.)
You can just use ListModel.get() to fetch an element from one view and ListModel.append() or ListModel.insert() to put it in another one (not exactly with same type)
The simple example:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
Window {
width: 400
height: 400
visible: true
Component.onCompleted: {
fillModel(model1);
fillModel(model2);
}
Component {
id: delegate
Rectangle {
width: parent.width
property int itemIndex: index
property var view: ListView.view
color: (itemIndex === view.currentIndex) ? "orange" : "white"
height: 20
Text { text: name; color: moved ? "red" : "black"; anchors.centerIn: parent }
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = itemIndex;
}
}
}
}
RowLayout {
anchors.fill: parent
ListView {
id: list1
Layout.fillHeight: true
Layout.fillWidth: true
model: ListModel { id: model1 }
delegate: delegate
}
ColumnLayout {
Layout.alignment: Qt.AlignCenter
width: 50
Button { text: ">>>"; onClicked: moveItem(list1,list2) }
Button { text: "<<<"; onClicked: moveItem(list2,list1) }
}
ListView {
id: list2
Layout.fillHeight: true
Layout.fillWidth: true
model: ListModel { id: model2 }
delegate: delegate
add: Transition {
id: list2Transition
enabled: false
property int fromX
property int fromY
ParallelAnimation {
NumberAnimation { properties: "x"; from: list2Transition.fromX; duration: 300; }
NumberAnimation { properties: "y"; from: list2Transition.fromY; duration: 300;easing.type: Easing.OutCirc; }
PropertyAnimation {property: "color"; from: "red"; to: "white"; duration: 500 }
}
}
}
}
function fillModel(model) {
for(var i = 0;i < 15;i ++)
model.append({name: "item" + i, moved: false});
}
function moveItem(listfrom,listto) {
var item = listfrom.model.get(listfrom.currentIndex);
var newPos1 = listfrom.parent.mapFromItem(listfrom,0,listfrom.currentIndex * 20);
var newPos2 = listto.parent.mapFromItem(listto,0,listto.currentIndex * 20);
list2Transition.fromX = newPos1.x - newPos2.x;
list2Transition.fromY = newPos1.y;
list2Transition.enabled = true;
item.moved = true;
listto.model.insert(listto.currentIndex, item);
listfrom.model.remove(listfrom.currentIndex, 1);
}
}
You can also do the same action by using drag & drop functionality.

How to limit the size of drop-down of a ComboBox in QML

I am using a ComboBox in QML and when populated with a lot of data it exceeds my main windows bottom boarder. From googling I have learned that the drop-down list of a ComboBox is put on top of the current application window and therefore it does not respect its boundaries.
Ideally I would want the ComboBox to never exceed the main applications boundary, but I can not find any property in the documentation.
A different approach would be to limit the number of visible items of the drop-down list so that it do not exceed the window limits for a given window geometry. I was not able to find this in the documentation either and I have run out of ideas.
Take a look to the ComboBox source code, the popup is of a Menu type and it doesn't have any property to limit its size. Moreover, the z property of the Menu is infinite, i.e. it's always on top.
If you Find no way but to use the ComboBox of Qt you can create two models one for visual purpose, I will call it visual model, you will show it in your ComboBox and the complete one , it will be the reference model. Items count in your VisualModel wil be equal to some int property maximumComboBoxItemsCount that you declare . you'll need o find a way that onHovered find the index under the mouse in the visualmodel if it's === to maximumComboBoxIemsCount you do visualModel.remove(0) et visualModel.add(referenceModel.get(maximum.. + 1) and you'll need another property minimumComboBoxIemsCount, same logic but for Scroll Up , I dont know if it will work. but it's an idea
I think there is no solution using the built-in component and you should create your own comboBox. You can start from the following code.
ComboBox.qml
import QtQuick 2.0
Item {
id: comboBox
property string initialText
property int maxHeight
property int selectedItem:0
property variant listModel
signal expanded
signal closed
// signal sgnSelectedChoice(var choice)
width: 100
height: 40
ComboBoxButton {
id: comboBoxButton
width: comboBox.width
height: 40
borderColor: "#fff"
radius: 10
margin: 5
borderWidth: 2
text: initialText
textSize: 12
onClicked: {
if (listView.height == 0)
{
listView.height = Math.min(maxHeight, listModel.count*comboBoxButton.height)
comboBox.expanded()
source = "qrc:/Images/iconUp.png"
}
else
{
listView.height = 0
comboBox.closed()
source = "qrc:/Images/iconDown.png"
}
}
}
Component {
id: comboBoxDelegate
Rectangle {
id: delegateRectangle
width: comboBoxButton.width
height: comboBoxButton.height
color: "#00000000"
radius: comboBoxButton.radius
border.width: comboBoxButton.borderWidth
border.color: comboBoxButton.borderColor
Text {
color: index == listView.currentIndex ? "#ffff00" : "#ffffff"
anchors.centerIn: parent
anchors.margins: 3
font.pixelSize: 12
text: value
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: {
listView.height = 0
listView.currentIndex = index
comboBox.selectedItem = index
tools.writePersistence(index,5)
comboBoxButton.text = value
comboBox.closed()
}
}
}
}
ListView {
id: listView
anchors.top: comboBoxButton.bottom
anchors.left: comboBoxButton.left
width: parent.width
height: 0
clip: true
model: listModel
delegate: comboBoxDelegate
currentIndex: selectedItem
}
onClosed: comboBoxButton.source = "qrc:/Images/iconDown.png"
Component.onCompleted: {
var cacheChoice = tools.getPersistence(5);
listView.currentIndex = tools.toInt(cacheChoice)
selectedItem = listView.currentIndex
comboBoxButton.text = cacheModel.get(selectedItem).value
}
}
ComboBoxButton.qml
import QtQuick 2.0
Item {
id: container
signal clicked
property string text
property alias source : iconDownUp.source
property string color: "#ffffff"
property int textSize: 12
property string borderColor: "#00000000"
property int borderWidth: 0
property int radius: 0
property int margin: 0
Rectangle {
id: buttonRectangle
anchors.fill: parent
color: "#00000000"
radius: container.radius
border.width: container.borderWidth
border.color: container.borderColor
Image {
id: image
anchors.fill: parent
source: "qrc:/Images/buttonBackground.png"
Image {
id: iconDownUp
source: "qrc:/Images/iconDown.png"
sourceSize.height:20
sourceSize.width: 20
anchors.verticalCenter: parent.verticalCenter
}
}
Text {
id:label
color: container.color
anchors.centerIn: parent
font.pixelSize: 10
text: container.text
font.bold: true
}
MouseArea {
id: mouseArea;
anchors.fill: parent
onClicked: {
container.clicked()
buttonRectangle.state = "pressed"
startTimer.start()
}
}
Timer{
id:startTimer
interval: 200
running: false;
repeat: false
onTriggered: buttonRectangle.state = ""
}
states: State {
name: "pressed"
when: mouseArea.pressed
PropertyChanges { target: image; scale: 0.7 }
PropertyChanges { target: label; scale: 0.7 }
}
transitions: Transition {
NumberAnimation { properties: "scale"; duration: 200; easing.type: Easing.InOutQuad }
}
}
}
I've used it in some software of mine, hence it is possible that It could not work "out of the box". I use it like this:
ComboBox{
id:cacheChoice
initialText: "None"
anchors.top: baseContainer.top
anchors.topMargin: 2
anchors.right: baseContainer.right
maxHeight: 500
listModel: cacheModel
onExpanded: {
cacheChoice.height = 500
}
onClosed: {
cacheChoice.height = 20
}
}
In case you are working with ComboBox from Qt Quick Controls 2, here's the source code for it:
https://github.com/qt/qtquickcontrols2/blob/5.12/src/imports/controls/ComboBox.qml
Based on that, this override of the behavior works to limit the height to something reasonable:
myComboBox.popup.contentItem.implicitHeight = Qt.binding(function () {
return Math.min(250, myComboBox.popup.contentItem.contentHeight);
});
It is possible to access the hidden MenuStyle within the ComboBoxStyle component. There you can use all the things and hidden things you have within a MenuStyle, including its maximum height.
The thing looks roughly like this.
Not pretty but it works well enough.
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.3
import QtQuick.Window 2.2
ComboBox {
id: comboBox
style: ComboBoxStyle {
// drop-down customization here
property Component __dropDownStyle: MenuStyle {
__maxPopupHeight: 400
__menuItemType: "comboboxitem" //not 100% sure if this is needed
}
}
As it came up resonantly in our team, here is a updated version of the idea shown above. The new version restricts the size automatically to the size of your application.
ComboBox {
id: root
style: ComboBoxStyle {
id: comboBoxStyle
// drop-down customization here
property Component __dropDownStyle: MenuStyle {
__maxPopupHeight: Math.max(55, //min value to keep it to a functional size even if it would not look nice
Math.min(400,
//limit the max size so the menu is inside the application bounds
comboBoxStyle.control.Window.height
- mapFromItem(comboBoxStyle.control, 0,0).y
- comboBoxStyle.control.height))
__menuItemType: "comboboxitem" //not 100% sure if this is needed
} //Component __dropDownStyle: MenuStyle
} //style: ComboBoxStyle
} //ComboBox

QObject::findChild() returns None without obvious reason

I'm new to Qt Quck and Qt5/PyQt, and now I've faced a strange problem. I'm trying to find an object with objectName "test" in the below QML definition like this:
self.rootObject().findChild(QObject, "test")
But the call returns None. However, if I move the objectName: "test" property to the parent Tab element, then it's found successfully. It's only not found whem inside the child Item. Similarly, addChannel, modifyChannel and removeChannel objects are also not found by findChild().
import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1
import "TouchStyles"
Item {
ListModel { }
TouchButtonFlatStyle { id: touchButtonFlat }
TouchTabViewStyle { id: touchTabView }
Rectangle {
width: 480
height: 230
TabView {
currentIndex: 0
tabPosition: 1
anchors.fill: parent
style: touchTabView
Tab {
title: "Play"
Item {
anchors.fill: parent
PianoKeyboard { anchors.centerIn: parent }
}
}
Tab {
title: "Channels"
Item {
objectName: "test"
ListView {
anchors.fill: parent
model: listModel
delegate: Channel {}
}
BorderImage {
border.bottom: 8
anchors.bottom: parent.bottom
source: "images/toolbar.png"
width: parent.width
height: 50
RowLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
Button { text: "Add"; objectName: "addChannel" }
Button { text: "Modify"; objectName: "modifyChannel" }
Button { text: "Remove"; objectName: "removeChannel" }
}
}
}
}
}
}
}
What am I doing wrong? The Qt documentation says that the search is performed recursively. Why doesn't it traverse the entire object tree?
The problem is related to the fact that tabs are "instantiated" only on demand. The first tab is always instantiated, so if you put the objectName there it will be found.
It will be found in the second tab only if you instantiate the second tab (select it). Similarly, using findChild on the TabView probably instantiates each tab (since it looking for them), so after that a findChild works even if second tab was not selected.
Conclusion: instantiate all tabs first (doing a findChild on the TabView is one way but may be a hack), then do the findChild for the item.

Resources